The project menu opens by section, and the workspace has its own panel
Like GitLab's: Plan, Code, Agents, Build, Deploy, Secure, Operate, Analyze and Settings, each one row that opens to its pages, built and Soon alike, instead of headings over one link each. The section holding the current page opens itself; others remember being left open. Sections with nothing built yet carry one Soon. Project settings are listed in the menu. The workspace's usage, settings and the account sit in a panel of their own at the bottom, ruled off full width.
1 file+238−990/1 viewed
| 1 | 1 | import { | |
| 2 | 2 | ArrowLeft, | |
| 3 | + | ChevronRight, | |
| 3 | 4 | BarChart3, | |
| 4 | 5 | BookMarked, | |
| 5 | 6 | BookOpen, | |
| 16 | 17 | Fingerprint, | |
| 17 | 18 | KeyRound, | |
| 18 | 19 | Box, | |
| 19 | − | Layers, | |
| 20 | 20 | LayoutDashboard, | |
| 21 | 21 | LayoutGrid, | |
| 22 | 22 | ListTree, | |
| 33 | 33 | Activity, | |
| 34 | 34 | Bot, | |
| 35 | 35 | Kanban, | |
| 36 | − | Package, | |
| 37 | − | PackageCheck, | |
| 38 | − | ScrollText, | |
| 39 | − | Server, | |
| 40 | 36 | ShieldCheck, | |
| 41 | − | Siren, | |
| 42 | − | Tag, | |
| 43 | − | ToggleRight, | |
| 44 | 37 | Rocket, | |
| 45 | 38 | X, | |
| 46 | 39 | } from "lucide-react"; | |
| 154 | 147 | ); | |
| 155 | 148 | } | |
| 156 | 149 | ||
| 150 | + | /** | |
| 151 | + | * A section of the project menu that opens to show its pages, as GitLab's | |
| 152 | + | * does: one row with an icon, and its pages indented under it. It opens | |
| 153 | + | * by itself when one of its pages is the current one, and otherwise | |
| 154 | + | * remembers whether it was left open. | |
| 155 | + | */ | |
| 156 | + | function SidebarSection({ | |
| 157 | + | title, | |
| 158 | + | icon, | |
| 159 | + | paths = [], | |
| 160 | + | soon = false, | |
| 161 | + | children, | |
| 162 | + | }: { | |
| 163 | + | title: string; | |
| 164 | + | icon: ReactNode; | |
| 165 | + | /** Path prefixes of its pages: one being current opens it. */ | |
| 166 | + | paths?: string[]; | |
| 167 | + | /** Nothing in it is built yet. */ | |
| 168 | + | soon?: boolean; | |
| 169 | + | children: ReactNode; | |
| 170 | + | }) { | |
| 171 | + | const { pathname } = useLocation(); | |
| 172 | + | const current = paths.some((prefix) => pathname === prefix || pathname.startsWith(prefix + "/")); | |
| 173 | + | const key = `g1t.sidebar.${title}`; | |
| 174 | + | const [open, setOpen] = useState<boolean>(current); | |
| 175 | + | // What the person left it as, once the page is in the browser. | |
| 176 | + | useEffect(() => { | |
| 177 | + | if (current) { | |
| 178 | + | setOpen(true); | |
| 179 | + | return; | |
| 180 | + | } | |
| 181 | + | try { | |
| 182 | + | const saved = localStorage.getItem(key); | |
| 183 | + | if (saved != null) setOpen(saved === "1"); | |
| 184 | + | } catch { | |
| 185 | + | // No storage: it stays as it is. | |
| 186 | + | } | |
| 187 | + | }, [current, key]); | |
| 188 | + | const toggle = () => { | |
| 189 | + | const next = !open; | |
| 190 | + | setOpen(next); | |
| 191 | + | try { | |
| 192 | + | localStorage.setItem(key, next ? "1" : "0"); | |
| 193 | + | } catch { | |
| 194 | + | // Remembered for this page only. | |
| 195 | + | } | |
| 196 | + | }; | |
| 197 | + | return ( | |
| 198 | + | <section> | |
| 199 | + | <button | |
| 200 | + | type="button" | |
| 201 | + | onClick={toggle} | |
| 202 | + | aria-expanded={open} | |
| 203 | + | className={`group flex h-8 w-full items-center gap-2.5 rounded-md px-2 text-left text-[0.8125rem] transition-colors hover:bg-raised/60 ${ | |
| 204 | + | current ? "text-fg" : soon ? "text-faint hover:text-muted" : "text-muted hover:text-fg" | |
| 205 | + | }`} | |
| 206 | + | > | |
| 207 | + | <span className={`shrink-0 ${current ? "text-muted" : "text-faint group-hover:text-muted"}`}>{icon}</span> | |
| 208 | + | <span className={`min-w-0 grow truncate ${current ? "font-medium" : ""}`}>{title}</span> | |
| 209 | + | {soon && !open && ( | |
| 210 | + | <span className="rounded-full px-1.5 py-px text-[0.625rem] font-medium tracking-wide text-muted uppercase ring-1 ring-line"> | |
| 211 | + | Soon | |
| 212 | + | </span> | |
| 213 | + | )} | |
| 214 | + | <ChevronRight | |
| 215 | + | size={14} | |
| 216 | + | className={`shrink-0 text-faint transition-transform duration-150 ${open ? "rotate-90" : ""}`} | |
| 217 | + | aria-hidden="true" | |
| 218 | + | /> | |
| 219 | + | </button> | |
| 220 | + | {open && ( | |
| 221 | + | <div className="relative mt-px mb-1 space-y-px before:absolute before:top-1 before:bottom-1 before:left-[1.1875rem] before:w-px before:bg-line"> | |
| 222 | + | {children} | |
| 223 | + | </div> | |
| 224 | + | )} | |
| 225 | + | </section> | |
| 226 | + | ); | |
| 227 | + | } | |
| 228 | + | ||
| 229 | + | /** A page inside a section: indented, without an icon of its own. */ | |
| 230 | + | function SidebarSubLink({ | |
| 231 | + | to, | |
| 232 | + | end, | |
| 233 | + | count, | |
| 234 | + | also, | |
| 235 | + | children, | |
| 236 | + | }: { | |
| 237 | + | to: string; | |
| 238 | + | end?: boolean; | |
| 239 | + | count?: number; | |
| 240 | + | also?: string | string[]; | |
| 241 | + | children: ReactNode; | |
| 242 | + | }) { | |
| 243 | + | const { pathname } = useLocation(); | |
| 244 | + | return ( | |
| 245 | + | <NavLink | |
| 246 | + | to={to} | |
| 247 | + | end={end} | |
| 248 | + | prefetch="intent" | |
| 249 | + | className={({ isActive, isPending }) => { | |
| 250 | + | const current = | |
| 251 | + | isActive || [also ?? []].flat().some((prefix) => pathname === prefix || pathname.startsWith(prefix + "/")); | |
| 252 | + | return `relative flex h-8 items-center gap-2 rounded-md pr-2 pl-[2.375rem] text-[0.8125rem] transition-colors ${ | |
| 253 | + | current | |
| 254 | + | ? "bg-raised font-medium text-fg before:absolute before:top-1.5 before:bottom-1.5 before:left-[1.1875rem] before:z-10 before:w-px before:bg-accent" | |
| 255 | + | : isPending | |
| 256 | + | ? "bg-raised/60 text-fg" | |
| 257 | + | : "text-muted hover:bg-raised/60 hover:text-fg" | |
| 258 | + | }`; | |
| 259 | + | }} | |
| 260 | + | > | |
| 261 | + | <span className="min-w-0 grow truncate">{children}</span> | |
| 262 | + | {count != null && count > 0 && ( | |
| 263 | + | <span className="rounded bg-line px-1.5 text-[0.6875rem] tabular-nums text-muted">{count}</span> | |
| 264 | + | )} | |
| 265 | + | </NavLink> | |
| 266 | + | ); | |
| 267 | + | } | |
| 268 | + | ||
| 269 | + | /** A page a section will have: shown so people can see where g1t is going. */ | |
| 270 | + | function SidebarSubSoon({ about, children }: { about: string; children: ReactNode }) { | |
| 271 | + | return ( | |
| 272 | + | <div | |
| 273 | + | title={about} | |
| 274 | + | aria-disabled="true" | |
| 275 | + | className="flex h-8 cursor-default items-center gap-2 rounded-md pr-2 pl-[2.375rem] text-[0.8125rem] text-faint" | |
| 276 | + | > | |
| 277 | + | <span className="grow truncate">{children}</span> | |
| 278 | + | <span className="rounded-full px-1.5 py-px text-[0.625rem] font-medium tracking-wide text-muted uppercase ring-1 ring-line"> | |
| 279 | + | Soon | |
| 280 | + | </span> | |
| 281 | + | </div> | |
| 282 | + | ); | |
| 283 | + | } | |
| 284 | + | ||
| 157 | 285 | function SidebarGroup({ title, action, children }: { title: string; action?: ReactNode; children: ReactNode }) { | |
| 158 | 286 | return ( | |
| 159 | 287 | <section className="mt-6"> | |
| 414 | 542 | <SidebarLink to={base} end icon={<LayoutGrid size={15} />}> | |
| 415 | 543 | Overview | |
| 416 | 544 | </SidebarLink> | |
| 545 | + | ||
| 546 | + | <SidebarSection title="Plan" icon={<Kanban size={15} />} paths={[`${base}/issues`, `${base}/plans`]}> | |
| 547 | + | <SidebarSubLink to={`${base}/issues`} count={repo.issues}> | |
| 548 | + | Issues | |
| 549 | + | </SidebarSubLink> | |
| 550 | + | {repo.member && <SidebarSubLink to={`${base}/plans`}>Outcomes</SidebarSubLink>} | |
| 551 | + | <SidebarSubSoon about="Issues and pull requests on boards, by state, owner or outcome.">Boards</SidebarSubSoon> | |
| 552 | + | <SidebarSubSoon about="Dates to land outcomes by, with what is left and what is at risk.">Milestones</SidebarSubSoon> | |
| 553 | + | <SidebarSubSoon about="Pages about the project that agents keep current as the code changes.">Wiki</SidebarSubSoon> | |
| 554 | + | </SidebarSection> | |
| 555 | + | ||
| 556 | + | <SidebarSection | |
| 557 | + | title="Code" | |
| 558 | + | icon={<Code2 size={15} />} | |
| 559 | + | paths={[`${base}/pulls`, `${base}/pull`, `${base}/queue`, `${base}/code`, `${base}/tree`, `${base}/blob`, `${base}/commits`, `${base}/commit`]} | |
| 560 | + | > | |
| 561 | + | <SidebarSubLink to={`${base}/pulls`} also={`${base}/pull`} count={repo.pulls}> | |
| 562 | + | Pull requests | |
| 563 | + | </SidebarSubLink> | |
| 564 | + | <SidebarSubLink to={`${base}/queue`}>Merge queue</SidebarSubLink> | |
| 565 | + | <SidebarSubLink to={`${base}/code`} also={[`${base}/tree`, `${base}/blob`]}> | |
| 566 | + | Files | |
| 567 | + | </SidebarSubLink> | |
| 568 | + | <SidebarSubLink to={`${base}/commits`} also={`${base}/commit`}> | |
| 569 | + | Commits | |
| 570 | + | </SidebarSubLink> | |
| 571 | + | <SidebarSubSoon about="Every branch, who and what is on it, how far behind it is, and its preview.">Branches</SidebarSubSoon> | |
| 572 | + | <SidebarSubSoon about="Tags, and the releases made from them.">Tags</SidebarSubSoon> | |
| 573 | + | </SidebarSection> | |
| 574 | + | ||
| 575 | + | <SidebarSection title="Agents" icon={<Bot size={15} />} soon> | |
| 576 | + | <SidebarSubSoon about="Every agent at work on this project now: what it is doing, what it knows, and a way to steer it."> | |
| 577 | + | At work | |
| 578 | + | </SidebarSubSoon> | |
| 579 | + | <SidebarSubSoon about="Every agent session that changed this project, searchable, with why-blame back to the lines it wrote."> | |
| 580 | + | Sessions | |
| 581 | + | </SidebarSubSoon> | |
| 582 | + | <SidebarSubSoon about="How agents should work here: conventions, commands and checks, kept with the code."> | |
| 583 | + | Playbooks | |
| 584 | + | </SidebarSubSoon> | |
| 585 | + | </SidebarSection> | |
| 586 | + | ||
| 587 | + | <SidebarSection title="Build" icon={<PlayCircle size={15} />} paths={[`${base}/actions`]}> | |
| 588 | + | <SidebarSubLink to={`${base}/actions`}>Actions</SidebarSubLink> | |
| 589 | + | <SidebarSubSoon about="g1t's machines, or your own, that run workflow jobs and checks.">Runners</SidebarSubSoon> | |
| 590 | + | <SidebarSubSoon about="What builds and workflow jobs produce, kept and downloadable.">Artifacts</SidebarSubSoon> | |
| 591 | + | <SidebarSubSoon about="Workflows that run on a timetable, and their history.">Schedules</SidebarSubSoon> | |
| 592 | + | </SidebarSection> | |
| 593 | + | ||
| 594 | + | <SidebarSection title="Deploy" icon={<Rocket size={15} />} paths={repo.member ? [`${base}/deployments`] : []}> | |
| 595 | + | {repo.member && <SidebarSubLink to={`${base}/deployments`}>Deployments</SidebarSubLink>} | |
| 596 | + | <SidebarSubSoon about="Staging and other environments, with required approvers and branch rules.">Environments</SidebarSubSoon> | |
| 597 | + | <SidebarSubSoon about="Tagged releases with notes written from what landed.">Releases</SidebarSubSoon> | |
| 598 | + | <SidebarSubSoon about="npm, container and other packages published from the project.">Packages</SidebarSubSoon> | |
| 599 | + | <SidebarSubSoon about="Turn features on per environment or per user, without a deploy.">Feature flags</SidebarSubSoon> | |
| 600 | + | </SidebarSection> | |
| 601 | + | ||
| 602 | + | <SidebarSection title="Secure" icon={<ShieldCheck size={15} />} soon> | |
| 603 | + | <SidebarSubSoon about="Every finding in one place, by severity, with the agent fixing each.">Security overview</SidebarSubSoon> | |
| 604 | + | <SidebarSubSoon about="Keys and tokens found in the code or its history, revoked and removed by an agent."> | |
| 605 | + | Secret scanning | |
| 606 | + | </SidebarSubSoon> | |
| 607 | + | <SidebarSubSoon about="Outdated and vulnerable packages, updated by agents, tested and landed through the queue."> | |
| 608 | + | Dependency updates | |
| 609 | + | </SidebarSubSoon> | |
| 610 | + | <SidebarSubSoon about="Code scanned for vulnerabilities on every change, each finding fixed through the queue."> | |
| 611 | + | Code scanning | |
| 612 | + | </SidebarSubSoon> | |
| 613 | + | </SidebarSection> | |
| 614 | + | ||
| 615 | + | <SidebarSection title="Operate" icon={<Activity size={15} />} soon> | |
| 616 | + | <SidebarSubSoon about="Requests, errors and CPU time of each deployment, and its logs.">Logs and metrics</SidebarSubSoon> | |
| 617 | + | <SidebarSubSoon about="Errors and incidents from the running app, each becoming an issue an agent can take."> | |
| 618 | + | Errors and incidents | |
| 619 | + | </SidebarSubSoon> | |
| 620 | + | <SidebarSubSoon about="Checks that the app answers, from around the world, and who is told when it does not.">Uptime</SidebarSubSoon> | |
| 621 | + | </SidebarSection> | |
| 622 | + | ||
| 623 | + | <SidebarSection title="Analyze" icon={<BarChart3 size={15} />} soon> | |
| 624 | + | <SidebarSubSoon about="How work flows: lead time, review time, and the share of changes agents make.">Insights</SidebarSubSoon> | |
| 625 | + | <SidebarSubSoon about="What the project costs, by agent run, sandbox, build and app.">Costs</SidebarSubSoon> | |
| 626 | + | <SidebarSubSoon about="Deploy frequency, lead time, change failure rate and time to restore.">Delivery metrics</SidebarSubSoon> | |
| 627 | + | </SidebarSection> | |
| 628 | + | ||
| 417 | 629 | {repo.member && ( | |
| 418 | − | <SidebarLink to={`${base}/deployments`} icon={<Rocket size={15} />}> | |
| 419 | − | Deployments | |
| 420 | − | </SidebarLink> | |
| 630 | + | <SidebarSection title="Settings" icon={<Settings size={15} />} paths={[`${base}/settings`]}> | |
| 631 | + | <SidebarSubLink to={`${base}/settings`} end> | |
| 632 | + | General | |
| 633 | + | </SidebarSubLink> | |
| 634 | + | <SidebarSubLink to={`${base}/settings/repository`}>Repository</SidebarSubLink> | |
| 635 | + | <SidebarSubLink to={`${base}/settings/deployments`}>Deployments</SidebarSubLink> | |
| 636 | + | <SidebarSubLink to={`${base}/settings/dependencies`}>Dependencies</SidebarSubLink> | |
| 637 | + | <SidebarSubLink to={`${base}/settings/secrets`}>Secrets and variables</SidebarSubLink> | |
| 638 | + | <SidebarSubLink to={`${base}/settings/webhooks`}>Webhooks</SidebarSubLink> | |
| 639 | + | </SidebarSection> | |
| 421 | 640 | )} | |
| 422 | 641 | </div> | |
| 423 | − | <SidebarGroup title="Code"> | |
| 424 | − | <SidebarLink to={`${base}/code`} icon={<Code2 size={15} />} also={[`${base}/tree`, `${base}/blob`]}> | |
| 425 | − | Code | |
| 426 | − | </SidebarLink> | |
| 427 | − | <SidebarLink to={`${base}/commits`} also={`${base}/commit`} icon={<History size={15} />}> | |
| 428 | − | Commits | |
| 429 | − | </SidebarLink> | |
| 430 | − | </SidebarGroup> | |
| 431 | − | <SidebarGroup title="Work"> | |
| 432 | − | <SidebarLink to={`${base}/issues`} icon={<CircleDot size={15} />} count={repo.issues}> | |
| 433 | − | Issues | |
| 434 | − | </SidebarLink> | |
| 435 | − | <SidebarLink to={`${base}/pulls`} also={`${base}/pull`} icon={<GitPullRequest size={15} />} count={repo.pulls}> | |
| 436 | − | Pull requests | |
| 437 | − | </SidebarLink> | |
| 438 | − | {repo.member && ( | |
| 439 | − | <SidebarLink to={`${base}/plans`} icon={<ListTree size={15} />}> | |
| 440 | − | Plan | |
| 441 | − | </SidebarLink> | |
| 442 | − | )} | |
| 443 | − | <SidebarLink to={`${base}/queue`} icon={<Layers size={15} />}> | |
| 444 | − | Merge queue | |
| 445 | − | </SidebarLink> | |
| 446 | − | <SidebarSoon icon={<Kanban size={15} />} about="Issues and pull requests on boards, by state, owner or outcome."> | |
| 447 | − | Boards | |
| 448 | − | </SidebarSoon> | |
| 449 | − | <SidebarSoon icon={<BookOpen size={15} />} about="Pages about the project that agents keep current as the code changes."> | |
| 450 | − | Wiki | |
| 451 | − | </SidebarSoon> | |
| 452 | − | </SidebarGroup> | |
| 453 | − | <SidebarGroup title="Agents"> | |
| 454 | − | <SidebarSoon icon={<Bot size={15} />} about="Every agent at work on this project now: what it is doing, what it knows, and a way to steer it."> | |
| 455 | − | Agents at work | |
| 456 | − | </SidebarSoon> | |
| 457 | − | <SidebarSoon icon={<ScrollText size={15} />} about="Every agent session that changed this project, searchable, with why-blame back to the lines it wrote."> | |
| 458 | − | Sessions | |
| 459 | − | </SidebarSoon> | |
| 460 | − | </SidebarGroup> | |
| 461 | − | <SidebarGroup title="Automate"> | |
| 462 | − | <SidebarLink to={`${base}/actions`} icon={<PlayCircle size={15} />}> | |
| 463 | − | Actions | |
| 464 | − | </SidebarLink> | |
| 465 | − | </SidebarGroup> | |
| 466 | − | <SidebarGroup title="Secure"> | |
| 467 | − | <SidebarSoon icon={<ShieldCheck size={15} />} about="Secret scanning, vulnerable dependencies and code scanning, each finding fixed by an agent through the queue."> | |
| 468 | − | Security and quality | |
| 469 | − | </SidebarSoon> | |
| 470 | − | <SidebarSoon icon={<PackageCheck size={15} />} about="Outdated packages updated by agents, tested and landed through the queue."> | |
| 471 | − | Dependency updates | |
| 472 | − | </SidebarSoon> | |
| 473 | − | </SidebarGroup> | |
| 474 | − | <SidebarGroup title="Deploy"> | |
| 475 | − | <SidebarSoon icon={<Server size={15} />} about="Staging and other environments, with required approvers and branch rules."> | |
| 476 | − | Environments | |
| 477 | − | </SidebarSoon> | |
| 478 | − | <SidebarSoon icon={<Tag size={15} />} about="Tagged releases with notes written from what landed."> | |
| 479 | − | Releases | |
| 480 | − | </SidebarSoon> | |
| 481 | − | <SidebarSoon icon={<Package size={15} />} about="npm, container and other packages published from the project."> | |
| 482 | − | Packages | |
| 483 | − | </SidebarSoon> | |
| 484 | − | <SidebarSoon icon={<ToggleRight size={15} />} about="Turn features on per environment or per user, without a deploy."> | |
| 485 | − | Feature flags | |
| 486 | − | </SidebarSoon> | |
| 487 | − | </SidebarGroup> | |
| 488 | − | <SidebarGroup title="Operate"> | |
| 489 | − | <SidebarSoon icon={<Activity size={15} />} about="Requests, errors and CPU time of each deployment, and its logs."> | |
| 490 | − | Logs and metrics | |
| 491 | − | </SidebarSoon> | |
| 492 | − | <SidebarSoon icon={<Siren size={15} />} about="Errors and incidents from the running app, each becoming an issue an agent can take."> | |
| 493 | − | Errors and incidents | |
| 494 | − | </SidebarSoon> | |
| 495 | − | </SidebarGroup> | |
| 496 | − | <SidebarGroup title="Insights"> | |
| 497 | − | <SidebarSoon icon={<BarChart3 size={15} />} about="How work flows: lead time, agents' share of changes, review time, what costs most."> | |
| 498 | − | Insights | |
| 499 | − | </SidebarSoon> | |
| 500 | − | </SidebarGroup> | |
| 501 | − | {repo.member && ( | |
| 502 | − | <SidebarGroup title="Project"> | |
| 503 | − | <SidebarLink to={`${base}/settings`} icon={<Settings size={15} />}> | |
| 504 | − | Settings | |
| 505 | − | </SidebarLink> | |
| 506 | − | </SidebarGroup> | |
| 507 | − | )} | |
| 508 | 642 | </nav> | |
| 509 | 643 | ); | |
| 510 | 644 | } | |
| 685 | 819 | </div> | |
| 686 | 820 | </div> | |
| 687 | 821 | ||
| 688 | − | {/* The workspace's own things sit at the bottom, by its usage and the account. */} | |
| 689 | − | <div className="space-y-2 p-2"> | |
| 822 | + | {/* The workspace's own things sit at the bottom, by its usage and the | |
| 823 | + | account: a panel of their own, ruled off from whatever menu is above, | |
| 824 | + | however long it grows. */} | |
| 825 | + | <div className="shrink-0 space-y-2 border-t border-line bg-surface/50 p-2 pt-3"> | |
| 690 | 826 | {ws && ( | |
| 691 | 827 | <div className="space-y-px"> | |
| 692 | − | <p className="px-2 pb-1 text-[0.6875rem] font-medium uppercase tracking-wider text-faint">{ws.slug}</p> | |
| 828 | + | <p className="flex items-center gap-2 px-2 pb-1 text-[0.6875rem] font-medium uppercase tracking-wider text-faint"> | |
| 829 | + | <span>Workspace</span> | |
| 830 | + | <span className="min-w-0 truncate font-mono normal-case tracking-normal text-muted">{ws.slug}</span> | |
| 831 | + | </p> | |
| 693 | 832 | <SidebarLink to={`/${ws.slug}/-/usage`} icon={<BarChart3 size={15} />}> | |
| 694 | 833 | Usage | |
| 695 | 834 | </SidebarLink> |