Untitled
unknown
plain_text
10 months ago
7.6 kB
6
Indexable
import { useContext } from 'react';
import { Context } from '../context/context';
import { MORE_BUTTON_ITEM_ID } from '../context';
import { FlatMenuItemChildsData } from '../types';
export const useMenu = () => {
const context = useContext(Context);
if (!context) {
throw new Error("useMenu must be used within a Provider");
}
const { flatMenu, setFlatMenu } = context;
const getItem = (itemId: string) => flatMenu[itemId] || null;
const getTopLevelItems = () => Object.values(flatMenu).filter((item) => !item.parentId && item.id !== MORE_BUTTON_ITEM_ID);
const getChildItems = (parentId: string) => flatMenu[parentId]?.childIds.map((id) => flatMenu[id]) || [];
const getMoreButtonItem = () => flatMenu[MORE_BUTTON_ITEM_ID];
const getVisibleItems = () => {
const topLevelItems = getTopLevelItems();
const { childIdsCollection = {} } = getMoreButtonItem();
return topLevelItems.filter(item => !childIdsCollection[item.id]);
}
const updateChildsMoreButtonItem = (childsData: FlatMenuItemChildsData) => {
setFlatMenu(prev => ({
...prev,
[MORE_BUTTON_ITEM_ID]: {
...getItem(MORE_BUTTON_ITEM_ID),
...childsData
}
}));
};
return { getItem, getTopLevelItems, getChildItems, getVisibleItems, getMoreButtonItem, updateChildsMoreButtonItem };
};
import { useRef, useState, useEffect, useCallback } from 'react';
import { FlatMenuItem, FlatMenuItemChildsData } from '@components/SecondaryNavigation/common/types';
import { debounce } from '@utils/debounce';
export const useAdaptiveTabs = (
tabs: FlatMenuItem[],
updateChildsMoreButtonItem: (childsData: FlatMenuItemChildsData) => void,
currentChildsMoreButtonItems: FlatMenuItemChildsData
// isRtl: boolean
) => {
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 }>({});
const calculateAvailableSpace = useCallback(() => {
if (!containerRef.current || !buttonBlockRef.current || !headingBlockRef.current) {
return;
}
const totalContainerWidth = containerRef.current.offsetWidth;
const buttonBlockWidth = buttonBlockRef.current.offsetWidth;
const headingTitleBlockWidth = headingBlockRef.current.offsetWidth;
const headingTitleMarginEnd = Number.parseInt(getComputedStyle(headingBlockRef.current).getPropertyValue('margin-inline-start'));
const availableWidth = totalContainerWidth - buttonBlockWidth - headingTitleBlockWidth - headingTitleMarginEnd;
setAvailableWidth(availableWidth);
}, []);
const handleResize = useCallback(
debounce(() => {
calculateAvailableSpace();
}, 50),
[calculateAvailableSpace]
);
useEffect(() => {
calculateAvailableSpace();
}, []);
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 } = {};
tabElements.forEach((el) => {
const id = el.getAttribute("data-id");
const marginEnd = Number.parseInt(getComputedStyle(el).getPropertyValue('margin-inline-end'))
if (id) {
newWidths[id] = el.getBoundingClientRect().width + marginEnd;
}
});
setTabWidths(prevWidths => (
JSON.stringify(prevWidths) === JSON.stringify(newWidths) ? prevWidths : newWidths
));
}, [tabs]);
console.log({tabs, availableWidth, tabWidths});
useEffect(() => {
let totalWidth = 0;
let hiddenTabsList: typeof tabs = [];
const processedTabs = [...tabs];
processedTabs.forEach((tab) => {
totalWidth += tabWidths[tab.id] || 0;
if (totalWidth > availableWidth) {
hiddenTabsList.push(tab);
}
});
const firstHiddenTabWidth = hiddenTabsList.length && (tabWidths[hiddenTabsList[0].id] || 0);
const nextWidth = totalWidth + firstHiddenTabWidth;
while (hiddenTabsList.length && nextWidth < availableWidth) {
const restoredHiddenTab = hiddenTabsList.shift();
if (restoredHiddenTab) {
totalWidth += tabWidths[restoredHiddenTab.id] || 0;
}
}
const moreButtonChildRelatedProps = {
childIds: hiddenTabsList.map(item => item.id),
childIdsCollection: hiddenTabsList.reduce<Record<string, {}>>((acc, currentItem) => {
acc[currentItem.id] = {};
return acc;
}, {})
}
if(JSON.stringify(currentChildsMoreButtonItems) === JSON.stringify(moreButtonChildRelatedProps)) {
return;
}
updateChildsMoreButtonItem(moreButtonChildRelatedProps);
}, [availableWidth, tabWidths]);
return { containerRef, buttonBlockRef, headingBlockRef };
};
import type { FC } from 'react';
import { Breadcrumb } from '@components/Breadcrumb';
import { HeadingTitle } from '@components/SecondaryNavigation/common/components/HeadingTitle';
import { useMenu } from '@components/SecondaryNavigation/common/hooks/useMenu';
import { useAdaptiveTabs } from '@components/SecondaryNavigation/common/hooks/useAdaptiveTabs';
import { TabItem } from './TabItem';
import styles from './styles/DesktopView.module.scss';
interface MenuItem {
title: string;
path: string;
active: boolean;
items?: MenuItem[];
}
interface DesktopViewProps {
heading: {
title: string;
path: string;
};
breadcrumb?: {
path: string;
title: string;
target?: string;
}[];
menu?: MenuItem[];
}
export const DesktopView: FC<DesktopViewProps> = ({ heading, breadcrumb = [], menu = []}) => {
const { getVisibleItems, getTopLevelItems, updateChildsMoreButtonItem, getMoreButtonItem } = useMenu();
const topLevelItems = getTopLevelItems();
const visibleItems = getVisibleItems();
const { childIds, childIdsCollection = {} } = getMoreButtonItem();
const {containerRef, buttonBlockRef, headingBlockRef} = useAdaptiveTabs(topLevelItems, updateChildsMoreButtonItem, { childIds, childIdsCollection });
// console.log(getTopLevelItems());
return (
<div className={styles.root}>
<div className={styles.wrapper} ref={containerRef}>
<div className={styles.navigationSection}>
<Breadcrumb className={styles.breadcrumb} items={breadcrumb} />
<div className={styles.navigationRow}>
<div className={styles.heading} ref={headingBlockRef}>
<HeadingTitle className={styles.headingTitle} title={heading.title} path={heading.path} />
<div className={styles.divider} />
</div>
<ul className={styles.tabMenu}>
{visibleItems.map(item => (
<li data-id={item.id} key={item.id} className={styles.tabItemWrapper} data-tab-item>
<TabItem itemId={item.id} />
</li>
))}
</ul>
</div>
</div>
<div className={styles.buttonSection} ref={buttonBlockRef}>
button section
</div>
</div>
</div>
);
};
Editor is loading...
Leave a Comment