import clsx from "clsx"; import { useEffect, useMemo, useState } from "react"; import ButtonEvent from "../buttons/ButtonEvent"; interface Item { title: string; url: string; items?: Item[]; } interface Items { items?: Item[]; } interface TocProps { title: string; toc: Items; } export function DashboardTableOfContents({ title, toc }: TocProps) { const itemIds = useMemo( () => toc.items ? toc.items .flatMap((item) => [item.url, item?.items?.map((item) => item.url)]) .flat() .filter(Boolean) .map((id) => id?.split("#")[1]) : [], [toc] ); const activeHeading = useActiveItem(itemIds.filter(Boolean) as string[]); const mounted = useMounted(); if (!toc?.items || !mounted) { return null; } return (

{title}

); } function useActiveItem(itemIds: string[]) { const [activeId, setActiveId] = useState(undefined); useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { setActiveId(entry.target.id ?? undefined); } }); }, { rootMargin: `0% 0% -80% 0%` } ); itemIds?.forEach((id) => { const element = document.getElementById(id); if (element) { observer.observe(element); } }); return () => { itemIds?.forEach((id) => { const element = document.getElementById(id); if (element) { observer.unobserve(element); } }); }; }, [itemIds]); return activeId; } interface TreeProps { tree: Items; level?: number; activeItem?: string; } function Tree({ tree, level = 1, activeItem }: TreeProps) { return tree?.items?.length && level < 3 ? ( ) : null; } function useMounted() { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); return mounted; }