UNPKG

monday-ui-react-core

Version:

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

213 lines (194 loc) • 5.44 kB
import React, { useCallback, useState, useMemo } from "react"; import PropTypes from "prop-types"; import cx from "classnames"; import NOOP from "lodash/noop"; import Dialog from "../Dialog/Dialog"; import Menu from "../Icon/Icons/components/Menu"; import DialogContentContainer from "../DialogContentContainer/DialogContentContainer"; import "./MenuButton.scss"; function BEMClass(className) { return `menu-button--wrapper--${className}`; } const showTrigger = ["click", "enter"]; const MOVE_BY = { main: 0, secondary: -6 }; const MenuButton = ({ componentClassName, openDialogComponentClassName, children, component, size, open, zIndex, ariaLabel, closeDialogOnContentClick, dialogOffset, dialogPosition, dialogClassName, dialogPaddingSize, onMenuHide, onMenuShow, disabled }) => { const [isOpen, setIsOpen] = useState(open); const onDialogDidHide = useCallback(() => { setIsOpen(false); onMenuHide(); }, [setIsOpen, onMenuHide]); const onDialogDidShow = useCallback(() => { setIsOpen(true); onMenuShow(); }, [setIsOpen, onMenuShow]); const hideTrigger = useMemo(() => { const triggers = ["clickoutside", "esckey"]; if (closeDialogOnContentClick) { triggers.push("onContentClick"); } return triggers; }, [closeDialogOnContentClick]); const clonedChildren = useMemo(() => { const childrenArr = React.Children.toArray(children); const cloned = childrenArr.map(child => { if (child.type && child.type.supportFocusOnMount) { return React.cloneElement(child, { focusOnMount: true }); } return child; }); return cloned; }, [children]); const content = useMemo(() => { if (!clonedChildren.length === 0) return <div />; return ( <DialogContentContainer size={dialogPaddingSize} type={DialogContentContainer.types.POPOVER}> {clonedChildren} </DialogContentContainer> ); }, [clonedChildren, dialogPaddingSize]); const computedDialogOffset = useMemo( () => ({ ...MOVE_BY, ...dialogOffset }), [dialogOffset] ); const Icon = component; const iconSize = size - 4; return ( <Dialog wrapperClassName={dialogClassName} position={dialogPosition} startingEdge="bottom" animationType="expand" content={content} moveBy={computedDialogOffset} showTrigger={showTrigger} hideTrigger={hideTrigger} onDialogDidShow={onDialogDidShow} onDialogDidHide={onDialogDidHide} referenceWrapperClassName={BEMClass("reference-icon")} zIndex={zIndex} > <button type="button" role="menu" className={cx("menu-button--wrapper", componentClassName, BEMClass(`size-${size}`), { [BEMClass("open")]: isOpen, [openDialogComponentClassName]: isOpen && openDialogComponentClassName, [BEMClass("disabled")]: disabled })} aria-haspopup="true" aria-expanded={isOpen} aria-label={ariaLabel} > <Icon size={Math.min(iconSize, 28).toString()} /> </button> </Dialog> ); }; const MenuButtonSizes = { XXS: "16", XS: "24", SMALL: "32", MEDIUM: "40", LARGE: "48" }; const DialogPositions = { BOTTOM: "bottom", BOTTOM_START: "bottom-start", BOTTOM_END: "bottom-end" }; MenuButton.sizes = MenuButtonSizes; MenuButton.paddingSizes = DialogContentContainer.sizes; MenuButton.dialogPositions = DialogPositions; MenuButton.propTypes = { componentClassName: PropTypes.string, /* Class name to add to the button when the dialog is open */ openDialogComponentClassName: PropTypes.string, /** * Receives React Component */ component: PropTypes.func, size: PropTypes.oneOf([ MenuButtonSizes.XXS, MenuButtonSizes.XS, MenuButtonSizes.SMALL, MenuButtonSizes.MEDIUM, MenuButtonSizes.LARGE ]), open: PropTypes.bool, zIndex: PropTypes.number, ariaLabel: PropTypes.string, closeDialogOnContentClick: PropTypes.bool, /* Class name to provide the element which wraps the popover/modal/dialog */ dialogClassName: PropTypes.string, /** * main - `dialogOffset.main` - main axis offset; `dialogOffset.secondary` secondary axis offset */ dialogOffset: PropTypes.shape({ main: PropTypes.number, secondary: PropTypes.number }), dialogPaddingSize: PropTypes.oneOf([ MenuButton.paddingSizes.NONE, MenuButton.paddingSizes.SMALL, MenuButton.paddingSizes.MEDIUM, MenuButton.paddingSizes.LARGE ]), dialogPosition: PropTypes.oneOf([ MenuButton.dialogPositions.BOTTOM_START, MenuButton.dialogPositions.BOTTOM, MenuButton.dialogPositions.BOTTOM_END ]), /* Callback function to be called when the menu is shown */ onMenuShow: PropTypes.func, /* Callback function to be called when the menu is shown */ onMenuHide: PropTypes.func, disabled: PropTypes.bool }; MenuButton.defaultProps = { componentClassName: "", component: Menu, size: MenuButtonSizes.SMALL, open: false, zIndex: null, ariaLabel: "Menu", closeDialogOnContentClick: false, dialogClassName: "", openDialogComponentClassName: "", dialogOffset: MOVE_BY, dialogPaddingSize: DialogContentContainer.sizes.MEDIUM, dialogPosition: MenuButton.dialogPositions.BOTTOM_START, onMenuShow: NOOP, onMenuHide: NOOP, disabled: false }; export default MenuButton;