@atlaskit/modal-dialog
Version:
A modal dialog displays content that requires user interaction, in a layer above the page.
123 lines (122 loc) • 3.97 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
/** @jsx jsx */
import { useMemo } from 'react';
import { css, jsx } from '@emotion/react';
import { useUID } from 'react-uid';
import mergeRefs from '@atlaskit/ds-lib/merge-refs';
import useAutoFocus from '@atlaskit/ds-lib/use-auto-focus';
import FocusRing from '@atlaskit/focus-ring';
import FadeIn from '@atlaskit/motion/fade-in';
import { N0, N30A, N60A } from '@atlaskit/theme/colors';
import { borderRadius, textColor } from '../constants';
import { ModalContext, ScrollContext } from '../context';
import useOnMotionFinish from '../hooks/use-on-motion-finish';
import { dialogHeight, dialogWidth } from '../utils';
import Positioner from './positioner';
const dialogStyles = css({
display: 'flex',
width: '100%',
maxWidth: '100vw',
height: '100%',
minHeight: 0,
maxHeight: '100vh',
// Flex-grow set to 0 to prevent this element from filling its parent flexbox container
flex: '0 1 auto',
flexDirection: 'column',
backgroundColor: `var(--ds-surface-overlay, ${N0})`,
color: textColor,
pointerEvents: 'auto',
'@media (min-width: 480px)': {
width: 'var(--modal-dialog-width)',
maxWidth: 'inherit',
// eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
marginRight: 'inherit',
// eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage-spacing
marginLeft: 'inherit',
borderRadius,
boxShadow: `var(--ds-shadow-overlay, ${`0 0 0 1px ${N30A}, 0 2px 1px ${N30A}, 0 0 20px -6px ${N60A}`})`
},
/**
* This is to support scrolling if the modal's children are wrapped in
* a form.
*/
// eslint-disable-next-line @repo/internal/styles/no-nested-styles
'& > form:only-child': {
display: 'inherit',
maxHeight: 'inherit',
flexDirection: 'inherit'
}
});
const viewportScrollStyles = css({
/**
* This ensures that the element fills the viewport on mobile
* while also allowing it to overflow if its height is larger than
* the viewport.
*/
minHeight: '100vh',
maxHeight: 'none',
'@media (min-width: 480px)': {
minHeight: 'var(--modal-dialog-height)'
}
});
const bodyScrollStyles = css({
'@media (min-width: 480px)': {
height: 'var(--modal-dialog-height)',
maxHeight: 'inherit'
}
});
const ModalDialog = props => {
const {
width = 'medium',
shouldScrollInViewport = false,
autoFocus,
stackIndex,
onClose,
onCloseComplete,
onOpenComplete,
height,
children,
testId
} = props;
const id = useUID();
const titleId = `modal-dialog-title-${id}`;
useAutoFocus(typeof autoFocus === 'object' ? autoFocus : undefined,
// When a user supplies a ref to focus we enable this hook
typeof autoFocus === 'object');
const [motionRef, onMotionFinish] = useOnMotionFinish({
onOpenComplete,
onCloseComplete
});
const modalDialogContext = useMemo(() => ({
testId,
titleId,
onClose
}), [testId, titleId, onClose]);
return jsx(Positioner, {
stackIndex: stackIndex,
shouldScrollInViewport: shouldScrollInViewport,
testId: testId
}, jsx(ModalContext.Provider, {
value: modalDialogContext
}, jsx(ScrollContext.Provider, {
value: shouldScrollInViewport
}, jsx(FadeIn, {
entranceDirection: "bottom",
onFinish: onMotionFinish
}, bottomFadeInProps => jsx(FocusRing, null, jsx("section", _extends({}, bottomFadeInProps, {
ref: mergeRefs([bottomFadeInProps.ref, motionRef]),
style: {
'--modal-dialog-width': dialogWidth(width),
'--modal-dialog-height': dialogHeight(height)
},
css: [dialogStyles, shouldScrollInViewport ? viewportScrollStyles : bodyScrollStyles],
role: "dialog",
"aria-labelledby": titleId,
"data-testid": testId,
"data-modal-stack": stackIndex,
tabIndex: -1,
"aria-modal": true
}), children))))));
};
// eslint-disable-next-line @repo/internal/react/require-jsdoc
export default ModalDialog;