redesign: add toc to ArticleContent

This commit is contained in:
Moritz Hölting 2026-03-18 14:31:42 +01:00
parent df43ac617b
commit 90fea1f2f6
2 changed files with 93 additions and 7 deletions

View File

@ -8,18 +8,72 @@ type Props = {
const { entry } = Astro.props; const { entry } = Astro.props;
const { Content } = await render(entry); const { Content, headings } = await render(entry);
--- ---
<Container size="md"> <div class="article-layout">
<!-- Sidebar -->
<aside
class="animate toc font-departure max-w-3xl mx-auto px-5 xl:fixed xl:top-24 xl:right-[max(2rem,calc((100vw-84rem)/2))] xl:h-max xl:w-66"
>
<h3 class="font-semibold text-[0.9rem] opacity-70 mb-2">
On this page
</h3>
<ul class="list-none p-0 m-0">
{
headings.map((h) => (
<li class={`level-${h.depth}`}>
<a href={`#${h.slug}`}>{h.text}</a>
</li>
))
}
</ul>
</aside>
<!-- Main content -->
<Container size="md">
<article class="animate content"> <article class="animate content">
<Content /> <Content />
</article> </article>
</Container> </Container>
</div>
<style>
.toc li {
margin-bottom: 0.4rem;
}
.toc li.level-2 {
margin-left: 0.5rem;
}
.toc li.level-3 {
margin-left: 1rem;
}
.toc a {
opacity: 0.7;
text-decoration: none;
transition: opacity 0.15s ease;
}
.toc a.active {
opacity: 1;
font-weight: 600;
}
.toc a:hover {
opacity: 1;
}
article.content {
grid-area: content;
}
</style>
<style is:global> <style is:global>
article.content :is(h1, h2, h3, h4, h5, h6) { article.content :is(h1, h2, h3, h4, h5, h6) {
font-family: "DepartureMono", monospace; font-family: "DepartureMono", monospace;
scroll-margin-top: 6rem;
} }
article.content a { article.content a {
@ -31,3 +85,33 @@ const { Content } = await render(entry);
text-decoration: underline; text-decoration: underline;
} }
</style> </style>
<script>
const headings = document.querySelectorAll(
"article.content h1[id], article.content h2[id], article.content h3[id]",
);
const tocLinks = document.querySelectorAll(".toc a");
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute("id");
tocLinks.forEach((link) => {
link.classList.toggle(
"active",
link.getAttribute("href") === `#${id}`,
);
});
}
});
},
{
rootMargin: "0px 0px -80% 0px",
threshold: 0.1,
},
);
headings.forEach((h) => observer.observe(h));
</script>

View File

@ -3,9 +3,10 @@ import { cn } from "@/lib/utils";
type Props = { type Props = {
size: "sm" | "md" | "lg" | "xl" | "2xl"; size: "sm" | "md" | "lg" | "xl" | "2xl";
class?: string;
}; };
const { size } = Astro.props; const { size, class: className } = Astro.props;
--- ---
<div <div
@ -16,6 +17,7 @@ const { size } = Astro.props;
size === "lg" && "max-w-5xl", size === "lg" && "max-w-5xl",
size === "xl" && "max-w-7xl", size === "xl" && "max-w-7xl",
size === "2xl" && "max-w-screen-2xl", size === "2xl" && "max-w-screen-2xl",
className,
)} )}
> >
<slot /> <slot />