UNPKG

mui-nav-menu

Version:

Navigation menu component based on material ui

173 lines (172 loc) 6.23 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { Grow, useControlled, useEventCallback, } from "@mui/material"; import React, { useEffect, useMemo, useRef, useState, } from "react"; import { MenuContext } from "./contexts/MenuContext"; import { useDidMountEffect } from "./hooks/useDidMountEffect"; import { useMenuNodes } from "./hooks/useMenuNodes"; import MenuRoot from "./MenuRoot"; import { parseItemsToNodes } from "./utils"; const NavMenu = ({ items: itemsProp, mode = "inline", inlineIndent = { base: 12, step: 12, }, openIds: openIdsProp, onOpenChange: onOpenChangeProp, onItemClick, defaultOpenIds, accordion, defaultSelectedIds, children, rootClassName, collapsed, popperClassName, disableSelection, checkSelectedFn, selectedIds: selectedIdsProp, onSelectedChange: onSelectedChangeProp, placement, defaultCollapseIcon, defaultExpandIcon, defaultPopupRootIcon, defaultPopupSubIcon, TransitionComponent = Grow, disableCloseOnSelect, trigger = "hover", arrow = true, enterDelay = 100, leaveDelay = 100, elevation = 2, linkComponent, prefixCls = "NavMenu", }) => { const restoredOpenIds = useRef(); const isClosedRef = useRef(true); const [openIds = [], setOpenIds] = useControlled({ name: "openIds", controlled: openIdsProp, default: defaultOpenIds, }); const openIdsRef = useRef([]); const { processItem, getChildrenIds, getNode, clearNodes, getDescendantIds, } = useMenuNodes(); const mergedMode = useMemo(() => { return collapsed && mode == "inline" ? "vertical" : mode; }, [mode, collapsed]); const handleOpenIds = (newOpenIds) => { openIdsRef.current = []; onOpenChangeProp && onOpenChangeProp(newOpenIds); setOpenIds(newOpenIds); }; const closeAll = () => { isClosedRef.current = true; handleOpenIds([]); }; const onInternalOpenChange = useEventCallback((id, open, idPaths) => { if (mergedMode == "inline" && accordion) { if (open) { handleOpenIds(idPaths); } else { handleOpenIds(openIdsRef.current.filter((openId) => openId != id)); } } else { let newOpenIds = openIdsRef.current.filter((openId) => openId != id); if (open) { if (idPaths.length > 1 && isClosedRef.current) { //Prevents direct ejection from second level if everything is closed return; } if (mergedMode == "inline") { newOpenIds.push(id); } else { newOpenIds = [...idPaths]; } isClosedRef.current = false; } else { //When closing, all child nodes need to be closed if (mergedMode != "inline") { const descendantIds = getDescendantIds(id); newOpenIds = newOpenIds.filter((id) => !descendantIds.includes(id)); } } handleOpenIds(newOpenIds); } }); const [focusedNodeId, setFocusedNodeId] = useState(); const [selectedIds = [], setSelectedIds] = useControlled({ name: "selectedIds", controlled: selectedIdsProp, default: defaultSelectedIds, }); const onSelectedChange = (newSelectedIds, closePopup) => { if (!disableSelection) { onSelectedChangeProp && onSelectedChangeProp(newSelectedIds); setSelectedIds(newSelectedIds); closePopup && setFocusedNodeId(null); } if (mergedMode != "inline" && closePopup && !disableCloseOnSelect) { closeAll(); } }; const contextValue = useMemo(() => { return { mode: mergedMode, inlineIndent, openIds, onInternalOpenChange, defaultOpenIds, popperClassName, collapsed, selectedIds, onSelectedChange, checkSelectedFn, TransitionComponent, onItemClick, getChildrenIds, getNode, focusedNodeId, setFocusedNodeId, elevation, arrow, placement, defaultCollapseIcon, defaultExpandIcon, defaultPopupRootIcon, defaultPopupSubIcon, prefixCls, trigger, linkComponent, enterDelay, leaveDelay, }; }, [ mergedMode, inlineIndent, openIds, defaultOpenIds, popperClassName, collapsed, selectedIds, onSelectedChange, checkSelectedFn, TransitionComponent, onItemClick, focusedNodeId, elevation, arrow, placement, defaultCollapseIcon, defaultExpandIcon, defaultPopupRootIcon, defaultPopupSubIcon, prefixCls, trigger, linkComponent, enterDelay, leaveDelay, ]); const itemNodes = useMemo(() => { return itemsProp ? parseItemsToNodes(itemsProp) : null; }, [itemsProp]); useDidMountEffect(() => { if (collapsed) { //store openids restoredOpenIds.current = [...openIdsRef.current]; handleOpenIds([]); } else { if (restoredOpenIds.current) { handleOpenIds(restoredOpenIds.current); restoredOpenIds.current = null; } } }, [collapsed]); const items = useMemo(() => { return children || itemNodes; }, [children, itemNodes]); useEffect(() => { clearNodes(); React.Children.forEach(items, (child, index) => { processItem(child, null, index); }); }, [items]); useEffect(() => { openIdsRef.current = openIds; isClosedRef.current = openIds.length == 0; }, [openIds]); return (_jsx(MenuContext.Provider, { value: contextValue, children: _jsx(MenuRoot, { className: rootClassName, children: items }) })); }; export default NavMenu;