import * as React from 'react' ; import { composeEventHandlers , useComposedRefs , useId } from './utils' ; const TabsContext = React . createContext < TabsContextValue > ( null ! ) ; const TabContext = React . createContext < TabContextValue > ( null ! ) ; const PanelContext = React . createContext < number > ( - 1 ) ; interface TabsProps extends Omit < React . ComponentPropsWithRef < 'div' > , 'onChange' > { index ? : number ; onChange ? ( index ) : void ; } export const Tabs = React . forwardRef < HTMLDivElement , TabsProps > ( ( { children , onChange , index : controlledIndex , id , ... props } , forwardedRef ) => { const isControlled = controlledIndex != null ; const { current : wasControlled } = React . useRef ( isControlled ) ; if ( ( ! isControlled && wasControlled ) || ( isControlled && ! wasControlled ) ) { console . warn ( 'Cannot change from controlled to uncontrolled or vice versa.' ) ; } const [ selectedIndex , setSelectedIndex ] = React . useState ( 0 ) ; const context = { tabsId : useId ( id ) , selectedIndex : isControlled ? controlledIndex : selectedIndex , setSelectedIndex : ( index ) => { onChange && onChange ( index ) ; if ( ! isControlled ) { setSelectedIndex ( index ) ; } } , } ; return ( < TabsContext . Provider value = { context } > < div { ... props } data - tabs = "" ref = { forwardedRef } > { children } < /div> < /TabsContext.Provider>); } ) ; interface TabListProps extends React . ComponentPropsWithRef < 'div' > { } export const TabList = React . forwardRef < HTMLDivElement , TabListProps > ( ( { children , ... props } , forwardedRef ) => { const totalTabs = React . Children . count ( children ) ; children = React . Children . map ( children , ( child , index ) => { return < TabContext . Provider value = { { index , totalTabs } } children = { child } / > ; } ) ; return ( < div { ... props } data - tab - list = "" role = "tablist" ref = { forwardedRef } > { children } < /div>); } ) ; interface TabProps extends React . ComponentPropsWithRef < 'button' > { } export const Tab = React . forwardRef < HTMLButtonElement , TabProps > ( ( { children , onClick , onKeyDown , disabled , ... props } , forwardedRef ) => { const { index , totalTabs } = React . useContext ( TabContext ) ; const { tabsId , selectedIndex , setSelectedIndex } = React . useContext ( TabsContext ) ; const selected = index === selectedIndex ; const mountedRef = React . useRef ( false ) ; const tabRef = React . useRef < HTMLButtonElement > ( null ) ; const ref = useComposedRefs ( tabRef , forwardedRef ) ; React . useEffect ( ( ) => { if ( mountedRef . current && selected ) { tabRef . current ?. focus ( ) ; } mountedRef . current = true ; } , [ selected ] ) ; function handleClick ( ) { setSelectedIndex ( index ) ; } function selectFirstTab ( ) { setSelectedIndex ( 0 ) ; } function selectLastTab ( ) { setSelectedIndex ( totalTabs - 1 ) ; } function selectPreviousTab ( ) { if ( selectedIndex != null && selectedIndex !== 0 ) { setSelectedIndex ( selectedIndex - 1 ) ; } } function selectNextTab ( ) { if ( selectedIndex != null && selectedIndex < totalTabs - 1 ) { setSelectedIndex ( selectedIndex + 1 ) ; } } function handleKeyDown ( event ) { switch ( event . key ) { case 'Home' : selectFirstTab ( ) ; break ; case 'End' : selectLastTab ( ) ; break ; case 'ArrowLeft' : selectPreviousTab ( ) ; break ; case 'ArrowRight' : selectNextTab ( ) ; break ; default : break ; } } return ( < button role = "tab" { ... props } id = { `tabs-${ tabsId }-tab-${ index }` } aria - controls = { `tabs-${ tabsId }-panel-${ index }` } aria - selected = { selected } disabled = { disabled } data - tab = "" data - selected = { selected ? '' : undefined } onClick = { composeEventHandlers ( onClick , handleClick ) } onKeyDown = { composeEventHandlers ( onKeyDown , handleKeyDown ) } ref = { ref } tabIndex = { selected ? 0 : - 1 } > { children } < /button>); } ) ; interface TabPanelsProps extends React . ComponentPropsWithRef < 'div' > { } export const TabPanels = React . forwardRef < HTMLDivElement , TabPanelsProps > ( ( { children , ... props } , forwardedRef ) => { children = React . Children . map ( children , ( child , index ) => { return < PanelContext . Provider value = { index } children = { child } / > ; } ) ; return ( < div { ... props } data - tab - panels = "" ref = { forwardedRef } > { children } < /div>); } ) ; interface TabPanelProps extends React . ComponentPropsWithRef < 'div' > { } export const TabPanel = React . forwardRef < HTMLDivElement , TabPanelProps > ( ( { children , ... props } , forwardedRef ) => { const index = React . useContext ( PanelContext ) ; const { tabsId , selectedIndex } = React . useContext ( TabsContext ) ; const selected = selectedIndex === index ; return ( < div role = "tabpanel" { ... props } id = { `tabs-${ tabsId }-tab-${ index }` } aria - labelledby = { `tabs-${ tabsId }-panel-${ index }` } hidden = { ! selected } data - tab - panel = "" ref = { forwardedRef } > { children } < /div>); } ) ; interface TabsContextValue { tabsId : string | undefined ; selectedIndex : number | undefined ; setSelectedIndex : ( index : number ) => void ; } interface TabContextValue { index : number ; totalTabs : number ; }