@primer/components
Version:
Primer react components
304 lines (263 loc) • 12.6 kB
JavaScript
"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;