mui-nav-menu
Version:
Navigation menu component based on material ui
211 lines (210 loc) • 8.92 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { alpha, ClickAwayListener, Collapse, List, Paper, Popper, useEventCallback, } from "@mui/material";
import classNames from "classnames";
import { useCallback, useMemo, useRef, useState, } from "react";
import { unstable_batchedUpdates } from "react-dom";
import { makeStyles } from "tss-react/mui";
import Arrow from "./Arrow";
import { useMenuContext } from "./contexts/MenuContext";
import PathContext, { useConnectedPath } from "./contexts/PathContext";
import ListMenuItem from "./ListMenuItem";
import { ConditionalWrapper } from "./utils";
const useStyle = makeStyles()((theme) => ({
popper: {
"& .MuiListItemButton-root": {
minWidth: 200,
},
"&.NestedLevel-isRoot": {
"& .MuiPaper-root": {
marginLeft: 0,
},
'&[data-popper-placement*="right"]': {
"& .PopperArrow": {
left: -5,
},
"& .MuiPaper-root": {
marginLeft: 10,
},
},
'&[data-popper-placement*="left"]': {
"& .PopperArrow": {
right: -5,
},
"& .MuiPaper-root": {
marginRight: 10,
},
},
'&[data-popper-placement*="top"]': {
"& .PopperArrow": {
bottom: -5,
},
"& .MuiPaper-root": {
marginBottom: 10,
},
},
'&[data-popper-placement*="bottom"]': {
"& .PopperArrow": {
top: -5,
},
"& .MuiPaper-root": {
marginTop: 10,
},
},
},
},
paper: {
transformOrigin: "top left",
position: "relative",
zIndex: 1,
marginLeft: 10,
"& .PopperContent": {
background: theme.palette.background.paper,
overflowY: "auto",
maxHeight: 500,
padding: 5,
borderRadius: 10,
},
},
isSubMenuActive: {
background: alpha("#000000", 0.05),
},
}));
const SubMenu = (props) => {
const { openIds = [], selectedIds, checkSelectedFn, mode, collapsed, inlineIndent, onInternalOpenChange, onSelectedChange, popperClassName, TransitionComponent, onItemClick, focusedNodeId, elevation, arrow, defaultCollapseIcon, defaultExpandIcon, defaultPopupRootIcon, defaultPopupSubIcon, placement: placementProp, prefixCls, trigger, enterDelay, leaveDelay, } = useMenuContext();
const { classes } = useStyle();
const { id, children, ...rest } = props;
const connectedIdPath = useConnectedPath(id);
// const { transitioning, transitionHandlers } = useTransitionState();
const isInlineMode = mode == "inline";
const [arrowRef, setArrowRef] = useState(null);
const subIsOpen = useMemo(() => {
return openIds.includes(id);
}, [openIds]);
const delayTimerRef = useRef();
const isSubMenuActive = subIsOpen && mode != "inline";
const isSelected = useMemo(() => {
if (checkSelectedFn) {
return checkSelectedFn(props);
}
return selectedIds ? selectedIds.includes(id) : false;
}, [selectedIds, checkSelectedFn]);
const [menuItemElement, setMenuItemElement] = useState(null);
const nestedLevel = useMemo(() => {
return connectedIdPath.length;
}, [connectedIdPath]);
const isRoot = nestedLevel == 1;
const setPopupVisible = useCallback((visible) => {
clearDelayTimer();
onInternalOpenChange &&
onInternalOpenChange(id, visible, connectedIdPath);
}, [connectedIdPath, onInternalOpenChange, openIds]);
const delaySetPopupVisible = useCallback((value, delayTime) => {
clearDelayTimer();
if (delayTime) {
delayTimerRef.current = setTimeout(() => {
setPopupVisible(value);
clearDelayTimer();
}, delayTime);
}
else {
setPopupVisible(value);
}
}, [openIds]);
const toggleInlineSubVisible = useCallback(() => {
onInternalOpenChange &&
onInternalOpenChange(id, !subIsOpen, connectedIdPath);
}, [openIds, id]);
const handleMouseEnter = (evt) => {
if (isRoot && trigger == "click") {
return;
}
delaySetPopupVisible(true, enterDelay);
};
const handleMouseLeave = (evt) => {
if (trigger == "click") {
return;
}
if (children) {
delaySetPopupVisible(false, leaveDelay);
}
};
const clearDelayTimer = () => {
if (delayTimerRef.current) {
clearTimeout(delayTimerRef.current);
delayTimerRef.current = null;
}
};
const handleItemClick = useEventCallback((event) => {
if (event.type !== "click") {
return;
}
if (!children) {
unstable_batchedUpdates(() => {
onSelectedChange(connectedIdPath, true);
});
}
else {
if (mode == "inline") {
toggleInlineSubVisible();
}
else if (trigger == "click" && isRoot) {
setPopupVisible(!subIsOpen);
}
}
onItemClick && onItemClick(id, connectedIdPath, event);
});
const handleClickAway = () => {
setPopupVisible(false);
};
const placement = useMemo(() => {
if (placementProp && isRoot) {
return placementProp;
}
if (mode == "horizontal" && isRoot) {
return "bottom-start";
}
return "right-start";
}, [mode, isRoot, placementProp]);
const modifiers = useMemo(() => {
return [
{
name: "flip",
enabled: true,
},
{
name: "preventOverflow",
enabled: true,
options: {
boundariesElement: "viewport",
},
},
{
name: "arrow",
enabled: isRoot && arrow,
options: {
element: arrowRef,
},
},
];
}, [arrowRef, isRoot, arrow]);
const setMenuItemElementRef = (ele) => {
if (mode !== "inline") {
setTimeout(() => {
setMenuItemElement(ele);
}, 200);
}
};
return (_jsxs("div", { className: classNames(prefixCls + "SubListItem-root", prefixCls + "ListItem-root"), ...(!isInlineMode && {
style: {
pointerEvents: rest.disabled ? "none" : undefined,
},
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
}), children: [_jsx(ListMenuItem, { id: id, hasChildren: !!children, ref: setMenuItemElementRef, onClick: handleItemClick, inlineIndent: inlineIndent, subIsOpen: subIsOpen, isSelected: isSelected, collapsed: collapsed, mode: mode, isFocused: focusedNodeId == id, connectedIdPath: connectedIdPath, expandIcon: defaultExpandIcon, collapseIcon: defaultCollapseIcon, popupRootIcon: defaultPopupRootIcon, popupSubIcon: defaultPopupSubIcon, className: classNames({
isSubMenuActive: isSubMenuActive,
[classes.isSubMenuActive]: isSubMenuActive,
}), ...rest }), children && isInlineMode && (_jsx(Collapse, { in: subIsOpen, timeout: "auto", unmountOnExit: true, children: _jsx(List, { className: prefixCls + "SubList-root", component: "div", disablePadding: true, children: _jsx(PathContext.Provider, { value: connectedIdPath, children: children }) }) })), children && !isInlineMode && (_jsx(Popper, { className: classNames(classes.popper, popperClassName, "NestedLevel-" + nestedLevel, {
"NestedLevel-isRoot": nestedLevel == 1,
}), transition: true, anchorEl: menuItemElement, placement: placement, open: subIsOpen && !!menuItemElement, modifiers: modifiers, children: ({ TransitionProps }) => (_jsx(ConditionalWrapper, { condition: isRoot && trigger == "click", wrapper: (child) => (_jsx(ClickAwayListener, { onClickAway: handleClickAway, children: _jsx("div", { children: child }) })), children: _jsx(TransitionComponent, { ...TransitionProps, children: _jsxs(Paper, { elevation: elevation, className: classes.paper, children: [_jsx("div", { className: "PopperContent", children: _jsx(List, { component: "div", disablePadding: true, children: _jsx(PathContext.Provider, { value: connectedIdPath, children: children }) }) }), isRoot && arrow && (_jsx(Arrow, { elevation: elevation, ref: setArrowRef }))] }) }) })) }))] }));
};
export default SubMenu;