"use client"; import { useReducedMotion } from "../motion/Preferences.tsx"; import { createContext, useContext, useId, useState, type KeyboardEvent, type ReactNode, } from "react"; import { AnimatePresence, motion } from "motion/react"; import { EASE_EXPO } from "../motion/easings.ts"; type TabsContextValue = { value: string; setValue: (v: string) => void; layoutId: string; id: string; }; const TabsContext = createContext(null); export function Tabs({ children, value, defaultValue = "", onChange, className = "", }: { children: ReactNode; value?: string; defaultValue?: string; onChange?: (v: string) => void; className?: string; }) { const [internal, setInternal] = useState(defaultValue); const current = value ?? internal; const id = useId(); const setValue = (v: string) => { if (value === undefined) setInternal(v); onChange?.(v); }; return (
{children}
); } export function TabsList({ children, label, className = "", }: { children: ReactNode; label: string; className?: string; }) { const ctx = useContext(TabsContext); const onKeyDown = (e: KeyboardEvent) => { if (!ctx) return; const keys = ["ArrowRight", "ArrowLeft", "Home", "End"]; if (!keys.includes(e.key)) return; const triggers = Array.from( e.currentTarget.querySelectorAll( "[role='tab']:not([disabled])", ), ); const idx = triggers.indexOf(document.activeElement as HTMLButtonElement); if (idx === -1) return; let next = idx; if (e.key === "ArrowRight") next = (idx + 1) % triggers.length; if (e.key === "ArrowLeft") next = (idx - 1 + triggers.length) % triggers.length; if (e.key === "Home") next = 0; if (e.key === "End") next = triggers.length - 1; e.preventDefault(); const target = triggers[next]!; target.focus(); ctx.setValue(target.dataset.value ?? ""); }; return (
{children}
); } export function TabsTrigger({ value, children, className = "", disabled = false, }: { value: string; children: ReactNode; className?: string; disabled?: boolean; }) { const reduce = useReducedMotion(); const ctx = useContext(TabsContext); if (!ctx) throw new Error("TabsTrigger must be used within "); const active = ctx.value === value; return ( ); } export function TabsPanel({ value, children, className = "", }: { value: string; children: ReactNode; className?: string; }) { const reduce = useReducedMotion(); const ctx = useContext(TabsContext); if (!ctx) throw new Error("TabsPanel must be used within "); if (ctx.value !== value) return null; return (
{children}
); }