'use client'; import { useEffect, useState } from 'react'; type Heading = { id: string; text: string; level: number; }; export function TableOfContents({ headings }: { headings: Heading[] }) { const [activeId, setActiveId] = useState(''); useEffect(() => { const handleScroll = () => { const headingElements = headings.map((heading) => ({ id: heading.id, element: document.getElementById(heading.id), })); // Find the first heading that is currently visible or just above the fold // We look for headings that are above the 150px mark let currentActiveId = ''; for (const { id, element } of headingElements) { if (!element) continue; const rect = element.getBoundingClientRect(); // If the heading is within the top portion of the screen // OR if we haven't found a better one yet, this one 'might' be it // We basically want the *last* heading that has a 'top' value <= some threshold if (rect.top <= 150) { currentActiveId = id; } } // If we are at the very bottom, it's likely the last item if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 50) { if (headings.length > 0) currentActiveId = headings[headings.length - 1].id; } if (currentActiveId) { setActiveId(currentActiveId); } }; window.addEventListener('scroll', handleScroll, { passive: true }); // Trigger once on mount handleScroll(); return () => window.removeEventListener('scroll', handleScroll); }, [headings]); if (headings.length === 0) return null; return ( ); }