UNPKG

@vtex/styleguide

Version:

> VTEX Styleguide React components ([Docs](https://vtex.github.io/styleguide))

273 lines (227 loc) 9.55 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _react = require("react"); var _react2 = _interopRequireDefault(_react); var _propTypes = require("prop-types"); var _propTypes2 = _interopRequireDefault(_propTypes); var _reactOverlays = require("react-overlays"); var _classnames = require("classnames"); var _classnames2 = _interopRequireDefault(_classnames); var _Toggle = require("../Toggle"); var _Toggle2 = _interopRequireDefault(_Toggle); var _withForwardedRef = require("../../modules/withForwardedRef"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } var MAX_Z_INDEX = 2147483647; var DEFAULT_Z_INDEX = 999; var CONTAINER_MARGIN = 6; var WINDOW_MARGIN = 10; var DEFAULT_DOCUMENT_ELEMENT = { scrollTop: 0, scrollLeft: 0, clientWidth: 0, clientHeight: 0 }; var Menu = /*#__PURE__*/ function (_Component) { _inheritsLoose(Menu, _Component); function Menu(props) { var _this; _this = _Component.call(this, props) || this; _this.state = { hasCalculatedSize: false, // hides the menu while calculating its size and position isUpwards: false, // opens the menu from bottom to top, if it doesn't fit on the screen otherwise isVisible: false, // triggers the opening animation menuHeight: 0, containerHeight: 0 }; _this.onWindowResize = function () { return _this.forceUpdate(); }; _this.getMenuBounds = function () { return _this.menuElement.current && _this.menuElement.current.getBoundingClientRect && _this.menuElement.current.getBoundingClientRect(); }; _this.getContainerBounds = function () { return _this.containerElement.current && _this.containerElement.current.getBoundingClientRect && _this.containerElement.current.getBoundingClientRect(); }; _this.containerElement = _react2.default.createRef(); _this.menuElement = props.forwardedRef || _react2.default.createRef(); return _this; } var _proto = Menu.prototype; _proto.componentDidMount = function componentDidMount() { if (window) window.addEventListener('resize', this.onWindowResize); }; _proto.componentWillUnmount = function componentWillUnmount() { if (window) window.removeEventListener('resize', this.onWindowResize); }; _proto.updateMenu = function updateMenu() { var _this2 = this; var menuBounds = this.getMenuBounds(); var containerBounds = this.getContainerBounds(); if (!menuBounds || !containerBounds) return; var containerHeight = containerBounds.height; var initialMenuHeight = menuBounds.height; var itemHeight = initialMenuHeight / this.props.options.length; var isOutOfBounds = menuBounds.top + initialMenuHeight + containerHeight > window.innerHeight; var isUpwards = isOutOfBounds && containerBounds.top > window.innerHeight / 2; var maxMenuHeight = isUpwards ? menuBounds.top - CONTAINER_MARGIN - WINDOW_MARGIN - containerHeight : window.innerHeight - menuBounds.top - CONTAINER_MARGIN - WINDOW_MARGIN; // Makes the height of the menu, if it doesn't entirely fit on the screen, // fall in the middle of an item, to hint that the menu scrolls var visibleItemsNum = Math.round(maxMenuHeight / itemHeight); var adjustedMenuHeight = visibleItemsNum * itemHeight - itemHeight / 2; var menuHeight = maxMenuHeight < initialMenuHeight ? adjustedMenuHeight : 0; this.setState({ containerHeight: containerHeight, menuHeight: menuHeight, isUpwards: isUpwards, hasCalculatedSize: true }, function () { // triggers the menu opening animation setTimeout(function () { _this2.setState({ isVisible: true }); }, 1); }); }; _proto.componentDidUpdate = function componentDidUpdate(prevProps) { if (!prevProps.open && this.props.open) { this.updateMenu(); } if (prevProps.open && !this.props.open) { this.setState({ hasCalculatedSize: false, isUpwards: false, isVisible: false, menuHeight: 0, containerHeight: 0 }); } }; _proto.render = function render() { var _this3 = this; var _this$props = this.props, options = _this$props.options, align = _this$props.align, open = _this$props.open, onClose = _this$props.onClose, children = _this$props.children, width = _this$props.width, zIndex = _this$props.zIndex; var _this$state = this.state, hasCalculatedSize = _this$state.hasCalculatedSize, isUpwards = _this$state.isUpwards, isVisible = _this$state.isVisible, menuHeight = _this$state.menuHeight; var isRight = align === 'right'; return _react2.default.createElement("div", { className: "relative" }, _react2.default.createElement("div", { ref: this.containerElement }, children), _react2.default.createElement(_reactOverlays.Overlay, { show: open }, function () { var _ref2; var _this3$getContainerBo = _this3.getContainerBounds(), top = _this3$getContainerBo.top, left = _this3$getContainerBo.left, right = _this3$getContainerBo.right, height = _this3$getContainerBo.height; var _ref = document ? document.documentElement : DEFAULT_DOCUMENT_ELEMENT, scrollTop = _ref.scrollTop, scrollLeft = _ref.scrollLeft, clientWidth = _ref.clientWidth, clientHeight = _ref.clientHeight; return _react2.default.createElement("div", { ref: _this3.menuElement, style: (_ref2 = { transform: !hasCalculatedSize || isVisible ? 'scale(1)' : 'scale(0.9, 0.6)', transformOrigin: (isRight ? '75%' : '25%') + " " + (isUpwards ? '100%' : '0'), transition: isVisible ? 'transform 50ms ease-out, opacity 25ms' : 'none' }, _ref2[isUpwards ? 'bottom' : 'top'] = isUpwards ? clientHeight - (top + scrollTop - CONTAINER_MARGIN) : top + scrollTop + height + CONTAINER_MARGIN, _ref2[isRight ? 'right' : 'left'] = isRight ? clientWidth - right : left + scrollLeft, _ref2.width = width, _ref2.zIndex = zIndex === 'max' ? MAX_Z_INDEX : zIndex, _ref2), className: "absolute ba b--muted-4 br2 shadow-5 " + (isRight ? 'right-0' : 'left-0') + "\n " + (isVisible ? 'o-100' : 'o-0') }, _react2.default.createElement("div", { className: "b2 br2 bg-base" }, _react2.default.createElement("div", { style: { height: menuHeight || 'auto' }, className: menuHeight ? 'overflow-auto' : '' }, options.map(function (option, index) { return _react2.default.createElement("button", { disabled: option.disabled, key: index, className: (0, _classnames2.default)('flex justify-between items-center t-body ph6 h-regular ma0 bg-transparent bn w-100 tl', { 'hover-bg-muted-5 pointer': !option.disabled }), "data-testid": option.testId || "menu-option-" + index, onClick: function onClick() { option.onClick(option); if (onClose) { onClose(); } } }, _react2.default.createElement("span", { className: (0, _classnames2.default)({ 'w-70 truncate': option.toggle, 'w-100 truncate': !option.toggle, 'c-disabled': option.disabled, 'c-danger': option.isDangerous }) }, option.label), option.toggle && _react2.default.createElement("div", { style: { pointerEvents: 'none' } }, _react2.default.createElement(_Toggle2.default, { size: "regular", semantic: option.toggle.semantic, checked: option.toggle.checked }))); })))); })); }; return Menu; }(_react.Component); Menu.defaultProps = { options: [], align: 'right', open: false, zIndex: DEFAULT_Z_INDEX }; Menu.propTypes = { /** The element which will open the menu--the menu will * be positioned around this element */ children: _propTypes2.default.node, /** @ignore Forwarded Ref */ forwardedRef: _withForwardedRef.refShape, /** Menu visibility (default is false) */ open: _propTypes2.default.bool, /** Menu Box width */ width: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), /** Menu options */ options: _propTypes2.default.arrayOf(_propTypes2.default.shape({ label: _propTypes2.default.node, onClick: _propTypes2.default.func, disabled: _propTypes2.default.bool, /** Optional testid property */ testId: _propTypes2.default.string, /** whether option has inline toggle */ toggle: _propTypes2.default.shape({ checked: _propTypes2.default.bool, semantic: _propTypes2.default.bool }) })), /** function to close the menu after clicking an option */ onClose: _propTypes2.default.func, /** Menu Box align (default is right) */ align: _propTypes2.default.oneOf(['right', 'left']), /** Default z-index to Menu view, default is 999 */ zIndex: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]) }; exports.default = (0, _withForwardedRef.withForwardedRef)(Menu);