UNPKG

@mskcc/carbon-react

Version:

Carbon react components for the MSKCC DSM

422 lines (412 loc) 14 kB
/** * MSKCC 2021, 2024 */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var cx = require('classnames'); var PropTypes = require('prop-types'); var React = require('react'); var useControllableState = require('../../internal/useControllableState.js'); var useMergedRefs = require('../../internal/useMergedRefs.js'); var usePrefix = require('../../internal/usePrefix.js'); var Menu = require('./Menu.js'); var MenuContext = require('./MenuContext.js'); var match = require('../../internal/keyboard/match.js'); var keys = require('../../internal/keyboard/keys.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx); var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes); var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var _span, _span2, _span3; const hoverIntentDelay = 150; // in ms const MenuItem = /*#__PURE__*/React__default["default"].forwardRef(function MenuItem(_ref, forwardRef) { let { children, className, disabled, kind = 'default', label = '', renderLabel, onClick, renderIcon: IconElement, shortcut, ...rest } = _ref; const prefix = usePrefix.usePrefix(); const context = React.useContext(MenuContext.MenuContext); const menuItem = React.useRef(); const ref = useMergedRefs.useMergedRefs([forwardRef, menuItem]); const [boundaries, setBoundaries] = React.useState({ x: -1, y: -1 }); const hasChildren = Boolean(children); const [submenuOpen, setSubmenuOpen] = React.useState(false); const hoverIntentTimeout = React.useRef(null); const isDisabled = disabled && !hasChildren; const isDanger = kind === 'danger' && !hasChildren; function registerItem() { context.dispatch({ type: 'registerItem', payload: { ref: menuItem, disabled: Boolean(disabled) } }); } function openSubmenu() { const { x, y, width, height } = menuItem.current.getBoundingClientRect(); setBoundaries({ x: [x, x + width], y: [y, y + height] }); setSubmenuOpen(true); } function closeSubmenu() { setSubmenuOpen(false); setBoundaries({ x: -1, y: -1 }); } function handleClick(e) { if (!isDisabled) { if (hasChildren) { openSubmenu(); } else { context.state.requestCloseRoot(e); if (onClick) { onClick(e); } } } } function handleMouseEnter() { hoverIntentTimeout.current = setTimeout(() => { openSubmenu(); }, hoverIntentDelay); } function handleMouseLeave() { clearTimeout(hoverIntentTimeout.current); closeSubmenu(); menuItem.current.focus(); } function handleKeyDown(e) { if (hasChildren && match.match(e, keys.ArrowRight)) { openSubmenu(); e.stopPropagation(); } if (match.match(e, keys.Enter) || match.match(e, keys.Space)) { handleClick(e); } if (rest.onKeyDown) { rest.onKeyDown(e); } } const classNames = cx__default["default"](className, `${prefix}--menu-item`, { [`${prefix}--menu-item--disabled`]: isDisabled, [`${prefix}--menu-item--danger`]: isDanger }); // on first render, register this menuitem in the context's state // (used for keyboard navigation) React.useEffect(() => { registerItem(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (renderLabel && hasChildren) { return /*#__PURE__*/React__default["default"].createElement("li", _rollupPluginBabelHelpers["extends"]({ role: "menuitem" }, rest, { ref: ref, className: cx__default["default"](className, `${prefix}--menu-item`, 'msk-menu-item--render-label'), tabIndex: "-1", "aria-disabled": isDisabled || null }), children); } return /*#__PURE__*/React__default["default"].createElement("li", _rollupPluginBabelHelpers["extends"]({ role: "menuitem" }, rest, { ref: ref, className: classNames, tabIndex: "-1", "aria-disabled": isDisabled || null, "aria-haspopup": hasChildren || null, "aria-expanded": hasChildren ? submenuOpen : null, onClick: handleClick, onMouseEnter: hasChildren ? handleMouseEnter : null, onMouseLeave: hasChildren ? handleMouseLeave : null, onKeyDown: handleKeyDown }), /*#__PURE__*/React__default["default"].createElement("div", { className: `${prefix}--menu-item__icon` }, IconElement && /*#__PURE__*/React__default["default"].createElement(IconElement, null)), /*#__PURE__*/React__default["default"].createElement("div", { className: `${prefix}--menu-item__label` }, label), shortcut && !hasChildren && /*#__PURE__*/React__default["default"].createElement("div", { className: `${prefix}--menu-item__shortcut` }, shortcut), hasChildren && /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("div", { className: `${prefix}--menu-item__shortcut` }, _span || (_span = /*#__PURE__*/React__default["default"].createElement("span", { className: "msk-icon msk--menu-item-shortcut-icon" }, "arrow_right"))), /*#__PURE__*/React__default["default"].createElement(Menu.Menu, { label: label, open: submenuOpen, onClose: () => { closeSubmenu(); menuItem.current.focus(); }, x: boundaries.x, y: boundaries.y }, children))); }); MenuItem.propTypes = { /** * Optionally provide another Menu to create a submenu. props.children can't be used to specify the content of the MenuItem itself. Use props.label instead. */ children: PropTypes__default["default"].node, /** * Additional CSS class names. */ className: PropTypes__default["default"].string, /** * Specify whether the MenuItem is disabled or not. */ disabled: PropTypes__default["default"].bool, /** * Specify the kind of the MenuItem. */ kind: PropTypes__default["default"].oneOf(['default', 'danger']), /** * A label titling the MenuItem. Will be rendered as its text content. */ label: PropTypes__default["default"].string, /** * Provide an optional function to be called when the MenuItem is clicked. */ onClick: PropTypes__default["default"].func, /** * This prop is not intended for use. The only supported icons are Checkmarks to depict single- and multi-selects. This prop is used by MenuItemSelectable and MenuItemRadioGroup automatically. */ renderIcon: PropTypes__default["default"].oneOfType([PropTypes__default["default"].func, PropTypes__default["default"].object]), /** * If true, renderLabel will be used to display the children of the MenuItem. It can be any valid React node. */ renderLabel: PropTypes__default["default"].bool, /** * Provide a shortcut for the action of this MenuItem. Note that the component will only render it as a hint but not actually register the shortcut. */ shortcut: PropTypes__default["default"].string }; const MenuItemSelectable = /*#__PURE__*/React__default["default"].forwardRef(function MenuItemSelectable(_ref2, forwardRef) { let { className, defaultSelected, label = '', onChange, selected, ...rest } = _ref2; const prefix = usePrefix.usePrefix(); const context = React.useContext(MenuContext.MenuContext); const [checked, setChecked] = useControllableState.useControllableState({ value: selected, onChange, defaultValue: defaultSelected ?? false }); function handleClick(e) { setChecked(!checked); if (onChange) { onChange(e); } } React.useEffect(() => { if (!context.state.hasIcons) { context.dispatch({ type: 'enableIcons' }); } }, [context.state.hasIcons, context]); const classNames = cx__default["default"](className, `${prefix}--menu-item-selectable--selected`); return /*#__PURE__*/React__default["default"].createElement(MenuItem, _rollupPluginBabelHelpers["extends"]({}, rest, { ref: forwardRef, label: label, className: classNames, role: "menuitemcheckbox", "aria-checked": checked, renderIcon: () => { return checked && (_span2 || (_span2 = /*#__PURE__*/React__default["default"].createElement("span", { className: "msk-icon msk--menu-item-checked-icon" }, "check"))); }, onClick: handleClick })); }); MenuItemSelectable.propTypes = { /** * Additional CSS class names. */ className: PropTypes__default["default"].string, /** * Specify whether the option should be selected by default. */ defaultSelected: PropTypes__default["default"].bool, /** * A label titling this option. */ label: PropTypes__default["default"].string, /** * Provide an optional function to be called when the selection state changes. */ onChange: PropTypes__default["default"].func, /** * Pass a bool to props.selected to control the state of this option. */ selected: PropTypes__default["default"].bool }; const MenuItemGroup = /*#__PURE__*/React__default["default"].forwardRef(function MenuItemGroup(_ref3, forwardRef) { let { children, className, label = '', ...rest } = _ref3; const prefix = usePrefix.usePrefix(); const classNames = cx__default["default"](className, `${prefix}--menu-item-group`); return /*#__PURE__*/React__default["default"].createElement("li", { className: classNames, role: "none", ref: forwardRef }, /*#__PURE__*/React__default["default"].createElement("ul", _rollupPluginBabelHelpers["extends"]({}, rest, { role: "group", "aria-label": label, className: "msk-menu-item--group-list" }), children)); }); MenuItemGroup.propTypes = { /** * A collection of MenuItems to be rendered within this group. */ children: PropTypes__default["default"].node, /** * Additional CSS class names. */ className: PropTypes__default["default"].string, /** * A required label titling this group. */ label: PropTypes__default["default"].string }; const defaultItemToString = item => item.toString(); const MenuItemRadioGroup = /*#__PURE__*/React__default["default"].forwardRef(function MenuItemRadioGroup(_ref4, forwardRef) { let { className, defaultSelectedItem, items, itemToString = defaultItemToString, label = '', onChange, selectedItem, ...rest } = _ref4; const prefix = usePrefix.usePrefix(); const context = React.useContext(MenuContext.MenuContext); const [selection, setSelection] = useControllableState.useControllableState({ value: selectedItem, onChange, defaultValue: defaultSelectedItem }); function handleClick(item, e) { setSelection(item); if (onChange) { onChange(e); } } React.useEffect(() => { if (!context.state.hasIcons) { context.dispatch({ type: 'enableIcons' }); } }, [context.state.hasIcons, context]); const classNames = cx__default["default"](className, `${prefix}--menu-item-radio-group`); return /*#__PURE__*/React__default["default"].createElement("li", { className: classNames, role: "none", ref: forwardRef }, /*#__PURE__*/React__default["default"].createElement("ul", _rollupPluginBabelHelpers["extends"]({}, rest, { role: "group", "aria-label": label, className: `msk-menu-item--radio-group-list` }), items.map((item, i) => /*#__PURE__*/React__default["default"].createElement(MenuItem, { key: i, label: itemToString(item), role: "menuitemradio", "aria-checked": item === selection, renderIcon: () => { return item === selection && (_span3 || (_span3 = /*#__PURE__*/React__default["default"].createElement("span", { className: "msk-icon msk--menu-item-selection-icon" }, "check"))); }, onClick: e => { handleClick(item, e); } })))); }); MenuItemRadioGroup.propTypes = { /** * Additional CSS class names. */ className: PropTypes__default["default"].string, /** * Specify the default selected item. Must match the type of props.items. */ defaultSelectedItem: PropTypes__default["default"].any, /** * Provide a function to convert an item to the string that will be rendered. Defaults to item.toString(). */ itemToString: PropTypes__default["default"].func, /** * Provide the options for this radio group. Can be of any type, as long as you provide an appropriate props.itemToString function. */ items: PropTypes__default["default"].array, /** * A label titling this radio group. */ label: PropTypes__default["default"].string, /** * Provide an optional function to be called when the selection changes. */ onChange: PropTypes__default["default"].func, /** * Provide props.selectedItem to control the state of this radio group. Must match the type of props.items. */ selectedItem: PropTypes__default["default"].any }; const MenuItemDivider = /*#__PURE__*/React__default["default"].forwardRef(function MenuItemDivider(_ref5, forwardRef) { let { className, ...rest } = _ref5; const prefix = usePrefix.usePrefix(); const classNames = cx__default["default"](className, `${prefix}--menu-item-divider`); return /*#__PURE__*/React__default["default"].createElement("li", _rollupPluginBabelHelpers["extends"]({}, rest, { className: classNames, role: "separator", ref: forwardRef })); }); MenuItemDivider.propTypes = { /** * Additional CSS class names. */ className: PropTypes__default["default"].string }; exports.MenuItem = MenuItem; exports.MenuItemDivider = MenuItemDivider; exports.MenuItemGroup = MenuItemGroup; exports.MenuItemRadioGroup = MenuItemRadioGroup; exports.MenuItemSelectable = MenuItemSelectable;