Untitled
unknown
plain_text
2 months ago
5.9 kB
2
Indexable
import type { FlatMenuItem, FlatMenuItemChildsData } from '@components/SecondaryNavigation/common/types'; import { debounce } from '@utils/debounce'; import { useCallback, useEffect, useRef, useState } from 'react'; export const useAdaptiveTabs = ( tabs: FlatMenuItem[], updateChildsMoreButtonItem: (childsData: FlatMenuItemChildsData) => void ) => { const containerRef = useRef<HTMLDivElement>(null); const buttonBlockRef = useRef<HTMLDivElement>(null); const headingBlockRef = useRef<HTMLDivElement>(null); const [availableWidth, setAvailableWidth] = useState(0); const [tabWidths, setTabWidths] = useState<{ [key: string]: number }>({}); // ✅ Use the extracted function const handleResize = useCallback( debounce(() => { setAvailableWidth( calculateAvailableSpace({ container: containerRef.current, buttonBlock: buttonBlockRef.current, headingBlock: headingBlockRef.current, }) ); }, 50), [] ); useEffect(() => { setAvailableWidth( calculateAvailableSpace({ container: containerRef.current, buttonBlock: buttonBlockRef.current, headingBlock: headingBlockRef.current, }) ); }, []); useEffect(() => { if (!containerRef.current) { return; } const observer = new ResizeObserver(handleResize); observer.observe(containerRef.current); return () => observer.disconnect(); }, [handleResize]); useEffect(() => { if (!containerRef.current) { return; } const tabElements = containerRef.current.querySelectorAll('[data-tab-item]'); const newWidths: { [key: string]: number } = {}; for (const el of tabElements) { const id = el.getAttribute('data-id'); const marginEnd = Number.parseInt(getComputedStyle(el).getPropertyValue('margin-inline-end')) || 0; if (id) { newWidths[id] = el.getBoundingClientRect().width + marginEnd; } } setTabWidths(newWidths); }, []); useEffect(() => { let totalWidth = 0; const hiddenTabsList: typeof tabs = []; const processedTabs = [...tabs]; for (const tab of processedTabs) { totalWidth += tabWidths[tab.id] || 0; if (totalWidth > availableWidth) { hiddenTabsList.push(tab); } } const moreButtonChildRelatedProps = { childIds: hiddenTabsList.map((item) => item.id), childIdsCollection: hiddenTabsList.reduce<Record<string, boolean>>((acc, currentItem) => { acc[currentItem.id] = true; return acc; }, {}), }; updateChildsMoreButtonItem(moreButtonChildRelatedProps); }, [availableWidth, tabWidths, tabs, updateChildsMoreButtonItem]); return { containerRef, buttonBlockRef, headingBlockRef }; }; const MORE_BUTTON_MARGIN_START = 20; const calculateAvailableSpace = ({ container, buttonBlock, headingBlock, }: { container: HTMLDivElement | null; buttonBlock: HTMLDivElement | null; headingBlock: HTMLDivElement | null; }): number => { if (!container || !buttonBlock || !headingBlock) { return 0; } const totalContainerWidth = container.offsetWidth; const buttonBlockWidth = buttonBlock.offsetWidth; const headingTitleBlockWidth = headingBlock.offsetWidth; const headingTitleMarginEnd = Number.parseInt(getComputedStyle(headingBlock).getPropertyValue('margin-inline-start')) || 0; const moreButtonElement = container.querySelector('[data-more-tab-item]'); let moreButtonWidthAndMargin = 0; if (moreButtonElement) { moreButtonWidthAndMargin = moreButtonElement.getBoundingClientRect().width + MORE_BUTTON_MARGIN_START; } return ( totalContainerWidth - buttonBlockWidth - headingTitleBlockWidth - headingTitleMarginEnd - moreButtonWidthAndMargin ); }; import { calculateAvailableSpace } from '../src/utils/calculateAvailableSpace'; describe('calculateAvailableSpace', () => { let container: HTMLDivElement; let buttonBlock: HTMLDivElement; let headingBlock: HTMLDivElement; let moreButton: HTMLDivElement; beforeEach(() => { document.body.innerHTML = ` <div id="container" style="width: 500px;"></div> <div id="buttonBlock" style="width: 100px;"></div> <div id="headingBlock" style="width: 150px; margin-inline-start: 10px;"></div> <div id="moreButton" style="width: 50px;"></div> `; container = document.getElementById('container') as HTMLDivElement; buttonBlock = document.getElementById('buttonBlock') as HTMLDivElement; headingBlock = document.getElementById('headingBlock') as HTMLDivElement; moreButton = document.getElementById('moreButton') as HTMLDivElement; // Mock `querySelector` to return the more button jest.spyOn(container, 'querySelector').mockReturnValue(moreButton); }); afterEach(() => { jest.restoreAllMocks(); }); it('should calculate available space correctly', () => { const result = calculateAvailableSpace({ container, buttonBlock, headingBlock }); expect(result).toBe(500 - 100 - 150 - 10 - 50 - 20); // 20 is MORE_BUTTON_MARGIN_START }); it('should return 0 if container is null', () => { const result = calculateAvailableSpace({ container: null, buttonBlock, headingBlock }); expect(result).toBe(0); }); it('should return 0 if buttonBlock is null', () => { const result = calculateAvailableSpace({ container, buttonBlock: null, headingBlock }); expect(result).toBe(0); }); it('should return 0 if headingBlock is null', () => { const result = calculateAvailableSpace({ container, buttonBlock, headingBlock: null }); expect(result).toBe(0); }); it('should return full container width if there are no other elements', () => { const result = calculateAvailableSpace({ container, buttonBlock: null, headingBlock: null }); expect(result).toBe(500); }); });
Editor is loading...
Leave a Comment