From 7f973e48840eb1ef297cea530b58a559c644a8a7 Mon Sep 17 00:00:00 2001 From: Michael Coker Date: Thu, 15 Oct 2026 15:19:30 -0500 Subject: [PATCH 1/2] fix: update useIsStuck to useEffect for prerendering --- packages/documentation-framework/hooks/useIsStuck.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/documentation-framework/hooks/useIsStuck.js b/packages/documentation-framework/hooks/useIsStuck.js index 20e3f0f47f..6e7f7ffd85 100644 --- a/packages/documentation-framework/hooks/useIsStuck.js +++ b/packages/documentation-framework/hooks/useIsStuck.js @@ -1,10 +1,16 @@ -import { useState, useLayoutEffect, useRef } from 'react'; +import { useState, useEffect, useLayoutEffect, useRef } from 'react'; + +// The prerenderer provides a partial document shim but no createElement. +const useIsomorphicLayoutEffect = + typeof document !== 'undefined' && typeof document.createElement === 'function' + ? useLayoutEffect + : useEffect; export const useIsStuck = (stickyElementId) => { const [isStuck, setIsStuck] = useState(false); const prevIsStuck = useRef(false); - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { const scrollElement = document.getElementById('ws-page-main'); const stickyElement = document.getElementById(stickyElementId); From 66ff5e80d7a600da4531d742b74a70088c4a544b Mon Sep 17 00:00:00 2001 From: Michael Coker Date: Thu, 15 Oct 2026 16:56:27 -0500 Subject: [PATCH 2/2] chore: fix sticky tabs scroll bugs --- .../hooks/useIsStuck.js | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/documentation-framework/hooks/useIsStuck.js b/packages/documentation-framework/hooks/useIsStuck.js index 6e7f7ffd85..429eb6292c 100644 --- a/packages/documentation-framework/hooks/useIsStuck.js +++ b/packages/documentation-framework/hooks/useIsStuck.js @@ -15,25 +15,30 @@ export const useIsStuck = (stickyElementId) => { const stickyElement = document.getElementById(stickyElementId); if (!scrollElement || !stickyElement) { - if (prevIsStuck.current !== false) { - setIsStuck(false); - prevIsStuck.current = false; - } + prevIsStuck.current = false; + setIsStuck(false); return; } const syncFromScroll = () => { - const newIsStuck = scrollElement.scrollTop >= stickyElement.offsetTop; - // Only update state if the value actually changed + // Compare rendered positions: offsetTop may be relative to a different + // ancestor than the scrolling page container. + const scrollportTop = scrollElement.getBoundingClientRect().top + scrollElement.clientTop; + const stickyTop = stickyElement.getBoundingClientRect().top; + const newIsStuck = stickyTop <= scrollportTop + 1; if (prevIsStuck.current !== newIsStuck) { - setIsStuck(newIsStuck); prevIsStuck.current = newIsStuck; + setIsStuck(newIsStuck); } }; syncFromScroll(); scrollElement.addEventListener('scroll', syncFromScroll, { passive: true }); - return () => scrollElement.removeEventListener('scroll', syncFromScroll); + window.addEventListener('resize', syncFromScroll); + return () => { + scrollElement.removeEventListener('scroll', syncFromScroll); + window.removeEventListener('resize', syncFromScroll); + }; }, [stickyElementId]); return isStuck;