Zum Inhalt springen
ZVV
Navigation

Navigation

Die Drei-Zonen-Shell (patterns/app-shell.md): Sidebar als Identitäts-Anker links, Topbar als Arbeitszeile, Footer unten. Du siehst diese Page bereits in der echten App-Shell — alles hier ist live.

App-Shell: Drei Zonen

Für Sidebar-Apps (≥ 4 Hauptbereiche) läuft die Sidebar über die volle Viewport-Höhe und trägt die App-Identität; die Topbar ist eine schmale Arbeitszeile in der Content-Spalte. Zwei Grundregeln: keine Doppel-Identität — Logo und Avatar existieren genau einmal in der Shell — und gleich hohe Bänder, damit oben und unten je eine durchgehende Linie quer über die App läuft.

components/layout/app-shell.tsxtsx
<div className="flex min-h-screen bg-background">
  <Sidebar />                       {/* full-height Identitäts-Anker */}
  <div className="flex min-h-screen min-w-0 flex-1 flex-col">
    <Topbar />                      {/* Arbeitszeile, h-12  ─┐ */}
    <main className="min-w-0 flex-1">{children}</main>
    <Footer />                      {/* md:h-14             ─┘ */}
  </div>
</div>

// Die Bänder sind in beiden Spalten gleich hoch:
//   oben  h-12  Identitäts-Header · Arbeitszeile
//   unten h-14  Konto-Zeile       · Footer
// → zwei durchgehende Linien quer über die App

Sidebar: drei Etagen

① Identitäts-Header mit Original-ZVV-Logo (Pflicht) und App-Name — exakt h-12, damit seine Unterkante mit der Arbeitszeile eine durchgehende Linie bildet. Darunter Mandanten-Chip (ohne Rolle) und ⌘K-Suche, ② scrollende Gruppen-Nav mit Uppercase-Labels, ③ Konto-Zeile aus User-Karte und Notification-Bell (Vercel-Muster) — einzeilig auf h-14, der Höhe des Footers. Links im Viewport live zu sehen; Rail-Breite und Gruppen-Zustand sind Cookie-persistent (kein Hydration-Flash).

components/layout/sidebar.tsx (Etage ①)tsx
{/* ① Header: h-12 — gleiche Höhe wie die Arbeitszeile,
    damit beide Unterkanten EINE durchgehende Linie bilden */}
<div className="flex h-12 shrink-0 items-center border-b px-4">
  <Link href="/">
    <Image src="/zvv-logo.svg" alt="ZVV" />
    <span>App-Name</span>
  </Link>
</div>

{/* ①b unter der Linie: Mandanten-Chip + Suche */}
<div className="shrink-0 space-y-1.5 px-2 pt-2">
  <div className="truncate px-1 text-[11px]">
    <span className="font-mono font-semibold">VBZ</span>
    <span className="text-muted-foreground"> · Verkehrsbetriebe Zürich</span>
  </div>
  <CommandTrigger />
</div>

Topbar: die Arbeitszeile

Kompakt (h-12), sticky, Backdrop-Blur, drei Zonen im Grid: links Einklappen-Toggle und Breadcrumb (der Weg zur Seite), in der Mitte der Seitentitel, rechts Platz für eine seitenbezogene Primary-Action plus Theme-Toggle. Keine Identität — Logo, Avatar, Suche und Bell wohnen in der Sidebar. Der Toggle sitzt hier statt im Sidebar-Fuss: so liegt er in beiden Rail-Zuständen an derselben Stelle, und der Fuss bleibt einzeilig.

  • Position: sticky top-0 z-30 (unter dem Env-Ribbon)
  • Höhe: h-12 — kompakt, damit Vertical für den Content bleibt
  • Background: bg-card/80 backdrop-blur dark:bg-muted/30 — gleiche Fläche wie Sidebar + Footer (Chrome-Rahmen)
  • Mobile: Sidebar ist ausgeblendet — die Topbar zeigt stattdessen Logo + App-Name
components/layout/topbar.tsxtsx
<header className="sticky top-0 z-30 grid h-12 grid-cols-[1fr_auto_1fr] items-center border-b bg-card/80 px-4 backdrop-blur dark:bg-muted/30">
  <div className="flex min-w-0 items-center gap-2">
    <SidebarToggle />               {/* kanonische Trigger-Position */}
    <Breadcrumbs omitCurrent />     {/* Weg zur Seite: ⌂ › Bereich */}
  </div>
  <PageTitle />                     {/* zentriert */}
  <div className="flex items-center justify-end gap-2">
    <PageAction />                  {/* Primary-Action, optional */}
    {collapsed && <NotificationBell />}
    <ThemeToggle />
  </div>
