@helpscout/hsds-react
Version:
React component library for Help Scout's Design System
554 lines (450 loc) • 20.5 kB
JavaScript
"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;