import { useEffect, useLayoutEffect, useRef } from "react"; import { useLocation } from "react-router-dom"; /** * Resets the window to the top on client navigation. React Router does not * restore scroll on its own, so without this a short new page would clamp to * wherever the previous (taller) page was scrolled — e.g. landing at the bottom * of a category page after clicking a card far down the home grid. * * Navigating to a *different* page always jumps to the top, even when the URL * carries `?post=`. The destination's own deep-link logic then aligns to * that post by scrolling DOWN from the top, instead of animating UP from the * previous page's bottom scroll — which looked like the page "scrolling up from * the bottom" after tapping a popular-section card. * * A hash (`#post-`, `#categories`, …) is left alone so the target page can * handle its own anchor alignment. A same-page `?post=` change (clicking * another card while already on the list) is also left alone so it doesn't * fight the in-page alignment. */ export function ScrollToTop() { const { pathname, search, hash } = useLocation(); const prevPathname = useRef(pathname); useEffect(() => { if (!("scrollRestoration" in window.history)) return; const previous = window.history.scrollRestoration; window.history.scrollRestoration = "manual"; return () => { window.history.scrollRestoration = previous; }; }, []); useLayoutEffect(() => { const pathnameChanged = prevPathname.current !== pathname; prevPathname.current = pathname; if (hash) return; // Entering a new page: always start at the top (post deep-links align // afterwards from here). if (pathnameChanged) { window.scrollTo({ top: 0, left: 0 }); return; } // Same page, search-only change (e.g. switching sort/filter): reset to top // unless it's an in-page post deep-link, which handles its own alignment. if (!new URLSearchParams(search).has("post")) { window.scrollTo({ top: 0, left: 0 }); } }, [pathname, search, hash]); return null; }