terra-menu
Version:
The menu is a popup component that displays a list of items, item groups, and dividers. Menu Items can be actionable, have toggle-style selection, or have nested submenu items. Menu Item groups are a single-select grouping of menu items. The Menu will det
425 lines (421 loc) • 19.2 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _react = _interopRequireDefault(require("react"));
var _reactIntl = require("react-intl");
var _propTypes = _interopRequireDefault(require("prop-types"));
var _terraArrange = _interopRequireDefault(require("terra-arrange"));
var _IconCheckmark = _interopRequireDefault(require("terra-icon/lib/icon/IconCheckmark"));
var _IconChevronRight = _interopRequireDefault(require("terra-icon/lib/icon/IconChevronRight"));
var _IconConsultInstructionsForUse = _interopRequireDefault(require("terra-icon/lib/icon/IconConsultInstructionsForUse"));
var _classnames = _interopRequireDefault(require("classnames"));
var _bind = _interopRequireDefault(require("classnames/bind"));
var _terraThemeContext = _interopRequireDefault(require("terra-theme-context"));
var _terraVisuallyHiddenText = _interopRequireDefault(require("terra-visually-hidden-text"));
var KeyCode = _interopRequireWildcard(require("keycode-js"));
var _MenuItemModule = _interopRequireDefault(require("./MenuItem.module.scss"));
var _MenuUtils = _interopRequireDefault(require("./_MenuUtils"));
var _excluded = ["text", "isDisabled", "isSelected", "isToggled", "isInstructionsForUse", "isSelectable", "isToggleable", "ariaDescribedBy", "subMenuItems", "isActive", "icon", "isHighlighted", "index", "totalItems", "intl"];
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } /* eslint-disable-next-line import/no-extraneous-dependencies */
var cx = _bind.default.bind(_MenuItemModule.default);
var contextTypes = {
isGroupItem: _propTypes.default.bool,
/**
* isSelectableMenu has been deprecated and will be removed on next major version release.
* Rename the `isSelectableMenu` prop to `isToggleableMenu`.
*/
isSelectableMenu: _propTypes.default.bool,
/**
* Indicates if the menu should be toggleable.
*/
isToggleableMenu: _propTypes.default.bool,
/**
* Indicates if the menu needs to reserve space on the left for each menu item if one
* or more of the items needs to display a custom icon, instructions for use icon, or
* a checkmark if the item is part of a group item.
*/
shouldReserveSpaceForIcon: _propTypes.default.bool
};
var propTypes = {
/**
* Sets the item's text.
*/
text: _propTypes.default.string,
/**
* Indicates if item should be disabled
*/
isDisabled: _propTypes.default.bool,
/**
* 
* isSelected has been deprecated and will be removed on next major version release.
* Rename the `isSelected` prop to `isToggled`.
*/
isSelected: _propTypes.default.bool,
/**
* 
* isSelectable has been deprecated and will be removed on next major version release.
* Rename the `isSelectable` prop to `isToggleable`.
*/
isSelectable: _propTypes.default.bool,
/**
* Indicates if the item is toggled. A toggled item is indicated with a checkmark.
*/
isToggled: _propTypes.default.bool,
/**
* Indicates if the item should be toggleable.
*/
isToggleable: _propTypes.default.bool,
/**
* Displays the eIFU (electronic instructions for use) icon for menu item if set to true. (This icon is used to indicate Help content that is the equivalent of an instruction manual)
*/
isInstructionsForUse: _propTypes.default.bool,
/**
* List of Menu.Items to display in a submenu when this item is clicked.
*/
subMenuItems: _propTypes.default.arrayOf(_propTypes.default.element),
/**
* Callback function for when item is clicked
*/
onClick: _propTypes.default.func,
/**
* Callback function for when toggleable state changes on a toggleabe item.
*/
onChange: _propTypes.default.func,
/**
* Indicates if the item has focus. This is used internally to control focus and does not set initial focus.
*/
isActive: _propTypes.default.bool,
/**
* Custom icon to display in the item
*/
icon: _propTypes.default.element,
/**
* @private
* Indicates if the item should display with a highlighted background. Reserved for Terra higher-order component approved usage only.
*/
isHighlighted: _propTypes.default.bool,
/**
* @private
* The index of the menu item.
*/
index: _propTypes.default.number,
/**
* @private
* Number of items in the menu.
*/
totalItems: _propTypes.default.number,
/**
* @private
* The intl object to be injected for translations. Provided by the injectIntl function.
*/
intl: _propTypes.default.shape({
formatMessage: _propTypes.default.func
}).isRequired,
/**
* If additional visible information text is used, provide a string containing the IDs for html elements that
* help describe the intent of the group of menu.
*/
ariaDescribedBy: _propTypes.default.string
};
var defaultProps = {
text: '',
isToggled: false,
isInstructionsForUse: false,
isActive: false,
isToggleable: undefined,
isDisabled: false,
subMenuItems: [],
isHighlighted: false
};
// TODO: remove isSelect and isSeletable props on the next major release
var MenuItem = /*#__PURE__*/function (_React$Component) {
function MenuItem(props, context) {
var _this;
(0, _classCallCheck2.default)(this, MenuItem);
_this = _callSuper(this, MenuItem, [props, context]);
var _this$props = _this.props,
isSelected = _this$props.isSelected,
isSelectable = _this$props.isSelectable,
isToggled = _this$props.isToggled,
isToggleable = _this$props.isToggleable;
_this.wrapOnClick = _this.wrapOnClick.bind(_this);
_this.wrapOnKeyDown = _this.wrapOnKeyDown.bind(_this);
_this.wrapOnKeyUp = _this.wrapOnKeyUp.bind(_this);
_this.handleToggled = _this.handleToggled.bind(_this);
_this.setItemNode = _this.setItemNode.bind(_this);
var toggled = isToggled || isSelected;
var toggleable = isToggleable || isSelectable;
_this.state = {
isToggled: toggled && toggleable && !context.isGroupItem,
isActive: false
};
return _this;
}
(0, _inherits2.default)(MenuItem, _React$Component);
return (0, _createClass2.default)(MenuItem, [{
key: "componentDidUpdate",
value: function componentDidUpdate() {
if (this.props.isActive && this.itemNode) {
this.itemNode.focus();
}
}
}, {
key: "handleToggled",
value: function handleToggled(event) {
event.preventDefault();
var _this$props2 = this.props,
isSelectable = _this$props2.isSelectable,
isToggleable = _this$props2.isToggleable;
if ((isToggleable || isSelectable) && !this.context.isGroupItem && !this.props.isDisabled) {
this.setState(function (prevState) {
return {
isToggled: !prevState.isToggled
};
});
if (this.props.onChange) {
this.props.onChange(event, !this.state.isToggled);
}
}
}
}, {
key: "setItemNode",
value: function setItemNode(node) {
if (node) {
this.itemNode = node;
}
}
}, {
key: "wrapOnClick",
value: function wrapOnClick(event) {
event.stopPropagation();
if (!this.props.isDisabled) {
this.handleToggled(event);
if (this.props.onClick) {
this.props.onClick(event);
}
}
}
}, {
key: "wrapOnKeyDown",
value: function wrapOnKeyDown(onKeyDown) {
var _this2 = this;
return function (event) {
if (event.nativeEvent.keyCode === KeyCode.KEY_RETURN || event.nativeEvent.keyCode === KeyCode.KEY_SPACE) {
_this2.handleToggled(event);
// Only add active style if the menu doesn't have a sub menu to avoid active style being stuck enabled
if (!(_this2.props.subMenuItems && _this2.props.subMenuItems.length > 0)) {
_this2.setState({
isActive: true
});
}
if (_this2.props.onClick) {
_this2.props.onClick(event);
}
// Remove active state when tab key is released while while holding the space key to avoid active style being stuck enabled
} else if (event.nativeEvent.keyCode === KeyCode.KEY_TAB) {
_this2.setState({
isActive: false
});
}
if (onKeyDown) {
onKeyDown(event);
}
};
}
}, {
key: "wrapOnKeyUp",
value: function wrapOnKeyUp(onKeyUp) {
var _this3 = this;
return function (event) {
if (event.nativeEvent.keyCode === KeyCode.KEY_RETURN || event.nativeEvent.keyCode === KeyCode.KEY_SPACE) {
_this3.setState({
isActive: false
});
}
if (onKeyUp) {
onKeyUp(event);
}
};
}
}, {
key: "render",
value: function render() {
var _this4 = this;
var _this$props3 = this.props,
text = _this$props3.text,
isDisabled = _this$props3.isDisabled,
isSelected = _this$props3.isSelected,
isToggled = _this$props3.isToggled,
isInstructionsForUse = _this$props3.isInstructionsForUse,
isSelectable = _this$props3.isSelectable,
isToggleable = _this$props3.isToggleable,
ariaDescribedBy = _this$props3.ariaDescribedBy,
subMenuItems = _this$props3.subMenuItems,
isActive = _this$props3.isActive,
icon = _this$props3.icon,
isHighlighted = _this$props3.isHighlighted,
index = _this$props3.index,
totalItems = _this$props3.totalItems,
intl = _this$props3.intl,
customProps = (0, _objectWithoutProperties2.default)(_this$props3, _excluded);
var _this$context = this.context,
isGroupItem = _this$context.isGroupItem,
isToggleableMenu = _this$context.isToggleableMenu,
isSelectableMenu = _this$context.isSelectableMenu,
shouldReserveSpaceForIcon = _this$context.shouldReserveSpaceForIcon;
var attributes = _objectSpread({}, customProps);
var toggled = isToggled || isSelected;
var toggleable = isToggleable || isSelectable;
var toggleableMenu = isToggleableMenu || isSelectableMenu;
// This is passed down by the single select list in group item and not needed
delete attributes.hasChevron;
attributes.onClick = this.wrapOnClick;
if (!isDisabled) {
attributes.onKeyDown = this.wrapOnKeyDown(attributes.onKeyDown);
attributes.onKeyUp = this.wrapOnKeyUp(attributes.Up);
attributes['data-terra-menu-interactive-item'] = true;
}
var markAsToggled = this.state.isToggled || isGroupItem && toggled && !isDisabled;
var textContainer = /*#__PURE__*/_react.default.createElement("div", {
className: cx('text')
}, text);
var hasChevron = subMenuItems.length > 0;
var itemClassNames = cx(['item', {
'is-highlighted': isHighlighted
}, {
'is-toggled': markAsToggled
}, {
'is-toggleable': toggleable
}, {
'is-disabled': isDisabled
}, {
'is-top-level': hasChevron
},
// eslint-disable-next-line quote-props
{
'active': this.state.isActive
}, attributes.className]);
var content = textContainer;
var navigationContent;
var ariaLiveValue = 'polite';
if (this.itemNode && index === 0 && totalItems !== 1) {
navigationContent = ". ".concat(intl.formatMessage({
id: 'Terra.menu.navigateMenuItem'
}));
if (!_MenuUtils.default.isMac() && this.itemNode.parentNode.getAttribute('data-submenu') !== 'true') {
navigationContent = '';
}
}
if (isGroupItem && !markAsToggled) {
ariaLiveValue = undefined;
}
var screenReaderResponse = /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, _MenuUtils.default.isMac() && /*#__PURE__*/_react.default.createElement(_terraVisuallyHiddenText.default, {
text: intl.formatMessage({
id: 'Terra.menu.index'
}, {
index: index + 1,
totalItems: totalItems
})
}), (isGroupItem || toggleable) && /*#__PURE__*/_react.default.createElement(_terraVisuallyHiddenText.default, {
"aria-live": ariaLiveValue,
text: markAsToggled ? intl.formatMessage({
id: 'Terra.menu.selected'
}) : intl.formatMessage({
id: 'Terra.menu.unselected'
})
}), /*#__PURE__*/_react.default.createElement(_terraVisuallyHiddenText.default, {
text: navigationContent
}), subMenuItems.length > 0 && /*#__PURE__*/_react.default.createElement(_terraVisuallyHiddenText.default, {
text: intl.formatMessage({
id: 'Terra.menu.itemWithSubmenu'
})
}), this.itemNode && this.itemNode.parentNode.getAttribute('data-submenu') === 'true' && index === 0 && /*#__PURE__*/_react.default.createElement(_terraVisuallyHiddenText.default, {
text: intl.formatMessage({
id: 'Terra.menu.exitSubmenu'
})
}));
if (hasChevron || toggleableMenu || isInstructionsForUse || icon || shouldReserveSpaceForIcon) {
var fitStartIcon = null;
if (isInstructionsForUse) {
fitStartIcon = /*#__PURE__*/_react.default.createElement(_IconConsultInstructionsForUse.default, {
className: cx('start-icon')
});
} else if (toggleableMenu) {
if (!toggleable && icon) {
fitStartIcon = /*#__PURE__*/_react.default.cloneElement(icon, {
className: cx([icon.props.className, 'start-icon'])
});
} else {
fitStartIcon = /*#__PURE__*/_react.default.createElement(_IconCheckmark.default, {
className: cx(['checkmark', 'start-icon'])
});
}
} else if (icon) {
fitStartIcon = /*#__PURE__*/_react.default.cloneElement(icon, {
className: cx([icon.props.className, 'start-icon'])
});
} else if (shouldReserveSpaceForIcon) {
fitStartIcon = /*#__PURE__*/_react.default.createElement(_IconCheckmark.default, {
className: cx(['checkmark', 'start-icon'])
});
}
content = /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_terraArrange.default, {
fitStart: fitStartIcon,
fill: textContainer,
fitEnd: hasChevron ? /*#__PURE__*/_react.default.createElement(_IconChevronRight.default, {
className: cx('chevron')
}) : null,
align: "center"
}), screenReaderResponse);
} else {
content = /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, textContainer, screenReaderResponse);
}
var role = 'menuitem';
var isMacOs = _MenuUtils.default.isMac();
if (isGroupItem && isMacOs) {
role = 'menuitemradio';
} else if (toggleable && isMacOs) {
role = 'menuitemcheckbox';
}
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
return /*#__PURE__*/_react.default.createElement(_terraThemeContext.default.Consumer, null, function (theme) {
return /*#__PURE__*/_react.default.createElement("li", (0, _extends2.default)({}, attributes, {
className: (0, _classnames.default)(itemClassNames, cx(theme.className)),
ref: _this4.setItemNode,
role: role,
"aria-selected": isMacOs && toggleable ? markAsToggled : undefined,
tabIndex: "0",
"aria-disabled": isDisabled
// stop event propagation in case Menu oppened inside the layout component that has its own event handler for that event.
// added for Menu Button support in terra-compact-interactive-list.
,
onFocus: function onFocus(event) {
return event.stopPropagation();
}
}), content);
});
}
}]);
}(_react.default.Component);
MenuItem.propTypes = propTypes;
MenuItem.defaultProps = defaultProps;
MenuItem.contextTypes = contextTypes;
var _default = exports.default = (0, _reactIntl.injectIntl)(MenuItem);