fun improvements #1
63
components/table-of-contents.tsx
Normal file
63
components/table-of-contents.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
"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("");
|
||||||
|
|
||||||
|
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 className="bg-white/20 backdrop-blur border border-white/30 rounded-lg p-4 lg:sticky lg:top-8">
|
||||||
|
<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}
|
||||||
|
style={{ paddingLeft: level === 3 ? "0.75rem" : "0" }}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={`#${slug}`}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user