import { Collapse , Tooltip } from '@material-ui/core' ; import clsx from 'clsx' ; import { Fragment , useEffect , useRef , useState } from 'react' ; import { ExternalLink } from '@/components/icons' ; import { Link } from '@/components/Link' ; import { Header } from '@/constants/toc' ; import { TOCHeader } from '@/context/jupyterBook' ; import { useCurrentPathname } from '@/hooks/useCurrentPathname' ; import { isExternalUrl } from '@/utils/url' ; interface Props { headers : Record < string , TOCHeader > ; rootHeaders : string [ ] ; } /**
 * Constant for applying styles to the title headers.
 */ const HEADER_TITLES = [ Header . Level2 , Header . Level3 ] ; /**
 * Component for rendering the global table of contents list. This uses a
 * recursive render function to render the global TOC data structure since the
 * data is deeply nested.
 */ export function GlobalTableOfContents ( { headers , rootHeaders } ) { const listRef = useRef < HTMLOListElement > ( null ) ; const itemContainerRef = useRef < HTMLDivElement > ( ) ; const itemsRef = useRef ( new Map < string , HTMLElement > ( ) ) ; const [ enabledTooltipSet , setEnabledTooltipSet ] = useState ( new Set < string > ( ) ) ; const currentPathname = useCurrentPathname ( ) ; useEffect ( ( ) => { const nextSet = new Set < string > ( ) ; const computedStyle = itemContainerRef . current && window . getComputedStyle ( itemContainerRef . current ) ; const paddingLeft = parseFloat ( computedStyle ?. paddingLeft ?? '0' ) ; const borderLeft = parseFloat ( computedStyle ?. borderLeft ?? '0' ) ; for ( const [ headerId , node ] of itemsRef . current . entries ( ) ) { const width = borderLeft + paddingLeft + node . offsetWidth ; if ( width > ( listRef . current ?. offsetWidth ?? 0 ) ) { nextSet . add ( headerId ) ; } } setEnabledTooltipSet ( nextSet ) ; } , [ ] ) ; param headerId returns function isLevel1Expanded ( headerId ) { const stack = [ headerId ] ; const rootHeaderSet = new Set ( rootHeaders ) ; while ( stack . length > 0 ) { const currentHeaderId = stack . pop ( ) ! ; if ( currentHeaderId === currentPathname && ! rootHeaderSet . has ( currentHeaderId ) ) { return true ; } for ( const childId of headers [ currentHeaderId ] . children ?? [ ] ) { stack . push ( childId ) ; } } return false ; } param headerId returns function render ( headerId ) { const header = headers [ headerId ] ; const isApiReference = headers [ '/api/stable/index.html' ] . children ?. includes ( header . href ) ; const hasChildren = ! isApiReference && ( header . children ?. length ?? 0 ) > 0 ; const headerLevel = header . level ?? 0 ; const children = hasChildren && headers [ headerId ] . children ?. map ( ( childId ) => render ( childId ) ) ; const isExternal = isExternalUrl ( header . href ) ; const isActive = header . href === currentPathname ; const isExpanded = headerLevel === Header . Level1 && isLevel1Expanded ( headerId ) ; param element function itemRefSetter ( element ) { if ( element ) { itemsRef . current . set ( headerId , element ) ; } else { itemsRef . current . delete ( headerId ) ; } } const contentNode = ( < span className = "truncate overflow-ellipsis" > { [ Header . Level1 , Header . Level3 ] . includes ( headerLevel ) || ! hasChildren ? ( < Link href = { header . href } newTab = { isExternal } ref = { itemRefSetter } > { header . text } < /Link>) : (<p ref={itemRefSetter}>{header.text}</p > ) } < /span>); return ( < Fragment key = { headerId } > < li className = { clsx ( HEADER_TITLES . includes ( headerLevel ) && 'border-l border-black py-1' ) } > { } < div ref = { ( el ) => { if ( headerLevel === Header . Level3 || ( headerLevel === Header . Level2 && ! hasChildren ) ) if ( el && ! itemContainerRef . current ) { itemContainerRef . current = el ; } else if ( ! el && itemContainerRef . current ) { itemContainerRef . current = undefined ; } } } className = { clsx ( 'flex items-center' , HEADER_TITLES . includes ( headerLevel ) && 'border-l-4 py-1 pl-4' , headerLevel === Header . Level1 && [ 'text-base' , ( isActive || isExpanded ) && 'font-bold' , ] , headerLevel === Header . Level2 && [ hasChildren ? 'text-xs font-bold uppercase tracking-widest' : 'text-sm hover:border-napari-primary' , isActive && ! hasChildren ? 'font-semibold border-black' : 'border-transparent' , ] , headerLevel === Header . Level3 && [ 'text-sm hover:border-napari-primary' , isActive ? 'font-semibold border-black' : 'border-transparent' , ] ) } > { } { enabledTooltipSet . has ( headerId ) ? ( < Tooltip arrow placement = "right" classes = { { arrow : 'text-black' , tooltip : 'bg-black max-w-min' , } } title = { < span className = "text-sm whitespace-nowrap" > { header . text } < /span>}> { contentNode } < /Tooltip>) : (contentNode)} { } { isExternal && ( < ExternalLink className = "ml-2 w-3 h-3" color = "#000" / > ) } < /div> < /li> { } { headerLevel === Header . Level1 ? ( < Collapse component = "li" in = { isExpanded } unmountOnExit > < ul > { children } < /ul> < /Collapse>) : (children)} < /Fragment>); } return ( < ul className = "space-y-4" ref = { listRef } > { rootHeaders . map ( ( href ) => render ( href ) ) } < /ul>); }