fun improvements (#1)
Some checks failed
Build and Deploy Blog / build-and-deploy-local (push) Failing after 11s

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-03-31 00:50:16 +00:00
parent 902521e1f3
commit 1ba5ee1b41
13 changed files with 424 additions and 66 deletions

View File

@@ -0,0 +1,64 @@
"use client";
import { useEffect, useState } from "react";
import type { Heading } from "@/lib/posts";
interface TableOfContentsProps {
headings: Heading[];
}
export default function TableOfContents({ headings }: TableOfContentsProps) {
const [activeSlug, setActiveSlug] = useState(() => headings[0]?.slug ?? "");
useEffect(() => {
if (headings.length === 0) return;
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
setActiveSlug(entry.target.id);
}
}
},
{ rootMargin: "0% 0% -70% 0%", threshold: 0 }
);
headings.forEach(({ slug }) => {
const el = document.getElementById(slug);
if (el) observer.observe(el);
});
return () => observer.disconnect();
}, [headings]);
if (headings.length === 0) return null;
return (
<nav>
<p className="text-xs font-bold uppercase tracking-widest text-blue-800 mb-3">
Contents
</p>
<ul className="space-y-1.5">
{headings.map(({ slug, text, level }) => (
<li
key={slug}
className={level === 3 ? "pl-3" : ""}
>
<a
href={`#${slug}`}
aria-current={activeSlug === slug ? "true" : undefined}
className={`text-sm leading-snug transition-colors duration-150 ${
activeSlug === slug
? "text-blue-700 font-semibold"
: "text-gray-600 hover:text-blue-600"
}`}
>
{text}
</a>
</li>
))}
</ul>
</nav>
);
}