UNPKG

@redocly/theme

Version:

Shared UI components lib

281 lines 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useTabs = useTabs; const react_1 = require("react"); function useTabs({ initialTab, totalTabs, containerRef }) { const [activeTab, setActiveTab] = (0, react_1.useState)(initialTab); const [visibleTabs, setVisibleTabs] = (0, react_1.useState)(Array.from({ length: totalTabs }, (_, i) => i)); const [overflowTabs, setOverflowTabs] = (0, react_1.useState)([]); const [allTabsHidden, setAllTabsHidden] = (0, react_1.useState)(false); const tabRefs = (0, react_1.useRef)([]); const tabLabelsRef = (0, react_1.useRef)([]); const resizeTimeoutRef = (0, react_1.useRef)(undefined); const [ready, setReady] = (0, react_1.useState)(false); const hasCalculatedOnce = (0, react_1.useRef)(false); const lastWidthRef = (0, react_1.useRef)(0); const originalOrderRef = (0, react_1.useRef)([]); (0, react_1.useEffect)(() => { originalOrderRef.current = Array.from({ length: totalTabs }, (_, i) => i); }, [totalTabs]); const setTabRef = (0, react_1.useCallback)((element, index) => { tabRefs.current[index] = element; if (element) { const label = element.getAttribute('data-label'); if (label) { tabLabelsRef.current[index] = label; } } }, []); const getTabId = (0, react_1.useCallback)((label, index) => { const cleanLabel = label.replace(/\s+/g, '-').toLowerCase(); return `${cleanLabel}-${index}`; }, []); const focusTab = (index) => { const currentElement = tabRefs.current[index]; if (currentElement) { currentElement.focus(); } }; const onTabSelect = (0, react_1.useCallback)((index) => { var _a; focusTab(index); const label = (_a = tabRefs.current[index]) === null || _a === void 0 ? void 0 : _a.getAttribute('data-label'); if (label) setActiveTab(label); }, []); const onTabClick = (0, react_1.useCallback)((labelOrIndex) => { var _a; let clickedIndex; if (typeof labelOrIndex === 'string') { clickedIndex = tabRefs.current.findIndex((ref) => (ref === null || ref === void 0 ? void 0 : ref.getAttribute('data-label')) === labelOrIndex); if (clickedIndex === -1) return; } else { clickedIndex = labelOrIndex; } if (allTabsHidden) { const label = tabLabelsRef.current[clickedIndex]; if (label) { setActiveTab(label); focusTab(clickedIndex); } return; } if (overflowTabs.includes(clickedIndex)) { const newVisibleTabs = [...visibleTabs]; const newOverflowTabs = [...overflowTabs]; const clickedIdxInOverflow = newOverflowTabs.indexOf(clickedIndex); if (clickedIdxInOverflow !== -1) { newOverflowTabs.splice(clickedIdxInOverflow, 1); } const lastVisible = newVisibleTabs.pop(); if (lastVisible !== undefined) { newOverflowTabs.unshift(lastVisible); } newVisibleTabs.push(clickedIndex); setVisibleTabs(newVisibleTabs); setOverflowTabs(newOverflowTabs); requestAnimationFrame(() => { var _a; const label = (_a = tabRefs.current[clickedIndex]) === null || _a === void 0 ? void 0 : _a.getAttribute('data-label'); if (label) { setActiveTab(label); focusTab(clickedIndex); } }); } else { const label = (_a = tabRefs.current[clickedIndex]) === null || _a === void 0 ? void 0 : _a.getAttribute('data-label'); if (label) { setActiveTab(label); focusTab(clickedIndex); } } }, [visibleTabs, overflowTabs, allTabsHidden]); const handleKeyboard = (0, react_1.useCallback)((event, index) => { let newIndex = index; if (event.key === 'ArrowRight') { newIndex = (index + 1) % totalTabs; } else if (event.key === 'ArrowLeft') { newIndex = (index - 1 + totalTabs) % totalTabs; } else if (event.key === 'Home') { event.preventDefault(); newIndex = 0; } else if (event.key === 'End') { event.preventDefault(); newIndex = totalTabs - 1; } else { return; } onTabSelect(newIndex); }, [totalTabs, onTabSelect]); const calculateVisibleTabs = (0, react_1.useCallback)(() => { const container = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current; if (!container) return; const contentWrapper = container.closest('div'); if (!contentWrapper) { setVisibleTabs(Array.from({ length: totalTabs }, (_, i) => i)); setOverflowTabs([]); setAllTabsHidden(false); return; } const containerWidth = container.offsetWidth - 60; const tabElements = container.querySelectorAll('[role="tab"]'); const moreButtonWidth = 80; const safetyMargin = 20; const tabWidths = Array.from(tabElements).map((el) => el.offsetWidth); const tabLabels = Array.from(tabElements).map((el) => el.getAttribute('data-label') || ''); const tabTypes = Array.from(tabElements).map((el) => el.getAttribute('data-type') || ''); const hasLongLabels = tabLabels.some((label) => label.length > 30); const minVisibleTabs = hasLongLabels ? 1 : 2; const activeTabIndex = tabRefs.current.findIndex((ref) => (ref === null || ref === void 0 ? void 0 : ref.getAttribute('data-label')) === activeTab); let currentWidth = 0; const visible = []; const overflow = []; let minTabsWidth = 0; Array.from({ length: minVisibleTabs }).forEach((_, i) => { if (i < tabWidths.length) { minTabsWidth += tabWidths[i] + (i > 0 ? moreButtonWidth + safetyMargin : 0); } }); if (minTabsWidth > containerWidth) { setVisibleTabs([]); setOverflowTabs(Array.from({ length: totalTabs }, (_, i) => i)); setAllTabsHidden(true); return; } const tabsByType = new Map(); Array.from({ length: totalTabs }).forEach((_, i) => { var _a; const type = tabTypes[i] || 'default'; if (!tabsByType.has(type)) { tabsByType.set(type, []); } (_a = tabsByType.get(type)) === null || _a === void 0 ? void 0 : _a.push(i); }); tabsByType.forEach((tabIndices) => { let typeCurrentWidth = currentWidth; const typeVisible = []; const typeOverflow = []; tabIndices.slice(0, minVisibleTabs).forEach((tabIndex) => { const tabWidth = tabWidths[tabIndex]; const projectedWidth = typeCurrentWidth + tabWidth + (typeVisible.length > 0 ? moreButtonWidth + safetyMargin : 0); if (projectedWidth <= containerWidth) { typeVisible.push(tabIndex); typeCurrentWidth += tabWidth; } else { typeOverflow.push(tabIndex); } }); tabIndices.slice(minVisibleTabs).forEach((tabIndex) => { const tabWidth = tabWidths[tabIndex]; const projectedWidth = typeCurrentWidth + tabWidth + moreButtonWidth + safetyMargin; if (projectedWidth <= containerWidth) { typeVisible.push(tabIndex); typeCurrentWidth += tabWidth; } else { typeOverflow.push(tabIndex); } }); visible.push(...typeVisible); overflow.push(...typeOverflow); currentWidth = typeCurrentWidth; }); if (activeTabIndex !== -1 && !visible.includes(activeTabIndex)) { if (visible.length > 0) { const removed = visible.pop(); if (removed !== undefined) { overflow.unshift(removed); } } visible.push(activeTabIndex); const activeOverflowIndex = overflow.indexOf(activeTabIndex); if (activeOverflowIndex !== -1) overflow.splice(activeOverflowIndex, 1); } setVisibleTabs(visible); setOverflowTabs(overflow); setAllTabsHidden(visible.length === 0); // eslint-disable-next-line react-hooks/exhaustive-deps }, [containerRef, totalTabs]); (0, react_1.useEffect)(() => { if (!(containerRef === null || containerRef === void 0 ? void 0 : containerRef.current)) return; const ensureTabsReady = () => { const allTabsReady = tabRefs.current.length === totalTabs && tabRefs.current.every((tab) => tab === null || tab === void 0 ? void 0 : tab.offsetWidth); if (!allTabsReady) { resizeTimeoutRef.current = requestAnimationFrame(ensureTabsReady); return; } calculateVisibleTabs(); hasCalculatedOnce.current = true; }; resizeTimeoutRef.current = requestAnimationFrame(ensureTabsReady); let resizeTimeout; const handleResize = () => { if (!hasCalculatedOnce.current) return; if (resizeTimeout) { cancelAnimationFrame(resizeTimeout); } resizeTimeout = requestAnimationFrame(() => { if (resizeTimeoutRef.current) { cancelAnimationFrame(resizeTimeoutRef.current); } resizeTimeoutRef.current = requestAnimationFrame(() => { const container = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current; if (!container) return; const currentWidth = container.offsetWidth; if (Math.abs(lastWidthRef.current - currentWidth) > 5) { lastWidthRef.current = currentWidth; calculateVisibleTabs(); } }); }); }; const resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(containerRef.current); window.addEventListener('resize', handleResize); return () => { resizeObserver.disconnect(); window.removeEventListener('resize', handleResize); if (resizeTimeoutRef.current) { cancelAnimationFrame(resizeTimeoutRef.current); } if (resizeTimeout) { cancelAnimationFrame(resizeTimeout); } }; }, [containerRef, totalTabs, calculateVisibleTabs]); (0, react_1.useEffect)(() => { const raf = requestAnimationFrame(() => { setReady(true); calculateVisibleTabs(); }); return () => cancelAnimationFrame(raf); }, [calculateVisibleTabs]); return { activeTab, setActiveTab, setTabRef, onTabClick, handleKeyboard, getTabId, visibleTabs, overflowTabs, ready, allTabsHidden, }; } //# sourceMappingURL=use-tabs.js.map