UNPKG

material-ui-nested-menu-item-v5

Version:
103 lines (102 loc) 4.6 kB
import React, { useState, useRef, useImperativeHandle } from 'react'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import ArrowRight from '@mui/icons-material/ArrowRight'; import clsx from 'clsx'; import makeStyles from '@mui/styles/makeStyles'; const TRANSPARENT = 'rgba(0,0,0,0)'; const useMenuItemStyles = makeStyles((theme) => ({ root: (props) => ({ backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT }) })); /** * Use as a drop-in replacement for `<MenuItem>` when you need to add cascading * menu elements as children to this component. */ const NestedMenuItem = React.forwardRef(function NestedMenuItem(props, ref) { const { parentMenuOpen, component = 'div', label, rightIcon = React.createElement(ArrowRight, null), children, className, tabIndex: tabIndexProp, MenuProps = {}, ContainerProps: ContainerPropsProp = {}, ...MenuItemProps } = props; const { ref: containerRefProp, ...ContainerProps } = ContainerPropsProp; const menuItemRef = useRef(null); useImperativeHandle(ref, () => menuItemRef.current); const containerRef = useRef(null); useImperativeHandle(containerRefProp, () => containerRef.current); const menuContainerRef = useRef(null); const [isSubMenuOpen, setIsSubMenuOpen] = useState(false); const handleMouseEnter = (event) => { setIsSubMenuOpen(true); if (ContainerProps?.onMouseEnter) { ContainerProps.onMouseEnter(event); } }; const handleMouseLeave = (event) => { setIsSubMenuOpen(false); if (ContainerProps?.onMouseLeave) { ContainerProps.onMouseLeave(event); } }; // Check if any immediate children are active const isSubmenuFocused = () => { const active = containerRef.current?.ownerDocument?.activeElement; for (const child of menuContainerRef.current?.children ?? []) { if (child === active) { return true; } } return false; }; const handleFocus = (event) => { if (event.target === containerRef.current) { setIsSubMenuOpen(true); } if (ContainerProps?.onFocus) { ContainerProps.onFocus(event); } }; const handleKeyDown = (event) => { if (event.key === 'Escape') { return; } if (isSubmenuFocused()) { event.stopPropagation(); } const active = containerRef.current?.ownerDocument?.activeElement; if (event.key === 'ArrowLeft' && isSubmenuFocused()) { containerRef.current?.focus(); } if (event.key === 'ArrowRight' && event.target === containerRef.current && event.target === active) { const firstChild = menuContainerRef.current?.children[0]; firstChild?.focus(); } }; const open = isSubMenuOpen && parentMenuOpen; const menuItemClasses = useMenuItemStyles({ open }); // Root element must have a `tabIndex` attribute for keyboard navigation let tabIndex; if (!props.disabled) { tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1; } return (React.createElement("div", { ...ContainerProps, ref: containerRef, onFocus: handleFocus, tabIndex: tabIndex, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onKeyDown: handleKeyDown }, React.createElement(MenuItem, { ...MenuItemProps, className: clsx(menuItemClasses.root, className), ref: menuItemRef }, label, rightIcon), React.createElement(Menu // Set pointer events to 'none' to prevent the invisible Popover div // from capturing events for clicks and hovers , { // Set pointer events to 'none' to prevent the invisible Popover div // from capturing events for clicks and hovers style: { pointerEvents: 'none' }, anchorEl: menuItemRef.current, anchorOrigin: { vertical: 'top', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, open: open, autoFocus: false, disableAutoFocus: true, disableEnforceFocus: true, onClose: () => { setIsSubMenuOpen(false); } }, React.createElement("div", { ref: menuContainerRef, style: { pointerEvents: 'auto' } }, children)))); }); export default NestedMenuItem;