'use client' // src/components/MobileTableOfContents.tsx import { useEffect, useRef, useState } from 'react' import { TableOfContents } from './TableOfContents' import type { Heading } from '@/lib/lexical-headings' interface MobileTableOfContentsProps { headings: Heading[] title: string url: string } function flatten( list: Heading[], acc: Array = [], ): Array { for (const h of list) { acc.push(h) if (h.children?.length) flatten(h.children, acc) } return acc } export function MobileTableOfContents({ headings, title, url }: MobileTableOfContentsProps) { const [open, setOpen] = useState(false) const [currentText, setCurrentText] = useState('Jump to section') const panelRef = useRef(null) const backdropRef = useRef(null) const flat = flatten(headings) // Scroll spy: track current heading useEffect(() => { const allHeadingEls = flat .map((h) => document.getElementById(h.id)) .filter((el): el is HTMLElement => !!el) function updateCurrent() { const y = window.scrollY + 140 let current: HTMLElement | null = null for (const el of allHeadingEls) { if (el.offsetTop <= y) current = el } setCurrentText( (current && current.textContent && current.textContent.trim()) || 'Jump to section', ) } let ticking = false function onScroll() { if (ticking) return ticking = true requestAnimationFrame(() => { updateCurrent() ticking = false }) } window.addEventListener('scroll', onScroll, { passive: true }) updateCurrent() return () => window.removeEventListener('scroll', onScroll) // eslint-disable-next-line react-hooks/exhaustive-deps }, [headings]) // Close on Escape useEffect(() => { function onKeyDown(e: KeyboardEvent) { if (e.key === 'Escape' && open) setOpen(false) } document.addEventListener('keydown', onKeyDown) return () => document.removeEventListener('keydown', onKeyDown) }, [open]) // Lock body scroll when open useEffect(() => { document.body.style.overflow = open ? 'hidden' : '' return () => { document.body.style.overflow = '' } }, [open]) return (
{/* Toggle pill */} {/* Backdrop */} ) }