From 01ba63c5cf4d0ebd07bb6717ed8a2a537be32b95 Mon Sep 17 00:00:00 2001 From: Michael Coker Date: Fri, 26 Jun 2026 13:47:59 -0500 Subject: [PATCH 1/3] fix(docs-framework): fixes isStickyStuck rendering bugs --- .../hooks/useIsStuck.js | 20 ++++++-- .../documentation-framework/templates/mdx.js | 48 +++++++++++-------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/packages/documentation-framework/hooks/useIsStuck.js b/packages/documentation-framework/hooks/useIsStuck.js index ce50706b73..c1f3b7fad7 100644 --- a/packages/documentation-framework/hooks/useIsStuck.js +++ b/packages/documentation-framework/hooks/useIsStuck.js @@ -1,25 +1,37 @@ -import { useState, useLayoutEffect } from 'react'; +import { useState, useLayoutEffect, useRef } from 'react'; export const useIsStuck = (stickyElementId) => { const [isStuck, setIsStuck] = useState(false); + const lastValueRef = useRef(false); useLayoutEffect(() => { const scrollElement = document.getElementById('ws-page-main'); const stickyElement = document.getElementById(stickyElementId); if (!scrollElement || !stickyElement) { - setIsStuck(false); + if (lastValueRef.current !== false) { + setIsStuck(false); + lastValueRef.current = false; + } return; } + // Get the initial offset of the sticky element relative to the scroll container + const initialTop = stickyElement.offsetTop; + const syncFromScroll = () => { - setIsStuck(scrollElement.scrollTop > stickyElement.getBoundingClientRect().top); + const newIsStuck = scrollElement.scrollTop >= initialTop; + // Only update state if the value actually changed + if (lastValueRef.current !== newIsStuck) { + setIsStuck(newIsStuck); + lastValueRef.current = newIsStuck; + } }; syncFromScroll(); scrollElement.addEventListener('scroll', syncFromScroll, { passive: true }); return () => scrollElement.removeEventListener('scroll', syncFromScroll); - }, []); + }, [stickyElementId]); return isStuck; }; diff --git a/packages/documentation-framework/templates/mdx.js b/packages/documentation-framework/templates/mdx.js index 97d269d6c6..0edb24ec9a 100644 --- a/packages/documentation-framework/templates/mdx.js +++ b/packages/documentation-framework/templates/mdx.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { PageSection, Title, @@ -25,6 +25,31 @@ import { convertToReactComponent } from '@patternfly/ast-helpers'; import { FunctionsTable } from '../components/functionsTable/functionsTable'; import { useIsStuck } from '../hooks/useIsStuck'; +const StickyTabs = React.memo(({ sourceKeys, tabNames, activeSource, path }) => { + const isStickyStuck = useIsStuck('ws-sticky-nav-tabs'); + + return ( + +
+
    + {sourceKeys.map((source, index) => ( +
  • trackEvent('tab_click', 'click_event', source.toUpperCase())} + > + + {tabNames[source]} + +
  • + ))} +
+
+
+ ); +}); +StickyTabs.displayName = 'StickyTabs'; + const MDXChildTemplate = ({ Component, source, toc = [], index = 0, id }) => { const { propComponents = [], @@ -168,7 +193,6 @@ const MDXChildTemplate = ({ Component, source, toc = [], index = 0, id }) => { }; export const MDXTemplate = ({ title, sources = [], path, id, componentsData }) => { - const isStickyStuck = useIsStuck('ws-sticky-nav-tabs'); const hasFeedbackButton = process.env.hasFeedbackButton; const isDeprecated = @@ -310,24 +334,7 @@ export const MDXTemplate = ({ title, sources = [], path, id, componentsData }) = {showTabs && ( - -
-
    - {sourceKeys.map((source, index) => ( -
  • trackEvent('tab_click', 'click_event', source.toUpperCase())} - > - - {tabNames[source]} - -
  • - ))} -
-
-
+ )} {isSinglePage && } @@ -336,6 +343,7 @@ export const MDXTemplate = ({ title, sources = [], path, id, componentsData }) = {sources .map((source, index) => { source.index = index; + source.id = id; return source; }) .map(MDXChildTemplate)} From b34540338bfd5054b9dad88dabedc109bc4cd638 Mon Sep 17 00:00:00 2001 From: Michael Coker Date: Fri, 26 Jun 2026 15:21:35 -0500 Subject: [PATCH 2/3] chore: pr feedback --- packages/documentation-framework/hooks/useIsStuck.js | 10 +++++----- packages/documentation-framework/templates/mdx.js | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/documentation-framework/hooks/useIsStuck.js b/packages/documentation-framework/hooks/useIsStuck.js index c1f3b7fad7..4946181fad 100644 --- a/packages/documentation-framework/hooks/useIsStuck.js +++ b/packages/documentation-framework/hooks/useIsStuck.js @@ -2,16 +2,16 @@ import { useState, useLayoutEffect, useRef } from 'react'; export const useIsStuck = (stickyElementId) => { const [isStuck, setIsStuck] = useState(false); - const lastValueRef = useRef(false); + const prevIsStuck = useRef(false); useLayoutEffect(() => { const scrollElement = document.getElementById('ws-page-main'); const stickyElement = document.getElementById(stickyElementId); if (!scrollElement || !stickyElement) { - if (lastValueRef.current !== false) { + if (prevIsStuck.current !== false) { setIsStuck(false); - lastValueRef.current = false; + prevIsStuck.current = false; } return; } @@ -22,9 +22,9 @@ export const useIsStuck = (stickyElementId) => { const syncFromScroll = () => { const newIsStuck = scrollElement.scrollTop >= initialTop; // Only update state if the value actually changed - if (lastValueRef.current !== newIsStuck) { + if (prevIsStuck.current !== newIsStuck) { setIsStuck(newIsStuck); - lastValueRef.current = newIsStuck; + prevIsStuck.current = newIsStuck; } }; diff --git a/packages/documentation-framework/templates/mdx.js b/packages/documentation-framework/templates/mdx.js index 0edb24ec9a..ecb1d3cde8 100644 --- a/packages/documentation-framework/templates/mdx.js +++ b/packages/documentation-framework/templates/mdx.js @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { PageSection, Title, @@ -343,7 +343,6 @@ export const MDXTemplate = ({ title, sources = [], path, id, componentsData }) = {sources .map((source, index) => { source.index = index; - source.id = id; return source; }) .map(MDXChildTemplate)} From 153b74afb30624264bdeb6a400e0cf3d43de3fa7 Mon Sep 17 00:00:00 2001 From: Michael Coker Date: Fri, 26 Jun 2026 15:49:29 -0500 Subject: [PATCH 3/3] chore: dont store sticky element offsetTop --- packages/documentation-framework/hooks/useIsStuck.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/documentation-framework/hooks/useIsStuck.js b/packages/documentation-framework/hooks/useIsStuck.js index 4946181fad..20e3f0f47f 100644 --- a/packages/documentation-framework/hooks/useIsStuck.js +++ b/packages/documentation-framework/hooks/useIsStuck.js @@ -16,11 +16,8 @@ export const useIsStuck = (stickyElementId) => { return; } - // Get the initial offset of the sticky element relative to the scroll container - const initialTop = stickyElement.offsetTop; - const syncFromScroll = () => { - const newIsStuck = scrollElement.scrollTop >= initialTop; + const newIsStuck = scrollElement.scrollTop >= stickyElement.offsetTop; // Only update state if the value actually changed if (prevIsStuck.current !== newIsStuck) { setIsStuck(newIsStuck);