"use client"; import { motion } from "motion/react"; import { type KeyboardEvent, type ReactNode, useState } from "react"; import { SPRING_LAYOUT } from "@/lib/ease"; import { cn } from "@/lib/utils"; export interface ExpandableTab { id: string; label: string; icon: ReactNode; } export interface ExpandableTabsProps { tabs: ExpandableTab[]; /** Controlled selected tab id. */ value?: string; /** Starting selection when uncontrolled. Defaults to the first tab. */ defaultValue?: string; onChange?: (id: string) => void; className?: string; } const NAV_KEYS = new Set(["ArrowRight", "ArrowLeft", "Home", "End"]); /** * A row of icon tabs where the selected one expands to reveal its label, * sliding a shared background pill to match. */ export function ExpandableTabs({ tabs, value, defaultValue, onChange, className }: ExpandableTabsProps) { const [uncontrolled, setUncontrolled] = useState(defaultValue ?? tabs[0]?.id); const isControlled = value !== undefined; const current = isControlled ? value : uncontrolled; const select = (id: string) => { if (!isControlled) setUncontrolled(id); onChange?.(id); }; // Arrow keys, Home, and End move between tabs and select them. const onKeyDown = (event: KeyboardEvent) => { if (!NAV_KEYS.has(event.key)) return; const buttons = Array.from(event.currentTarget.querySelectorAll('[role="tab"]')); const index = buttons.indexOf(document.activeElement as HTMLButtonElement); if (index < 0) return; event.preventDefault(); const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (index + (event.key === "ArrowRight" ? 1 : -1) + buttons.length) % buttons.length; buttons[next].focus(); buttons[next].click(); }; return (
{tabs.map((tab) => { const active = tab.id === current; return ( ); })}
); }