From cd98d6637f4740f9384a3642ab7d32700ddb4394 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Fri, 1 May 2026 09:34:04 +0800 Subject: [PATCH] feat: add ChapterNav component with mobile support --- components/updates/chapter-nav.tsx | 95 ++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 components/updates/chapter-nav.tsx diff --git a/components/updates/chapter-nav.tsx b/components/updates/chapter-nav.tsx new file mode 100644 index 0000000..74cecc9 --- /dev/null +++ b/components/updates/chapter-nav.tsx @@ -0,0 +1,95 @@ +"use client" + +import { useEffect, useState } from "react" +import { ListIcon } from "lucide-react" +import type { UpdateHeading } from "@/lib/updates" + +export function ChapterNav({ + headings, +}: { + headings: UpdateHeading[] +}) { + const [activeId, setActiveId] = useState(headings[0]?.id ?? "") + const [mobileOpen, setMobileOpen] = useState(false) + + useEffect(() => { + const observers: IntersectionObserver[] = [] + + headings.forEach((heading) => { + const element = document.getElementById(heading.id) + if (!element) return + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveId(heading.id) + } + }) + }, + { + rootMargin: "-20% 0px -70% 0px", + }, + ) + observer.observe(element) + observers.push(observer) + }) + + return () => { + observers.forEach((observer) => observer.disconnect()) + } + }, [headings]) + + if (headings.length === 0) return null + + return ( + <> + {/* Mobile toggle button */} + + + {/* Mobile dropdown */} + {mobileOpen && ( +
setMobileOpen(false)} + /> + )} + + + ) +}