Files
mcpedia/apps/web/app/components/TOC.tsx
T

55 lines
1.6 KiB
TypeScript
Raw Normal View History

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import GithubSlugger from "github-slugger";
interface TOCEntry {
id: string;
text: string;
level: number;
}
/** Extract h2/h3 headings from markdown and render as a clickable TOC. The
* heading `id`s are generated by rehype-slug (via github-slugger) in Markdown.tsx,
* so the same slugger is used here to produce matching anchors. */
export default function TOC({ source }: { source: string }) {
const [toc, setToc] = useState<TOCEntry[]>([]);
useEffect(() => {
const slugger = new GithubSlugger();
const lines = source.split("\n");
const entries: TOCEntry[] = [];
for (const line of lines) {
const m = line.match(/^(#{2,3})\s+(.+?)\s*#*\s*$/);
if (m) {
const level = m[1].length;
const text = m[2].trim();
entries.push({ id: slugger.slug(text), text, level });
}
}
setToc(entries);
}, [source]);
if (toc.length === 0) return null;
return (
<nav className="mb-6 text-sm">
<h3 className="text-xs font-semibold text-zinc-500 uppercase mb-2">
Contents
</h3>
<ul className="space-y-1">
{toc.map((entry) => (
<li key={entry.id} className={entry.level === 3 ? "ml-3" : ""}>
<Link
href={`#${entry.id}`}
className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-200 hover:underline"
>
{entry.text}
</Link>
</li>
))}
</ul>
</nav>
);
}