Untitled

 avatar
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