"use client"; import * as React from "react"; import { cn } from "../../lib/utils"; import { motion, AnimatePresence } from "framer-motion"; export interface TabsProps extends React.HTMLAttributes { defaultValue?: string; value?: string; onValueChange?: (value: string) => void; } interface TabsContextValue { value: string; onValueChange: (value: string) => void; updateIndicator: () => void; indicatorStyle: React.CSSProperties; registerTabTrigger: (value: string, element: HTMLButtonElement | null) => void; registerTabsList: (element: HTMLDivElement | null) => void; } const TabsContext = React.createContext({ value: "", onValueChange: () => {}, updateIndicator: () => {}, indicatorStyle: {}, registerTabTrigger: () => {}, registerTabsList: () => {}, }); const Tabs = React.forwardRef( ({ className, defaultValue, value, onValueChange, children, ...props }, ref) => { const [internalValue, setInternalValue] = React.useState(defaultValue || ""); const [indicatorStyle, setIndicatorStyle] = React.useState({}); const tabsListRef = React.useRef(null); const tabTriggerRefs = React.useRef(new Map()); const controlled = value !== undefined; const currentValue = controlled ? value : internalValue; const registerTabsList = React.useCallback((element: HTMLDivElement | null) => { tabsListRef.current = element; }, []); const registerTabTrigger = React.useCallback((value: string, element: HTMLButtonElement | null) => { if (element) { tabTriggerRefs.current.set(value, element); } else { tabTriggerRefs.current.delete(value); } }, []); const updateIndicator = React.useCallback(() => { if (tabsListRef.current && currentValue) { const activeTab = tabTriggerRefs.current.get(currentValue); if (activeTab) { const tabRect = activeTab.getBoundingClientRect(); const listRect = tabsListRef.current.getBoundingClientRect(); setIndicatorStyle({ left: `${tabRect.left - listRect.left}px`, width: `${tabRect.width}px`, }); } } }, [currentValue]); React.useEffect(() => { updateIndicator(); window.addEventListener("resize", updateIndicator); return () => window.removeEventListener("resize", updateIndicator); }, [updateIndicator]); const handleValueChange = React.useCallback( (newValue: string) => { if (!controlled) setInternalValue(newValue); onValueChange?.(newValue); }, [controlled, onValueChange] ); return (
{children}
); } ); Tabs.displayName = "Tabs"; const TabsList = React.forwardRef>( ({ className, ...props }, ref) => { const { indicatorStyle, registerTabsList } = React.useContext(TabsContext); return (
{ if (typeof ref === "function") ref(el); else if (ref) ref.current = el; registerTabsList(el); }} className={cn( `relative inline-flex h-8 items-center justify-center rounded-full bg-muted text-primary`, className )} {...props} > {props.children}
); } ); TabsList.displayName = "TabsList"; const TabsTrigger = React.forwardRef< HTMLButtonElement, React.ButtonHTMLAttributes & { value: string } >(({ className, value, ...props }, ref) => { const { value: selectedValue, onValueChange, registerTabTrigger, updateIndicator } = React.useContext(TabsContext); const isActive = selectedValue === value; const triggerRef = React.useRef(null); React.useEffect(() => { registerTabTrigger(value, triggerRef.current); return () => registerTabTrigger(value, null); }, [value, registerTabTrigger]); React.useEffect(() => { if (isActive) updateIndicator(); }, [isActive, updateIndicator]); return (