Files
Webserver/components/mdx/TableOfContents.tsx
Akshay Kolli 014b1836c0
Some checks failed
Deploy Website / build-and-deploy (push) Has been cancelled
Codex fixes
2026-05-25 09:49:40 -04:00

61 lines
2.0 KiB
TypeScript

'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<string>(() => headings[0]?.id ?? '');
useEffect(() => {
const elements = headings
.map((heading) => document.getElementById(heading.id))
.filter((element): element is HTMLElement => Boolean(element));
if (elements.length === 0) return;
const observer = new IntersectionObserver((entries) => {
const visible = entries
.filter((entry) => entry.isIntersecting)
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
if (visible[0]?.target.id) {
setActiveId(visible[0].target.id);
}
}, {
rootMargin: '-20% 0px -65% 0px',
threshold: 0,
});
elements.forEach((element) => observer.observe(element));
return () => observer.disconnect();
}, [headings]);
if (headings.length === 0) return null;
return (
<nav aria-label="Table of contents" className="text-left">
<h4 className="eyebrow mb-4">Contents</h4>
<ul className="space-y-2">
{Array.isArray(headings) && headings.map((heading) => (
<li key={heading.id} className={heading.level > 2 ? 'pl-3' : undefined}>
<a
href={`#${heading.id}`}
className={`block text-[0.82rem] leading-6 transition-colors ${activeId === heading.id
? 'text-ink'
: 'text-muted hover:text-ink'
}`}
>
{heading.text}
</a>
</li>
))}
</ul>
</nav>
);
}