feat(t5): ein Tabs-Baustein für Listen, Einstellungen und Statistik (T5a A8)
Backend E2E / e2e (push) Failing after 13m2s
Backend Image / build-and-push (push) Successful in 7m55s
Frontend Image / build-and-push (push) Successful in 27m46s

- ui/tabs.tsx nur noch Variante segment: Leiste auf --muted, aktiver Tab
  mit Kartenfläche, fetter Schrift und 3-px-Balken in --primary-ink;
  44 px bei Touch, kompakt mit Maus; inaktive Schrift ≥ 6,27:1.
- ActiveHistoryTabs als Vorlage auf Tabs für 10 Listen (Pfeiltasten
  schalten direkt, Daten sind geladen), mit toolbar und heading.
- Einstellungen (Automatisierung, Benutzer & Rollen) und Statistik mit
  manueller Aktivierung: Pfeil bewegt den Fokus, Enter/Leertaste wählt;
  ?tab= über router.replace. TabSwitcher entfernt.
- Menüeinträge (Dropdown, Select) mit innerem Fokusring ≥ 3:1.
- Aufgaben: der Prioritätsfilter lädt die Liste jetzt neu (queryKey).
- Wächter: keine eigenen tablist/tab/tabpanel außerhalb ui/tabs.tsx.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick.RosenloecherandClaude Opus 5.5 committed 2026-10-16 20:08:36 +02:00
1 parent 759d85dc67
commit fce9d10192
26 files changed
+1137 -606

No files matched your search

@@ -1,53 +1,69 @@
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
export type EntityLifecycleTab = "active" | "history";
interface ActiveHistoryTabsProps {
export interface ActiveHistoryTabsProps {
value: EntityLifecycleTab;
onChange: (value: EntityLifecycleTab) => void;
onValueChange: (value: EntityLifecycleTab) => void;
activeCount?: number;
historyCount?: number;
/** A section heading that opens the row; the tabs then sit on its right. */
heading?: ReactNode;
/** Controls next to the tabs (a filter, a create button), outside the panels. */
toolbar?: ReactNode;
/** `end` pushes the toolbar to the far right of the row (a create button). */
toolbarAlign?: "start" | "end";
/** The list of each panel; only the selected one is mounted. */
children: { active: ReactNode; history: ReactNode };
}
const withCount = (label: string, count: number | undefined) =>
count === undefined ? label : `${label} (${count})`;
const isLifecycleTab = (value: unknown): value is EntityLifecycleTab =>
value === "active" || value === "history";
/**
* Template on Tabs (spec 10.4): „Aktiv“ and „Historie“, each panel holds its list. Both panels
* filter data the page has already loaded, so the arrow keys switch at once (activateOnFocus).
*/
export function ActiveHistoryTabs({
value,
onChange,
onValueChange,
activeCount,
historyCount,
heading,
toolbar,
toolbarAlign = "start",
children,
}: ActiveHistoryTabsProps) {
return (
<div
role="tablist"
className="inline-flex w-fit rounded-xl border border-border/60 bg-muted/40 p-1"
<Tabs
value={value}
onValueChange={(next) => {
if (isLifecycleTab(next)) onValueChange(next);
}}
>
<button
type="button"
role="tab"
aria-selected={value === "active"}
onClick={() => onChange("active")}
className={cn(
"rounded-lg px-4 py-1.5 text-sm font-medium transition-colors",
value === "active"
? "bg-card text-foreground shadow-[var(--shadow-soft)]"
: "text-muted-foreground hover:text-foreground",
<div className="flex flex-wrap items-center gap-3">
{heading}
<TabsList
aria-label="Aktiv oder Historie"
activateOnFocus
className={cn(heading !== undefined && "ml-auto")}
>
<TabsTrigger value="active">{withCount("Aktiv", activeCount)}</TabsTrigger>
<TabsTrigger value="history">{withCount("Historie", historyCount)}</TabsTrigger>
</TabsList>
{toolbarAlign === "end" && toolbar !== undefined ? (
<div className="ml-auto">{toolbar}</div>
) : (
toolbar
)}
>
Aktiv{activeCount !== undefined ? ` (${activeCount})` : ""}
</button>
<button
type="button"
role="tab"
aria-selected={value === "history"}
onClick={() => onChange("history")}
className={cn(
"rounded-lg px-4 py-1.5 text-sm font-medium transition-colors",
value === "history"
? "bg-card text-foreground shadow-[var(--shadow-soft)]"
: "text-muted-foreground hover:text-foreground",
)}
>
Historie{historyCount !== undefined ? ` (${historyCount})` : ""}
</button>
</div>
</div>
<TabsContent value="active">{children.active}</TabsContent>
<TabsContent value="history">{children.history}</TabsContent>
</Tabs>
);
}