From a80319e6d8f9422446808b5d6c07adfab4f014ae Mon Sep 17 00:00:00 2001 From: ADRIEN D Date: Wed, 20 Aug 2025 16:17:47 +0200 Subject: [PATCH 1/2] feat: add sidebar scroll position persistence --- src/layouts/withSidebar.js | 74 +++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/src/layouts/withSidebar.js b/src/layouts/withSidebar.js index 646ac94..e2c0bc9 100644 --- a/src/layouts/withSidebar.js +++ b/src/layouts/withSidebar.js @@ -1,5 +1,6 @@ -import { useState, useRef, useEffect } from 'react'; +import { useState, useRef, useEffect, useLayoutEffect } from 'react'; import Link from 'next/link'; +import { useRouter } from 'next/router'; import TableOfContents from '../components/TableOfContents'; import { Container } from '../components/Container'; import { Footer } from '../components/Footer'; @@ -20,6 +21,8 @@ export default function WithSidebar(props) { const sidebarEl = useRef(null); const [prevLink, setPrevLink] = useState(null); const [nextLink, setNextLink] = useState(null); + const [isScrollRestored, setIsScrollRestored] = useState(false); + const router = useRouter(); const Sidebar = meta.section && sidebars[meta.section] ? sidebars[meta.section] : null; let childrenWithTOC; @@ -63,18 +66,81 @@ export default function WithSidebar(props) { } }; + const saveSidebarScrollPosition = () => { + if (sidebarEl.current && meta.section) { + const scrollTop = sidebarEl.current.scrollTop; + sessionStorage.setItem(`sidebar-scroll-${meta.section}`, scrollTop.toString()); + } + }; + + const restoreSidebarScrollPosition = () => { + if (sidebarEl.current && meta.section && !isScrollRestored) { + const savedScrollTop = sessionStorage.getItem(`sidebar-scroll-${meta.section}`); + if (savedScrollTop) { + const scrollValue = parseInt(savedScrollTop, 10); + sidebarEl.current.style.scrollBehavior = 'auto'; + sidebarEl.current.scrollTop = scrollValue; + setIsScrollRestored(true); + requestAnimationFrame(() => { + if (sidebarEl.current) { + sidebarEl.current.style.scrollBehavior = ''; + } + }); + } else { + setIsScrollRestored(true); + } + } + }; + useEffect(() => { setPrevNextLinks(); }, []); + useEffect(() => { + setIsScrollRestored(false); + }, [meta.section]); + + useLayoutEffect(() => { + if (sidebarEl.current && meta.section) { + restoreSidebarScrollPosition(); + } + }, [meta.section, isScrollRestored]); + + useEffect(() => { + const sidebarElement = sidebarEl.current; + if (!sidebarElement) return; + + const handleScroll = () => { + saveSidebarScrollPosition(); + }; + + const handleBeforeUnload = () => { + saveSidebarScrollPosition(); + }; + + sidebarElement.addEventListener('scroll', handleScroll); + window.addEventListener('beforeunload', handleBeforeUnload); + + const handleRouteChangeStart = () => { + saveSidebarScrollPosition(); + }; + + router.events.on('routeChangeStart', handleRouteChangeStart); + + return () => { + sidebarElement.removeEventListener('scroll', handleScroll); + window.removeEventListener('beforeunload', handleBeforeUnload); + router.events.off('routeChangeStart', handleRouteChangeStart); + }; + }, [router.events, meta.section]); + return ( <>
Date: Wed, 20 Aug 2025 21:22:06 +0200 Subject: [PATCH 2/2] feat: reset sidebar scroll when switching documentation sections --- src/layouts/withSidebar.js | 43 ++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/layouts/withSidebar.js b/src/layouts/withSidebar.js index e2c0bc9..eb7c7a6 100644 --- a/src/layouts/withSidebar.js +++ b/src/layouts/withSidebar.js @@ -67,15 +67,15 @@ export default function WithSidebar(props) { }; const saveSidebarScrollPosition = () => { - if (sidebarEl.current && meta.section) { + if (sidebarEl.current) { const scrollTop = sidebarEl.current.scrollTop; - sessionStorage.setItem(`sidebar-scroll-${meta.section}`, scrollTop.toString()); + sessionStorage.setItem('sidebar-scroll', scrollTop.toString()); } }; const restoreSidebarScrollPosition = () => { - if (sidebarEl.current && meta.section && !isScrollRestored) { - const savedScrollTop = sessionStorage.getItem(`sidebar-scroll-${meta.section}`); + if (sidebarEl.current && !isScrollRestored) { + const savedScrollTop = sessionStorage.getItem('sidebar-scroll'); if (savedScrollTop) { const scrollValue = parseInt(savedScrollTop, 10); sidebarEl.current.style.scrollBehavior = 'auto'; @@ -93,18 +93,39 @@ export default function WithSidebar(props) { }; useEffect(() => { - setPrevNextLinks(); - }, []); + const getSectionFromPath = (path) => { + const cleanPath = path.split('#')[0].split('?')[0]; + const segments = cleanPath.split('/').filter(Boolean); + return segments[0] || ''; + }; + + const currentSection = getSectionFromPath(router.asPath); + const previousSection = sessionStorage.getItem('current-doc-section'); + + if (previousSection && previousSection !== currentSection) { + sessionStorage.setItem('sidebar-scroll', '0'); + setIsScrollRestored(false); + if (sidebarEl.current) { + sidebarEl.current.style.scrollBehavior = 'auto'; + sidebarEl.current.scrollTop = 0; + requestAnimationFrame(() => { + if (sidebarEl.current) sidebarEl.current.style.scrollBehavior = ''; + }); + } + } + + sessionStorage.setItem('current-doc-section', currentSection); + }, [router.asPath]); useEffect(() => { - setIsScrollRestored(false); - }, [meta.section]); + setPrevNextLinks(); + }, []); useLayoutEffect(() => { - if (sidebarEl.current && meta.section) { + if (sidebarEl.current) { restoreSidebarScrollPosition(); } - }, [meta.section, isScrollRestored]); + }, [isScrollRestored]); useEffect(() => { const sidebarElement = sidebarEl.current; @@ -132,7 +153,7 @@ export default function WithSidebar(props) { window.removeEventListener('beforeunload', handleBeforeUnload); router.events.off('routeChangeStart', handleRouteChangeStart); }; - }, [router.events, meta.section]); + }, [router.events]); return ( <>