TabList.tsx 1.1 KB

1234567891011121314151617181920212223
  1. interface TabOption<T extends string> { id: T; label: string }
  2. interface Props<T extends string> {
  3. tabs: Array<TabOption<T>>;
  4. active: T;
  5. onChange: (tab: T) => void;
  6. ariaLabel: string;
  7. className?: string;
  8. }
  9. export function TabList<T extends string>({ tabs, active, onChange, ariaLabel, className = "" }: Props<T>) {
  10. const move = (event: React.KeyboardEvent<HTMLDivElement>) => {
  11. if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
  12. event.preventDefault();
  13. const index = tabs.findIndex((entry) => entry.id === active);
  14. const next = (index + (event.key === "ArrowRight" ? 1 : -1) + tabs.length) % tabs.length;
  15. onChange(tabs[next].id);
  16. event.currentTarget.querySelectorAll<HTMLButtonElement>('[role="tab"]')[next]?.focus();
  17. };
  18. return <div className={className} role="tablist" aria-label={ariaLabel} onKeyDown={move}>
  19. {tabs.map((entry) => <button key={entry.id} type="button" role="tab" tabIndex={active === entry.id ? 0 : -1} aria-selected={active === entry.id} className={active === entry.id ? "active" : ""} onClick={() => onChange(entry.id)}>{entry.label}</button>)}
  20. </div>;
  21. }