import { useRef, useState, type KeyboardEvent, type ReactNode } from "react"; import "./Tabs.css"; export interface TabDefinition { id: string; label: string; content: ReactNode; badge?: ReactNode; } export interface TabsProps { tabs: TabDefinition[]; defaultTabId?: string; ariaLabel?: string; } export default function Tabs({ tabs, defaultTabId, ariaLabel }: TabsProps) { const initial = defaultTabId && tabs.some((t) => t.id === defaultTabId) ? defaultTabId : tabs[0]?.id; const [activeId, setActiveId] = useState(initial); const buttonRefs = useRef>(new Map()); if (tabs.length === 0) return null; const active = tabs.find((t) => t.id === activeId) ?? tabs[0]; const focusTab = (id: string) => { setActiveId(id); const node = buttonRefs.current.get(id); if (node) node.focus(); }; const handleKeyDown = (event: KeyboardEvent) => { const currentIndex = tabs.findIndex((t) => t.id === active.id); if (currentIndex < 0) return; switch (event.key) { case "ArrowRight": case "ArrowDown": { event.preventDefault(); const next = tabs[(currentIndex + 1) % tabs.length]; focusTab(next.id); return; } case "ArrowLeft": case "ArrowUp": { event.preventDefault(); const prev = tabs[(currentIndex - 1 + tabs.length) % tabs.length]; focusTab(prev.id); return; } case "Home": { event.preventDefault(); focusTab(tabs[0].id); return; } case "End": { event.preventDefault(); focusTab(tabs[tabs.length - 1].id); return; } } }; const panelId = `${ariaLabel ?? "tabs"}-panel`.replace(/\s+/g, "-"); return (
{tabs.map((tab) => { const isActive = tab.id === active.id; return ( ); })}
{active.content}
); }