import React, { ReactNode, ReactElement, MouseEvent, KeyboardEvent, useRef, useState, useEffect, } from 'react'; import css from '../../utils/css'; import { TabWrapper, TabList, Tab, TabPanel, ActiveTabIndicator, } from './StyledSubTabs'; import { focusTab, getTabVariant, getTabWidth } from './utils'; import { fromUndefinedable, getOrElse, map } from '../../fp/Option'; import { pipe } from '../../fp/function'; import { CommonProps } from '../common'; export interface SubTabsProps extends Omit { /** * Unique identifier for SubTabs container. This will be combined with the id of each SubTab child to generate ARIA accessibility attributes. */ id: string; /* * onChange event handler receiving id of upcoming active SubTab. */ 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; id: string | number; panel: ReactNode; title: string; }[]; } const subTabPrefix = 'subTabs'; const SubTabs = ({ onChange, renderActiveTabPanelOnly = false, selectedTabId, tabs, id, className, style, sx = {}, 'data-test-id': dataTestId, }: SubTabsProps): ReactElement => { const tabRefs = useRef>([]); const [tabWidths, setTabWidths] = useState(); const [indicatorWidth, setIndiCatorWidth] = useState(); const [indicatorLeft, setIndiCatorLeft] = useState(); const [focusTabIndex, setFocusTabIndex] = useState(0); 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]); } }; useEffect(() => { const selectedTabIndex = tabs.findIndex(tab => tab.id === selectedTabId); setFocusTabIndex(selectedTabIndex); if (tabWidths === undefined) return; setIndiCatorWidth(tabWidths[selectedTabIndex]); setIndiCatorLeft( selectedTabIndex === 0 ? '0' : `calc(${tabWidths.slice(0, selectedTabIndex).join(' + ')})` ); }, [ selectedTabId, tabs, setFocusTabIndex, tabWidths, setIndiCatorWidth, setIndiCatorLeft, ]); useEffect(() => { setTimeout(() => { setTabWidths(tabRefs.current.map(tabElement => getTabWidth(tabElement))); }, 200); }, [tabs]); useEffect(() => { tabRefs.current = tabRefs.current.slice(0, tabs.length); }, [tabs]); 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) )} onClick={(e: MouseEvent): void => { onChange(tab.id); e.preventDefault(); }} ref={(el: HTMLButtonElement): void => { tabRefs.current[index] = el; }} > {tab.title} ); })} {tabs.map(tab => { const hidden = selectedTabId !== tab.id; return hidden === false || renderActiveTabPanelOnly === false ? ( ) : null; })} ); }; export default SubTabs;