From 0af8af4b078e3eb2b1917aefd209984a181ddfa7 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso Date: Thu, 13 Nov 2025 15:22:28 +0100 Subject: [PATCH 1/2] Fix active tab visibility when activeTab is modified externally when it's controlled --- packages/lib/src/tabs/Tabs.tsx | 58 ++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/tabs/Tabs.tsx b/packages/lib/src/tabs/Tabs.tsx index 2d028d83d..93e5a6f89 100644 --- a/packages/lib/src/tabs/Tabs.tsx +++ b/packages/lib/src/tabs/Tabs.tsx @@ -118,6 +118,28 @@ const DxcTabs = ({ children, iconPosition = "left", margin, tabIndex = 0 }: Tabs }; }, [activeTabId, childrenArray, iconPosition, innerFocusIndex, tabIndex]); + const scrollToActiveTab = () => { + if (!refTabListContainer.current || !refTabList.current || !(viewWidth < totalTabsWidth)) return; + + const activeTab = refTabList.current.querySelector('[aria-selected="true"]') as HTMLElement; + if (!activeTab) return; + + const containerWidth = refTabListContainer.current.offsetWidth; + const containerScrollLeft = refTabListContainer.current.scrollLeft; + const tabOffsetLeft = activeTab.offsetLeft; + const tabWidth = activeTab.offsetWidth; + + const isTabFullyVisible = + tabOffsetLeft >= containerScrollLeft && tabOffsetLeft + tabWidth <= containerScrollLeft + containerWidth; + + if (!isTabFullyVisible) { + const targetScroll = tabOffsetLeft - 50; + refTabListContainer.current.scrollTo({ + left: Math.max(0, targetScroll), + }); + } + }; + const scrollLimitCheck = () => { const container = refTabListContainer.current; if (container) { @@ -176,7 +198,9 @@ const DxcTabs = ({ children, iconPosition = "left", margin, tabIndex = 0 }: Tabs }; useEffect(() => { - if (refTabList.current) + const container = refTabListContainer.current; + + if (refTabList.current) { setTotalTabsWidth(() => { let total = 0; refTabList.current?.querySelectorAll('[role="tab"]').forEach((tab, index) => { @@ -187,9 +211,39 @@ const DxcTabs = ({ children, iconPosition = "left", margin, tabIndex = 0 }: Tabs }); return total; }); - scrollLimitCheck(); + } + + const handleScroll = () => scrollLimitCheck(); + if (container) { + container.addEventListener("scroll", handleScroll); + } + + return () => { + if (container) { + container.removeEventListener("scroll", handleScroll); + } + }; }, [viewWidth, totalTabsWidth]); + useEffect(() => { + const isControlled = childrenArray.some((child) => typeof child.props.active !== "undefined"); + if (isControlled) { + const activeChild = childrenArray.find((child) => child.props.active && !child.props.disabled); + if (activeChild) { + const newActiveId = activeChild.props.label ?? activeChild.props.tabId ?? ""; + if (newActiveId !== activeTabId) { + setActiveTabId(newActiveId); + } + } + } + + if (activeTabId && viewWidth && viewWidth < totalTabsWidth) { + setTimeout(() => { + scrollToActiveTab(); + }, 100); + } + }, [childrenArray, activeTabId, viewWidth, totalTabsWidth]); + return ( <> From bb52d44b6332f28f0de199f95cca5ad28daf6fd3 Mon Sep 17 00:00:00 2001 From: PelayoFelgueroso Date: Tue, 18 Nov 2025 13:10:18 +0100 Subject: [PATCH 2/2] Resolve the issue with a simpler approach --- packages/lib/src/tabs/Tabs.tsx | 62 +++------------------------------- 1 file changed, 5 insertions(+), 57 deletions(-) diff --git a/packages/lib/src/tabs/Tabs.tsx b/packages/lib/src/tabs/Tabs.tsx index 93e5a6f89..ddf60c375 100644 --- a/packages/lib/src/tabs/Tabs.tsx +++ b/packages/lib/src/tabs/Tabs.tsx @@ -118,28 +118,6 @@ const DxcTabs = ({ children, iconPosition = "left", margin, tabIndex = 0 }: Tabs }; }, [activeTabId, childrenArray, iconPosition, innerFocusIndex, tabIndex]); - const scrollToActiveTab = () => { - if (!refTabListContainer.current || !refTabList.current || !(viewWidth < totalTabsWidth)) return; - - const activeTab = refTabList.current.querySelector('[aria-selected="true"]') as HTMLElement; - if (!activeTab) return; - - const containerWidth = refTabListContainer.current.offsetWidth; - const containerScrollLeft = refTabListContainer.current.scrollLeft; - const tabOffsetLeft = activeTab.offsetLeft; - const tabWidth = activeTab.offsetWidth; - - const isTabFullyVisible = - tabOffsetLeft >= containerScrollLeft && tabOffsetLeft + tabWidth <= containerScrollLeft + containerWidth; - - if (!isTabFullyVisible) { - const targetScroll = tabOffsetLeft - 50; - refTabListContainer.current.scrollTo({ - left: Math.max(0, targetScroll), - }); - } - }; - const scrollLimitCheck = () => { const container = refTabListContainer.current; if (container) { @@ -198,9 +176,7 @@ const DxcTabs = ({ children, iconPosition = "left", margin, tabIndex = 0 }: Tabs }; useEffect(() => { - const container = refTabListContainer.current; - - if (refTabList.current) { + if (refTabList.current) setTotalTabsWidth(() => { let total = 0; refTabList.current?.querySelectorAll('[role="tab"]').forEach((tab, index) => { @@ -211,38 +187,10 @@ const DxcTabs = ({ children, iconPosition = "left", margin, tabIndex = 0 }: Tabs }); return total; }); - } - - const handleScroll = () => scrollLimitCheck(); - if (container) { - container.addEventListener("scroll", handleScroll); - } - - return () => { - if (container) { - container.removeEventListener("scroll", handleScroll); - } - }; - }, [viewWidth, totalTabsWidth]); - - useEffect(() => { - const isControlled = childrenArray.some((child) => typeof child.props.active !== "undefined"); - if (isControlled) { - const activeChild = childrenArray.find((child) => child.props.active && !child.props.disabled); - if (activeChild) { - const newActiveId = activeChild.props.label ?? activeChild.props.tabId ?? ""; - if (newActiveId !== activeTabId) { - setActiveTabId(newActiveId); - } - } - } - - if (activeTabId && viewWidth && viewWidth < totalTabsWidth) { - setTimeout(() => { - scrollToActiveTab(); - }, 100); - } - }, [childrenArray, activeTabId, viewWidth, totalTabsWidth]); + setTimeout(() => { + scrollLimitCheck(); + }, 0); + }, [viewWidth, totalTabsWidth, activeTabId]); return ( <>