- globals.css: eine Basisregel :where(:focus-visible) mit --focus-width und --focus-offset (Sonne 3 px), Firefox-:-moz-focusring gleichgezogen; --text-xs 0.8125rem. - Fokus-Codemod (scripts/codemod-t5-focus.mjs): eigene Ringe und outline-none raus, sm:h-/sm:size- zu pointer-fine:, Label-Zeilen mit focus-ring-within; Skript-Fokus (Lücken-Liste, „Gespeichert“) bleibt. - Button/Input 44 px, kompakt nur mit Maus; Icon-Knöpfe mit unsichtbarer 44-px-Trefferfläche bei Touch, Nachbarn mit genug Abstand. - 16 px in Feldern, Labels, Auswahl, Leerzuständen, Listentiteln. - .holo-stage auf allen Beute-Bühnen, .stage-card holt den Seitenring. - Baukasten-Wächter (src/styles/baukasten-guard.spec.ts) mit den Regeln aus A3–A5, Ausnahmen mit Grund; Kalender-Auswahl als Inset-Ring. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
35 lines
1.0 KiB
TypeScript
35 lines
1.0 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
|
|
interface TabSwitcherProps<T extends string> {
|
|
tabs: { value: T; label: string }[];
|
|
value: T;
|
|
onChange: (value: T) => void;
|
|
}
|
|
|
|
export function TabSwitcher<T extends string>({ tabs, value, onChange }: TabSwitcherProps<T>) {
|
|
return (
|
|
<div
|
|
role="tablist"
|
|
className="inline-flex w-fit flex-wrap gap-1 rounded-xl border border-border/60 bg-muted/40 p-1"
|
|
>
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.value}
|
|
type="button"
|
|
role="tab"
|
|
aria-selected={value === tab.value}
|
|
onClick={() => onChange(tab.value)}
|
|
className={cn(
|
|
"min-h-11 pointer-fine:min-h-9 rounded-lg px-4 py-1.5 text-sm font-medium transition-colors motion-reduce:transition-none",
|
|
value === tab.value
|
|
? "bg-card text-foreground shadow-[var(--shadow-soft)]"
|
|
: "text-muted-foreground hover:text-foreground",
|
|
)}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|