UNPKG

@mskcc/carbon-react

Version:

Carbon react components for the MSKCC DSM

418 lines (410 loc) 15 kB
/** * MSKCC 2021, 2024 */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var PropTypes = require('prop-types'); var React = require('react'); var cx = require('classnames'); var iconsReact = require('@carbon/icons-react'); var toggleClass = require('../../tools/toggleClass.js'); var Button = require('../Button/Button.js'); require('../Button/Button.Skeleton.js'); var ButtonSet = require('../ButtonSet/ButtonSet.js'); var requiredIfGivenPropIsTruthy = require('../../prop-types/requiredIfGivenPropIsTruthy.js'); var wrapFocus = require('../../internal/wrapFocus.js'); var setupGetInstanceId = require('../../tools/setupGetInstanceId.js'); var usePrefix = require('../../internal/usePrefix.js'); var match = require('../../internal/keyboard/match.js'); var keys = require('../../internal/keyboard/keys.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes); var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx); const getInstanceId = setupGetInstanceId["default"](); const Modal = /*#__PURE__*/React__default["default"].forwardRef(function Modal(_ref, ref) { let { 'aria-label': ariaLabelProp, children, className, modalHeading = '', modalLabel = '', modalAriaLabel, passiveModal = false, secondaryButtonText, primaryButtonText, open, onRequestClose = () => {}, onRequestSubmit = () => {}, onSecondarySubmit, primaryButtonDisabled = false, danger, alert, secondaryButtons, selectorPrimaryFocus = '[data-modal-primary-focus]', // eslint-disable-line selectorsFloatingMenus, // eslint-disable-line shouldSubmitOnEnter, // eslint-disable-line size, hasScrollingContent = false, closeButtonLabel, preventCloseOnClickOutside = false, // eslint-disable-line isFullWidth, primaryButtonKind = 'primary', secondaryButtonKind = 'tertiary', primaryButtonClassName, secondaryButtonClassName, ...rest } = _ref; const prefix = usePrefix.usePrefix(); const button = React.useRef(); const secondaryButton = React.useRef(); const innerModal = React.useRef(); const startTrap = React.useRef(); const endTrap = React.useRef(); const modalInstanceId = `modal-${getInstanceId()}`; const modalLabelId = `${prefix}--modal-header__label--${modalInstanceId}`; const modalHeadingId = `${prefix}--modal-header__heading--${modalInstanceId}`; const modalBodyId = `${prefix}--modal-body--${modalInstanceId}`; const modalCloseButtonClass = `${prefix}--modal-close`; function isCloseButton(element) { return !onSecondarySubmit && element === secondaryButton.current || element.classList.contains(modalCloseButtonClass); } function handleKeyDown(evt) { if (open) { if (match.match(evt, keys.Escape)) { onRequestClose(evt); } if (match.match(evt, keys.Enter) && shouldSubmitOnEnter && !isCloseButton(evt.target)) { onRequestSubmit(evt); } } } function handleMousedown(evt) { if (innerModal.current && !innerModal.current.contains(evt.target) && !wrapFocus.elementOrParentIsFloatingMenu(evt.target, selectorsFloatingMenus) && !preventCloseOnClickOutside) { onRequestClose(evt); } } function handleBlur(_ref2) { let { target: oldActiveNode, relatedTarget: currentActiveNode } = _ref2; if (open && currentActiveNode && oldActiveNode) { const { current: bodyNode } = innerModal; const { current: startTrapNode } = startTrap; const { current: endTrapNode } = endTrap; wrapFocus["default"]({ bodyNode, startTrapNode, endTrapNode, currentActiveNode, oldActiveNode, selectorsFloatingMenus }); } } const onSecondaryButtonClick = onSecondarySubmit ? onSecondarySubmit : onRequestClose; const modalClasses = cx__default["default"](`${prefix}--modal`, { [`${prefix}--modal-tall`]: !passiveModal, 'is-visible': open, [`${prefix}--modal--danger`]: danger, [className]: className }); const containerClasses = cx__default["default"](`${prefix}--modal-container`, { [`${prefix}--modal-container--${size}`]: size, [`${prefix}--modal-container--full-width`]: isFullWidth }); const contentClasses = cx__default["default"](`${prefix}--modal-content`, { [`${prefix}--modal-scroll-content`]: hasScrollingContent }); const footerClasses = cx__default["default"](`${prefix}--modal-footer`, { [`${prefix}--modal-footer--three-button`]: Array.isArray(secondaryButtons) && secondaryButtons.length === 2 }); const ariaLabel = modalLabel || ariaLabelProp || modalAriaLabel || modalHeading; const getAriaLabelledBy = modalLabel ? modalLabelId : modalHeadingId; const hasScrollingContentProps = hasScrollingContent ? { tabIndex: 0, role: 'region', 'aria-label': ariaLabel, 'aria-labelledby': getAriaLabelledBy } : {}; const alertDialogProps = {}; if (alert && passiveModal) { alertDialogProps.role = 'alert'; } if (alert && !passiveModal) { alertDialogProps.role = 'alertdialog'; alertDialogProps['aria-describedby'] = modalBodyId; } React.useEffect(() => { return () => { toggleClass["default"](document.body, `${prefix}--body--with-modal-open`, false); }; }, [prefix]); React.useEffect(() => { toggleClass["default"](document.body, `${prefix}--body--with-modal-open`, open); }, [open, prefix]); React.useEffect(() => { const initialFocus = focusContainerElement => { const containerElement = focusContainerElement || innerModal.current; const primaryFocusElement = containerElement ? containerElement.querySelector(danger ? `.${prefix}--btn--secondary` : selectorPrimaryFocus) : null; if (primaryFocusElement) { return primaryFocusElement; } return button && button.current; }; const focusButton = focusContainerElement => { const target = initialFocus(focusContainerElement); if (target) { target.focus(); } }; if (open) { focusButton(innerModal.current); } }, [open, selectorPrimaryFocus, danger, prefix]); const modalButton = /*#__PURE__*/React__default["default"].createElement("button", { className: modalCloseButtonClass, type: "button", onClick: onRequestClose, title: ariaLabel, "aria-label": closeButtonLabel ? closeButtonLabel : 'close', ref: button }, /*#__PURE__*/React__default["default"].createElement(iconsReact.Close, { size: 20, "aria-hidden": "true", tabIndex: "-1", className: `${modalCloseButtonClass}__icon` })); const modalBody = /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({ ref: innerModal, role: "dialog" }, alertDialogProps, { className: containerClasses, "aria-label": ariaLabel, "aria-modal": "true", tabIndex: "-1" }), /*#__PURE__*/React__default["default"].createElement("div", { className: `${prefix}--modal-header` }, passiveModal && modalButton, modalLabel && /*#__PURE__*/React__default["default"].createElement("h2", { id: modalLabelId, className: `${prefix}--modal-header__label` }, modalLabel), /*#__PURE__*/React__default["default"].createElement("h3", { id: modalHeadingId, className: `${prefix}--modal-header__heading` }, modalHeading), !passiveModal && modalButton), /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({ id: modalBodyId, className: contentClasses }, hasScrollingContentProps), children), hasScrollingContent && /*#__PURE__*/React__default["default"].createElement("div", { className: `${prefix}--modal-content--overflow-indicator` }), !passiveModal && /*#__PURE__*/React__default["default"].createElement(ButtonSet["default"], { className: footerClasses }, Array.isArray(secondaryButtons) && secondaryButtons.length <= 2 ? secondaryButtons.map((_ref3, i) => { let { buttonText, onClick: onButtonClick, buttonKind: extraButtonKind = 'tertiary', buttonClassName: extraClassName } = _ref3; return /*#__PURE__*/React__default["default"].createElement(Button["default"], { key: `${buttonText}-${i}`, kind: extraButtonKind, onClick: onButtonClick, className: extraClassName }, buttonText); }) : secondaryButtonText && /*#__PURE__*/React__default["default"].createElement(Button["default"], { kind: secondaryButtonKind, onClick: onSecondaryButtonClick, ref: secondaryButton, className: secondaryButtonClassName }, secondaryButtonText), /*#__PURE__*/React__default["default"].createElement(Button["default"], { className: primaryButtonClassName, kind: danger ? 'danger' : primaryButtonKind, disabled: primaryButtonDisabled, onClick: onRequestSubmit, ref: button }, primaryButtonText))); return /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({}, rest, { onKeyDown: handleKeyDown, onMouseDown: handleMousedown, onBlur: handleBlur, className: modalClasses, role: "presentation", ref: ref }), /*#__PURE__*/React__default["default"].createElement("span", { ref: startTrap, tabIndex: "0", role: "link", className: `${prefix}--visually-hidden` }, "Focus sentinel"), modalBody, /*#__PURE__*/React__default["default"].createElement("span", { ref: endTrap, tabIndex: "0", role: "link", className: `${prefix}--visually-hidden` }, "Focus sentinel")); }); Modal.propTypes = { /** * Specify whether the Modal is displaying an alert, error or warning * Should go hand in hand with the danger prop. */ alert: PropTypes__default["default"].bool, /** * Required props for the accessibility label of the header */ ['aria-label']: requiredIfGivenPropIsTruthy["default"]('hasScrollingContent', PropTypes__default["default"].string), /** * Provide the contents of your Modal */ children: PropTypes__default["default"].node, /** * Specify an optional className to be applied to the modal root node */ className: PropTypes__default["default"].string, /** * Specify an label for the close button of the modal; defaults to close */ closeButtonLabel: PropTypes__default["default"].string, /** * Specify whether the Modal is for dangerous actions */ danger: PropTypes__default["default"].bool, /** * Specify whether the modal contains scrolling content */ hasScrollingContent: PropTypes__default["default"].bool, /** * Specify the DOM element ID of the top-level node. */ id: PropTypes__default["default"].string, /** * Specify whether or not the Modal content should have any inner padding. */ isFullWidth: PropTypes__default["default"].bool, /** * Specify a label to be read by screen readers on the modal root node */ modalAriaLabel: PropTypes__default["default"].string, /** * Specify the content of the modal header title. */ modalHeading: PropTypes__default["default"].node, /** * Specify the content of the modal header label. */ modalLabel: PropTypes__default["default"].node, /** * Specify a handler for keypresses. */ onKeyDown: PropTypes__default["default"].func, /** * Specify a handler for closing modal. * The handler should care of closing modal, e.g. changing `open` prop. */ onRequestClose: PropTypes__default["default"].func, /** * Specify a handler for "submitting" modal. * The handler should care of closing modal, e.g. changing `open` prop, if necessary. */ onRequestSubmit: PropTypes__default["default"].func, /** * Specify a handler for the secondary button. * Useful if separate handler from `onRequestClose` is desirable */ onSecondarySubmit: PropTypes__default["default"].func, /** * Specify whether the Modal is currently open */ open: PropTypes__default["default"].bool, /** * Specify whether the modal should be button-less */ passiveModal: PropTypes__default["default"].bool, /** * Prevent closing on click outside of modal */ preventCloseOnClickOutside: PropTypes__default["default"].bool, /** * Specify whether the Button should be disabled, or not */ primaryButtonDisabled: PropTypes__default["default"].bool, /** * Specify the text for the primary button */ primaryButtonText: PropTypes__default["default"].node, /** * Specify kind of the primary button */ primaryButtonKind: PropTypes__default["default"].string, /** * Specify an optional className to be applied to the primary button */ primaryButtonClassName: PropTypes__default["default"].string, /** * Specify the text for the secondary button */ secondaryButtonText: PropTypes__default["default"].node, /** * Specify kind of the secondary button */ secondaryButtonKind: PropTypes__default["default"].string, /** * Specify an optional className to be applied to the secondary button */ secondaryButtonClassName: PropTypes__default["default"].string, /** * Specify an array of config objects for secondary buttons * (`Array<{ * buttonText: string, * onClick: function, * buttonKind: string * buttonClassName: string * }>`). */ secondaryButtons: (props, propName, componentName) => { if (props.secondaryButtons) { if (!Array.isArray(props.secondaryButtons) || props.secondaryButtons.length !== 2) { return new Error(`${propName} needs to be an array of two button config objects`); } const shape = { buttonText: PropTypes__default["default"].node, onClick: PropTypes__default["default"].func }; props[propName].forEach(secondaryButton => { PropTypes__default["default"].checkPropTypes(shape, secondaryButton, propName, componentName); }); } return null; }, /** * Specify a CSS selector that matches the DOM element that should * be focused when the Modal opens */ selectorPrimaryFocus: PropTypes__default["default"].string, /** * Specify CSS selectors that match DOM elements working as floating menus. * Focusing on those elements won't trigger "focus-wrap" behavior */ selectorsFloatingMenus: PropTypes__default["default"].arrayOf(PropTypes__default["default"].string), /** * Specify if Enter key should be used as "submit" action */ shouldSubmitOnEnter: PropTypes__default["default"].bool, /** * Specify the size variant. */ size: PropTypes__default["default"].oneOf(['xs', 'sm', 'md', 'lg']) }; var Modal$1 = Modal; exports["default"] = Modal$1;