feat(t5): ein Tabs-Baustein für Listen, Einstellungen und Statistik (T5a A8)
- 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:
1 parent
759d85dc67
commit
fce9d10192
26 files changed
+1137
-606
No files matched your search
@@ -0,0 +1,67 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "./dropdown-menu";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
||||
|
||||
/**
|
||||
* Menu and select items keep `outline-hidden` (the item itself is the moving highlight), so the
|
||||
* accent fill alone was the focus cue, and it stays below 3:1 on the popover. Each item now
|
||||
* draws a 2 px inset ring in --ring while focused (≥ 3:1 in all four sets, contrast.spec.ts).
|
||||
*/
|
||||
const FOCUS_CUE = ["focus:inset-ring-2", "focus:inset-ring-ring"];
|
||||
|
||||
function expectFocusCue(element: HTMLElement) {
|
||||
const classes = element.className.split(/\s+/);
|
||||
for (const token of FOCUS_CUE) expect(classes, token).toContain(token);
|
||||
}
|
||||
|
||||
describe("menu items show a focus cue of at least 3:1 (task A8)", () => {
|
||||
it("every dropdown item kind draws the inset ring while focused", () => {
|
||||
render(
|
||||
<DropdownMenu open>
|
||||
<DropdownMenuTrigger>Mehr</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>Exportieren</DropdownMenuItem>
|
||||
<DropdownMenuItem variant="destructive">Löschen</DropdownMenuItem>
|
||||
<DropdownMenuCheckboxItem checked>Mit Details</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuRadioGroup value="a">
|
||||
<DropdownMenuRadioItem value="a">Liste</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>Weitere</DropdownMenuSubTrigger>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>,
|
||||
);
|
||||
expectFocusCue(screen.getByRole("menuitem", { name: "Exportieren" }));
|
||||
expectFocusCue(screen.getByRole("menuitem", { name: "Löschen" }));
|
||||
expectFocusCue(screen.getByRole("menuitemcheckbox", { name: "Mit Details" }));
|
||||
expectFocusCue(screen.getByRole("menuitemradio", { name: "Liste" }));
|
||||
expectFocusCue(screen.getByRole("menuitem", { name: "Weitere" }));
|
||||
});
|
||||
|
||||
it("a select option draws the inset ring while focused", () => {
|
||||
render(
|
||||
<Select open value="a">
|
||||
<SelectTrigger aria-label="Status">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="a">Aktiv</SelectItem>
|
||||
<SelectItem value="b">Aufgelöst</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>,
|
||||
);
|
||||
for (const option of screen.getAllByRole("option")) expectFocusCue(option);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user