"use client"; import { useEffect, useRef, useState } from "react"; import { cn } from "@/components/ui/cn"; export interface SectionLink { id: string; label: string; count?: number | null; } /** * In-page anchor navigation for stacked detail sections (sticky under the header, horizontal scroll on mobile). * Tracks the active section with an IntersectionObserver (the section whose top is closest below the sticky bar) * and keeps the active pill scrolled into view on narrow screens. Props unchanged from the server version. */ export function SectionNav({ items, className }: { items: SectionLink[]; className?: string }) { const list = items.filter((it) => it.count === undefined || it.count === null || it.count > 0); const [active, setActive] = useState(null); const navRef = useRef(null); useEffect(() => { if (typeof IntersectionObserver === "undefined") return; const els = list.map((it) => document.getElementById(it.id)).filter((e): e is HTMLElement => !!e); if (!els.length) return; const ratios = new Map(); const pick = () => { // the first section (in DOM order) whose top is at or above the sticky offset and still on screen wins; // fall back to the most visible one const offset = 120; let best: string | null = null; for (const el of els) { const r = el.getBoundingClientRect(); if (r.top <= offset && r.bottom > offset) { best = el.id; break; } } if (!best) { let max = 0; for (const [id, v] of ratios) if (v > max) [best, max] = [id, v]; } if (best) setActive(best); }; const io = new IntersectionObserver( (entries) => { for (const e of entries) ratios.set(e.target.id, e.isIntersecting ? e.intersectionRatio : 0); pick(); }, { rootMargin: "-120px 0px -40% 0px", threshold: [0, 0.1, 0.25, 0.5, 0.75, 1] }, ); els.forEach((el) => io.observe(el)); const onScroll = () => pick(); window.addEventListener("scroll", onScroll, { passive: true }); pick(); return () => { io.disconnect(); window.removeEventListener("scroll", onScroll); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [list.map((l) => l.id).join("|")]); useEffect(() => { if (!active || !navRef.current) return; const a = navRef.current.querySelector(`a[href="#${active}"]`); a?.scrollIntoView({ block: "nearest", inline: "center", behavior: "smooth" }); }, [active]); if (list.length < 2) return null; return ( ); }