UNPKG

@szhsin/react-menu

Version:

React component for building accessible menu, dropdown, submenu, context menu, and more

101 lines (98 loc) 3.07 kB
import { useContext, useMemo } from 'react'; import { jsx } from 'react/jsx-runtime'; import { withHovering } from '../utils/withHovering.mjs'; import { useItemState } from '../hooks/useItemState.mjs'; import { EventHandlersContext, RadioGroupContext, menuClass, menuItemClass, roleNone, roleMenuitem, Keys } from '../utils/constants.mjs'; import { mergeProps, safeCall, commonProps } from '../utils/utils.mjs'; import { useBEM } from '../hooks/useBEM.mjs'; import { useCombinedRef } from '../hooks/useCombinedRef.mjs'; const MenuItem = /*#__PURE__*/withHovering('MenuItem', function MenuItem({ className, value, href, type, checked, disabled, children, onClick, isHovering, itemRef, externalRef, ...restProps }) { const isDisabled = !!disabled; const { mouseOver, setHover, ...restStateProps } = useItemState(itemRef, itemRef, isHovering, isDisabled); const eventHandlers = useContext(EventHandlersContext); const radioGroup = useContext(RadioGroupContext); const isRadio = type === 'radio'; const isCheckBox = type === 'checkbox'; const isAnchor = !!href && !isDisabled && !isRadio && !isCheckBox; const isChecked = isRadio ? radioGroup.value === value : isCheckBox ? !!checked : false; const handleClick = e => { if (isDisabled) { e.stopPropagation(); e.preventDefault(); return; } const event = { value, syntheticEvent: e }; if (e.key !== undefined) event.key = e.key; if (isCheckBox) event.checked = !isChecked; if (isRadio) event.name = radioGroup.name; safeCall(onClick, event); if (isRadio) safeCall(radioGroup.onRadioChange, event); eventHandlers.handleClick(event, isCheckBox || isRadio); }; const handleKeyDown = e => { if (!isHovering) return; switch (e.key) { case Keys.ENTER: e.preventDefault(); case Keys.SPACE: isAnchor ? itemRef.current.click() : handleClick(e); } }; const modifiers = useMemo(() => ({ type, disabled: isDisabled, hover: mouseOver || isHovering, checked: isChecked, anchor: isAnchor }), [type, isDisabled, mouseOver, isHovering, isChecked, isAnchor]); const mergedProps = mergeProps({ ...restStateProps, onPointerDown: setHover, onKeyDown: handleKeyDown, onClick: handleClick }, restProps); const menuItemProps = { role: isRadio ? 'menuitemradio' : isCheckBox ? 'menuitemcheckbox' : roleMenuitem, 'aria-checked': isRadio || isCheckBox ? isChecked : undefined, ...commonProps(isDisabled, isHovering), ...mergedProps, ref: useCombinedRef(externalRef, itemRef), className: useBEM({ block: menuClass, element: menuItemClass, modifiers, className }), children: useMemo(() => safeCall(children, modifiers), [children, modifiers]) }; return isAnchor ? /*#__PURE__*/jsx("li", { role: roleNone, children: /*#__PURE__*/jsx("a", { href: href, ...menuItemProps }) }) : /*#__PURE__*/jsx("li", { ...menuItemProps }); }); export { MenuItem };