Untitled
unknown
plain_text
10 months ago
5.9 kB
6
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