UNPKG

@helpscout/hsds-react

Version:

React component library for Help Scout's Design System

554 lines (450 loc) 20.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = exports.ModalComponent = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose")); var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _reactDom = _interopRequireDefault(require("react-dom")); var _Modal = _interopRequireDefault(require("./Modal.ActionFooter")); var _Modal2 = _interopRequireDefault(require("./Modal.Body")); var _Modal3 = _interopRequireDefault(require("./Modal.Content")); var _Modal4 = _interopRequireDefault(require("./Modal.Footer")); var _Modal5 = _interopRequireDefault(require("./Modal.Header")); var _Modal6 = _interopRequireDefault(require("./Modal.HeaderV2")); var _Modal7 = _interopRequireDefault(require("./Modal.Overlay")); var _CloseButton = _interopRequireDefault(require("../CloseButton")); var _EventListener = _interopRequireDefault(require("../EventListener")); var _KeypressListener = _interopRequireDefault(require("../KeypressListener")); var _PortalWrapper = _interopRequireDefault(require("../PortalWrapper")); var _Keys = _interopRequireDefault(require("../../constants/Keys")); var _classnames = _interopRequireDefault(require("classnames")); var _focus = require("../../utilities/focus"); var _node = require("../../utilities/node"); var _Modal8 = require("./Modal.utils"); var _Modal9 = require("./Modal.css"); var _jsxRuntime = require("react/jsx-runtime"); var modalBaseZIndex = 1040; var portalOptions = { id: 'Modal', zIndex: modalBaseZIndex, preventEscActionElements: ['DropList__MenuList', 'DropList__Combobox__input', 'FieldInput__input', 'EditableTextarea__Textarea'] }; var modalV2Animation = { delay: 0, duration: 250, easing: 'boop', sequence: 'fade scale' }; var overlayV2Animation = { delay: 0, duration: 250, easing: 'ease-in-out', sequence: 'fade' }; function noop() {} var Modal = /*#__PURE__*/function (_React$PureComponent) { (0, _inheritsLoose2.default)(Modal, _React$PureComponent); function Modal() { var _this; for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _React$PureComponent.call.apply(_React$PureComponent, [this].concat(args)) || this; _this.documentnode = void 0; _this.cardnode = void 0; _this.closenode = void 0; _this.scrollableNode = void 0; _this.handleOnResize = function () { _this.positionCloseNode(); }; _this.handleOnTab = function (event) { var containTabKeyPress = _this.props.containTabKeyPress; if (!containTabKeyPress || !_this.cardNode || !_this.documentNode) return; var focusableNodes = (0, _focus.findFocusableNodes)(_this.cardNode); var focusedNodeIndex = _this.getFocusNodeIndexFromEvent(event); if (focusedNodeIndex === focusableNodes.length - 1) { event.preventDefault(); if (focusableNodes && focusableNodes[0]) focusableNodes[0].focus(); } }; _this.handleOnShiftTab = function (event) { var containTabKeyPress = _this.props.containTabKeyPress; if (!containTabKeyPress || !_this.cardNode || !_this.documentNode) return; var focusableNodes = (0, _focus.findFocusableNodes)(_this.cardNode); var focusedNodeIndex = _this.getFocusNodeIndexFromEvent(event); if (focusedNodeIndex === 0) { event.preventDefault(); var i = focusableNodes.length - 1; if (i > -1 && focusableNodes && focusableNodes[i]) focusableNodes[i].focus(); } }; _this.getFocusNodeIndexFromEvent = function (event) { if (!event || !_this.cardNode || !_this.documentNode) return 0; var focusedNode = event.target; var focusableNodes = (0, _focus.findFocusableNodes)(_this.cardNode); var focusedNodeIndex = Array.prototype.indexOf.call(focusableNodes, focusedNode); return focusedNodeIndex; }; _this.focusModalCard = function () { var modalFocusTimeout = _this.props.modalFocusTimeout; setTimeout(function () { if (_this.cardNode) { _this.cardNode.focus(); } }, modalFocusTimeout); }; _this.positionCloseNode = function (scrollableNode) { setTimeout(function () { var scrollNode = scrollableNode || _this.scrollableNode; if (!_this.closeNode || !(0, _node.isNodeElement)(scrollNode)) return; var defaultOffset = _this.props.closeIconOffset + 1; var offset = scrollNode.offsetWidth - scrollNode.clientWidth + defaultOffset + "px"; _this.closeNode.style.right = offset; }, _this.props.closeIconRepositionDelay); }; _this.getCloseMarkup = function () { var _this$props = _this.props, closeIcon = _this$props.closeIcon, forceClosePortal = _this$props.forceClosePortal, isHsApp = _this$props.isHsApp; var shouldRenderClose = closeIcon && !isHsApp; return shouldRenderClose && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Modal9.CloseUI, { className: "c-Modal__close", ref: _this.setCloseNode, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_CloseButton.default, { onClick: forceClosePortal }) }); }; _this.getChildrenMarkup = function () { var children = _this.props.children; return _react.default.Children.map(children, function (child) { if (!child) return child; var displayName = child.type.displayName; if (child && (displayName === 'ModalContent' || displayName === 'ModalBody')) { return /*#__PURE__*/_react.default.cloneElement(child, { scrollableRef: _this.setScrollableNode }); } return child; }); }; _this.getInnerContentMarkup = function () { var _this$props2 = _this.props, cardClassName = _this$props2.cardClassName, className = _this$props2.className, description = _this$props2.description, icon = _this$props2.icon, iconSize = _this$props2.iconSize, illo = _this$props2.illo, illoSize = _this$props2.illoSize, kind = _this$props2.kind, modalAnimationDelay = _this$props2.modalAnimationDelay, modalAnimationDuration = _this$props2.modalAnimationDuration, modalAnimationEasing = _this$props2.modalAnimationEasing, modalAnimationSequence = _this$props2.modalAnimationSequence, numSteps = _this$props2.numSteps, portalIsOpen = _this$props2.portalIsOpen, seamless = _this$props2.seamless, step = _this$props2.step, style = _this$props2.style, title = _this$props2.title, version = _this$props2.version, rest = (0, _objectWithoutPropertiesLoose2.default)(_this$props2, ["cardClassName", "className", "description", "icon", "iconSize", "illo", "illoSize", "kind", "modalAnimationDelay", "modalAnimationDuration", "modalAnimationEasing", "modalAnimationSequence", "numSteps", "portalIsOpen", "seamless", "step", "style", "title", "version"]); var v2 = version === 2; var modalKindClassName = (0, _Modal8.getModalKindClassName)(kind); var componentClassName = (0, _classnames.default)('c-Modal__Card', v2 && 'v2', v2 && modalKindClassName, cardClassName); var childrenMarkup = _this.getChildrenMarkup(); var closeMarkup = v2 ? null : _this.getCloseMarkup(); var headerMarkup = v2 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Modal6.default, { icon: icon, iconSize: iconSize, illo: illo, illoSize: illoSize, description: description, title: title, kind: kind, numSteps: numSteps, step: step }) : null; var contentMarkup = !seamless ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_Modal9.CardUI, (0, _extends2.default)({}, rest, { className: componentClassName, seamless: true, role: "dialog", nodeRef: _this.setCardNode, tabIndex: "-1", children: [closeMarkup, headerMarkup, childrenMarkup] })) : /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", { className: "c-Modal__innerContent", role: "dialog", children: [headerMarkup, childrenMarkup] }); var exit = !v2; var easing = v2 ? modalV2Animation.easing : modalAnimationEasing; var delay = v2 ? modalV2Animation.delay : modalAnimationDelay; var duration = v2 ? modalV2Animation.duration : modalAnimationDuration; var sequence = v2 ? modalV2Animation.sequence : modalAnimationSequence; return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Modal9.AnimatedCardContainerUI, { className: "c-Modal__Card-container", delay: delay, duration: duration, easing: easing, in: portalIsOpen, exit: exit, sequence: sequence, children: contentMarkup }); }; _this.getOverlayMarkup = function () { var _this$props3 = _this.props, forceClosePortal = _this$props3.forceClosePortal, isHsApp = _this$props3.isHsApp, overlayAnimationDelay = _this$props3.overlayAnimationDelay, overlayAnimationDuration = _this$props3.overlayAnimationDuration, overlayAnimationEasing = _this$props3.overlayAnimationEasing, overlayAnimationSequence = _this$props3.overlayAnimationSequence, overlayClassName = _this$props3.overlayClassName, portalIsOpen = _this$props3.portalIsOpen, version = _this$props3.version; var v2 = version === 2; var overlayClassNames = (0, _classnames.default)(v2 && 'is-dark', overlayClassName); var easing = v2 ? overlayV2Animation.easing : overlayAnimationEasing; var delay = v2 ? overlayV2Animation.delay : overlayAnimationDelay; var duration = v2 ? overlayV2Animation.duration : overlayAnimationDuration; var sequence = v2 ? overlayV2Animation.sequence : overlayAnimationSequence; var props = { className: overlayClassNames, isOpen: portalIsOpen, isHsApp: isHsApp, onClick: forceClosePortal, overlayAnimationDelay: delay, overlayAnimationDuration: duration, overlayAnimationEasing: easing, overlayAnimationSequence: sequence }; return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Modal7.default, (0, _extends2.default)({}, props)); }; _this.setCardNode = function (node) { _this.cardNode = node; }; _this.setCloseNode = function (node) { _this.closeNode = node; }; _this.setScrollableNode = function (node) { _this.scrollableNode = node; }; return _this; } var _proto = Modal.prototype; _proto.UNSAFE_componentWillMount = function UNSAFE_componentWillMount() { this.documentNode = (0, _node.getClosestDocument)(_reactDom.default.findDOMNode(this)); }; _proto.componentDidMount = function componentDidMount() { var focusModalOnShow = this.props.focusModalOnShow; this.positionCloseNode(); if (focusModalOnShow) { this.focusModalCard(); } }; _proto.getChildContext = function getChildContext() { return { positionCloseNode: this.positionCloseNode }; }; _proto.render = function render() { var _this$props4 = this.props, blocksGlobalHotkeys = _this$props4.blocksGlobalHotkeys, className = _this$props4.className, dataCy = _this$props4['data-cy'], isOpen = _this$props4.isOpen, isHsApp = _this$props4.isHsApp, kind = _this$props4.kind, state = _this$props4.state, style = _this$props4.style, title = _this$props4.title, version = _this$props4.version, zIndex = _this$props4.zIndex, rest = (0, _objectWithoutPropertiesLoose2.default)(_this$props4, ["blocksGlobalHotkeys", "className", "data-cy", "isOpen", "isHsApp", "kind", "state", "style", "title", "version", "zIndex"]); var v2 = version === 2; var modalKindClassName = (0, _Modal8.getModalKindClassName)(kind); var componentClassName = (0, _classnames.default)('c-Modal', v2 && 'v2', isOpen && 'is-open', state === 'danger' && 'is-danger', v2 && modalKindClassName, className); var innerWrapperClassName = (0, _classnames.default)('c-Modal__innerWrapper', v2 && 'v2', v2 && modalKindClassName); var styles = (0, _extends2.default)({}, style, { zIndex: zIndex }); return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_Modal9.ModalUI, (0, _extends2.default)({}, rest, { className: componentClassName, "data-blocks-global-hotkeys": blocksGlobalHotkeys, "data-cy": dataCy, role: "document", style: styles, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_KeypressListener.default, { keyCode: _Keys.default.TAB, handler: this.handleOnTab, type: "keydown" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_KeypressListener.default, { keyCode: _Keys.default.TAB, modifier: "shift", handler: this.handleOnShiftTab, type: "keydown" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_EventListener.default, { event: "resize", handler: this.handleOnResize }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Modal9.InnerWrapperUI, (0, _extends2.default)({}, rest, { className: innerWrapperClassName, isHsApp: isHsApp, tabIndex: "0", children: this.getInnerContentMarkup() })), this.getOverlayMarkup()] })); }; return Modal; }(_react.default.PureComponent); Modal.childContextTypes = { positionCloseNode: noop }; Modal.ActionFooter = _Modal.default; Modal.Body = _Modal2.default; Modal.Content = _Modal3.default; Modal.Footer = _Modal4.default; Modal.Header = _Modal5.default; Modal.Overlay = _Modal7.default; Modal.HeaderV2 = _Modal6.default; Modal.defaultProps = { blocksGlobalHotkeys: true, closeIcon: true, closeIconOffset: 10, closeIconRepositionDelay: 0, closePortal: noop, containTabKeyPress: true, 'data-cy': 'Modal', description: null, focusModalOnShow: true, icon: null, iconSize: '24', illo: null, illoSize: 60, isHsApp: false, isOpen: false, kind: _Modal8.MODAL_KIND.DEFAULT, modalAnimationDelay: 0, modalAnimationDuration: 200, modalAnimationEasing: 'bounce', modalAnimationSequence: 'fade down', modalFocusTimeout: 90, numSteps: 1, onScroll: noop, overlayAnimationDelay: 0, overlayAnimationDuration: 200, overlayAnimationEasing: 'ease', overlayAnimationSequence: 'fade', portalIsOpen: true, seamless: false, state: '', status: '', step: 1, style: {}, timeout: 80, version: 1, wrapperClassName: 'c-ModalWrapper', zIndex: 1 }; Modal.propTypes = { /** Adds a data-blocks-global-hotkeys="true" to the modal element in case you want to signal that no global hotkeys should be allowed when the modal is on screen */ blocksGlobalHotkeys: _propTypes.default.bool, /** Custom class names to be added to the child `Card` component. */ cardClassName: _propTypes.default.string, /** Custom class names to be added to the component. */ className: _propTypes.default.string, /** Shows/hides the component's close icon UI. */ closeIcon: _propTypes.default.bool, /** Amount of time before the `CloseButton` gets repositioned. */ closeIconRepositionDelay: _propTypes.default.number, closePortal: _propTypes.default.func, closeIconOffset: _propTypes.default.number, /** Prevents tab/shift+tab focus from leaving the Modal. */ containTabKeyPress: _propTypes.default.bool, /** Renders in version 2 Modals beneath the title. */ description: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.object]), /** Used with `path` and React Router. Renders if path matches _exactly_ */ exact: _propTypes.default.bool, /** If you don't want the focus to be moved to the Modal when it enters */ focusModalOnShow: _propTypes.default.bool, forceClosePortal: _propTypes.default.func, /** Renders as an `Icon` in the top left corner of a version 2 Modal header. */ icon: _propTypes.default.string, /** The size to render the provided `Icon` in a version 2 Modal header. */ iconSize: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), id: _propTypes.default.string, /** Expects an `Illo` to be displayed in a version 2 Modal header. */ illo: _propTypes.default.any, /** The size to render the provided `Illo` in a version 2 Modal header. */ illoSize: _propTypes.default.number, /** Shows/hides the component. */ isOpen: _propTypes.default.bool, isHsApp: _propTypes.default.bool, /** The kind of version 2 Modal style to apply. */ kind: _propTypes.default.oneOf(['alert', 'default', 'branded', 'sequence']), /** Custom animation delay for the child `Card` component. */ modalAnimationDelay: _propTypes.default.number, /** Custom animation duration for the child `Card` component. */ modalAnimationDuration: _propTypes.default.number, /** Custom animation easing for the child `Card` component. */ modalAnimationEasing: _propTypes.default.string, /** Custom animation sequence for the child `Card` component. */ modalAnimationSequence: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), /** Amount of time (`ms`) before the Modal force focuses. */ modalFocusTimeout: _propTypes.default.number, /** Total number of steps to be used in a version 2 Sequence Modal. */ numSteps: _propTypes.default.number, /** Fires when the component is mounted, but not rendered. */ onBeforeClose: _propTypes.default.func, /** Fires as soon as the component has rendered. */ onOpen: _propTypes.default.func, /** Fires when the component is about to unmount. */ onBeforeOpen: _propTypes.default.func, /** Fires after the component is unmounted. */ onClose: _propTypes.default.func, /** Custom class names to be added to the child `Overlay` component. */ overlayClassName: _propTypes.default.string, /** Custom animation delay for the child `Overlay` component. */ overlayAnimationDelay: _propTypes.default.number, /** Custom animation duration for the child `Overlay` component. */ overlayAnimationDuration: _propTypes.default.number, /** Custom animation easing for the child `Overlay` component. */ overlayAnimationEasing: _propTypes.default.string, /** Custom animation sequence for the child `Overlay` component. */ overlayAnimationSequence: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), /** Renders component based on a [React Router path](https://reacttraining.com/react-router/web/api/Route/path-string). */ path: _propTypes.default.string, portalIsOpen: _propTypes.default.bool, /** A CSS selector to render content, instead of the `<body>`. (Portal prop)*/ renderTo: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.object]), /** Renders content with the standard `Card` UI. */ seamless: _propTypes.default.bool, /** State to use when styling a version 2 Modal (currently only `danger` state is custom styled). */ state: _propTypes.default.oneOf(['', 'danger']), status: _propTypes.default.string, /** Current step to be used in a version 2 Sequence Modal. */ step: _propTypes.default.number, /** Custom styles */ style: _propTypes.default.any, timeout: _propTypes.default.number, /** The UI the user clicks to trigger the modal. */ trigger: _propTypes.default.any, /** Version of the Modal styles to apply (version 2 is the new standard, version 1 is legacy). */ version: _propTypes.default.number, /** Custom className to add to the PortalWrapper component. */ wrapperClassName: _propTypes.default.string, /** Custom z-index rule directly on the modal */ zIndex: _propTypes.default.number, /** Data attr for Cypress tests. */ 'data-cy': _propTypes.default.string }; var ModalComponent = Modal; exports.ModalComponent = ModalComponent; var _default = (0, _PortalWrapper.default)(portalOptions)(Modal); exports.default = _default;