"use client"; import { useEffect, useState, useRef } from "react"; interface TOCItem { id: string; text: string; level: number; } function useHeadings(): TOCItem[] { const [headings, setHeadings] = useState([]); useEffect(() => { const elements = Array.from( document.querySelectorAll("article h2, article h3") ) as HTMLElement[]; const parsed = elements.map((el) => ({ id: el.id, text: el.textContent ?? "", level: el.tagName === "H2" ? 2 : 3, })); // eslint-disable-next-line react-hooks/set-state-in-effect setHeadings(parsed); }, []); return headings; } export function TableOfContents() { const [activeId, setActiveId] = useState(""); const observerRef = useRef(null); const headings = useHeadings(); useEffect(() => { observerRef.current = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { setActiveId(entry.target.id); } }); }, { rootMargin: "-20% 0px -70% 0px", threshold: 0 } ); const elements = Array.from( document.querySelectorAll("article h2, article h3") ) as HTMLElement[]; elements.forEach((el) => observerRef.current?.observe(el)); return () => observerRef.current?.disconnect(); }, []); if (headings.length === 0) return null; return ( ); }