Untitled
unknown
plain_text
2 months ago
7.6 kB
2
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