Skip to content

Commit cfff787

Browse files
brabojclaude
andcommitted
feat: add collapsible mobile ToC for in-page navigation
Add an "On this page" details element that lists h2 headings, visible only at <=960px where the sidebar ToC is hidden. Placed after the h1 in every chapter page. Closes #110 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c1d16af commit cfff787

3 files changed

Lines changed: 82 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
interface Heading {
3+
depth: number;
4+
slug: string;
5+
text: string;
6+
}
7+
8+
const { headings = [] } = Astro.props as { headings: Heading[] };
9+
const h2s = headings.filter((h) => h.depth === 2);
10+
---
11+
12+
{h2s.length > 0 && (
13+
<details class="mobile-toc">
14+
<summary>On this page</summary>
15+
<ul>
16+
{h2s.map((h) => (
17+
<li>
18+
<a href={`#${h.slug}`}>{h.text}</a>
19+
</li>
20+
))}
21+
</ul>
22+
</details>
23+
)}

astro-site/src/layouts/DocLayout.astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Header from "../components/Header.astro";
44
import TableOfContents from "../components/TableOfContents.astro";
55
import TutorialLinks from "../components/TutorialLinks.astro";
66
import Footer from "../components/Footer.astro";
7+
import MobileToc from "../components/MobileToc.astro";
78
import site from "../data/site.json";
89
910
interface Props {
@@ -66,6 +67,7 @@ const ogImage = new URL("/tutorial-git/images/og-banner.png", Astro.site);
6667
<div class="resize-handle" data-side="left"></div>
6768
<main class="content" id="main-content">
6869
<h1>{title}</h1>
70+
<MobileToc headings={headings} />
6971
<slot />
7072
</main>
7173
<div class="resize-handle" data-side="right"></div>

astro-site/src/styles/global.css

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,11 @@ a:hover {
572572
color: var(--color-fg-muted);
573573
}
574574

575+
/* Mobile ToC — hidden on desktop, shown when sidebar disappears */
576+
.mobile-toc {
577+
display: none;
578+
}
579+
575580
/* Responsive */
576581
@media (max-width: 960px) {
577582
.site-main {
@@ -583,6 +588,58 @@ a:hover {
583588
.resize-handle {
584589
display: none;
585590
}
591+
592+
.mobile-toc {
593+
display: block;
594+
margin-bottom: var(--space-lg);
595+
border: 1px solid var(--color-border);
596+
border-radius: 4px;
597+
padding: 0;
598+
}
599+
600+
.mobile-toc summary {
601+
padding: var(--space-sm) var(--space-md);
602+
font-weight: 600;
603+
font-size: var(--font-size-base);
604+
color: var(--color-fg);
605+
cursor: pointer;
606+
list-style: none;
607+
}
608+
609+
.mobile-toc summary::after {
610+
content: "\25B6";
611+
float: right;
612+
font-size: 0.7em;
613+
color: var(--color-fg-faint);
614+
transition: transform 0.15s;
615+
}
616+
617+
.mobile-toc[open] summary::after {
618+
transform: rotate(90deg);
619+
}
620+
621+
.mobile-toc summary::-webkit-details-marker {
622+
display: none;
623+
}
624+
625+
.mobile-toc ul {
626+
list-style: none;
627+
padding: 0 var(--space-md) var(--space-sm);
628+
}
629+
630+
.mobile-toc li {
631+
padding: var(--space-xs) 0;
632+
}
633+
634+
.mobile-toc a {
635+
color: var(--color-link);
636+
text-decoration: none;
637+
font-size: var(--font-size-sm);
638+
}
639+
640+
.mobile-toc a:hover {
641+
text-decoration: underline;
642+
}
586643
}
587644

588645
@media (max-width: 768px) {

0 commit comments

Comments
 (0)