import React, { ReactElement, ReactNode, useRef, useState, useEffect, KeyboardEvent, MouseEvent, } from 'react'; import css from '../../utils/css'; import { TabWrapper, TabList, Tab, TabTitleIcon, TabPanel } from './StyledTabs'; import Icon, { IconName } from '../Icon'; import { focusTab, getTabVariant } from './utils'; import { fromUndefinedable, getOrElse, map } from '../../fp/Option'; import { pipe } from '../../fp/function'; import SubTabs from './SubTabs'; import { CommonProps } from '../common'; interface TabsProps extends Omit { /** * Unique identifier for Tabs container. This will be combined with the id of each Tab child to generate ARIA accessibility attributes. */ id: string; /* * onChange event handler receiving id of upcoming active Tab. */ onChange: (tabId: string | number) => void; /** * Whether inactive tab panels should be removed from the DOM and unmounted in React. */ renderActiveTabPanelOnly?: boolean; /** * Current selected tab id. */ selectedTabId: string | number; /** * List of Tab to be rendered. Each Tab must have an unquie id. */ tabs: { disabled?: boolean; icon?: IconName | ReactElement; id: string | number; panel: ReactNode; title: string; }[]; } const tabPrefix = 'tabs'; const Tabs = ({ renderActiveTabPanelOnly = false, tabs, selectedTabId, onChange, id, className, style, sx = {}, 'data-test-id': dataTestId, }: TabsProps): ReactElement => { const tabRefs = useRef>([]); const [focusTabIndex, setFocusTabIndex] = useState(0); useEffect(() => { setFocusTabIndex(tabs.findIndex(tab => tab.id === selectedTabId)); }, [selectedTabId, tabs]); useEffect(() => { tabRefs.current = tabRefs.current.slice(0, tabs.length); }, [tabs]); const onTabListKeyDown = (e: KeyboardEvent): void => { // Move right if (e.keyCode === 39) { const nextIndex = focusTabIndex + 1; const newFocusIndex = nextIndex >= tabs.length ? 0 : nextIndex; setFocusTabIndex(newFocusIndex); focusTab(tabRefs.current[newFocusIndex]); } // Move left if (e.keyCode === 37) { const nextIndex = focusTabIndex - 1; const newFocusIndex = nextIndex < 0 ? tabs.length - 1 : nextIndex; setFocusTabIndex(newFocusIndex); focusTab(tabRefs.current[newFocusIndex]); } }; return ( {tabs.map((tab, index) => { const active = selectedTabId === tab.id; const variant = getTabVariant({ active, disabled: tab.disabled !== undefined ? tab.disabled : false, }); return ( (focusedTab.id === tab.id ? 0 : -1)), getOrElse(() => -1) )} ref={(el: HTMLButtonElement): void => { tabRefs.current[index] = el; }} onClick={(e: MouseEvent): void => { onChange(tab.id); e.preventDefault(); }} themeVariant={variant} > {tab.icon !== undefined && ( )} {tab.title} ); })} {tabs.map(tab => { const hidden = selectedTabId !== tab.id; return hidden === false || renderActiveTabPanelOnly === false ? ( ) : null; })} ); }; Tabs.Sub = SubTabs; export default Tabs;