Skip to content

Commit 64a38e0

Browse files
rsbhclaude
andcommitted
fix: preserve sidebar scroll position across navigations
Move Layout to layout.tsx and render only Page in page.tsx. Sidebar scroll preserved via module-level variable targeting Apsara Sidebar.Main scrollable element. Remove duplicate border-right and overflow-y from sidebar CSS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 45f1117 commit 64a38e0

File tree

4 files changed

+51
-21
lines changed

4 files changed

+51
-21
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { loadConfig } from '@/lib/config'
2+
import { buildPageTree } from '@/lib/source'
3+
import { getTheme } from '@/themes/registry'
4+
5+
export default function DocsLayout({ children }: { children: React.ReactNode }) {
6+
const config = loadConfig()
7+
const tree = buildPageTree()
8+
const { Layout, className } = getTheme(config.theme?.name)
9+
10+
return (
11+
<Layout config={config} tree={tree} classNames={{ layout: className }}>
12+
{children}
13+
</Layout>
14+
)
15+
}

packages/chronicle/src/app/[[...slug]]/page.tsx

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,27 @@ export default async function DocsPage({ params }: PageProps) {
2626
notFound()
2727
}
2828

29-
const { Layout, Page, className } = getTheme(config.theme?.name)
29+
const { Page } = getTheme(config.theme?.name)
3030

3131
const data = page.data as PageData
3232
const MDXBody = data.body
3333

3434
const tree = buildPageTree()
3535

3636
return (
37-
<Layout config={config} tree={tree} classNames={{ layout: className }}>
38-
<Page
39-
page={{
40-
slug: slug ?? [],
41-
frontmatter: {
42-
title: data.title,
43-
description: data.description,
44-
},
45-
content: <MDXBody components={mdxComponents} />,
46-
toc: data.toc ?? [],
47-
}}
48-
config={config}
49-
tree={tree}
50-
/>
51-
</Layout>
37+
<Page
38+
page={{
39+
slug: slug ?? [],
40+
frontmatter: {
41+
title: data.title,
42+
description: data.description,
43+
},
44+
content: <MDXBody components={mdxComponents} />,
45+
toc: data.toc ?? [],
46+
}}
47+
config={config}
48+
tree={tree}
49+
/>
5250
)
5351
}
5452

packages/chronicle/src/themes/default/Layout.module.css

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616

1717
.sidebar {
1818
width: 260px;
19-
padding: var(--rs-space-5);
20-
border-right: 1px solid var(--rs-color-border-base-primary);
21-
overflow-y: auto;
2219
position: sticky;
2320
top: 0;
2421
height: 100vh;

packages/chronicle/src/themes/default/Layout.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useMemo } from "react";
3+
import { useMemo, useEffect, useRef, useCallback } from "react";
44
import { usePathname } from "next/navigation";
55
import NextLink from "next/link";
66
import { cx } from "class-variance-authority";
@@ -22,8 +22,28 @@ const iconMap: Record<string, React.ReactNode> = {
2222
"method-patch": <MethodBadge method="PATCH" size="micro" />,
2323
};
2424

25+
let savedScrollTop = 0;
26+
2527
export function Layout({ children, config, tree, classNames }: ThemeLayoutProps) {
2628
const pathname = usePathname();
29+
const sidebarRef = useRef<HTMLElement>(null);
30+
31+
const getScrollEl = useCallback(() => {
32+
return sidebarRef.current?.querySelector('[class*="main"]') as HTMLElement | null;
33+
}, []);
34+
35+
useEffect(() => {
36+
const el = getScrollEl();
37+
if (!el) return;
38+
const onScroll = () => { savedScrollTop = el.scrollTop; };
39+
el.addEventListener('scroll', onScroll);
40+
return () => el.removeEventListener('scroll', onScroll);
41+
}, [getScrollEl]);
42+
43+
useEffect(() => {
44+
const el = getScrollEl();
45+
if (el) requestAnimationFrame(() => { el.scrollTop = savedScrollTop; });
46+
}, [pathname, getScrollEl]);
2747

2848
return (
2949
<Flex direction="column" className={cx(styles.layout, classNames?.layout)}>
@@ -55,7 +75,7 @@ export function Layout({ children, config, tree, classNames }: ThemeLayoutProps)
5575
</Navbar.End>
5676
</Navbar>
5777
<Flex className={cx(styles.body, classNames?.body)}>
58-
<Sidebar defaultOpen collapsible={false} className={cx(styles.sidebar, classNames?.sidebar)}>
78+
<Sidebar ref={sidebarRef} defaultOpen collapsible={false} className={cx(styles.sidebar, classNames?.sidebar)}>
5979
<Sidebar.Main>
6080
{tree.children.map((item) => (
6181
<SidebarNode

0 commit comments

Comments
 (0)