UNPKG

@vtex/styleguide

Version:

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

231 lines (190 loc) 8.06 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 _classnames = require("classnames"); var _classnames2 = _interopRequireDefault(_classnames); var _Close = require("../icon/Close"); var _Close2 = _interopRequireDefault(_Close); var _withDeviceHoc = require("../utils/withDeviceHoc"); var _withDeviceHoc2 = _interopRequireDefault(_withDeviceHoc); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } var DEFAULT_WIDTH = 292; var CONTAINER_MARGIN = 6; var WINDOW_MARGIN = 10; 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.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 = _react2.default.createRef(); return _this; } var _proto = Menu.prototype; _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 : window.innerHeight - menuBounds.top - containerHeight - 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 _this$props = this.props, width = _this$props.width, align = _this$props.align, open = _this$props.open, children = _this$props.children, button = _this$props.button, isMobile = _this$props.isMobile, options = _this$props.options, testId = _this$props.testId; var _this$state = this.state, hasCalculatedSize = _this$state.hasCalculatedSize, isUpwards = _this$state.isUpwards, isVisible = _this$state.isVisible, menuHeight = _this$state.menuHeight, containerHeight = _this$state.containerHeight; var isRight = align === 'right'; var optionsLabel = options && options.label || ''; var styles = { boxShadow: '0px 1px 18px rgba(0, 0, 0, 0.14)' }; if (isMobile) { styles = _extends({}, styles, { top: 0, height: '100%' }); } if (!isMobile) { var _extends2; styles = _extends({}, styles, (_extends2 = { 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' }, _extends2[isUpwards ? 'bottom' : 'top'] = containerHeight + CONTAINER_MARGIN, _extends2)); } var openContainerClasses = (0, _classnames2.default)('fixed absolute-ns w-100 w-auto-ns z-999 ba bw1 b--muted-4 bg-base', { 'right-0': isRight, 'left-0': !isRight, br0: isMobile, br2: !isMobile, 'o-100': isVisible, 'o-0': !isVisible }); return _react2.default.createElement("div", { className: "relative" }, _react2.default.createElement("div", { "data-testid": testId, ref: this.containerElement }, button), open && _react2.default.createElement(_react2.default.Fragment, null, _react2.default.createElement("div", { ref: this.menuElement, style: styles, className: openContainerClasses }, _react2.default.createElement("div", { className: "b2-ns br2-ns bg-base h-100 h-auto-ns", style: { width: isMobile ? '100%' : width || DEFAULT_WIDTH } }, _react2.default.createElement("div", { className: menuHeight ? 'overflow-auto h-100 h-auto-ns' : 'h-100 h-auto-ns' }, isMobile && _react2.default.createElement("div", { className: "flex justify-between flex-row items-baseline pa6 mh3" }, _react2.default.createElement("div", { className: "truncate f3 pr6" }, optionsLabel), _react2.default.createElement("div", { onClick: this.props.onBackgroundClick }, _react2.default.createElement(_Close2.default, { size: 20, color: "currentColor" }))), children))))); }; return Menu; }(_react.Component); Menu.defaultProps = { options: [], align: 'right', open: false }; Menu.propTypes = { /** The element which will open the menu--the menu will * be positioned around this element */ children: _propTypes2.default.node, /** Menu visibility (default is false) */ open: _propTypes2.default.bool, options: _propTypes2.default.array, button: _propTypes2.default.element, /** Menu Box width (default is 292px) */ width: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), /** 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']), /** Function to handle callback on overlay click */ onBackgroundClick: _propTypes2.default.func, /** Boolean if the device is mobile */ isMobile: _propTypes2.default.bool, /** String used as id to serve test purposes */ testId: _propTypes2.default.string }; exports.default = (0, _withDeviceHoc2.default)(Menu);