import * as React from "react"; import { useState } from "react"; import { cn } from "@/lib/utils"; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, DropdownMenuPortal, } from "@/components/ui/dropdown-menu"; import { Button } from "@/components/ui/button"; export type DynamicTabItem = { value: string; label: string; }; export type DynamicTabsProps = { /** Full list of available tabs (selected + unselected). */ allTabs: DynamicTabItem[]; /** Values of the tabs currently shown in the bar. */ selectedTabs: string[]; /** Currently active tab value. */ activeTab: string; onTabChange: (value: string) => void; /** Called when the user adds/removes tabs via the overflow dropdown. */ onTabsChange?: (next: string[]) => void; /** Label for the overflow toggle button. */ expandLabel?: string; className?: string; }; export function DynamicTabs({ allTabs, selectedTabs, activeTab, onTabChange, onTabsChange, expandLabel = "More", className, }: DynamicTabsProps) { // Staged selections inside the dropdown (committed on close). const [staged, setStaged] = useState>(new Set()); const [open, setOpen] = useState(false); const selectedSet = new Set(selectedTabs); const handleOpenChange = (next: boolean) => { if (!next && staged.size > 0) { // Commit: append new tabs preserving original order const ordered = allTabs .filter((t) => staged.has(t.value)) .map((t) => t.value); onTabsChange?.([...selectedTabs, ...ordered]); setStaged(new Set()); } setOpen(next); }; const handleToggle = (value: string, checked: boolean) => { if (selectedSet.has(value)) return; // already shown — no-op setStaged((prev) => { const next = new Set(prev); checked ? next.add(value) : next.delete(value); return next; }); }; const hasOverflow = allTabs.some((t) => !selectedSet.has(t.value)); return ( // Outer wrapper is NOT role="tablist" — the dropdown must live outside it
{/* Tab buttons only — valid children of tablist */}
{selectedTabs.map((value) => { const tab = allTabs.find((t) => t.value === value); if (!tab) return null; const isActive = value === activeTab; return ( ); })}
{/* Overflow dropdown lives outside the tablist to satisfy aria-required-children */} {hasOverflow && ( {expandLabel} {allTabs.map((tab) => ( handleToggle(tab.value, checked) } > {tab.label} ))} )}
); } function ChevronDownIcon({ className }: { className?: string }) { return ( ); }