@mskcc/carbon-react
Version:
Carbon react components for the MSKCC DSM
422 lines (412 loc) • 14 kB
JavaScript
/**
* 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;