Skip to content

Commit cf769f5

Browse files
committed
Adding a Scroll-To-top Button
1 parent 2bd6189 commit cf769f5

File tree

1 file changed

+120
-44
lines changed

1 file changed

+120
-44
lines changed

src/components/Layout/Toc.tsx

Lines changed: 120 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,58 +6,134 @@ import cx from 'classnames';
66
import {useTocHighlight} from './useTocHighlight';
77
import type {Toc} from '../MDX/TocContext';
88

9+
import {useState, useEffect} from 'react';
10+
11+
function ScrollToTop() {
12+
const [isVisible, setIsVisible] = useState(false);
13+
14+
// 控制按钮显示
15+
const toggleVisibility = () => {
16+
if (window.pageYOffset > 300) {
17+
setIsVisible(true);
18+
} else {
19+
setIsVisible(false);
20+
}
21+
};
22+
23+
// 滚动到顶部
24+
const scrollToTop = () => {
25+
window.scrollTo({
26+
top: 0,
27+
behavior: 'smooth',
28+
});
29+
};
30+
31+
useEffect(() => {
32+
window.addEventListener('scroll', toggleVisibility);
33+
return () => {
34+
window.removeEventListener('scroll', toggleVisibility);
35+
};
36+
}, []);
37+
38+
return (
39+
<>
40+
{isVisible && (
41+
<div
42+
onClick={scrollToTop}
43+
style={{
44+
position: 'fixed',
45+
bottom: '40px',
46+
right: '40px',
47+
cursor: 'pointer',
48+
backgroundColor: '#007bff',
49+
borderRadius: '50%',
50+
width: '40px',
51+
height: '40px',
52+
display: 'flex',
53+
justifyContent: 'center',
54+
alignItems: 'center',
55+
boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
56+
transition: 'all 0.3s ease',
57+
opacity: '0.8',
58+
zIndex: 1000,
59+
'&:hover': {
60+
opacity: 1,
61+
transform: 'translateY(-2px)',
62+
},
63+
}}>
64+
<svg
65+
width="20"
66+
height="20"
67+
viewBox="0 0 24 24"
68+
fill="none"
69+
stroke="white"
70+
strokeWidth="2"
71+
strokeLinecap="round"
72+
strokeLinejoin="round">
73+
<path d="M12 19V5M5 12l7-7 7 7" />
74+
</svg>
75+
</div>
76+
)}
77+
</>
78+
);
79+
}
80+
981
export function Toc({headings}: {headings: Toc}) {
1082
const {currentIndex} = useTocHighlight();
1183
// TODO: We currently have a mismatch between the headings in the document
1284
// and the headings we find in MarkdownPage (i.e. we don't find Recap or Challenges).
1385
// Select the max TOC item we have here for now, but remove this after the fix.
1486
const selectedIndex = Math.min(currentIndex, headings.length - 1);
87+
1588
return (
16-
<nav role="navigation" className="pt-20 sticky top-0 end-0">
17-
{headings.length > 0 && (
18-
<h2 className="mb-3 lg:mb-3 uppercase tracking-wide font-bold text-sm text-secondary dark:text-secondary-dark px-4 w-full">
19-
On this page
20-
</h2>
21-
)}
22-
<div
23-
className="h-full overflow-y-auto ps-4 max-h-[calc(100vh-7.5rem)]"
24-
style={{
25-
overscrollBehavior: 'contain',
26-
}}>
27-
<ul className="space-y-2 pb-16">
28-
{headings.length > 0 &&
29-
headings.map((h, i) => {
30-
if (!h.url && process.env.NODE_ENV === 'development') {
31-
console.error('Heading does not have URL');
32-
}
33-
return (
34-
<li
35-
key={`heading-${h.url}-${i}`}
36-
className={cx(
37-
'text-sm px-2 rounded-s-xl',
38-
selectedIndex === i
39-
? 'bg-highlight dark:bg-highlight-dark'
40-
: null,
41-
{
42-
'ps-4': h?.depth === 3,
43-
hidden: h.depth && h.depth > 3,
44-
}
45-
)}>
46-
<a
89+
<>
90+
<nav role="navigation" className="pt-20 sticky top-0 end-0">
91+
{headings.length > 0 && (
92+
<h2 className="mb-3 lg:mb-3 uppercase tracking-wide font-bold text-sm text-secondary dark:text-secondary-dark px-4 w-full">
93+
On this page
94+
</h2>
95+
)}
96+
<div
97+
className="h-full overflow-y-auto ps-4 max-h-[calc(100vh-7.5rem)]"
98+
style={{
99+
overscrollBehavior: 'contain',
100+
}}>
101+
<ul className="space-y-2 pb-16">
102+
{headings.length > 0 &&
103+
headings.map((h, i) => {
104+
if (!h.url && process.env.NODE_ENV === 'development') {
105+
console.error('Heading does not have URL');
106+
}
107+
return (
108+
<li
109+
key={`heading-${h.url}-${i}`}
47110
className={cx(
111+
'text-sm px-2 rounded-s-xl',
48112
selectedIndex === i
49-
? 'text-link dark:text-link-dark font-bold'
50-
: 'text-secondary dark:text-secondary-dark',
51-
'block hover:text-link dark:hover:text-link-dark leading-normal py-2'
52-
)}
53-
href={h.url}>
54-
{h.text}
55-
</a>
56-
</li>
57-
);
58-
})}
59-
</ul>
60-
</div>
61-
</nav>
113+
? 'bg-highlight dark:bg-highlight-dark'
114+
: null,
115+
{
116+
'ps-4': h?.depth === 3,
117+
hidden: h.depth && h.depth > 3,
118+
}
119+
)}>
120+
<a
121+
className={cx(
122+
selectedIndex === i
123+
? 'text-link dark:text-link-dark font-bold'
124+
: 'text-secondary dark:text-secondary-dark',
125+
'block hover:text-link dark:hover:text-link-dark leading-normal py-2'
126+
)}
127+
href={h.url}>
128+
{h.text}
129+
</a>
130+
</li>
131+
);
132+
})}
133+
</ul>
134+
</div>
135+
</nav>
136+
<ScrollToTop />
137+
</>
62138
);
63139
}

0 commit comments

Comments
 (0)