diff --git a/components/Sidebar/index.jsx b/components/Sidebar/index.jsx index 3eb0c6a..c53fa96 100644 --- a/components/Sidebar/index.jsx +++ b/components/Sidebar/index.jsx @@ -1,3 +1,4 @@ +import { useLayoutEffect } from 'react'; import SideBar from '@node-core/ui-components/Containers/Sidebar'; import withIsland from '@doc-kit/generator-react/html/ui/islands/withIsland.jsx'; import { sidebar } from '../../site.json' with { type: 'json' }; @@ -16,14 +17,49 @@ const PrefetchLink = props => ; * * @param {{ pathname: string }} props */ -const Sidebar = ({ pathname }) => ( - -); +const Sidebar = ({ pathname }) => { + // Each navigation is a full page load, so the sidebar always starts at the + // top. Scroll the active link into view (stateless, no storage needed). + useLayoutEffect(() => { + const active = document.querySelector(`aside a[href="${pathname}"]`); + const aside = active?.closest('aside'); + const scroller = + aside && aside.scrollHeight > aside.clientHeight + ? aside + : aside?.parentElement; + + if ( + !scroller || + !active || + scroller.scrollHeight <= scroller.clientHeight + ) { + return; + } + + console.log(pathname); + + const scrollerRect = scroller.getBoundingClientRect(); + const activeRect = active.getBoundingClientRect(); + + // Only scroll if the active link is outside the visible area + if ( + activeRect.top < scrollerRect.top || + activeRect.bottom > scrollerRect.bottom + ) { + scroller.scrollTop += + activeRect.top - scrollerRect.top - scroller.clientHeight / 2; + } + }, [pathname]); + + return ( + + ); +}; export default withIsland(Sidebar, { name: 'Sidebar', on: { idle: true } }); diff --git a/template.html b/template.html index c7ecc11..556c25e 100644 --- a/template.html +++ b/template.html @@ -10,6 +10,20 @@ rel="canonical" href="https://nodejs.org/learn${metadata.path.replace('/index', '')}" /> + ${preloads} ${assets} ${head}