seedproject-web/app/src/components/TableOfContents.astro
Carlos Arias 1559ce017d chore: scaffold SeedProject base (Phase 1)
Clean-room copy of the reusable engines from comiida, with all
instance data, secrets, dependencies, and build output excluded:
- app/         Astro theme skeleton (no comiida blog posts; hero image -> placeholder)
- api/         SeedProject PHP framework (no vendor/.env/config.php)
- content-pipeline/  engine only (scripts/admin/prompts; empty runtime state)
- astroagent.config.json + app/.astroagent/skills

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYHWLHihq3v9nxNwoPCKSn
2026-07-04 22:53:10 +00:00

63 lines
1.8 KiB
Text

---
const { items = [] } = Astro.props;
---
{
items.length > 0 && (
<div data-toc>
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">On this page</div>
<ul class="mt-3 space-y-2 border-l border-border">
{items.map((item) => (
<li data-toc-item class="relative" style={`padding-left: ${item.level === 3 ? 24 : 12}px`}>
<a
href={`#${item.id}`}
data-toc-link={item.id}
class="block py-0.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
>
{item.text}
</a>
</li>
))}
</ul>
</div>
)
}
<style>
[data-toc-item].is-active::before {
background: var(--color-primary);
bottom: 0.125rem;
content: "";
left: -1px;
position: absolute;
top: 0.125rem;
width: 1px;
}
</style>
<script>
const toc = document.querySelector("[data-toc]");
if (toc) {
const links = [...toc.querySelectorAll("[data-toc-link]")];
const setActive = (id) => {
links.forEach((link) => {
const active = link.getAttribute("data-toc-link") === id;
link.classList.toggle("text-primary", active);
link.classList.toggle("text-muted-foreground", !active);
link.closest("[data-toc-item]")?.classList.toggle("is-active", active);
});
};
const observer = new IntersectionObserver(
(entries) => {
const visible = entries.filter((entry) => entry.isIntersecting);
if (visible[0]) setActive(visible[0].target.id);
},
{ rootMargin: "-80px 0px -70% 0px" },
);
links.forEach((link) => {
const id = link.getAttribute("data-toc-link");
const heading = id ? document.getElementById(id) : null;
if (heading) observer.observe(heading);
});
}
</script>