@vtex/styleguide
Version:
> VTEX Styleguide React components ([Docs](https://vtex.github.io/styleguide))
231 lines (190 loc) • 8.06 kB
JavaScript
"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);