import { useEffect, useRef, useState, type ReactNode } from "react"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; export interface TabStripItem { id: string; label: string; icon?: ReactNode; /** Small count after the label. */ count?: number; } /** Scrollable underline tabs with overflow arrows. */ export function TabStrip({ tabs, activeTab, onTabChange, isActive, variant = "primary", trailing, className, }: { tabs: TabStripItem[]; activeTab: string; onTabChange: (id: string) => void; isActive?: (id: string) => boolean; variant?: "primary" | "secondary"; /** Sits after the tabs, outside the scroll area. */ trailing?: ReactNode; className?: string; }) { const ref = useRef(null); const { t } = useTranslation(); const [scroll, setScroll] = useState({ left: false, right: false }); useEffect(() => { const el = ref.current; if (!el) return; const update = () => setScroll({ left: el.scrollLeft > 1, right: el.scrollLeft + el.clientWidth < el.scrollWidth - 1, }); const observer = new ResizeObserver(update); observer.observe(el); if (el.firstElementChild) observer.observe(el.firstElementChild); el.addEventListener("scroll", update); update(); return () => { observer.disconnect(); el.removeEventListener("scroll", update); }; }, [tabs]); useEffect(() => { const el = ref.current; if (!el) return; const onWheel = (e: WheelEvent) => { if (e.deltaY === 0) return; e.preventDefault(); el.scrollLeft += e.deltaY; }; el.addEventListener("wheel", onWheel, { passive: false }); return () => el.removeEventListener("wheel", onWheel); }, []); const renderTab = (tab: TabStripItem) => { const active = isActive ? isActive(tab.id) : activeTab === tab.id; return ( ); }; const overflow = scroll.left || scroll.right; return (
{overflow && ( )}
{tabs.map(renderTab)}
{overflow && ( )} {trailing && (
{trailing}
)}
); }