Page outline: scroll to active item (#3578)

This commit is contained in:
Zeno Kapitein
2025-08-20 15:55:16 +02:00
committed by GitHub
parent ffa866cb99
commit 6217a2ed3b
3 changed files with 34 additions and 12 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Page outline: scroll to active item
@@ -40,6 +40,7 @@ export function PageAside(props: {
'group/aside',
'hidden',
'pt-8',
'pb-4',
'xl:flex',
'xl:max-3xl:chat-open:hidden',
@@ -111,7 +112,7 @@ export function PageAside(props: {
>
<PageAsideHeader context={context} />
{page.layout.outline ? (
<div className="overflow-y-auto border-tint xl:max-2xl:page-api-block:border-t">
<div className="flex shrink flex-col overflow-hidden">
{document ? (
<React.Suspense fallback={null}>
<PageAsideSections document={document} context={context} />
@@ -165,11 +166,7 @@ async function PageAsideSections(props: { document: JSONDocument; context: GitBo
const sections = await getDocumentSections(context, document);
return sections.length > 1 ? (
<div className="-mt-8 pt-8 pb-5 empty:hidden xl:max-2xl:page-api-block:mt-0 xl:max-2xl:page-api-block:p-2">
<ScrollSectionsList sections={sections} />
</div>
) : null;
return sections.length > 1 ? <ScrollSectionsList sections={sections} /> : null;
}
function PageAsideActions(props: {
@@ -199,7 +196,7 @@ function PageAsideActions(props: {
'border-t',
'first:border-none',
'border-tint-subtle',
'py-5',
'pt-5',
'first:pt-0',
'xl:max-2xl:page-api-block:p-5',
'empty:hidden'
@@ -275,7 +272,7 @@ async function PageAsideFooter(props: { context: GitBookSiteContext }) {
'sticky bottom-0 z-10 mt-auto flex flex-col',
'bg-tint-base theme-gradient-tint:bg-gradient-tint theme-gradient:bg-gradient-primary theme-muted:bg-tint-subtle [html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle',
'border-tint-subtle xl:max-2xl:page-api-block:border-t xl:max-2xl:page-api-block:p-2',
'py-4'
'pt-4'
)}
>
{/* Mode Switcher */}
@@ -1,5 +1,4 @@
'use client';
import { motion } from 'framer-motion';
import React from 'react';
import { useScrollActiveId } from '@/components/hooks';
@@ -15,6 +14,11 @@ import { AsideSectionHighlight } from './AsideSectionHighlight';
*/
const SECTION_INTERSECTING_THRESHOLD = 0.9;
/**
* The offset from the top of the page when scrolling to the active item.
*/
const ACTIVE_ITEM_OFFSET = 100;
const springCurve = {
type: 'spring',
stiffness: 700,
@@ -39,10 +43,25 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
enabled,
});
const scrollContainerRef = React.useRef<HTMLUListElement>(null);
const activeItemRef = React.useRef<HTMLLIElement>(null);
React.useEffect(() => {
if (activeId && activeItemRef.current && scrollContainerRef.current) {
scrollContainerRef.current?.scrollTo({
top: activeItemRef.current.offsetTop - ACTIVE_ITEM_OFFSET,
behavior: 'smooth',
});
}
}, [activeId]);
return (
<ul className="flex flex-col border-tint-subtle sidebar-list-line:border-l">
<ul
className="relative flex flex-col overflow-y-auto border-tint-subtle sidebar-list-line:border-l pb-5 xl:max-2xl:page-api-block:mt-0 xl:max-2xl:page-api-block:p-2"
ref={scrollContainerRef}
>
{sections.map((section) => (
<motion.li
<li
key={section.id}
className={tcls(
'flex',
@@ -54,6 +73,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
'mb-0.5',
section.depth > 1 && ['ml-3', 'my-0', 'sidebar-list-line:ml-0']
)}
ref={activeId === section.id ? activeItemRef : null}
>
{activeId === section.id ? (
<AsideSectionHighlight
@@ -148,7 +168,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
{section.title}
</span>
</a>
</motion.li>
</li>
))}
</ul>
);