"use client"; import { motion, MotionConfig, type Transition } from "motion/react"; import { createContext, useContext, useId, useState, type ReactNode } from "react"; import { cn } from "@/lib/utils"; type Variant = "pill" | "underline" | "segment"; type Ctx = { value: string; setValue: (v: string) => void; layoutId: string; variant: Variant; }; const TabsCtx = createContext(null); function useTabs() { const ctx = useContext(TabsCtx); if (!ctx) throw new Error("Tabs.* must be used inside "); return ctx; } // Weighty spring — borrowed from dimi.me/lab/animated-tabs. const transition: Transition = { type: "spring", stiffness: 170, damping: 24, mass: 1.2, }; export function Tabs({ defaultValue, value, onValueChange, variant = "pill", children, className, }: { defaultValue?: string; value?: string; onValueChange?: (v: string) => void; variant?: Variant; children: ReactNode; className?: string; }) { const [internal, setInternal] = useState(defaultValue ?? ""); const layoutId = useId(); const controlled = value !== undefined; const current = controlled ? value : internal; const setValue = (v: string) => { if (!controlled) setInternal(v); onValueChange?.(v); }; return (
{children}
); } const listClasses: Record = { pill: "inline-flex items-center gap-1 rounded-full bg-(--color-bg-elev) p-1", underline: "inline-flex items-center gap-1 border-b border-(--color-border)", segment: "inline-flex items-center gap-0 rounded-lg bg-(--color-bg-elev) p-0.5", }; export function TabsList({ children, className }: { children: ReactNode; className?: string }) { const { variant } = useTabs(); return (
{children}
); } export function TabsTrigger({ value, children, className }: { value: string; children: ReactNode; className?: string }) { const { value: current, setValue, layoutId, variant } = useTabs(); const active = current === value; if (variant === "underline") { return ( ); } // Pill + Segment use the same trick: a max-contrast pill slides via layoutId, // text uses `mix-blend-exclusion` so it inverts dynamically against the moving bg. const radius = variant === "pill" ? "rounded-full" : "rounded-md"; return (
{active ? ( ) : null}
); } export function TabsContent({ value, children, className }: { value: string; children: ReactNode; className?: string }) { const { value: current } = useTabs(); if (current !== value) return null; return ( {children} ); }