feat(frontend): add Tabs primitive + useHashTab hook

- useHashTab: pure-TS hook reading window.location.hash, falls back to
  defaultId, hashchange listener cleaned up on unmount
- Tabs: <Tabs items activeId onChange> with tab-underline / tab-underline-active
  recipes and count-pill (rounded-pill exception per DESIGN.md)
- index.css: tab-underline*, tab-count-pill*, alert-*, table-compact recipes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Knacky
2026-06-21 22:10:59 +02:00
parent 89fb38b107
commit bca39dcca7
3 changed files with 112 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
interface TabItem {
id: string;
label: string;
count?: number;
}
interface TabsProps {
items: TabItem[];
activeId: string;
onChange: (id: string) => void;
}
export function Tabs({ items, activeId, onChange }: TabsProps): JSX.Element {
return (
<div
role="tablist"
className="flex items-end border-b border-hairline gap-xs"
>
{items.map((item) => {
const isActive = item.id === activeId;
return (
<button
key={item.id}
role="tab"
aria-selected={isActive}
type="button"
onClick={() => onChange(item.id)}
className={`tab-underline${isActive ? ' tab-underline-active' : ''} pb-xs`}
>
{item.label}
{item.count !== undefined ? (
<span
className={`ml-xxs tab-count-pill${isActive ? ' tab-count-pill-active' : ''}`}
>
{item.count}
</span>
) : null}
</button>
);
})}
</div>
);
}