@procore/core-react
Version:
React library of Procore Design Guidelines
440 lines (431 loc) • 20.1 kB
JavaScript
var _excluded = ["children", "compact", "onClose", "qa"],
_excluded2 = ["children"],
_excluded3 = ["compact", "placement", "width"],
_excluded4 = ["children", "compact", "noSideSpacing"],
_excluded5 = ["children"],
_excluded6 = ["children"],
_excluded7 = ["aria-describedby", "aria-details", "aria-labelledby", "aria-label", "aria-modal", "children", "compact", "howToClose", "id", "onClickOverlay", "onClose", "open", "placement", "role"],
_excluded8 = ["children", "headline", "howToClose", "onClose"];
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
import { Clear } from '@procore/core-icons/dist';
import { useId } from '@react-aria/utils';
import React, { useEffect } from 'react';
import { Button } from '../Button';
import { useModalDialogLike } from '../OverlayTrigger/a11yPresets';
import { Portal } from '../Portal';
import { Heading, LevelContext, Section, SectionProvider } from '../Section/Section';
import { useDeprecation } from '../_hooks/Deprecation';
import { useLayoutEventListener } from '../_hooks/EventListener';
import { OverridableFocusScope } from '../_hooks/FocusScopeOverride';
import { useI18nContext } from '../_hooks/I18n';
import { useOverflowObserver } from '../_hooks/OverflowObserver';
import { useScrollLock } from '../_hooks/ScrollLock';
import { Visibility } from '../_hooks/Visibility';
import { addSubcomponents } from '../_utils/addSubcomponents';
import { CloseWithConfirmContext, useCloseWithConfirmContext, useCloseWithConfirmState } from '../_utils/closeWithConfirm';
import { mergeRefs } from '../_utils/mergeRefs';
import { ChildRegistryProvider, useModalChildRegistryDispatch } from './ChildRegistry.context';
import { fadeInClassName, fadeOutClassName, StyledModal, StyledModalBody, StyledModalButtons, StyledModalCancel, StyledModalContainer, StyledModalContent, StyledModalFooter, StyledModalFooterNotation, StyledModalFooterSummary, StyledModalHeader, StyledModalHeading, StyledModalScrim, StyledModalWarningIcon } from './Modal.styles';
import { useInitializeModalFocus } from './useInitializeModalFocus';
function noop() {}
var defaultStartLevel = 2;
var initialBodyLevel = 3;
var ModalCloseContext = /*#__PURE__*/React.createContext({
howToClose: [],
onClose: undefined
});
var ModalPrimaryHeadingCheckContext = /*#__PURE__*/React.createContext({
inModalHeader: false
});
var ModalBodyScrollContext = /*#__PURE__*/React.createContext({
isBodyScrolled: false,
setIsBodyScrolled: noop,
ariaLabelledBy: undefined
});
var ModalClosableHeader = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
var children = _ref.children,
_ref$compact = _ref.compact,
compact = _ref$compact === void 0 ? false : _ref$compact,
externalOnClose = _ref.onClose,
qa = _ref.qa,
props = _objectWithoutProperties(_ref, _excluded);
var _React$useContext = React.useContext(ModalBodyScrollContext),
isBodyScrolled = _React$useContext.isBodyScrolled;
var _React$useContext2 = React.useContext(ModalCloseContext),
howToClose = _React$useContext2.howToClose,
onClose = _React$useContext2.onClose;
var I18n = useI18nContext();
var modalClosableHeaderRef = React.useRef(null);
var _useCloseWithConfirmC = useCloseWithConfirmContext(),
closeWithConfirm = _useCloseWithConfirmC.closeWithConfirm;
var registerChildRef = useModalChildRegistryDispatch();
useEffect(function () {
registerChildRef({
child: 'header',
ref: modalClosableHeaderRef
});
}, []);
function onClickButton(e) {
if (onClose && howToClose !== null && howToClose !== void 0 && howToClose.includes('x')) {
closeWithConfirm(onClose)(e, 'x');
return;
}
closeWithConfirm(externalOnClose)(e);
}
return /*#__PURE__*/React.createElement(StyledModalHeader, _extends({
$compact: compact,
$isBodyScrolled: isBodyScrolled
}, props, {
ref: mergeRefs(ref, modalClosableHeaderRef),
"data-id": "modal-header"
}), /*#__PURE__*/React.createElement(ModalPrimaryHeadingCheckContext.Provider, {
value: {
inModalHeader: true
}
}, /*#__PURE__*/React.createElement(StyledModalHeading, {
$compact: compact
}, children), onClose && howToClose !== null && howToClose !== void 0 && howToClose.includes('x') || externalOnClose ? /*#__PURE__*/React.createElement(StyledModalCancel, null, /*#__PURE__*/React.createElement(Button, {
"aria-label": I18n.t('core.modal.a11y.close'),
"data-internal": "close-button",
"data-qa": qa === null || qa === void 0 ? void 0 : qa.closeButton,
icon: /*#__PURE__*/React.createElement(Clear, null),
onClick: onClickButton,
size: compact ? 'sm' : 'md',
variant: "tertiary"
})) : null));
});
var ModalClosableFooterButtons = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
var children = _ref2.children,
props = _objectWithoutProperties(_ref2, _excluded2);
var _React$useContext3 = React.useContext(ModalCloseContext),
howToClose = _React$useContext3.howToClose,
onClose = _React$useContext3.onClose;
var I18n = useI18nContext();
var _useCloseWithConfirmC2 = useCloseWithConfirmContext(),
closeWithConfirm = _useCloseWithConfirmC2.closeWithConfirm;
var modalClosableFooterButtonsRef = React.useRef(null);
var registerChildRef = useModalChildRegistryDispatch();
useEffect(function () {
registerChildRef({
child: 'closeableButtons',
ref: modalClosableFooterButtonsRef
});
}, []);
function onClickButton(e) {
closeWithConfirm(onClose)(e, 'footer-button');
}
return /*#__PURE__*/React.createElement(StyledModalButtons, _extends({}, props, {
ref: mergeRefs(ref, modalClosableFooterButtonsRef)
}), (howToClose === null || howToClose === void 0 ? void 0 : howToClose.includes('footer-button')) && /*#__PURE__*/React.createElement(Button, {
onClick: onClickButton,
variant: "tertiary"
}, I18n.t('core.modal.cancel')), children);
});
var ModalContainer = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
var _ref3$compact = _ref3.compact,
compact = _ref3$compact === void 0 ? false : _ref3$compact,
placement = _ref3.placement,
width = _ref3.width,
props = _objectWithoutProperties(_ref3, _excluded3);
var _React$useContext4 = React.useContext(ModalCloseContext),
onClose = _React$useContext4.onClose;
var modalContainerRef = React.useRef(null);
useInitializeModalFocus({
modalContainerRef: modalContainerRef,
hasOnClose: !!onClose
});
return /*#__PURE__*/React.createElement(StyledModalContainer, _extends({}, props, {
shadowStrength: 4,
$compact: compact,
$placement: placement,
$width: width,
ref: mergeRefs(ref, modalContainerRef)
}));
});
var ModalBody = /*#__PURE__*/React.forwardRef(function (_ref4, ref) {
var children = _ref4.children,
_ref4$compact = _ref4.compact,
compact = _ref4$compact === void 0 ? false : _ref4$compact,
_ref4$noSideSpacing = _ref4.noSideSpacing,
noSideSpacing = _ref4$noSideSpacing === void 0 ? false : _ref4$noSideSpacing,
props = _objectWithoutProperties(_ref4, _excluded4);
var _useOverflowObserver = useOverflowObserver(),
isOverflowingY = _useOverflowObserver.isOverflowingY,
overflowRef = _useOverflowObserver.ref;
var scrollRef = React.useRef(null);
var modalBodyRef = React.useRef(null);
var _React$useContext5 = React.useContext(ModalBodyScrollContext),
ariaLabelledBy = _React$useContext5.ariaLabelledBy,
setIsBodyScrolled = _React$useContext5.setIsBodyScrolled;
var registerChildRef = useModalChildRegistryDispatch();
useEffect(function () {
registerChildRef({
child: 'body',
ref: modalBodyRef
});
}, []);
useLayoutEventListener({
event: 'scroll',
handler: function handler(e) {
setIsBodyScrolled(e.currentTarget.scrollTop > 0);
},
scope: scrollRef
});
return /*#__PURE__*/React.createElement(SectionProvider, null, /*#__PURE__*/React.createElement(LevelContext.Provider, {
value: initialBodyLevel
}, /*#__PURE__*/React.createElement(StyledModalBody, _extends({}, props, {
$compact: compact,
$isOverflowingY: isOverflowingY,
$noSideSpacing: noSideSpacing,
ref: mergeRefs(overflowRef, scrollRef, ref, modalBodyRef),
tabIndex: 0,
role: "region",
"aria-labelledby": ariaLabelledBy
}), children)));
});
var ModalFooterSummary = /*#__PURE__*/React.forwardRef(function (_ref5, ref) {
var children = _ref5.children,
props = _objectWithoutProperties(_ref5, _excluded5);
return /*#__PURE__*/React.createElement(StyledModalFooterSummary, _extends({}, props, {
ref: ref
}), children);
});
var ModalFooterNotation = /*#__PURE__*/React.forwardRef(function (_ref6, ref) {
var children = _ref6.children,
props = _objectWithoutProperties(_ref6, _excluded6);
return /*#__PURE__*/React.createElement(StyledModalFooterNotation, _extends({
"aria-live": "polite",
intent: "small",
color: "gray45",
italic: true
}, props, {
ref: ref
}), children);
});
var Modal_ = /*#__PURE__*/React.forwardRef(function (_ref7, ref) {
var ariaDescribedby = _ref7['aria-describedby'],
ariaDetails = _ref7['aria-details'],
ariaLabelledby = _ref7['aria-labelledby'],
ariaLabel = _ref7['aria-label'],
ariaModal = _ref7['aria-modal'],
children = _ref7.children,
_ref7$compact = _ref7.compact,
compact = _ref7$compact === void 0 ? false : _ref7$compact,
howToClose = _ref7.howToClose,
id = _ref7.id,
_ref7$onClickOverlay = _ref7.onClickOverlay,
onClickOverlay = _ref7$onClickOverlay === void 0 ? noop : _ref7$onClickOverlay,
onClose = _ref7.onClose,
_ref7$open = _ref7.open,
open = _ref7$open === void 0 ? false : _ref7$open,
_ref7$placement = _ref7.placement,
placement = _ref7$placement === void 0 ? 'center' : _ref7$placement,
role = _ref7.role,
props = _objectWithoutProperties(_ref7, _excluded7);
useScrollLock(open);
var defaultLabelId = useId();
var labelledbyId = ariaLabel ? id : ariaLabelledby !== null && ariaLabelledby !== void 0 ? ariaLabelledby : defaultLabelId;
var _React$useState = React.useState(open),
_React$useState2 = _slicedToArray(_React$useState, 2),
visible = _React$useState2[0],
setVisible = _React$useState2[1];
var _React$useState3 = React.useState(''),
_React$useState4 = _slicedToArray(_React$useState3, 2),
fadeType = _React$useState4[0],
setFadeType = _React$useState4[1];
var closeWithConfirmState = useCloseWithConfirmState();
var closeWithConfirm = closeWithConfirmState.closeWithConfirm;
React.useEffect(function () {
if (open) {
setVisible(true);
var fadeTimer = setTimeout(function () {
setFadeType(fadeInClassName);
}, 0);
return function () {
return clearTimeout(fadeTimer);
};
} else {
setFadeType(fadeOutClassName);
}
}, [open]);
var onTransitionEnd = function onTransitionEnd() {
if (fadeType === fadeOutClassName) {
setVisible(false);
}
};
var handleKeyDown = function handleKeyDown(e) {
if (!onClose) return;
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
closeWithConfirm(onClose)(e, 'x');
}
};
var _useModalDialogLike = useModalDialogLike({
'aria-describedby': ariaDescribedby,
'aria-details': ariaDetails,
'aria-labelledby': labelledbyId,
'aria-label': ariaLabel,
'aria-modal': ariaModal,
id: id,
isOpen: open,
role: role
}),
dialogProps_ = _useModalDialogLike.dialogProps;
var dialogProps = onClose && (role === 'dialog' || role === 'alertdialog') ? dialogProps_ : {};
function onClickScrim(e) {
onClickOverlay(e);
if (onClose && howToClose !== null && howToClose !== void 0 && howToClose.includes('scrim')) {
closeWithConfirm(onClose)(e, 'scrim');
}
}
return visible ? /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement(ModalCloseContext.Provider, {
value: {
onClose: onClose,
howToClose: howToClose
}
}, /*#__PURE__*/React.createElement(LevelContext.Provider, {
value: defaultStartLevel
}, /*#__PURE__*/React.createElement(ChildRegistryProvider, null, /*#__PURE__*/React.createElement(OverridableFocusScope
// TODO breaking - always
, {
autoFocus: !!onClose,
contain: !!onClose,
restoreFocus: !!onClose
}, /*#__PURE__*/React.createElement(StyledModal, _extends({
className: fadeType,
ref: ref,
onTransitionEnd: onTransitionEnd,
onKeyDown: handleKeyDown
}, dialogProps, {
"data-id": "modal"
}), /*#__PURE__*/React.createElement(StyledModalScrim, {
"data-qa": "core-modal-scrim",
onClick: onClickScrim
}), /*#__PURE__*/React.createElement(ModalContainer, _extends({}, props, {
compact: compact,
placement: placement
}), /*#__PURE__*/React.createElement(StyledModalContent, {
$compact: compact
}, /*#__PURE__*/React.createElement(ModalBodyScrollObserver, {
ariaLabelledBy: labelledbyId
}, /*#__PURE__*/React.createElement(CloseWithConfirmContext.Provider, {
value: closeWithConfirmState
}, children)))))))))) : null;
});
var ModalBodyScrollObserver = function ModalBodyScrollObserver(_ref8) {
var ariaLabelledBy = _ref8.ariaLabelledBy,
children = _ref8.children;
var _React$useState5 = React.useState(false),
_React$useState6 = _slicedToArray(_React$useState5, 2),
isBodyScrolled = _React$useState6[0],
setIsBodyScrolled = _React$useState6[1];
return /*#__PURE__*/React.createElement(ModalBodyScrollContext.Provider, {
value: {
ariaLabelledBy: ariaLabelledBy,
isBodyScrolled: isBodyScrolled,
setIsBodyScrolled: setIsBodyScrolled
}
}, children);
};
export var ConfirmModal = /*#__PURE__*/React.forwardRef(function (_ref9, ref) {
var children = _ref9.children,
headline = _ref9.headline,
_ref9$howToClose = _ref9.howToClose,
howToClose = _ref9$howToClose === void 0 ? [] : _ref9$howToClose,
onClose = _ref9.onClose,
props = _objectWithoutProperties(_ref9, _excluded8);
var isDialog = props.role === 'dialog' || props.role === 'alertdialog';
// When role="dialog", pass onClose to Modal for focus management and Escape key.
// The header X button is handled via ModalCloseContext when howToClose includes 'x'.
var configuredModalProps = isDialog && onClose ? {
onClose: onClose,
howToClose: ['x'].concat(_toConsumableArray(howToClose))
} : undefined;
var headerOnClose = !isDialog && onClose ? onClose : undefined;
return /*#__PURE__*/React.createElement(Modal, _extends({}, props, configuredModalProps, {
ref: ref
}), /*#__PURE__*/React.createElement(ModalClosableHeader, {
onClose: headerOnClose
}, /*#__PURE__*/React.createElement(StyledModalWarningIcon, {
size: "lg"
}), /*#__PURE__*/React.createElement(ModalHeading, null, headline)), children);
});
/**
* Applies proper semantics regardless of where in the Modal.
* @a11y The primary heading that is auto linked to the dialog and scroll region with
* aria-labelledby, when inside the Modal.Header.
*/
var ModalHeading = function ModalHeading(props) {
var _React$useContext6 = React.useContext(ModalBodyScrollContext),
ariaLabelledBy = _React$useContext6.ariaLabelledBy;
var _React$useContext7 = React.useContext(ModalPrimaryHeadingCheckContext),
inModalHeader = _React$useContext7.inModalHeader;
return /*#__PURE__*/React.createElement(Heading, _extends({
id: inModalHeader ? ariaLabelledBy : undefined
}, props));
};
function ModalState(props) {
useDeprecation({
oldThing: 'Modal.State'
});
return /*#__PURE__*/React.createElement(Visibility, props);
}
Modal_.displayName = 'Modal';
ConfirmModal.displayName = 'ConfirmModal';
ModalBody.displayName = 'Modal.Body';
ModalFooterNotation.displayName = 'Modal.FooterNotation';
ModalFooterSummary.displayName = 'Modal.FooterSummary';
var Body = ModalBody;
var Container = ModalContainer;
var Content = StyledModalContent;
var FooterSummary = ModalFooterSummary;
var Footer = StyledModalFooter;
var FooterButtons = ModalClosableFooterButtons;
var FooterNotation = ModalFooterNotation;
var Header = ModalClosableHeader;
var Overlay = StyledModal;
var Scrim = StyledModalScrim;
var State = ModalState;
/**
We use modals to present additional actions, information, or forms on pages
where a user’s experience would benefit from remaining on the same page.
They can include graphics, inputs, buttons, and other elements as needed to
produce the workflow or action needed.
Do not include modals on pages with complicated UI layouts.
@since 10.19.0
@see [Storybook](https://stories.core.procore.com/?path=/story/core-react_demos-modal--demo)
@see [Design Guidelines](https://design.procore.com/modal)
*/
export var Modal = addSubcomponents({
Body: Body,
Container: Container,
Content: Content,
Footer: Footer,
FooterButtons: FooterButtons,
FooterNotation: FooterNotation,
FooterSummary: FooterSummary,
Header: Header,
/**
* @a11y The primary heading inside the `Modal.Header` that is auto linked
* to the dialog and scroll region with aria-labelledby.
*/
Heading: ModalHeading,
Overlay: Overlay,
Scrim: Scrim,
State: State,
Section: Section
}, Modal_);
//# sourceMappingURL=Modal.js.map