mui-nav-menu
Version:
Navigation menu component based on material ui
173 lines (172 loc) • 6.23 kB
JavaScript
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;