Skip to content

Commit ebfeb50

Browse files
committed
feat: toc auto scroll
1 parent d7f1531 commit ebfeb50

File tree

9 files changed

+57
-11
lines changed

9 files changed

+57
-11
lines changed

packages/theme-default/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"nprogress": "^0.2.0",
5151
"react": "^19.1.1",
5252
"react-dom": "^19.1.1",
53+
"scroll-into-view-if-needed": "^3.1.0",
5354
"shiki": "^3.12.2"
5455
},
5556
"devDependencies": {

packages/theme-default/src/components/Aside/index.scss

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
flex-direction: column;
44
border-left: 1px solid var(--rp-c-divider-light);
55
padding-left: 20px;
6-
7-
max-width: calc(100vh - var(--rp-nav-height) - var(--rp-sidebar-menu-height));
8-
// padding-bottom: 100px;
6+
padding-right: 20px;
97

108
&__title {
119
font-size: 14px;
@@ -36,5 +34,11 @@
3634
flex-direction: column;
3735
flex: 1;
3836
gap: 6px;
37+
38+
// allow scroll when there are too many toc items
39+
@media (max-width: 1280px) {
40+
max-height: 60vh;
41+
overflow: auto scroll;
42+
}
3943
}
4044
}

packages/theme-default/src/components/Aside/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function Aside() {
4242
{outlineTitle}
4343
<ReadPercent size={14} strokeWidth={2} />
4444
</div>
45-
<nav className="rp-aside__toc">
45+
<nav className="rp-aside__toc rspress-scrollbar">
4646
<Toc />
4747
</nav>
4848
<div className="rp-aside__divider" />

packages/theme-default/src/components/SidebarMenu/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,10 @@ export const SidebarMenu = forwardRef(
9191
{uiSwitch?.showAside && (
9292
<button
9393
type="button"
94+
disabled={headers.length === 0}
9495
onClick={e => {
96+
e.preventDefault();
9597
e.stopPropagation();
96-
if (headers.length === 0) return;
9798
openAside();
9899
}}
99100
className="rp-sidebar-menu__right"

packages/theme-default/src/components/SidebarMenu/useClickOutside.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ function useClickOutside(
2020
return;
2121
};
2222
document.addEventListener('mousedown', listener);
23-
document.addEventListener('touchstart', listener);
23+
document.addEventListener('touchend', listener);
2424
return () => {
2525
document.removeEventListener('mousedown', listener);
26-
document.removeEventListener('touchstart', listener);
26+
document.removeEventListener('touchend', listener);
2727
};
2828
}, [ref, handler]);
2929
}

packages/theme-default/src/components/Toc/TocItem.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
renderInlineMarkdown,
77
} from '../../logic/utils';
88
import './TocItem.scss';
9+
import { useEffect, useRef } from 'react';
10+
import scrollIntoView from 'scroll-into-view-if-needed';
911

1012
export const TocItem = ({
1113
header,
@@ -16,9 +18,28 @@ export const TocItem = ({
1618
baseHeaderLevel: number;
1719
active: boolean;
1820
}) => {
21+
const ref = useRef<HTMLAnchorElement>(null);
22+
23+
useEffect(() => {
24+
if (active && ref.current) {
25+
scrollIntoView(ref.current, {
26+
behavior: 'smooth',
27+
block: 'center',
28+
inline: 'center',
29+
boundary: element => {
30+
const isBoundary =
31+
element.classList.contains('rp-doc-layout__aside') ||
32+
element.classList.contains('rspress-doc');
33+
return !isBoundary;
34+
},
35+
});
36+
}
37+
}, [active]);
38+
1939
return (
2040
<Link
2141
href={`#${header.id}`}
42+
ref={ref}
2243
title={parseInlineMarkdownText(header.text)}
2344
className={clsx('rp-toc-item', {
2445
'rp-toc-item--active': active,

packages/theme-default/src/components/Toc/useActiveAnchor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { useLocation } from '@rspress/runtime';
12
import type { Header } from '@rspress/shared';
23
import { useEffect, useMemo, useRef, useState } from 'react';
34

45
const useVisibleAnchors = (headers: Header[]): string[] => {
56
const [visibleAnchors, setVisibleAnchors] = useState<string[]>([]);
7+
const { hash } = useLocation();
68

79
useEffect(() => {
810
const handleScroll = () => {
@@ -27,7 +29,7 @@ const useVisibleAnchors = (headers: Header[]): string[] => {
2729
return () => {
2830
window.removeEventListener('scroll', handleScroll);
2931
};
30-
}, [headers]);
32+
}, [headers, hash]);
3133

3234
return visibleAnchors;
3335
};

packages/theme-default/src/layout/DocLayout/index.scss

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,15 @@
149149
}
150150
.rp-doc-layout {
151151
&__aside {
152+
max-height: calc(
153+
100vh - var(--rp-nav-height) - var(--rp-sidebar-menu-height)
154+
);
155+
152156
position: fixed;
153157
margin-top: var(--rp-sidebar-menu-height);
154158
background-color: color-mix(in srgb, var(--rp-c-bg) 60%, transparent);
155159
backdrop-filter: blur(25px);
156-
padding: 20px 20px 20px 0px;
160+
padding: 20px 0px 20px 0px;
157161
max-width: 100%;
158162

159163
border-radius: var(--rp-radius-small);
@@ -179,8 +183,6 @@
179183
@media (max-width: 960px) {
180184
:root {
181185
--rp-content-padding-x: 24px;
182-
--rp-z-index-nav: 40;
183-
--rp-z-index-sidebar: 70;
184186
}
185187
.rp-doc-layout {
186188
&__sidebar {

pnpm-lock.yaml

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)