UNPKG

monday-ui-react-core

Version:

Official monday.com UI resources for application development in React.js

242 lines (211 loc) • 6.49 kB
import React, { useCallback, useRef, useEffect, useLayoutEffect } from "react"; import PropTypes from "prop-types"; import isFunction from "lodash/isFunction"; import cx from "classnames"; import Icon from "../../Icon/Icon"; import DropdownChevronRight from "../../Icon/Icons/components/DropdownChevronRight"; import DialogContentContainer from "../../DialogContentContainer/DialogContentContainer"; import useMergeRefs from "../../../hooks/useMergeRefs"; import useIsOverflowing from "../../../hooks/useIsOverflowing"; import useIsMouseOver from "../../../hooks/useIsMouseOver"; import usePopover from "../../../hooks/usePopover"; import useMenuItemMouseEvents from "./hooks/useMenuItemMouseEvents"; import useMenuItemKeyboardEvents from "./hooks/useMenuItemKeyboardEvents"; import "./MenuItem.scss"; const MenuItem = ({ classname, title, label, icon, menuRef, iconType, disabled, onClick, activeItemIndex, setActiveItemIndex, index, children, isParentMenuVisible, resetOpenSubMenuIndex, hasOpenSubMenu, setSubMenuIsOpenByIndex, closeMenu }) => { const isActive = activeItemIndex === index; const isSubMenuOpen = !!children && isActive && hasOpenSubMenu; const hasChildren = !!children; const shouldShowSubMenu = hasChildren && isParentMenuVisible && isSubMenuOpen; const submenuChild = children && React.Children.only(children); let menuChild; if (submenuChild && submenuChild.type && submenuChild.type.isMenu) { menuChild = submenuChild; } else if (submenuChild) { console.Error( "menu item can acceept only menu element as first level child, this element is not valid: ", submenuChild ); } const ref = useRef(null); const titleRef = useRef(); const childRef = useRef(); const referenceElementRef = useRef(null); const popperElementRef = useRef(null); const popperElement = popperElementRef.current; const referenceElement = referenceElementRef.current; const childElement = childRef.current; const isTitleHovered = useIsMouseOver({ ref: titleRef }); const isTitleHoveredAndOverflowing = useIsOverflowing({ ref: isTitleHovered && titleRef }); const { styles, attributes } = usePopover(referenceElement, popperElement, { isOpen: isSubMenuOpen }); const isMouseEnter = useMenuItemMouseEvents( ref, resetOpenSubMenuIndex, setSubMenuIsOpenByIndex, isActive, setActiveItemIndex, index, hasChildren ); const { onClickCallback } = useMenuItemKeyboardEvents( onClick, disabled, isActive, index, setActiveItemIndex, hasChildren, shouldShowSubMenu, setSubMenuIsOpenByIndex, menuRef, isMouseEnter, closeMenu ); useLayoutEffect(() => { if (shouldShowSubMenu && childElement) { requestAnimationFrame(() => { childElement.focus(); }); } }, [shouldShowSubMenu, childElement]); const closeSubMenu = useCallback( (options = {}) => { setSubMenuIsOpenByIndex(index, false); if (options.propagate) { closeMenu(options); } }, [setSubMenuIsOpenByIndex, index, closeMenu] ); const mergedRef = useMergeRefs({ refs: [ref, referenceElementRef] }); const renderSubMenuIconIfNeeded = () => { if (!hasChildren) return null; return ( <div className="monday-style-menu-item__sub_menu_icon-wrapper"> <Icon clickable={false} icon={DropdownChevronRight} iconLabel={title} className="monday-style-menu-item__sub_menu_icon" ignoreFocusStyle /> </div> ); }; const renderMenuItemIconIfNeeded = () => { if (!icon) return null; let finalIconType = iconType; if (!finalIconType) { finalIconType = isFunction(icon) ? Icon.type.SVG : Icon.type.ICON_FONT; } return ( <div className="monday-style-menu-item__icon-wrapper"> <Icon iconType={finalIconType} clickable={false} icon={icon} iconLabel={title} className="monday-style-menu-item__icon" ignoreFocusStyle /> </div> ); }; return ( <div aria-haspopup={!!children} className={cx("monday-style-menu-item", classname, { "monday-style-menu-item--disabled": disabled, "monday-style-menu-item--focused": isActive })} ref={mergedRef} onClick={onClickCallback} > {renderMenuItemIconIfNeeded()} {// show tooltip if needed isTitleHoveredAndOverflowing && null} <div ref={titleRef} className="monday-style-menu-item__title"> {title} </div> {label && ( <div ref={titleRef} className="monday-style-menu-item__label"> {label} </div> )} {renderSubMenuIconIfNeeded()} <div style={{ ...styles.popper, visibility: shouldShowSubMenu ? "visible" : "hidden" }} // eslint-disable-next-line react/jsx-props-no-spreading {...attributes.popper} className="monday-style-menu-item__popover" ref={popperElementRef} > {menuChild && shouldShowSubMenu && ( <DialogContentContainer> {React.cloneElement(menuChild, { ...menuChild?.props, isVisible: shouldShowSubMenu, isSubMenu: true, onClose: closeSubMenu, ref: childRef })} </DialogContentContainer> )} </div> </div> ); }; MenuItem.iconType = Icon.type; MenuItem.defaultProps = { classname: "", title: "", lebel: "", icon: "", iconType: undefined, disabled: false, onClick: undefined, activeItemIndex: -1, setActiveItemIndex: undefined, index: undefined, isParentMenuVisible: false, hasOpenSubMenu: false, setSubMenuIsOpenByIndex: undefined, resetOpenSubMenuIndex: undefined }; MenuItem.propTypes = { classname: PropTypes.string, title: PropTypes.string, icon: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), iconType: PropTypes.oneOf([Icon.type.SVG, Icon.type.ICON_FONT]), disabled: PropTypes.bool, onClick: PropTypes.func, activeItemIndex: PropTypes.number, setActiveItemIndex: PropTypes.func, index: PropTypes.number, isParentMenuVisible: PropTypes.bool, resetOpenSubMenuIndex: PropTypes.func, hasOpenSubMenu: PropTypes.bool, setSubMenuIsOpenByIndex: PropTypes.func }; MenuItem.isSelectable = true; MenuItem.isMenuChild = true; export default MenuItem;