From e32f61f017314c6a0c6b032f76dd89d2e3396c61 Mon Sep 17 00:00:00 2001 From: Marcello Fitton Date: Thu, 26 Feb 2026 14:21:46 -0800 Subject: [PATCH] fix: only scroll active sidebar items into view when not already visible --- .../src/hooks/useScrollActiveItemIntoView.js | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/useScrollActiveItemIntoView.js b/frontend/src/hooks/useScrollActiveItemIntoView.js index 8bc4bd93b..7973b9523 100644 --- a/frontend/src/hooks/useScrollActiveItemIntoView.js +++ b/frontend/src/hooks/useScrollActiveItemIntoView.js @@ -1,5 +1,18 @@ import { useEffect, useRef } from "react"; +/** + * Walks up the DOM tree from `el` to find the nearest ancestor + * that is actually scrollable (i.e. its content overflows). + */ +function findScrollableParent(el) { + let node = el.parentElement; + while (node && node !== document.body) { + if (node.scrollHeight > node.clientHeight) return node; + node = node.parentElement; + } + return null; +} + /** * Hook that scrolls an element into view when it becomes active. * @param {Object} options - The options for the hook. @@ -16,8 +29,25 @@ export default function useScrollActiveItemIntoView({ const ref = useRef(null); useEffect(() => { - if (isActive) { - ref.current.scrollIntoView({ + if (isActive && ref.current) { + const el = ref.current; + + // Skip scrolling if the element is already fully visible within its + // scrollable container. This prevents the sidebar from jumping when + // the active item is already in view. + // Walk up the DOM to find the nearest ancestor that actually scrolls + // (scrollHeight > clientHeight), rather than matching by class name, + // so this works regardless of how the scroll container is styled. + const scrollParent = findScrollableParent(el); + if (scrollParent) { + const parentRect = scrollParent.getBoundingClientRect(); + const elRect = el.getBoundingClientRect(); + const isVisible = + elRect.top >= parentRect.top && elRect.bottom <= parentRect.bottom; + if (isVisible) return; + } + + el.scrollIntoView({ behavior, block, });