</header>

Topbar-only: die Topbar als Brand-Träger

Die zweite Ausprägung (docs/03 §2.1), für Apps ohne Sidebar — Statuspage, Single-Section- Tools, Marketing-Showcases. Ohne Sidebar gibt es keinen anderen Ort für Identität und Person, also trägt die Topbar beides selbst: Logo und App-Name links, h-16 statt h-12. Ab vier Hauptbereichen ist stattdessen die Drei-Zonen-Shell richtig.

ZVVStatuspage
Die echte Komponente, im Rahmen vorgeführt — kein nachgezeichnetes Markup. Der dritte Nav-Eintrag zeigt den Pre-Login-Zustand.
  • Logo: 43 × 20 aus LOGO_CHROME — dasselbe Mass wie im Sidebar-Kopf. Die Identität schrumpft nicht, wenn sie die Zone wechselt.
  • App-Name: Text ohne Zweitfarbe, erbt text-foreground. Kein «ZVV» im Namen — das Logo daneben nennt den Absender bereits.
  • Untergrenze: 20 px Logo + Schutzraum = 30 px. Unter h-10 trägt ein Band die Identität nicht mehr.
components/layout/topbar-brand.tsxtsx
{/* h-16 statt h-12: ohne Sidebar trägt die Topbar
    Identität UND Person selbst (docs/03 §2.1) */}
<header className="sticky top-0 z-40 flex h-16 items-center gap-3 border-b bg-card/80 px-4 backdrop-blur-xl sm:px-6 dark:bg-muted/30">
  <Link href="/" className="flex shrink-0 items-center gap-2">
    <Image src="/zvv-logo.svg" width={LOGO_CHROME.breite} height={LOGO_CHROME.hoehe} />
    {/* App-Name als Text, ohne Zweitfarbe — er erbt text-foreground */}
    <span className="text-sm font-semibold tracking-tight">{appName}</span>
  </Link>
  <Breadcrumbs />                   {/* hidden auf Mobile */}
  <div className="flex-1" />        {/* Spacer */}
  <PrimaryNav items={nav} />        {/* max 4–6 Pills */}
  <span className="h-6 w-px bg-border" />
  <UserSwitcher variant="topbar" />
  <ThemeToggle />
</header>

Breadcrumbs

Pfad-basiert, automatisch generiert. Erstes Element ist das Home-Icon (der App-Name steht schon im Sidebar-Header). Gezeigt wird der Weg zur Seite — die Seite selbst steht als Titel in der Mitte, nicht doppelt. Weil das Showcase überwiegend einstufig ist, tritt die Nav-Gruppe als Eltern-Ebene ein; ohne sie bliebe hier nur das Home-Icon stehen. Navigiere durch die Sektionen, um es live zu sehen.

/tokens → Breadcrumb ⌂ › Foundation · Titel Color-Tokens
/forms/simple → Breadcrumb ⌂ › Daten › Formulare · Titel Einfach

Konto-Zeile / UserSwitcher

Die Konto-Zeile dockt unten in der Sidebar an: User-Karte (Avatar + Name + Rolle, Dropdown öffnet nach oben) plus Notification-Bell daneben. Im Atlas-Showcase ist die Karte ein Mock-Persona-Switcher (Live-Demo für RBAC) — in Atlas-Apps ein User-Menu mit Konto + Logout. Klick unten links auf die Karte; wechsle die Persona, um den Mandanten-Chip oben umspringen zu sehen.

Theme-Toggle

4-State Cycle: System → Light → Dark → Auto. Auto folgt einem festen Clock-Fenster 08:00–18:00 Zürich-Zeit (lib/zurich-time.ts, DST-korrekt via Intl). Klick rechts oben (Icon ändert sich pro State).

Footer

Konsistent über alle Atlas-Apps. Links: Copyright. Mitte: Atlas-Spec + Repo + Changelog. Rechts: Version + Commit + Stack-Hinweis.

components/layout/footer.tsxtsx
{/* md:h-14 = Höhe der Konto-Zeile → durchgehende Linie unten */}
<footer className="border-t bg-card md:h-14 dark:bg-muted/30">
  <div className="flex h-full max-w-7xl gap-3 px-4 py-6 text-[11px] text-muted-foreground md:py-0">
    <span>© {year} ZVV · App-Name</span>
    <Link href={ATLAS_DOCS}>Spec</Link>
    <Link href={REPO}>Repo</Link>
    <span className="font-mono">v{version} · {commit}</span>
  </div>
</footer>