From ff409870c4eb9d039d4d2f7eef58910036db0867 Mon Sep 17 00:00:00 2001 From: Bissbert <43237892+Bissbert@users.noreply.github.com> Date: Thu, 16 Jul 2026 18:25:00 +0200 Subject: [PATCH] fix(learn): pin desktop TOC on scroll + mobile bottom-sheet section switcher - desktop TOC rail was never actually sticky on scroll - the grid's items-start shrink-wrapped the TOC cell so position:sticky had no travel room; fixed with lg:self-stretch on the TOC cell (measured pinned at top:80px through scrollY 4000) - mobile "On this page" floating thumb-zone button opens a native bottom sheet (focus trap, Escape/backdrop/link-tap dismiss, focus restore, reduced-motion + both themes), replacing the top-of-article details that scrolled out of reach - one shared IntersectionObserver keeps scroll-spy in sync across desktop rail + mobile sheet 502 tests, browser scroll-verified. --- src/components/learn/TableOfContents.astro | 298 +++++++++++++++++---- src/pages/learn/[...slug].astro | 2 +- 2 files changed, 242 insertions(+), 58 deletions(-) diff --git a/src/components/learn/TableOfContents.astro b/src/components/learn/TableOfContents.astro index d56d47c..4b889e3 100644 --- a/src/components/learn/TableOfContents.astro +++ b/src/components/learn/TableOfContents.astro @@ -10,9 +10,17 @@ * Renders two variants, each responsible for hiding itself at the "wrong" * breakpoint via Tailwind's `lg:` variant, so the page only needs to * include this component once: - * - Mobile ( above the article body. + * - Mobile ( bottom sheet listing + * the same sections. A top-of-article control would scroll out of + * reach on a long article, so this replaces that pattern entirely + * rather than running both (avoids two redundant mobile TOCs). * - Desktop (lg+): a sticky aside rail with scroll-spy highlighting. * + * Both variants share the `.toc-link` class + `data-toc-target` attribute, + * so the single scroll-spy observer below keeps the active-section + * indication in sync in both places at once. + * * The page decides layout/grid placement; this component only owns its own * content and internal responsive visibility. */ @@ -32,18 +40,56 @@ const show = items.length >= 4; {show && ( <> - {/* Mobile: collapsible, closed by default, rendered above the article body */} -
- - On this page - -
+
{/* Desktop: sticky rail with scroll-spy */}