import clsx from "clsx"; import { FC, HTMLAttributes, ReactNode, useEffect, useRef, useState, } from "react"; import ChevronDown from "@dndbeyond/fontawesome-cache/svgs/solid/chevron-down.svg"; import { Popover } from "../Popover"; import styles from "./styles.module.css"; /** * This component renders a set of tabs with associated content panels. * It supports different variants for tab behavior, including default, * collapse, and toggle modes. Tabs can be limited in number of visible * items, with overflow handled via a "More" dropdown menu. * ** Variants: * - default: Standard tab behavior where clicking a tab shows its content. * - collapse: Always has a selected tab, but clicking it again collapses the content. * - fullwidth: Tabs stretch to fill the container width. */ interface TabItem { label: ReactNode; content: ReactNode; className?: string; id: string; } interface TabListProps extends HTMLAttributes { tabs: Array; variant?: "default" | "collapse" | "fullwidth"; defaultActiveId?: string; maxNavItemsShow?: number; onChangeTab?: (id: string) => void; themed?: boolean; forceShow?: boolean; } export const TabList: FC = ({ className, tabs, defaultActiveId, maxNavItemsShow = 7, variant = "default", onChangeTab, themed, forceShow, ...props }) => { const tabListRef = useRef(null!); const [activeTab, setActiveTab] = useState(defaultActiveId || tabs[0]?.id); const [isTabVisible, setIsTabVisible] = useState( forceShow ?? activeTab !== "" ); const tabsWithContent = tabs.filter((tab) => tab); const visibleTabs = tabsWithContent.slice(0, maxNavItemsShow); const hiddenTabs = tabsWithContent.slice(maxNavItemsShow); const selectedTab = tabs.find((tab) => tab?.id === activeTab); const isToggleable = variant === "collapse"; const handleTabClick = (id: string) => { if (id === activeTab && variant === "collapse") { setIsTabVisible(!isTabVisible); } if (id !== activeTab) { setActiveTab(id); setIsTabVisible(true); if (onChangeTab) { onChangeTab(id); } } }; useEffect( () => { // Update CSS variable for number of tabs const tabListStyles = tabListRef.current?.style; if (tabListStyles) { tabListStyles.setProperty("--tab-count", tabs.length.toString()); tabListStyles.setProperty( "--tab-active-position", tabs.indexOf(selectedTab as TabItem).toString() ); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [tabs.length, selectedTab] ); return ( {/* List of visible tabs */} {visibleTabs.map((tab: TabItem) => ( handleTabClick(tab.id)} data-testid={tab.id} > {tab.label} {isToggleable && ( )} ))} {/* Toggle and menu for hidden tabs */} {hiddenTabs.length > 0 && ( More} > {hiddenTabs.map((tab: TabItem) => ( handleTabClick(tab.id)} > {tab.label} ))} )} {/* Content of the active tab */} {isTabVisible && activeTab !== "" && selectedTab?.content} ); };