import MenuItemEntry from "components/system/Menu/MenuItemEntry"; import menuTransition from "components/system/Menu/menuTransition"; import StyledMenu from "components/system/Menu/StyledMenu"; import { useMenu } from "contexts/menu/index"; import type { MenuState } from "contexts/menu/useMenuContextState"; import { useCallback, useEffect, useRef, useState } from "react"; import type { Position } from "react-rnd"; import { FOCUSABLE_ELEMENT, ONE_TIME_PASSIVE_EVENT, PREVENT_SCROLL, } from "utils/constants"; import { haltEvent, viewHeight, viewWidth } from "utils/functions"; type MenuProps = { subMenu?: MenuState; }; export const topLeftPosition = (): Position => ({ x: 0, y: 0, }); const Menu: FC = ({ subMenu }) => { const { menu: baseMenu = {}, setMenu } = useMenu(); const { items, x = 0, y = 0 } = subMenu || baseMenu; const [offset, setOffset] = useState(topLeftPosition); const menuRef = useRef(null); const resetMenu = useCallback( ({ relatedTarget }: Partial = {}) => { if ( !(relatedTarget instanceof HTMLElement) || !menuRef.current?.contains(relatedTarget) ) { setMenu(Object.create(null) as MenuState); } }, [setMenu] ); const isSubMenu = Boolean(subMenu); useEffect(() => { if (items && !subMenu) { const focusedElement = document.activeElement; if ( focusedElement instanceof HTMLElement && focusedElement !== document.body ) { const options: AddEventListenerOptions = { capture: true, ...ONE_TIME_PASSIVE_EVENT, }; const menuUnfocused = ({ relatedTarget, type, }: FocusEvent | MouseEvent): void => { if ( !(relatedTarget instanceof HTMLElement) || !menuRef.current?.contains(relatedTarget) ) { resetMenu(); } focusedElement.removeEventListener( type === "click" ? "blur" : "click", menuUnfocused ); }; focusedElement.addEventListener("click", menuUnfocused, options); focusedElement.addEventListener("blur", menuUnfocused, options); } else { menuRef.current?.focus(PREVENT_SCROLL); } } }, [items, resetMenu, subMenu]); useEffect(() => { if (!menuRef.current) return; const { height = 0, width = 0, x: menuX = 0, y: menuY = 0, } = menuRef.current?.getBoundingClientRect() || {}; const [vh, vw] = [viewHeight(), viewWidth()]; const bottomOffset = y + height > vh ? vh - y : 0; const topAdjustedBottomOffset = bottomOffset + height > vh ? 0 : bottomOffset; const subMenuOffscreenX = Boolean(subMenu) && menuX + width > vw; const subMenuOffscreenY = Boolean(subMenu) && menuY + height > vh; const newOffset = { x: Math.round(Math.max(0, x + width - vw)) + (subMenuOffscreenX ? Math.round(width + (subMenu?.x || 0)) : 0), y: Math.round(Math.max(0, y + height - (vh - topAdjustedBottomOffset))) + (subMenuOffscreenY ? Math.round(height + (subMenu?.y || 0)) : 0), }; const adjustedOffsetX = subMenuOffscreenX && menuX - newOffset.x < 0 ? newOffset.x - (newOffset.x - menuX) : 0; setOffset( adjustedOffsetX > 0 ? { ...newOffset, x: adjustedOffsetX } : newOffset ); }, [items, subMenu, x, y]); return items ? (
    {items.map((item, index) => ( ))}
) : // eslint-disable-next-line unicorn/no-null null; }; export default Menu;