/** * Table of Contents for v2 technique pages with nested sections. * * Renders a nested list of anchor links matching the H2/H3 section structure. * Uses slugified headings as IDs for scroll targeting. * Receives activeId from parent (TechniquePage) which owns the IntersectionObserver. * Smooth-scrolls to target on click with offset for the sticky title bar. */ import { useCallback } from "react"; import type { BodySectionV2 } from "../api"; export function slugify(text: string): string { return text .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, ""); } interface TableOfContentsProps { sections: BodySectionV2[]; activeId: string; } export default function TableOfContents({ sections, activeId }: TableOfContentsProps) { const handleClick = useCallback((e: React.MouseEvent, targetId: string) => { e.preventDefault(); const el = document.getElementById(targetId); if (el) { el.scrollIntoView({ behavior: "smooth", block: "start" }); // Update URL hash without jumping window.history.replaceState(null, "", `#${targetId}`); } }, []); if (sections.length === 0) return null; return ( ); }