@orfeas126/box-ui-elements
Version:
Box UI Elements
181 lines (179 loc) • 7.89 kB
JavaScript
const _excluded = ["children", "className", "isDisabled"];
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) { _defineProperty(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 _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var s = Object.getOwnPropertySymbols(e); for (r = 0; r < s.length; r++) o = s[r], t.includes(o) || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } return t; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
import * as React from 'react';
import classNames from 'classnames';
import omit from 'lodash/omit';
import debounce from 'lodash/debounce';
import './SubmenuItem.scss';
import Arrow16 from '../../icon/fill/Arrow16';
const SUBMENU_LEFT_ALIGNED_CLASS = 'is-left-aligned';
const SUBMENU_BOTTOM_ALIGNED_CLASS = 'is-bottom-aligned';
const SUBMENU_RIGHT_BOTTOM_ALIGNED_CLASS = 'is-right-bottom-aligned';
/**
* A menu-item component which triggers open a submenu
*
* @NOTE: Nested submenus are NOT currently supported, switching
* focus with arrow keys in the subsubmenu is not working properly.
*/
class SubmenuItem extends React.Component {
constructor(...args) {
super(...args);
_defineProperty(this, "state", {
isSubmenuOpen: false,
submenuFocusIndex: null
});
_defineProperty(this, "getMenuAlignmentClasses", () => {
if (!this.submenuTriggerEl || !this.submenuEl) {
return {};
}
const {
rightBoundaryElement,
bottomBoundaryElement
} = this.props;
const submenuElBounding = this.submenuEl.getBoundingClientRect();
const submenuTriggerElBounding = this.submenuTriggerEl.getBoundingClientRect();
const rightBoundaryElementBounding = rightBoundaryElement ? rightBoundaryElement.getBoundingClientRect() : {
right: window.innerWidth
};
const bottomBoundaryElementBounding = bottomBoundaryElement ? bottomBoundaryElement.getBoundingClientRect() : {
bottom: window.innerHeight
};
const isLeftAligned = submenuTriggerElBounding.right + submenuElBounding.width > rightBoundaryElementBounding.right;
const isBottomAligned = submenuTriggerElBounding.top + submenuElBounding.height > bottomBoundaryElementBounding.bottom;
const isRightBottomAligned = submenuTriggerElBounding.bottom + submenuElBounding.height > bottomBoundaryElementBounding.bottom;
return {
[SUBMENU_LEFT_ALIGNED_CLASS]: isLeftAligned,
[SUBMENU_BOTTOM_ALIGNED_CLASS]: isBottomAligned,
[SUBMENU_RIGHT_BOTTOM_ALIGNED_CLASS]: isRightBottomAligned // Used only in medium-screen viewport sizes
};
});
_defineProperty(this, "handleMenuItemClick", event => {
const {
isDisabled,
onClick
} = this.props;
// If aria-disabled is passed as a prop, we should ignore clicks on this menu item
if (isDisabled) {
event.stopPropagation();
event.preventDefault();
return;
}
if (onClick) {
onClick(event);
}
// If event target is triggering submenu element, do not propagate to close menu
if (this.submenuEl && !this.submenuEl.contains(event.target)) {
event.stopPropagation();
event.preventDefault();
}
});
_defineProperty(this, "handleKeyDown", event => {
switch (event.key) {
case ' ':
case 'Enter':
case 'ArrowRight':
event.stopPropagation();
event.preventDefault();
this.openSubmenuAndFocus();
break;
default:
break;
}
});
_defineProperty(this, "closeSubmenu", debounce(() => {
this.setState({
isSubmenuOpen: false
});
}, 50));
_defineProperty(this, "closeSubmenuAndFocusTrigger", isKeyboardEvent => {
this.closeSubmenu();
if (this.submenuTriggerEl && isKeyboardEvent) {
this.submenuTriggerEl.focus();
}
});
_defineProperty(this, "openSubmenu", () => {
this.closeSubmenu.cancel();
const {
onOpen
} = this.props;
if (onOpen) {
onOpen();
}
this.setState({
isSubmenuOpen: true,
submenuFocusIndex: null
});
});
_defineProperty(this, "openSubmenuAndFocus", () => {
const {
onOpen
} = this.props;
if (onOpen) {
onOpen();
}
this.setState({
isSubmenuOpen: true,
submenuFocusIndex: 0
});
});
}
render() {
const _this$props = this.props,
{
children,
className,
isDisabled
} = _this$props,
rest = _objectWithoutProperties(_this$props, _excluded);
const {
isSubmenuOpen,
submenuFocusIndex
} = this.state;
const elements = React.Children.toArray(children);
const submenuTriggerContent = elements[0];
const submenu = elements[1];
if (elements.length !== 2 || !submenuTriggerContent || !submenu) {
throw new Error('SubmenuItem must have exactly two children, a trigger component and a <Menu>');
}
const chevron = /*#__PURE__*/React.createElement(Arrow16, {
className: "menu-item-arrow",
width: 12,
height: 12
});
const menuItemProps = _objectSpread(_objectSpread({}, omit(rest, ['bottomBoundaryElement', 'onClick', 'onOpen', 'rightBoundaryElement', 'role', 'tabIndex'])), {}, {
'aria-disabled': isDisabled ? 'true' : undefined,
'aria-expanded': isSubmenuOpen ? 'true' : 'false',
'aria-haspopup': 'true',
className: classNames('menu-item', 'submenu-target', className),
onClick: this.handleMenuItemClick,
onMouseLeave: this.closeSubmenu,
onMouseEnter: this.openSubmenu,
onKeyDown: this.handleKeyDown,
ref: ref => {
this.submenuTriggerEl = ref;
},
role: 'menuitem',
tabIndex: -1
});
const submenuProps = {
className: classNames(submenu.props.className, 'submenu', this.getMenuAlignmentClasses()),
initialFocusIndex: submenuFocusIndex,
// Hide the menu instead of unmounting it. Otherwise onMouseLeave won't work.
isHidden: !isSubmenuOpen,
isSubmenu: true,
onClose: this.closeSubmenuAndFocusTrigger,
setRef: ref => {
this.submenuEl = ref;
}
};
return /*#__PURE__*/React.createElement("li", menuItemProps, submenuTriggerContent, chevron, /*#__PURE__*/React.cloneElement(submenu, submenuProps));
}
}
export default SubmenuItem;
//# sourceMappingURL=SubmenuItem.js.map