import React, { type ReactNode, useRef } from 'react'; import { useId } from '../../hooks/useId'; import { classNames } from '../../utils'; import { useTabs } from './Tabs'; import styles from './Tabs.module.css'; type Context = { onKeyDown: (event: React.KeyboardEvent) => void; index: number; register: (index: number, ref: TabRef) => void; }; export const TabListContext = React.createContext(null); export const useTabListContext = () => { const context = React.useContext(TabListContext); if (context === null) { throw new Error('useTabListContext should be within a TabListContext'); } return context; }; export type TabRef = React.RefObject; export type TabListProps = { /** * The Tab items to display */ children: ReactNode[]; /** * Whether the TabList should fit the container * @default false */ isFitted?: boolean; /** * className for the element */ className?: string; }; export const TabList = ({ children, isFitted, className }: TabListProps) => { const { activeTabIndex } = useTabs(); const tablistId = useId(); const tabMapRef = useRef([]); const descendants = React.Children.count(children); function register(index: number, ref: TabRef) { tabMapRef.current[index] = ref; } // Only used for keyboard navigation function focusNewTab(newIndex: number) { const newCurrentTabRef = tabMapRef.current[newIndex]; if (newCurrentTabRef) { newCurrentTabRef.current?.focus(); } } function handleKeyDown(event: React.KeyboardEvent) { switch (event.key) { case 'Home': focusNewTab(0); break; case 'End': focusNewTab(descendants - 1); break; case 'ArrowLeft': focusNewTab((activeTabIndex - 1 + descendants) % descendants); break; case 'ArrowRight': focusNewTab((activeTabIndex + 1) % descendants); break; } } return (
{React.Children.map(children, (child, index) => ( {child} ))}
); }; type TabIndicatorProps = { activeTabIndex: number; containerId: string; }; type StyleState = { '--_size-indicator': string; '--_offset-indicator': string; }; function TabIndicator({ activeTabIndex, containerId }: TabIndicatorProps) { const [style, setStyle] = React.useState({ '--_size-indicator': '', '--_offset-indicator': '', }); React.useLayoutEffect(() => { const container = document.getElementById(containerId); const selectedTab = container?.querySelector( '[aria-selected="true"]', ); if (selectedTab) { setStyle({ '--_offset-indicator': `${selectedTab.offsetLeft}px`, '--_size-indicator': `${selectedTab.offsetWidth}px`, }); } }, [activeTabIndex, containerId]); return (
} /> ); }