63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
|
||
type VerseEntry = {
|
||
verse: number;
|
||
verse_end?: number;
|
||
reference: string;
|
||
};
|
||
|
||
export function VerseIndex({
|
||
verses,
|
||
chapter,
|
||
}: {
|
||
verses: VerseEntry[];
|
||
chapter: string;
|
||
}) {
|
||
const [activeVerse, setActiveVerse] = useState<number | null>(null);
|
||
|
||
useEffect(() => {
|
||
const observer = new IntersectionObserver(
|
||
(entries) => {
|
||
for (const entry of entries) {
|
||
if (entry.isIntersecting) {
|
||
const id = entry.target.id;
|
||
const num = parseInt(id.replace("verse-", ""));
|
||
if (!isNaN(num)) setActiveVerse(num);
|
||
}
|
||
}
|
||
},
|
||
{ rootMargin: "-20% 0px -60% 0px" },
|
||
);
|
||
|
||
const articles = document.querySelectorAll("article[id^='verse-']");
|
||
articles.forEach((el) => observer.observe(el));
|
||
|
||
return () => observer.disconnect();
|
||
}, []);
|
||
|
||
return (
|
||
<aside className="hidden lg:block w-48 shrink-0 sticky top-[89px] self-start max-h-[calc(100vh-89px)] overflow-y-auto py-8 pr-4">
|
||
<h2 className="text-xs font-bold uppercase tracking-wider text-amber-600 dark:text-amber-500 mb-3 px-2">
|
||
Verses
|
||
</h2>
|
||
<nav className="grid grid-cols-3 gap-0.5">
|
||
{verses.map((v) => (
|
||
<a
|
||
key={v.verse}
|
||
href={`#verse-${v.verse}`}
|
||
className={`px-2 py-1 rounded text-sm text-center transition-colors ${
|
||
activeVerse === v.verse
|
||
? "bg-amber-200 dark:bg-amber-900/40 text-amber-900 dark:text-amber-300 font-medium"
|
||
: "text-zinc-600 dark:text-zinc-400 hover:bg-amber-100 dark:hover:bg-zinc-800 hover:text-zinc-900 dark:hover:text-zinc-200"
|
||
}`}
|
||
>
|
||
{v.verse_end ? `${v.verse}–${v.verse_end}` : v.verse}
|
||
</a>
|
||
))}
|
||
</nav>
|
||
</aside>
|
||
);
|
||
}
|