import { Scrollspy as ScrollSpy } from '@makotot/ghostui/src/Scrollspy'; import { Link, useLocation, useRouteMeta, useSiteData, useTabMeta } from 'dumi'; import React, { useEffect, useRef, useState, useMemo, type FC, type RefObject } from 'react'; import { Text, List, ListItem, chakra, useColorModeValue } from '@chakra-ui/react'; const Href = chakra(Link); const Toc: FC = () => { const { pathname, search } = useLocation(); const meta = useRouteMeta(); const tabMeta = useTabMeta(); const { loading } = useSiteData(); const prevIndexRef = useRef(0); const [sectionRefs, setSectionRefs] = useState[]>([]); const borderLeftColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200'); // only render h2 ~ h4 const toc = useMemo(() => { let computedToc = meta.toc; if (tabMeta) { computedToc = tabMeta.toc; } // only render h2 ~ h4 return computedToc.filter(({ depth }) => depth > 1 && depth < 4); }, [meta, tabMeta]); useEffect(() => { // wait for page component ready (DOM ready) if (!loading) { // find all valid headings as ref elements const refs = toc.map(({ id }) => ({ // @ts-ignore current: document.getElementById(id) })); setSectionRefs(refs as any); } }, [pathname, search, loading]); return sectionRefs.length ? ( {({ currentElementIndexInViewport }) => { // for keep prev item active when no item in viewport if (currentElementIndexInViewport > -1) prevIndexRef.current = currentElementIndexInViewport; const activeIndex = currentElementIndexInViewport > -1 ? currentElementIndexInViewport : prevIndexRef.current; return ( {toc .filter(({ depth }) => depth > 1 && depth < 4) .map((item, i) => { const link = `${search}#${encodeURIComponent(item.id)}`; return ( {item.title} ); })} ); }} ) : null; }; export default Toc;