New source found from dndbeyond.com

This commit is contained in:
2026-07-08 01:00:09 -07:00
parent 9a983a6d7b
commit dcefa0d2f2
2865 changed files with 222467 additions and 49053 deletions
+73 -16
View File
@@ -1,11 +1,30 @@
import clsx from "clsx";
import { FC, HTMLAttributes, ReactNode, useState } from "react";
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;
@@ -15,44 +34,79 @@ interface TabItem {
interface TabListProps extends HTMLAttributes<HTMLElement> {
tabs: Array<TabItem | null>;
variant?: "default" | "collapse" | "toggle";
variant?: "default" | "collapse" | "fullwidth";
defaultActiveId?: string;
maxNavItemsShow?: number;
onChangeTab?: (id: string) => void;
themed?: boolean;
forceShow?: boolean;
}
export const TabList: FC<TabListProps> = ({
className,
tabs,
defaultActiveId,
maxNavItemsShow = 7,
variant = "default",
onChangeTab,
themed,
forceShow,
...props
}) => {
const [activeTab, setActiveTab] = useState(
defaultActiveId ? defaultActiveId : tabs[0]?.id
const tabListRef = useRef<HTMLDivElement>(null!);
const [activeTab, setActiveTab] = useState(defaultActiveId || tabs[0]?.id);
const [isTabVisible, setIsTabVisible] = useState(
forceShow ?? activeTab !== ""
);
const [isTabVisible, setIsTabVisible] = useState(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 && variant === "toggle") {
setActiveTab("");
}
if (id !== activeTab) {
setActiveTab(id);
setIsTabVisible(true);
if (onChangeTab) {
onChangeTab(id);
}
}
};
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);
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 (
<div {...props}>
<div
className={clsx([
styles.tabList,
styles[variant],
themed && styles.themed,
className,
])}
ref={tabListRef}
{...props}
>
<menu className={styles.tabs}>
{/* List of visible tabs */}
{visibleTabs.map((tab: TabItem) => (
@@ -62,10 +116,13 @@ export const TabList: FC<TabListProps> = ({
role="radio"
aria-checked={activeTab === tab.id}
onClick={() => handleTabClick(tab.id)}
data-testid={tab.id}
>
{tab.label}
{variant !== "default" && (
<ChevronDown className={!isTabVisible ? styles.closed : ""} />
{isToggleable && (
<ChevronDown
className={clsx([!isTabVisible && styles.closed])}
/>
)}
</button>
</li>