adrian-altner.com/src/components/TableOfContents.astro
2026-03-30 14:16:43 +02:00

113 lines
2.4 KiB
Text

---
interface Heading {
depth: number;
slug: string;
text: string;
}
interface Props {
headings: Heading[];
}
const { headings } = Astro.props;
---
<nav class="toc">
<p class="toc__label">Table of Content</p>
<ol class="toc__list">
{headings.map((h) => (
<li class={`toc__item toc__item--h${h.depth}`}>
<a href={`#${h.slug}`} class="toc__link">{h.text}</a>
</li>
))}
</ol>
</nav>
<style>
.toc {
/* sticky is handled by the parent .toc-sidebar */
}
.toc__label {
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #888;
margin-bottom: 0.75rem;
padding-bottom: 0.75rem;
border-top: 2px solid #111;
padding-top: 0.75rem;
}
.toc__list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.toc__item--h3 {
padding-left: 0.85rem;
}
.toc__link {
font-size: 0.8rem;
color: #666;
text-decoration: none;
line-height: 1.4;
display: block;
}
.toc__link:hover {
color: var(--accent);
}
.toc__link--active {
color: #111;
font-weight: 500;
}
</style>
<script>
const links = document.querySelectorAll<HTMLAnchorElement>(".toc__link");
const headingIds = [...links].map((a) => a.getAttribute("href")?.slice(1) ?? "");
const headingEls = headingIds
.map((id) => document.getElementById(id))
.filter(Boolean) as HTMLElement[];
function update() {
const nearBottom =
window.scrollY + window.innerHeight >= document.body.scrollHeight - 50;
let activeId = headingIds[0] ?? "";
if (nearBottom) {
activeId = headingIds[headingIds.length - 1] ?? activeId;
} else {
const threshold = window.scrollY + window.innerHeight * 0.25;
for (const el of headingEls) {
if (el.offsetTop <= threshold) activeId = el.id;
}
}
links.forEach((a) => {
const href = a.getAttribute("href")?.slice(1);
a.classList.toggle("toc__link--active", href === activeId);
});
}
window.addEventListener("scroll", update, { passive: true });
update();
links.forEach((a) => {
a.addEventListener("click", (e) => {
e.preventDefault();
const id = a.getAttribute("href")?.slice(1);
document.getElementById(id ?? "")?.scrollIntoView({ behavior: "smooth" });
});
});
</script>