UNPKG

@primer/components

Version:
304 lines (263 loc) • 12.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Dialog = void 0; var _react = _interopRequireWildcard(require("react")); var _styledComponents = _interopRequireDefault(require("styled-components")); var _Button = _interopRequireWildcard(require("../Button")); var _Box = _interopRequireDefault(require("../Box")); var _constants = require("../constants"); var _hooks = require("../hooks"); var _useFocusTrap = require("../hooks/useFocusTrap"); var _sx = _interopRequireDefault(require("../sx")); var _StyledOcticon = _interopRequireDefault(require("../StyledOcticon")); var _octiconsReact = require("@primer/octicons-react"); var _useFocusZone = require("../hooks/useFocusZone"); var _focusZone = require("../behaviors/focusZone"); var _Portal = _interopRequireDefault(require("../Portal")); var _useCombinedRefs = require("../hooks/useCombinedRefs"); var _ssr = require("@react-aria/ssr"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } 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); } const ANIMATION_DURATION = '200ms'; /** * Props that characterize a button to be rendered into the footer of * a Dialog. */ const Backdrop = (0, _styledComponents.default)('div').withConfig({ displayName: "Dialog__Backdrop", componentId: "sc-11pkgky-0" })(["position:fixed;top:0;left:0;bottom:0;right:0;display:flex;align-items:center;justify-content:center;background-color:rgba(0,0,0,0.4);animation:dialog-backdrop-appear ", " ", ";@keyframes dialog-backdrop-appear{0%{opacity:0;}100%{opacity:1;}}"], ANIMATION_DURATION, (0, _constants.get)('animation.easeOutCubic')); const heightMap = { small: '480px', large: '640px', auto: 'auto' }; const widthMap = { small: '296px', medium: '320px', large: '480px', xlarge: '640px' }; const StyledDialog = _styledComponents.default.div.withConfig({ displayName: "Dialog__StyledDialog", componentId: "sc-11pkgky-1" })(["display:flex;flex-direction:column;background-color:", ";box-shadow:", ";min-width:296px;max-width:calc(100vw - 64px);max-height:calc(100vh - 64px);width:", ";height:", ";border-radius:12px;opacity:1;animation:overlay--dialog-appear ", " ", ";@keyframes overlay--dialog-appear{0%{opacity:0;transform:scale(0.5);}100%{opacity:1;transform:scale(1);}}", ";", ";", ";"], (0, _constants.get)('colors.canvas.overlay'), (0, _constants.get)('shadows.overlay.shadow'), props => { var _props$width; return widthMap[(_props$width = props.width) !== null && _props$width !== void 0 ? _props$width : 'xlarge']; }, props => { var _props$height; return heightMap[(_props$height = props.height) !== null && _props$height !== void 0 ? _props$height : 'auto']; }, ANIMATION_DURATION, (0, _constants.get)('animation.easeOutCubic'), _constants.COMMON, _constants.POSITION, _sx.default); const DefaultHeader = ({ dialogLabelId, title, subtitle, dialogDescriptionId, onClose }) => { const onCloseClick = (0, _react.useCallback)(() => { onClose('close-button'); }, [onClose]); return /*#__PURE__*/_react.default.createElement(Dialog.Header, null, /*#__PURE__*/_react.default.createElement(_Box.default, { display: "flex" }, /*#__PURE__*/_react.default.createElement(_Box.default, { display: "flex", px: 2, py: "6px", flexDirection: "column", flexGrow: 1 }, /*#__PURE__*/_react.default.createElement(Dialog.Title, { id: dialogLabelId }, title !== null && title !== void 0 ? title : 'Dialog'), subtitle && /*#__PURE__*/_react.default.createElement(Dialog.Subtitle, { id: dialogDescriptionId }, subtitle)), /*#__PURE__*/_react.default.createElement(Dialog.CloseButton, { onClose: onCloseClick }))); }; DefaultHeader.displayName = "DefaultHeader"; const DefaultBody = ({ children }) => { return /*#__PURE__*/_react.default.createElement(Dialog.Body, null, children); }; DefaultBody.displayName = "DefaultBody"; const DefaultFooter = ({ footerButtons }) => { const { containerRef: footerRef } = (0, _useFocusZone.useFocusZone)({ bindKeys: _focusZone.FocusKeys.ArrowHorizontal | _focusZone.FocusKeys.Tab, focusInStrategy: 'closest' }); return footerButtons ? /*#__PURE__*/_react.default.createElement(Dialog.Footer, { ref: footerRef }, /*#__PURE__*/_react.default.createElement(Dialog.Buttons, { buttons: footerButtons })) : null; }; const _Dialog = /*#__PURE__*/_react.default.forwardRef((props, forwardedRef) => { const { title = 'Dialog', subtitle = '', renderHeader, renderBody, renderFooter, onClose, role = 'dialog', width = 'xlarge', height = 'auto', footerButtons = [] } = props; const dialogLabelId = (0, _ssr.useSSRSafeId)(); const dialogDescriptionId = (0, _ssr.useSSRSafeId)(); const autoFocusedFooterButtonRef = (0, _react.useRef)(null); for (const footerButton of footerButtons) { if (footerButton.autoFocus) { footerButton.ref = autoFocusedFooterButtonRef; } } const defaultedProps = { ...props, title, subtitle, role, dialogLabelId, dialogDescriptionId }; const dialogRef = (0, _react.useRef)(null); const combinedRef = (0, _useCombinedRefs.useCombinedRefs)(dialogRef, forwardedRef); const backdropRef = (0, _react.useRef)(null); (0, _useFocusTrap.useFocusTrap)({ containerRef: dialogRef, restoreFocusOnCleanUp: true, initialFocusRef: autoFocusedFooterButtonRef }); (0, _hooks.useOnEscapePress)(event => { onClose('escape'); event.preventDefault(); }, [onClose]); const header = (renderHeader !== null && renderHeader !== void 0 ? renderHeader : DefaultHeader)(defaultedProps); const body = (renderBody !== null && renderBody !== void 0 ? renderBody : DefaultBody)(defaultedProps); const footer = (renderFooter !== null && renderFooter !== void 0 ? renderFooter : DefaultFooter)(defaultedProps); return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_Portal.default, null, /*#__PURE__*/_react.default.createElement(Backdrop, { ref: backdropRef }, /*#__PURE__*/_react.default.createElement(StyledDialog, { width: width, height: height, ref: combinedRef, role: role, "aria-labelledby": dialogLabelId, "aria-describedby": dialogDescriptionId }, header, body, footer)))); }); _Dialog.displayName = 'Dialog'; const Header = (0, _styledComponents.default)(_Box.default).attrs({ as: 'header' }).withConfig({ displayName: "Dialog__Header", componentId: "sc-11pkgky-2" })(["box-shadow:0 1px 0 ", ";padding:", ";z-index:1;flex-shrink:0;"], (0, _constants.get)('colors.border.default'), (0, _constants.get)('space.2')); const Title = (0, _styledComponents.default)(_Box.default).withConfig({ displayName: "Dialog__Title", componentId: "sc-11pkgky-3" })(["font-size:", ";font-weight:", ";"], (0, _constants.get)('fontSizes.1'), (0, _constants.get)('fontWeights.bold')); const Subtitle = (0, _styledComponents.default)(_Box.default).withConfig({ displayName: "Dialog__Subtitle", componentId: "sc-11pkgky-4" })(["font-size:", ";margin-top:", ";color:", ";"], (0, _constants.get)('fontSizes.0'), (0, _constants.get)('space.1'), (0, _constants.get)('colors.fg.muted')); const Body = (0, _styledComponents.default)(_Box.default).withConfig({ displayName: "Dialog__Body", componentId: "sc-11pkgky-5" })(["flex-grow:1;overflow:auto;padding:", ";"], (0, _constants.get)('space.3')); const Footer = (0, _styledComponents.default)(_Box.default).attrs({ as: 'footer' }).withConfig({ displayName: "Dialog__Footer", componentId: "sc-11pkgky-6" })(["box-shadow:0 -1px 0 ", ";padding:", ";display:flex;flex-flow:wrap;justify-content:flex-end;z-index:1;flex-shrink:0;button{margin-left:", ";&:first-child{margin-left:0;}}"], (0, _constants.get)('colors.border.default'), (0, _constants.get)('space.3'), (0, _constants.get)('space.1')); const buttonTypes = { normal: _Button.default, primary: _Button.ButtonPrimary, danger: _Button.ButtonDanger }; const Buttons = ({ buttons }) => { var _buttons$find; const autoFocusRef = (0, _hooks.useProvidedRefOrCreate)((_buttons$find = buttons.find(button => button.autoFocus)) === null || _buttons$find === void 0 ? void 0 : _buttons$find.ref); let autoFocusCount = 0; const [hasRendered, setHasRendered] = (0, _react.useState)(0); (0, _react.useEffect)(() => { // hack to work around dialogs originating from other focus traps. if (hasRendered === 1) { var _autoFocusRef$current; (_autoFocusRef$current = autoFocusRef.current) === null || _autoFocusRef$current === void 0 ? void 0 : _autoFocusRef$current.focus(); } else { setHasRendered(hasRendered + 1); } }, [autoFocusRef, hasRendered]); return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, buttons.map((dialogButtonProps, index) => { const { content, buttonType = 'normal', autoFocus = false, ...buttonProps } = dialogButtonProps; const ButtonElement = buttonTypes[buttonType]; return /*#__PURE__*/_react.default.createElement(ButtonElement, _extends({ key: index }, buttonProps, { ref: autoFocus && autoFocusCount === 0 ? (autoFocusCount++, autoFocusRef) : null }), content); })); }; const DialogCloseButton = (0, _styledComponents.default)(_Button.default).withConfig({ displayName: "Dialog__DialogCloseButton", componentId: "sc-11pkgky-7" })(["border-radius:4px;background:transparent;border:0;vertical-align:middle;color:", ";padding:", ";align-self:flex-start;line-height:normal;box-shadow:none;"], (0, _constants.get)('colors.fg.muted'), (0, _constants.get)('space.2')); const CloseButton = ({ onClose }) => { return /*#__PURE__*/_react.default.createElement(DialogCloseButton, { "aria-label": "Close", onClick: onClose }, /*#__PURE__*/_react.default.createElement(_StyledOcticon.default, { icon: _octiconsReact.XIcon })); }; CloseButton.displayName = "CloseButton"; /** * A dialog is a type of overlay that can be used for confirming actions, asking * for disambiguation, and presenting small forms. They generally allow the user * to focus on a quick task without having to navigate to a different page. * * Dialogs appear in the page after a direct user interaction. Don't show dialogs * on page load or as system alerts. * * Dialogs appear centered in the page, with a visible backdrop that dims the rest * of the window for focus. * * All dialogs have a title and a close button. * * Dialogs are modal. Dialogs can be dismissed by clicking on the close button, * pressing the escape key, or by interacting with another button in the dialog. * To avoid losing information and missing important messages, clicking outside * of the dialog will not close it. * * The sub components provided (e.g. Header, Title, etc.) are available for custom * renderers only. They are not intended to be used otherwise. */ const Dialog = Object.assign(_Dialog, { Header, Title, Subtitle, Body, Footer, Buttons, CloseButton }); exports.Dialog = Dialog;