import { useLayoutEffect, useState } from 'react'; function getOverflowedTabs( tabs: T[], tabsRef: React.RefObject, tabsPanelRef: React.RefObject ) { const container = tabsRef.current; const tabsPanel = tabsPanelRef.current; if (!container || !tabsPanel) return []; const itemWidths = [...tabsPanel.children].map(item => getOuterWidth(item)); const hidableTabs = itemWidths.slice(0, -1); const moreItemsWidth = itemWidths.at(-1) ?? 0; const availableWidth = container.getBoundingClientRect().width - getHorizontalChrome(container) - getHorizontalChrome(tabsPanel); const visibleCount = getVisibleCount(hidableTabs, availableWidth, moreItemsWidth); return visibleCount === tabs.length ? [] : tabs.slice(visibleCount); } function getVisibleCount(tabsWidths: number[], availableWidth: number, moreItemsWidth: number) { const totalWidth = tabsWidths.reduce((sum, width) => sum + width, 0); if (totalWidth <= availableWidth) { return tabsWidths.length; } let visibleItemsWidth = moreItemsWidth; let visibleCount = 0; while (visibleCount < tabsWidths.length && visibleItemsWidth + (tabsWidths[visibleCount] ?? 0) <= availableWidth) { visibleItemsWidth += tabsWidths[visibleCount] ?? 0; visibleCount++; } return Math.max(visibleCount, 1); } function getOuterWidth(element: Element) { const style = window.getComputedStyle(element); return ( element.getBoundingClientRect().width + (Number.parseFloat(style.marginLeft) || 0) + (Number.parseFloat(style.marginRight) || 0) ); } function getHorizontalChrome(element: Element) { const style = window.getComputedStyle(element); return ( (Number.parseFloat(style.borderLeftWidth) || 0) + (Number.parseFloat(style.borderRightWidth) || 0) + (Number.parseFloat(style.paddingLeft) || 0) + (Number.parseFloat(style.paddingRight) || 0) ); } export function useTabsOverflow( tabs: T[], tabsRef: React.RefObject, tabsPanelRef: React.RefObject ): T[] { const [overflowedTabs, setOverflowedTabs] = useState([]); useLayoutEffect(() => { if (!tabsRef.current) return; setOverflowedTabs(getOverflowedTabs(tabs, tabsRef, tabsPanelRef)); const resizeObserver = new ResizeObserver(() => { setOverflowedTabs(getOverflowedTabs(tabs, tabsRef, tabsPanelRef)); }); resizeObserver.observe(tabsRef.current); if (tabsPanelRef.current) { resizeObserver.observe(tabsPanelRef.current); } return () => resizeObserver.disconnect(); }, [tabs, tabsRef, tabsPanelRef]); return overflowedTabs; }