import React, {useState, useRef, useImperativeHandle} from 'react' import {makeStyles} from '@material-ui/core/styles' import Menu, {MenuProps} from '@material-ui/core/Menu' import MenuItem, {MenuItemProps} from '@material-ui/core/MenuItem' import ArrowRight from '@material-ui/icons/ArrowRight' import clsx from 'clsx' export interface NestedMenuItemProps extends Omit { /** * Open state of parent ``, used to close decendent menus when the * root menu is closed. */ parentMenuOpen: boolean /** * Component for the container element. * @default 'div' */ component?: React.ElementType /** * Effectively becomes the `children` prop passed to the `` * element. */ label?: React.ReactNode /** * @default */ rightIcon?: React.ReactNode /** * Props passed to container element. */ ContainerProps?: React.HTMLAttributes & React.RefAttributes /** * Props passed to sub `` element */ MenuProps?: Omit /** * @see https://material-ui.com/api/list-item/ */ button?: true | undefined } const TRANSPARENT = 'rgba(0,0,0,0)' const useMenuItemStyles = makeStyles((theme) => ({ root: (props: any) => ({ backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT }) })) /** * Use as a drop-in replacement for `` when you need to add cascading * menu elements as children to this component. */ const NestedMenuItem = React.forwardRef< HTMLLIElement | null, NestedMenuItemProps >(function NestedMenuItem(props, ref) { const { parentMenuOpen, component = 'div', label, rightIcon = , 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: React.MouseEvent) => { setIsSubMenuOpen(true) if (ContainerProps?.onMouseEnter) { ContainerProps.onMouseEnter(event) } } const handleMouseLeave = (event: React.MouseEvent) => { 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: React.FocusEvent) => { if (event.target === containerRef.current) { setIsSubMenuOpen(true) } if (ContainerProps?.onFocus) { ContainerProps.onFocus(event) } } const handleKeyDown = (event: React.KeyboardEvent) => { 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] as | HTMLElement | undefined 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 (
{label} {rightIcon} { setIsSubMenuOpen(false) }} >
{children}
) }) export default NestedMenuItem