Skip to content

Commit a80319e

Browse files
committed
feat: add sidebar scroll position persistence
1 parent c54ac10 commit a80319e

File tree

1 file changed

+70
-4
lines changed

1 file changed

+70
-4
lines changed

src/layouts/withSidebar.js

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { useState, useRef, useEffect } from 'react';
1+
import { useState, useRef, useEffect, useLayoutEffect } from 'react';
22
import Link from 'next/link';
3+
import { useRouter } from 'next/router';
34
import TableOfContents from '../components/TableOfContents';
45
import { Container } from '../components/Container';
56
import { Footer } from '../components/Footer';
@@ -20,6 +21,8 @@ export default function WithSidebar(props) {
2021
const sidebarEl = useRef(null);
2122
const [prevLink, setPrevLink] = useState(null);
2223
const [nextLink, setNextLink] = useState(null);
24+
const [isScrollRestored, setIsScrollRestored] = useState(false);
25+
const router = useRouter();
2326
const Sidebar =
2427
meta.section && sidebars[meta.section] ? sidebars[meta.section] : null;
2528
let childrenWithTOC;
@@ -63,18 +66,81 @@ export default function WithSidebar(props) {
6366
}
6467
};
6568

69+
const saveSidebarScrollPosition = () => {
70+
if (sidebarEl.current && meta.section) {
71+
const scrollTop = sidebarEl.current.scrollTop;
72+
sessionStorage.setItem(`sidebar-scroll-${meta.section}`, scrollTop.toString());
73+
}
74+
};
75+
76+
const restoreSidebarScrollPosition = () => {
77+
if (sidebarEl.current && meta.section && !isScrollRestored) {
78+
const savedScrollTop = sessionStorage.getItem(`sidebar-scroll-${meta.section}`);
79+
if (savedScrollTop) {
80+
const scrollValue = parseInt(savedScrollTop, 10);
81+
sidebarEl.current.style.scrollBehavior = 'auto';
82+
sidebarEl.current.scrollTop = scrollValue;
83+
setIsScrollRestored(true);
84+
requestAnimationFrame(() => {
85+
if (sidebarEl.current) {
86+
sidebarEl.current.style.scrollBehavior = '';
87+
}
88+
});
89+
} else {
90+
setIsScrollRestored(true);
91+
}
92+
}
93+
};
94+
6695
useEffect(() => {
6796
setPrevNextLinks();
6897
}, []);
6998

99+
useEffect(() => {
100+
setIsScrollRestored(false);
101+
}, [meta.section]);
102+
103+
useLayoutEffect(() => {
104+
if (sidebarEl.current && meta.section) {
105+
restoreSidebarScrollPosition();
106+
}
107+
}, [meta.section, isScrollRestored]);
108+
109+
useEffect(() => {
110+
const sidebarElement = sidebarEl.current;
111+
if (!sidebarElement) return;
112+
113+
const handleScroll = () => {
114+
saveSidebarScrollPosition();
115+
};
116+
117+
const handleBeforeUnload = () => {
118+
saveSidebarScrollPosition();
119+
};
120+
121+
sidebarElement.addEventListener('scroll', handleScroll);
122+
window.addEventListener('beforeunload', handleBeforeUnload);
123+
124+
const handleRouteChangeStart = () => {
125+
saveSidebarScrollPosition();
126+
};
127+
128+
router.events.on('routeChangeStart', handleRouteChangeStart);
129+
130+
return () => {
131+
sidebarElement.removeEventListener('scroll', handleScroll);
132+
window.removeEventListener('beforeunload', handleBeforeUnload);
133+
router.events.off('routeChangeStart', handleRouteChangeStart);
134+
};
135+
}, [router.events, meta.section]);
136+
70137
return (
71138
<>
72139
<Header />
73140
<Container className="flex">
74141
<div
75-
className={`${
76-
opened ? '' : 'hidden'
77-
} dark:bg-dark fixed top-16 left-0 z-20 mr-4 w-64 flex-none bg-white text-sm shadow-lg sm:mr-6 lg:relative lg:top-0 lg:mr-8 lg:block lg:shadow-none xl:mr-10`}
142+
className={`${opened ? '' : 'hidden'
143+
} dark:bg-dark fixed top-16 left-0 z-20 mr-4 w-64 flex-none bg-white text-sm shadow-lg sm:mr-6 lg:relative lg:top-0 lg:mr-8 lg:block lg:shadow-none xl:mr-10`}
78144
>
79145
<div
80146
className="sticky top-0 max-h-[calc(100vh-64px)] overflow-y-auto overscroll-contain px-4 py-10 pt-26 lg:px-0 lg:pt-10"

0 commit comments

Comments
 (0)