Files
website/src/components/post/ToC.astro
2026-09-30 16:20:40 +02:00

100 lines
1.9 KiB
Plaintext

---
import type { Heading } from '@/lib/markdoc/types'
type Props = {
headings: Heading[]
min?: number
level?: number
}
const { headings, min = 3, level = 3 } = Astro.props
const entries = headings.filter((heading) => heading.level <= level)
---
{entries.length >= min && (
<nav class="toc" aria-labelledby="toc-title">
<h2 class="title" id="toc-title">
Contents
</h2>
<ol>
{entries.map((heading) => (
<li class:list={['entry', `entry--${heading.level}`]}>
<a href={`#${heading.id}`}>
<span class="n" aria-hidden="true" />
<span class="label">{heading.text}</span>
</a>
</li>
))}
</ol>
</nav>
)}
<style>
.toc {
@mixin border-t var(--border-card), solid, var(--color-border-strong);
counter-reset: toc;
position: sticky;
top: calc(var(--layout-header-height) + var(--size-10));
}
.title {
@mixin typo_label;
@mixin my var(--size-0);
@mixin py var(--size-4);
@mixin border-b var(--border-hairline), solid, var(--color-border-normal);
font-size: var(--type--2);
}
ol {
list-style: none;
}
a {
@mixin py var(--size-3);
@mixin border-b var(--border-hairline), solid, var(--color-border-subtle);
display: grid;
grid-template-columns: 4ch minmax(0, 1fr);
font-family: var(--font-mono);
font-size: var(--type--1);
line-height: var(--leading-2);
color: var(--color-text-primary);
text-decoration: none;
&:hover,
&:focus-visible {
background-color: var(--color-surface-hover);
}
}
.n {
color: var(--color-text-quiet);
}
.entry--2 {
counter-increment: toc;
& .n {
&::before {
content: counter(toc, upper-roman);
}
}
}
.entry--3 {
& a {
@mixin ps var(--size-8);
}
& .label {
color: var(--color-text-secondary);
}
& .n {
&::before {
content: '⟫';
}
}
}
</style>