Zwei Dinge, die beim Ansehen aufgefallen sind.
**Das Rechteck auf dem Panel.** Das Logo bringt sein rosa Feld selbst mit, und
auf der rosa Flaeche stand es als Rechteck in leicht anderem Ton darauf. Der
Ton war dabei derselbe — verschoben hat ihn die Lichtblende, die ueber der
Panelflaeche liegt und unter dem Logo endete.
Das Manual kennt fuer den Schriftzug zwei Faelle, und sie brauchen
verschiedene Dateien: auf Weiss gehoert er nach §4.1 in ein rechteckiges rosa
Feld, in einem grossflaechigen rosa Umfeld dagegen wird er nach §3.1 nur mit
Freiraum integriert — die Flaeche ist ja schon da. Es gibt deshalb jetzt
manner-logo-auf-rosa.svg ohne eigenes Feld, und die rosa Flaechen benutzen es.
Damit bleibt nichts mehr uebrig, wo die Blende enden koennte.
Die Blende selbst ist weg. Sie war Weiss auf Rosa und hellte die Markenfarbe
so weit auf, dass sie kaum noch die Markenfarbe war; geblieben ist ein feines
Raster und eine leichte Tiefe zur unteren Ecke, beides aus ink.
**Schwarz auf der Marke.** "Uebersicht", "Berichte" und der Rest der Kopfzeile
standen in ink. Das traegt zwar (7.47:1), sieht aber aus wie Text, der aus
Versehen auf der Marke gelandet ist. Alles Geschriebene auf Rosa steht jetzt
in brand-700: 6.60:1, und es ist dieselbe Paarung, aus der der Schriftzug
selbst besteht — blaue Lettern auf rosa Feld.
Dabei noch eine Schwaeche gefunden: das Abzeichen an der Glocke kam als
danger-solid auf dem rosa Band nur auf 2.78:1 und blieb unter den 3:1, die
WCAG 1.4.11 fuer ein bedeutungstragendes Element verlangt. Mit danger-text
sind es 3.26:1, und die Ziffer darin steht mit 7.12:1 besser als vorher.
**Und ausgemistet.** Die Logodatei wog 74 kB, weil die Vorlage aus Seite 3 des
Manuals gezogen wurde und die ganze Seite mitgenommen hat: 219 Elemente, die
den Fliesstext jener Seite zeichnen ("Der Manner Schriftzug ... darf nicht
veraendert werden"), unsichtbar rosa auf Rosa oder ausserhalb des Beschnitts.
Das eigentliche Logo sind vier Pfade. Beide Fassungen wiegen jetzt 18,7 kB.
Lint, Typen, Schemaabgleich, 552 Tests und der Build sind sauber. Im Browser
nicht gesehen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
101 lines
4.0 KiB
TypeScript
101 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { BarChart3, Building2, History, LayoutGrid, Network, Upload, Users, X } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { useEffect } from "react";
|
|
import { AppWortmarke } from "@/components/brand/Logo";
|
|
|
|
const NAV_ITEMS = [
|
|
{ href: "/", label: "Übersicht", icon: LayoutGrid },
|
|
{ href: "/employees", label: "Mitarbeiter:innen", icon: Users },
|
|
{ href: "/orgchart", label: "Organigramm", icon: Network },
|
|
{ href: "/positions", label: "Positionen", icon: Building2 },
|
|
{ href: "/reports", label: "Berichte", icon: BarChart3 },
|
|
{ href: "/audit", label: "Audit-Log", icon: History },
|
|
{ href: "/import", label: "Import", icon: Upload },
|
|
] as const;
|
|
|
|
export function isActiveRoute(pathname: string, href: string): boolean {
|
|
return href === "/" ? pathname === "/" : pathname.startsWith(href);
|
|
}
|
|
|
|
// Permanent rail from lg up, slide-in drawer below it. Same markup either
|
|
// way — the breakpoint only changes how the <nav> is positioned — so the
|
|
// nav list has exactly one definition.
|
|
export function Sidebar({ open, onClose }: { open: boolean; onClose: () => void }) {
|
|
const pathname = usePathname();
|
|
|
|
// A route change means the user tapped a nav item; the drawer has to get
|
|
// out of the way on its own, since it covers the page it just navigated to.
|
|
useEffect(() => {
|
|
onClose();
|
|
}, [pathname, onClose]);
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const onKey = (e: KeyboardEvent) => {
|
|
if (e.key === "Escape") onClose();
|
|
};
|
|
window.addEventListener("keydown", onKey);
|
|
return () => window.removeEventListener("keydown", onKey);
|
|
}, [open, onClose]);
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
onClick={onClose}
|
|
aria-hidden
|
|
className={`fixed inset-0 z-40 bg-black/40 transition-opacity lg:hidden ${
|
|
open ? "opacity-100" : "pointer-events-none opacity-0"
|
|
}`}
|
|
/>
|
|
<nav
|
|
aria-label="Hauptnavigation"
|
|
className={`fixed left-0 top-0 z-50 flex h-dvh w-[264px] flex-col border-r border-border bg-white transition-transform duration-200 lg:z-30 lg:w-[236px] lg:translate-x-0 ${
|
|
open ? "translate-x-0" : "-translate-x-full"
|
|
}`}
|
|
>
|
|
{/* pl-2 statt pl-5: der Freiraum des Logos bringt seinen eigenen
|
|
Abstand mit (siehe components/brand/Logo.tsx), und beides
|
|
uebereinander schoebe die Wortmarke sichtbar aus der Flucht der
|
|
Navigationspunkte darunter. */}
|
|
{/* Rosa wie die Kopfzeile daneben: beide zusammen sind ein
|
|
durchgehendes Band ueber der Anwendung, in dem das Logo mit seinem
|
|
eigenen rosa Feld aufgeht statt als Rechteck darauf zu liegen. */}
|
|
<div className="flex h-14 shrink-0 items-center justify-between border-b border-accent-600/40 bg-accent-500 pl-2 pr-3">
|
|
<AppWortmarke hoehe={24} aufRosa textKlasse="text-brand-700" />
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
aria-label="Navigation schließen"
|
|
className="-mr-1 rounded p-2 text-brand-700 hover:bg-accent-100 lg:hidden"
|
|
>
|
|
<X className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
{/* pb keeps the last item clear of the iOS home indicator. */}
|
|
<ul className="flex-1 space-y-1 overflow-y-auto px-3 py-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
|
|
{NAV_ITEMS.map(({ href, label, icon: Icon }) => {
|
|
const active = isActiveRoute(pathname, href);
|
|
return (
|
|
<li key={href}>
|
|
<Link
|
|
href={href}
|
|
aria-current={active ? "page" : undefined}
|
|
className={`flex items-center gap-3 rounded px-3 py-2.5 text-sm font-semibold transition-colors ${
|
|
active ? "bg-brand-100 text-brand-700" : "text-ink-body hover:bg-surface"
|
|
}`}
|
|
>
|
|
<Icon className="h-4 w-4 shrink-0" />
|
|
{label}
|
|
</Link>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</nav>
|
|
</>
|
|
);
|
|
}
|