"use client"; import * as React from "react"; export type TabDef = { id: string; label: string; content: React.ReactNode; }; type Props = { defaultTabId: string; tabs: TabDef[]; }; function normalizeHash(hash: string) { return hash.replace(/^#/, "").trim(); } export function TabsClient({ defaultTabId, tabs }: Props) { const tabIds = React.useMemo(() => new Set(tabs.map((t) => t.id)), [tabs]); const [activeId, setActiveId] = React.useState(defaultTabId); React.useEffect(() => { const fromHash = normalizeHash(window.location.hash); if (fromHash && tabIds.has(fromHash)) { setActiveId(fromHash); return; } const base = `${window.location.pathname}${window.location.search}`; window.history.replaceState(null, "", `${base}#${defaultTabId}`); }, [defaultTabId, tabIds]); React.useEffect(() => { const onHashChange = () => { const next = normalizeHash(window.location.hash); if (next && tabIds.has(next)) setActiveId(next); }; window.addEventListener("hashchange", onHashChange); return () => window.removeEventListener("hashchange", onHashChange); }, [tabIds]); const selectTab = React.useCallback((id: string) => { setActiveId(id); const base = `${window.location.pathname}${window.location.search}`; window.history.replaceState(null, "", `${base}#${id}`); }, []); return (
{tabs.map((t) => { const active = t.id === activeId; return ( ); })}
{tabs.map((t) => { const active = t.id === activeId; return ( ); })}
); }