"use client"; import Link from "next/link"; import { createContext, useContext, useId, useState, type KeyboardEvent, type ReactNode } from "react"; import { cn } from "./cn"; interface TabsCtx { value: string; setValue: (v: string) => void; id: string; } const Ctx = createContext(null); /** Underline tabs — controlled or uncontrolled. Arrow keys move focus/selection. */ export function Tabs({ value, defaultValue, onValueChange, children, className }: { value?: string; defaultValue?: string; onValueChange?: (v: string) => void; children: ReactNode; className?: string }) { const [inner, setInner] = useState(defaultValue ?? ""); const id = useId(); const v = value ?? inner; const set = (nv: string) => { setInner(nv); onValueChange?.(nv); }; return (
{children}
); } export function TabList({ children, className, label }: { children: ReactNode; className?: string; label?: string }) { const onKey = (e: KeyboardEvent) => { if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return; const tabs = Array.from(e.currentTarget.querySelectorAll('[role="tab"]')); const i = tabs.findIndex((t) => t === document.activeElement); if (i < 0) return; const n = tabs[(i + (e.key === "ArrowRight" ? 1 : tabs.length - 1)) % tabs.length]; n?.focus(); n?.click(); e.preventDefault(); }; return (
{children}
); } export function Tab({ value, children, count, className }: { value: string; children: ReactNode; count?: number | null; className?: string }) { const ctx = useContext(Ctx); if (!ctx) throw new Error("Tab outside Tabs"); const active = ctx.value === value; return ( ); } export function TabPanel({ value, children, className }: { value: string; children: ReactNode; className?: string }) { const ctx = useContext(Ctx); if (!ctx) throw new Error("TabPanel outside Tabs"); if (ctx.value !== value) return null; return (
{children}
); } /** Link-based tabs (server-friendly; state lives in the URL). */ export function LinkTabs({ items, className }: { items: Array<{ href: string; label: ReactNode; active: boolean; count?: number | null }>; className?: string }) { return ( ); }