"use client"; import { useCallback, useEffect, useState } from "react"; import { Link } from "@/components/common/Link"; import clsx from "clsx"; import { type Section, type Subsection } from "@/lib/sections"; export function TableOfContents({ tableOfContents }: { tableOfContents: Array
}) { let [currentSection, setCurrentSection] = useState(tableOfContents[0]?.id); let getHeadings = useCallback((tableOfContents: Array
) => { return tableOfContents .flatMap((node) => [node.id, ...node.children.map((child) => child.id)]) .map((id) => { let el = document.getElementById(id); if (!el) return null; let style = window.getComputedStyle(el); let scrollMt = parseFloat(style.scrollMarginTop); let top = window.scrollY + el.getBoundingClientRect().top - scrollMt; return { id, top }; }) .filter((x): x is { id: string; top: number } => x !== null); }, []); useEffect(() => { if (tableOfContents.length === 0) return; const headings = getHeadings(tableOfContents); function onScroll() { const top = window.scrollY; let current = headings[0]?.id; for (const heading of headings) { if (top >= heading.top - 10) { current = heading.id; } else { break; } } setCurrentSection(current); } window.addEventListener("scroll", onScroll, { passive: true }); onScroll(); return () => { window.removeEventListener("scroll", onScroll); }; }, [getHeadings, tableOfContents]); function isActive(section: Section | Subsection) { if (section.id === currentSection) { return true; } if (!section.children) { return false; } return section.children.findIndex(isActive) > -1; } return (
); }