Commit2289dc6unknown_key
feat(settings): repo settings sidebar nav with links to all sub-pages
feat(settings): repo settings sidebar nav with links to all sub-pages Adds a sticky left-hand sidebar to the repo settings page that lists all 13 sub-settings destinations (General, Collaborators, Automation, Branch Protection, Protected Tags, Rulesets, Webhooks, Environments, Pages, Workflow Secrets, Mirrors, Dep Updater, Audit Log) so users can navigate directly instead of typing URLs. Uses .rsettings-* CSS class namespace, matches the existing dark theme via CSS vars, highlights the active item, and collapses to a horizontal strip on narrow viewports. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1Wr1gducAJ51tSHJuJ9tY
1 file changed+138−02289dc6d2fe35ff783de8e2e1633d8b376fec302
1 changed file+138−0
Modifiedsrc/routes/repo-settings.tsx+138−0View fileUnifiedSplit
@@ -499,6 +499,77 @@ const repoSettingsStyles = `
499499 .repo-settings-danger-head,
500500 .repo-settings-danger-body { padding-left: var(--space-4); padding-right: var(--space-4); }
501501 }
502
503 /* ─── Settings sidebar layout ─── */
504 .rsettings-layout {
505 display: flex;
506 gap: var(--space-5);
507 align-items: flex-start;
508 }
509 .rsettings-sidebar {
510 width: 220px;
511 flex-shrink: 0;
512 position: sticky;
513 top: var(--space-4);
514 background: var(--bg-elevated);
515 border: 1px solid var(--border);
516 border-radius: 12px;
517 overflow: hidden;
518 }
519 .rsettings-sidebar-label {
520 padding: 10px 14px 8px;
521 font-size: 11px;
522 font-weight: 700;
523 letter-spacing: 0.07em;
524 text-transform: uppercase;
525 color: var(--text-subtle, var(--text-muted));
526 border-bottom: 1px solid var(--border);
527 }
528 .rsettings-nav {
529 display: flex;
530 flex-direction: column;
531 padding: 6px;
532 gap: 2px;
533 }
534 .rsettings-nav-item {
535 display: flex;
536 align-items: center;
537 gap: 8px;
538 padding: 7px 10px;
539 border-radius: 8px;
540 font-size: 13.5px;
541 font-weight: 500;
542 color: var(--text-muted);
543 text-decoration: none;
544 transition: background 100ms ease, color 100ms ease;
545 line-height: 1.3;
546 }
547 .rsettings-nav-item:hover {
548 background: var(--bg-surface, rgba(255,255,255,0.04));
549 color: var(--text);
550 }
551 .rsettings-nav-item.is-active {
552 background: var(--bg-surface, rgba(255,255,255,0.06));
553 color: var(--text);
554 font-weight: 600;
555 }
556 .rsettings-nav-divider {
557 height: 1px;
558 background: var(--border);
559 margin: 4px 6px;
560 }
561 .rsettings-content {
562 flex: 1;
563 min-width: 0;
564 }
565 (max-width: 860px) {
566 .rsettings-layout { flex-direction: column; }
567 .rsettings-sidebar {
568 width: 100%;
569 position: static;
570 }
571 .rsettings-nav { flex-direction: row; flex-wrap: wrap; }
572 }
502573`;
503574
504575/** Hero header for the repo settings page. */
@@ -547,6 +618,64 @@ function Banner(props: { kind: "success" | "error"; text: string }) {
547618 );
548619}
549620
621/** Left-hand settings navigation sidebar. */
622function RepoSettingsSidebar(props: { owner: string; repo: string; currentPath: string }) {
623 const { owner, repo, currentPath } = props;
624 const base = `/${owner}/${repo}`;
625
626 type NavItem =
627 | { kind: "link"; label: string; href: string }
628 | { kind: "divider" };
629
630 const items: NavItem[] = [
631 { kind: "link", label: "General", href: `${base}/settings` },
632 { kind: "link", label: "Collaborators", href: `${base}/settings/collaborators` },
633 { kind: "link", label: "Automation", href: `${base}/settings/automation` },
634 { kind: "divider" },
635 { kind: "link", label: "Branch Protection", href: `${base}/gates` },
636 { kind: "link", label: "Protected Tags", href: `${base}/settings/protected-tags` },
637 { kind: "link", label: "Rulesets", href: `${base}/settings/rulesets` },
638 { kind: "divider" },
639 { kind: "link", label: "Webhooks", href: `${base}/settings/webhooks` },
640 { kind: "link", label: "Environments", href: `${base}/settings/environments` },
641 { kind: "link", label: "Pages", href: `${base}/settings/pages` },
642 { kind: "link", label: "Workflow Secrets", href: `${base}/settings/secrets` },
643 { kind: "divider" },
644 { kind: "link", label: "Mirrors", href: `${base}/settings/mirror` },
645 { kind: "link", label: "Dep Updater", href: `${base}/settings/dep-updater` },
646 { kind: "link", label: "Audit Log", href: `${base}/settings/audit` },
647 ];
648
649 return (
650 <nav class="rsettings-sidebar" aria-label="Settings navigation">
651 <div class="rsettings-sidebar-label">Settings</div>
652 <ul class="rsettings-nav" role="list" style="list-style:none;margin:0;padding:6px;">
653 {items.map((item) => {
654 if (item.kind === "divider") {
655 return <li class="rsettings-nav-divider" role="separator" />;
656 }
657 // Exact match for "General" (base settings URL), prefix match for sub-pages
658 const isActive =
659 item.href === `${base}/settings`
660 ? currentPath === `${base}/settings` || currentPath === `${base}/settings/`
661 : currentPath === item.href || currentPath.startsWith(item.href + "/");
662 return (
663 <li>
664 <a
665 href={item.href}
666 class={`rsettings-nav-item${isActive ? " is-active" : ""}`}
667 aria-current={isActive ? "page" : undefined}
668 >
669 {item.label}
670 </a>
671 </li>
672 );
673 })}
674 </ul>
675 </nav>
676 );
677}
678
550679// Settings page
551680repoSettings.get("/:owner/:repo/settings", requireAuth, requireRepoAccess("admin"), async (c) => {
552681 const { owner: ownerName, repo: repoName } = c.req.param();
@@ -596,6 +725,13 @@ repoSettings.get("/:owner/:repo/settings", requireAuth, requireRepoAccess("admin
596725 <style dangerouslySetInnerHTML={{ __html: repoSettingsStyles }} />
597726 <div class="repo-settings-container">
598727 <RepoSettingsHero owner={ownerName} repo={repoName} />
728 <div class="rsettings-layout">
729 <RepoSettingsSidebar
730 owner={ownerName}
731 repo={repoName}
732 currentPath={`/${ownerName}/${repoName}/settings`}
733 />
734 <div class="rsettings-content">
599735
600736 {success && (
601737 <Banner kind="success" text={decodeURIComponent(success)} />
@@ -1308,6 +1444,8 @@ repoSettings.get("/:owner/:repo/settings", requireAuth, requireRepoAccess("admin
13081444 </form>
13091445 </div>
13101446 </section>
1447 </div>{/* .rsettings-content */}
1448 </div>{/* .rsettings-layout */}
13111449 </div>
13121450 </Layout>
13131451 );
13141452