@atlaskit/modal-dialog 12.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +2111 -0
  2. package/LICENSE +13 -0
  3. package/README.md +13 -0
  4. package/__perf__/default.tsx +42 -0
  5. package/__perf__/interactions.tsx +136 -0
  6. package/__perf__/scroll.tsx +98 -0
  7. package/codemods/12.0.0-lite-mode.ts +51 -0
  8. package/codemods/__tests__/12.0.0-lite-mode.test.ts +493 -0
  9. package/codemods/__tests__/handle-prop-spread.tsx +276 -0
  10. package/codemods/__tests__/inline-WidthNames-declaration.test.ts +260 -0
  11. package/codemods/__tests__/map-actions-prop.tsx +436 -0
  12. package/codemods/__tests__/map-body-from-props.test.ts +645 -0
  13. package/codemods/__tests__/map-container-from-props.test.ts +323 -0
  14. package/codemods/__tests__/map-footer-from-props.test.ts +544 -0
  15. package/codemods/__tests__/map-header-from-props.test.ts +559 -0
  16. package/codemods/__tests__/map-heading-prop.tsx +438 -0
  17. package/codemods/__tests__/remove-appearance-prop.test.ts +79 -0
  18. package/codemods/__tests__/remove-component-override-props.test.ts +153 -0
  19. package/codemods/__tests__/remove-is-chromeless.tsx +182 -0
  20. package/codemods/__tests__/rename-appearance-type.test.ts +52 -0
  21. package/codemods/__tests__/rename-inner-component-prop-types.test.ts +82 -0
  22. package/codemods/__tests__/rename-scrollBehavior-to-shouldScrollInViewport.test.ts +237 -0
  23. package/codemods/internal/constants.tsx +41 -0
  24. package/codemods/internal/utils.tsx +223 -0
  25. package/codemods/migrations/handle-prop-spread.tsx +51 -0
  26. package/codemods/migrations/inline-WidthNames-declaration.ts +92 -0
  27. package/codemods/migrations/map-actions-prop.tsx +430 -0
  28. package/codemods/migrations/map-body-from-props.ts +147 -0
  29. package/codemods/migrations/map-container-from-props.ts +72 -0
  30. package/codemods/migrations/map-footer-from-props.ts +107 -0
  31. package/codemods/migrations/map-header-from-props.ts +101 -0
  32. package/codemods/migrations/map-heading-prop.tsx +193 -0
  33. package/codemods/migrations/remove-appearance-prop.ts +27 -0
  34. package/codemods/migrations/remove-component-override-props.ts +84 -0
  35. package/codemods/migrations/remove-is-chromeless.tsx +42 -0
  36. package/codemods/migrations/rename-appearance-type.ts +9 -0
  37. package/codemods/migrations/rename-inner-component-prop-types.ts +28 -0
  38. package/codemods/migrations/rename-scrollBehavior-to-shouldScrollInViewport.ts +82 -0
  39. package/dist/cjs/hooks.js +22 -0
  40. package/dist/cjs/index.js +63 -0
  41. package/dist/cjs/internal/components/modal-dialog.js +155 -0
  42. package/dist/cjs/internal/components/positioner.js +89 -0
  43. package/dist/cjs/internal/components/scroll-container.js +138 -0
  44. package/dist/cjs/internal/constants.js +48 -0
  45. package/dist/cjs/internal/context.js +13 -0
  46. package/dist/cjs/internal/hooks/use-modal-stack.js +110 -0
  47. package/dist/cjs/internal/hooks/use-on-motion-finish.js +24 -0
  48. package/dist/cjs/internal/hooks/use-prevent-programmatic-scroll.js +55 -0
  49. package/dist/cjs/internal/hooks/use-scroll.js +20 -0
  50. package/dist/cjs/internal/utils.js +35 -0
  51. package/dist/cjs/modal-body.js +66 -0
  52. package/dist/cjs/modal-footer.js +40 -0
  53. package/dist/cjs/modal-header.js +43 -0
  54. package/dist/cjs/modal-title.js +108 -0
  55. package/dist/cjs/modal-transition.js +21 -0
  56. package/dist/cjs/modal-wrapper.js +126 -0
  57. package/dist/cjs/types.js +5 -0
  58. package/dist/cjs/version.json +5 -0
  59. package/dist/es2019/hooks.js +11 -0
  60. package/dist/es2019/index.js +7 -0
  61. package/dist/es2019/internal/components/modal-dialog.js +120 -0
  62. package/dist/es2019/internal/components/positioner.js +78 -0
  63. package/dist/es2019/internal/components/scroll-container.js +97 -0
  64. package/dist/es2019/internal/constants.js +27 -0
  65. package/dist/es2019/internal/context.js +3 -0
  66. package/dist/es2019/internal/hooks/use-modal-stack.js +85 -0
  67. package/dist/es2019/internal/hooks/use-on-motion-finish.js +17 -0
  68. package/dist/es2019/internal/hooks/use-prevent-programmatic-scroll.js +39 -0
  69. package/dist/es2019/internal/hooks/use-scroll.js +11 -0
  70. package/dist/es2019/internal/utils.js +22 -0
  71. package/dist/es2019/modal-body.js +50 -0
  72. package/dist/es2019/modal-footer.js +30 -0
  73. package/dist/es2019/modal-header.js +30 -0
  74. package/dist/es2019/modal-title.js +94 -0
  75. package/dist/es2019/modal-transition.js +10 -0
  76. package/dist/es2019/modal-wrapper.js +88 -0
  77. package/dist/es2019/types.js +1 -0
  78. package/dist/es2019/version.json +5 -0
  79. package/dist/esm/hooks.js +11 -0
  80. package/dist/esm/index.js +7 -0
  81. package/dist/esm/internal/components/modal-dialog.js +131 -0
  82. package/dist/esm/internal/components/positioner.js +76 -0
  83. package/dist/esm/internal/components/scroll-container.js +114 -0
  84. package/dist/esm/internal/constants.js +27 -0
  85. package/dist/esm/internal/context.js +3 -0
  86. package/dist/esm/internal/hooks/use-modal-stack.js +96 -0
  87. package/dist/esm/internal/hooks/use-on-motion-finish.js +16 -0
  88. package/dist/esm/internal/hooks/use-prevent-programmatic-scroll.js +44 -0
  89. package/dist/esm/internal/hooks/use-scroll.js +11 -0
  90. package/dist/esm/internal/utils.js +22 -0
  91. package/dist/esm/modal-body.js +49 -0
  92. package/dist/esm/modal-footer.js +29 -0
  93. package/dist/esm/modal-header.js +29 -0
  94. package/dist/esm/modal-title.js +93 -0
  95. package/dist/esm/modal-transition.js +10 -0
  96. package/dist/esm/modal-wrapper.js +96 -0
  97. package/dist/esm/types.js +1 -0
  98. package/dist/esm/version.json +5 -0
  99. package/dist/types/hooks.d.ts +1 -0
  100. package/dist/types/index.d.ts +8 -0
  101. package/dist/types/internal/components/modal-dialog.d.ts +3 -0
  102. package/dist/types/internal/components/positioner.d.ts +10 -0
  103. package/dist/types/internal/components/scroll-container.d.ts +20 -0
  104. package/dist/types/internal/constants.d.ts +25 -0
  105. package/dist/types/internal/context.d.ts +20 -0
  106. package/dist/types/internal/hooks/use-modal-stack.d.ts +13 -0
  107. package/dist/types/internal/hooks/use-on-motion-finish.d.ts +4 -0
  108. package/dist/types/internal/hooks/use-prevent-programmatic-scroll.d.ts +7 -0
  109. package/dist/types/internal/hooks/use-scroll.d.ts +1 -0
  110. package/dist/types/internal/utils.d.ts +3 -0
  111. package/dist/types/modal-body.d.ts +16 -0
  112. package/dist/types/modal-footer.d.ts +16 -0
  113. package/dist/types/modal-header.d.ts +16 -0
  114. package/dist/types/modal-title.d.ts +26 -0
  115. package/dist/types/modal-transition.d.ts +3 -0
  116. package/dist/types/modal-wrapper.d.ts +5 -0
  117. package/dist/types/types.d.ts +90 -0
  118. package/extract-react-types/modal-attributes.tsx +5 -0
  119. package/hooks/package.json +7 -0
  120. package/modal-body/package.json +7 -0
  121. package/modal-dialog/package.json +7 -0
  122. package/modal-footer/package.json +7 -0
  123. package/modal-header/package.json +7 -0
  124. package/modal-title/package.json +7 -0
  125. package/modal-transition/package.json +7 -0
  126. package/package.json +113 -0
  127. package/types/package.json +7 -0
@@ -0,0 +1,17 @@
1
+ import { useCallback, useRef } from 'react';
2
+ export default function useOnMotionFinish({
3
+ onOpenComplete,
4
+ onCloseComplete
5
+ }) {
6
+ const motionRef = useRef(null);
7
+ const onMotionFinish = useCallback(state => {
8
+ if (state === 'entering' && onOpenComplete) {
9
+ onOpenComplete(motionRef.current, true);
10
+ }
11
+
12
+ if (state === 'exiting' && onCloseComplete) {
13
+ onCloseComplete(motionRef.current);
14
+ }
15
+ }, [onOpenComplete, onCloseComplete]);
16
+ return [motionRef, onMotionFinish];
17
+ }
@@ -0,0 +1,39 @@
1
+ import { useCallback, useLayoutEffect, useState } from 'react';
2
+ import useWindowEvent from '@atlaskit/ds-lib/use-window-event';
3
+ /**
4
+ * Returns how far the body is scrolled from the top of the viewport.
5
+ *
6
+ * ____
7
+ * ||____|| <-- overflow
8
+ * | | <-- viewport
9
+ * |____|
10
+ *
11
+ * Scroll distance is the height of overflow outside the viewport.
12
+ */
13
+
14
+ function getScrollDistance() {
15
+ var _document$documentEle, _document$body;
16
+
17
+ return window.pageYOffset || ((_document$documentEle = document.documentElement) === null || _document$documentEle === void 0 ? void 0 : _document$documentEle.scrollTop) || ((_document$body = document.body) === null || _document$body === void 0 ? void 0 : _document$body.scrollTop) || 0;
18
+ }
19
+ /**
20
+ * Prevents programatic scrolling of the viewport with `scrollIntoView`.
21
+ * Should be used in conjunction with a scroll lock to prevent a user from scrolling.
22
+ *
23
+ * @returns scroll top offset of the viewport
24
+ */
25
+
26
+
27
+ export default function usePreventProgrammaticScroll() {
28
+ const [scrollTopOffset, setScrollTopOffset] = useState(0);
29
+ useLayoutEffect(() => {
30
+ setScrollTopOffset(getScrollDistance());
31
+ }, []);
32
+ const onWindowScroll = useCallback(() => {
33
+ if (getScrollDistance() !== scrollTopOffset) {
34
+ window.scrollTo(window.pageXOffset, scrollTopOffset);
35
+ }
36
+ }, [scrollTopOffset]);
37
+ useWindowEvent('scroll', onWindowScroll);
38
+ return scrollTopOffset;
39
+ }
@@ -0,0 +1,11 @@
1
+ import { useContext } from 'react';
2
+ import { ScrollContext } from '../context';
3
+ export default function useScroll() {
4
+ const shouldScrollInViewport = useContext(ScrollContext);
5
+
6
+ if (shouldScrollInViewport == null) {
7
+ throw Error('@atlaskit/modal-dialog: Scroll context unavailable – this component needs to be a child of ModalDialog.');
8
+ }
9
+
10
+ return shouldScrollInViewport;
11
+ }
@@ -0,0 +1,22 @@
1
+ import { width } from './constants';
2
+ export const dialogWidth = input => {
3
+ if (!input) {
4
+ return 'auto';
5
+ }
6
+
7
+ const isWidthName = width.values.indexOf(input.toString()) !== -1;
8
+ const widthName = isWidthName && input;
9
+
10
+ if (widthName) {
11
+ return `${width.widths[widthName]}px`;
12
+ }
13
+
14
+ return typeof input === 'number' ? `${input}px` : input;
15
+ };
16
+ export const dialogHeight = input => {
17
+ if (!input) {
18
+ return 'auto';
19
+ }
20
+
21
+ return typeof input === 'number' ? `${input}px` : input;
22
+ };
@@ -0,0 +1,50 @@
1
+ /** @jsx jsx */
2
+ import React from 'react';
3
+ import { css, jsx } from '@emotion/core';
4
+ import { TouchScrollable } from 'react-scrolllock';
5
+ import { useModal } from './hooks';
6
+ import ScrollContainer from './internal/components/scroll-container';
7
+ import { keylineHeight, padding } from './internal/constants';
8
+ import useScroll from './internal/hooks/use-scroll';
9
+ const bodyStyles = css({
10
+ /* This ensures the body fills the whole space between header and footer. */
11
+ flex: '1 1 auto'
12
+ });
13
+ /**
14
+ * Adding the padding here avoids cropping the keyline on its sides.
15
+ * The combined vertical spacing is maintained by subtracting the
16
+ * keyline height from header and footer. */
17
+
18
+ const bodyScrollStyles = css({
19
+ padding: `${keylineHeight}px ${padding}px`
20
+ });
21
+ /**
22
+ * Keylines will not be shown if scrolling in viewport so we do
23
+ * not account for them in this case. */
24
+
25
+ const viewportScrollStyles = css({
26
+ padding: `0px ${padding}px`
27
+ });
28
+
29
+ const ModalBody = props => {
30
+ const {
31
+ children,
32
+ testId: userDefinedTestId
33
+ } = props;
34
+ const {
35
+ testId: modalTestId
36
+ } = useModal();
37
+ const shouldScrollInViewport = useScroll();
38
+ const testId = userDefinedTestId || modalTestId && `${modalTestId}--body`;
39
+ return shouldScrollInViewport ? jsx("div", {
40
+ css: [bodyStyles, viewportScrollStyles],
41
+ "data-testid": testId
42
+ }, children) : jsx(TouchScrollable, null, jsx(ScrollContainer, {
43
+ testId: userDefinedTestId || modalTestId
44
+ }, jsx("div", {
45
+ css: [bodyStyles, bodyScrollStyles],
46
+ "data-testid": testId
47
+ }, children)));
48
+ };
49
+
50
+ export default ModalBody;
@@ -0,0 +1,30 @@
1
+ /** @jsx jsx */
2
+ import { css, jsx } from '@emotion/core';
3
+ import { useModal } from './hooks';
4
+ import { footerItemGap, keylineHeight, padding } from './internal/constants';
5
+ const footerStyles = css({
6
+ display: 'flex',
7
+ padding,
8
+ paddingTop: `${padding - keylineHeight}px`,
9
+ position: 'relative',
10
+ alignItems: 'center',
11
+ justifyContent: 'flex-end',
12
+ gap: `${footerItemGap}px`
13
+ });
14
+
15
+ const ModalFooter = props => {
16
+ const {
17
+ children,
18
+ testId: userDefinedTestId
19
+ } = props;
20
+ const {
21
+ testId: modalTestId
22
+ } = useModal();
23
+ const testId = userDefinedTestId || modalTestId && `${modalTestId}--footer`;
24
+ return jsx("div", {
25
+ css: footerStyles,
26
+ "data-testid": testId
27
+ }, children);
28
+ };
29
+
30
+ export default ModalFooter;
@@ -0,0 +1,30 @@
1
+ /** @jsx jsx */
2
+ import React from 'react';
3
+ import { css, jsx } from '@emotion/core';
4
+ import { useModal } from './hooks';
5
+ import { keylineHeight, padding } from './internal/constants';
6
+ const headerStyles = css({
7
+ display: 'flex',
8
+ padding: padding,
9
+ paddingBottom: `${padding - keylineHeight}px`,
10
+ position: 'relative',
11
+ alignItems: 'center',
12
+ justifyContent: 'space-between'
13
+ });
14
+
15
+ const ModalHeader = props => {
16
+ const {
17
+ children,
18
+ testId: userDefinedTestId
19
+ } = props;
20
+ const {
21
+ testId: modalTestId
22
+ } = useModal();
23
+ const testId = userDefinedTestId || modalTestId && `${modalTestId}--header`;
24
+ return jsx("div", {
25
+ css: headerStyles,
26
+ "data-testid": testId
27
+ }, children);
28
+ };
29
+
30
+ export default ModalHeader;
@@ -0,0 +1,94 @@
1
+ /** @jsx jsx */
2
+ import { css, jsx } from '@emotion/core';
3
+ import ErrorIcon from '@atlaskit/icon/glyph/error';
4
+ import WarningIcon from '@atlaskit/icon/glyph/warning';
5
+ import { useModal } from './hooks';
6
+ import { iconColor, titleIconMargin } from './internal/constants';
7
+ const fontSize = 20;
8
+ const lineHeight = 1;
9
+ const adjustedLineHeight = 1.2;
10
+ const titleStyles = css({
11
+ display: 'flex',
12
+ minWidth: 0,
13
+ margin: 0,
14
+ alignItems: 'center',
15
+ fontSize: `${fontSize}px`,
16
+ fontStyle: 'inherit',
17
+ fontWeight: 500,
18
+ letterSpacing: `-0.008em`,
19
+ lineHeight: lineHeight
20
+ });
21
+ const textStyles = css({
22
+ minWidth: 0,
23
+
24
+ /**
25
+ * This ensures that the element fills the whole header space
26
+ * and its content does not overflow (since flex items don't
27
+ * shrink past its content size by default). */
28
+ flex: '1 1 auto',
29
+ wordWrap: 'break-word'
30
+ });
31
+ const iconStyles = css({
32
+ marginRight: `${titleIconMargin}px`,
33
+
34
+ /* Keeps the size of the icon the same, in case the text element grows in width. */
35
+ flex: '0 0 auto'
36
+ });
37
+ /**
38
+ * When the title is truncated (not multi-line), we adjust the
39
+ * line height to avoid cropping the descenders. This removes
40
+ * the extra spacing that we get from that adjustment. */
41
+
42
+ const lineHeightOffset = fontSize - fontSize * adjustedLineHeight;
43
+ const truncatedTextStyles = css({
44
+ marginTop: `${lineHeightOffset / 2}px`,
45
+ marginBottom: `${lineHeightOffset / 2}px`,
46
+ lineHeight: adjustedLineHeight,
47
+ overflow: 'hidden',
48
+ textOverflow: 'ellipsis',
49
+ whiteSpace: 'nowrap'
50
+ });
51
+ const truncatedTextIconStyles = css({
52
+ marginBottom: `${lineHeightOffset / 2}px`,
53
+ lineHeight: 1.2
54
+ });
55
+
56
+ const TitleIcon = ({
57
+ appearance,
58
+ isMultiline
59
+ }) => {
60
+ const Icon = appearance === 'danger' ? ErrorIcon : WarningIcon;
61
+ return jsx("span", {
62
+ css: [iconStyles, !isMultiline && truncatedTextIconStyles]
63
+ }, jsx(Icon, {
64
+ label: `${appearance} icon`,
65
+ primaryColor: iconColor[appearance]
66
+ }));
67
+ };
68
+
69
+ const ModalTitle = props => {
70
+ const {
71
+ appearance,
72
+ children,
73
+ isMultiline = true,
74
+ testId: userDefinedTestId
75
+ } = props;
76
+ const {
77
+ titleId,
78
+ testId: modalTestId
79
+ } = useModal();
80
+ const testId = userDefinedTestId || modalTestId && `${modalTestId}--title`;
81
+ return jsx("h1", {
82
+ css: titleStyles,
83
+ "data-testid": testId
84
+ }, appearance && jsx(TitleIcon, {
85
+ appearance: appearance,
86
+ isMultiline: isMultiline
87
+ }), jsx("span", {
88
+ id: titleId,
89
+ css: [textStyles, !isMultiline && truncatedTextStyles],
90
+ "data-testid": testId && `${testId}-text`
91
+ }, children));
92
+ };
93
+
94
+ export default ModalTitle;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import ExitingPersistence from '@atlaskit/motion/exiting-persistence';
3
+
4
+ const ModalTransition = props => {
5
+ return /*#__PURE__*/React.createElement(ExitingPersistence, {
6
+ appear: true
7
+ }, props.children);
8
+ };
9
+
10
+ export default ModalTransition;
@@ -0,0 +1,88 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+
3
+ /** @jsx jsx */
4
+ import { useCallback } from 'react';
5
+ import { css, jsx } from '@emotion/core';
6
+ import FocusLock from 'react-focus-lock';
7
+ import ScrollLock, { TouchScrollable } from 'react-scrolllock';
8
+ import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next';
9
+ import Blanket from '@atlaskit/blanket';
10
+ import noop from '@atlaskit/ds-lib/noop';
11
+ import useCloseOnEscapePress from '@atlaskit/ds-lib/use-close-on-escape-press';
12
+ import FadeIn from '@atlaskit/motion/fade-in';
13
+ import Portal from '@atlaskit/portal';
14
+ import { layers } from '@atlaskit/theme/constants';
15
+ import ModalDialog from './internal/components/modal-dialog';
16
+ import useModalStack from './internal/hooks/use-modal-stack';
17
+ import usePreventProgrammaticScroll from './internal/hooks/use-prevent-programmatic-scroll';
18
+ const fillScreenStyles = css({
19
+ width: '100vw',
20
+ height: '100vh',
21
+ position: 'fixed',
22
+ top: 0,
23
+ left: 0,
24
+ overflowY: 'auto',
25
+ WebkitOverflowScrolling: 'touch'
26
+ });
27
+
28
+ const ModalWrapper = props => {
29
+ const {
30
+ autoFocus = true,
31
+ shouldCloseOnEscapePress = true,
32
+ shouldCloseOnOverlayClick = true,
33
+ shouldScrollInViewport = false,
34
+ stackIndex: stackIndexOverride,
35
+ onClose = noop,
36
+ onStackChange = noop,
37
+ isBlanketHidden,
38
+ testId,
39
+ ...modalDialogProps
40
+ } = props;
41
+ const calculatedStackIndex = useModalStack({
42
+ onStackChange
43
+ });
44
+ const stackIndex = stackIndexOverride || calculatedStackIndex;
45
+ const isForeground = stackIndex === 0; // When a user supplies a ref to focus we skip auto focus via react-focus-lock
46
+
47
+ const autoFocusLock = typeof autoFocus === 'boolean' ? autoFocus : false;
48
+ const onCloseHandler = usePlatformLeafEventHandler({
49
+ fn: onClose,
50
+ action: 'closed',
51
+ componentName: 'modalDialog',
52
+ packageName: "@atlaskit/modal-dialog",
53
+ packageVersion: "12.0.2"
54
+ });
55
+ const onBlanketClicked = useCallback(e => {
56
+ if (shouldCloseOnOverlayClick) {
57
+ onCloseHandler(e);
58
+ }
59
+ }, [shouldCloseOnOverlayClick, onCloseHandler]);
60
+ usePreventProgrammaticScroll();
61
+ useCloseOnEscapePress({
62
+ onClose: onCloseHandler,
63
+ isDisabled: !shouldCloseOnEscapePress || !isForeground
64
+ });
65
+ const modalDialogWithBlanket = jsx(Blanket, {
66
+ isTinted: !isBlanketHidden,
67
+ onBlanketClicked: onBlanketClicked,
68
+ testId: testId && `${testId}--blanket`
69
+ }, jsx(ModalDialog, _extends({
70
+ testId: testId,
71
+ autoFocus: autoFocus,
72
+ stackIndex: stackIndex,
73
+ onClose: onCloseHandler,
74
+ shouldScrollInViewport: shouldScrollInViewport
75
+ }, modalDialogProps)));
76
+ return jsx(Portal, {
77
+ zIndex: layers.modal()
78
+ }, jsx(FadeIn, null, fadeInProps => jsx("div", _extends({}, fadeInProps, {
79
+ css: fillScreenStyles,
80
+ "aria-hidden": !isForeground
81
+ }), jsx(FocusLock, {
82
+ autoFocus: autoFocusLock,
83
+ disabled: !isForeground,
84
+ returnFocus: true
85
+ }, jsx(ScrollLock, null), shouldScrollInViewport ? jsx(TouchScrollable, null, modalDialogWithBlanket) : modalDialogWithBlanket))));
86
+ };
87
+
88
+ export default ModalWrapper;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@atlaskit/modal-dialog",
3
+ "version": "12.0.2",
4
+ "sideEffects": false
5
+ }
@@ -0,0 +1,11 @@
1
+ import { useContext } from 'react';
2
+ import { ModalContext } from './internal/context';
3
+ export var useModal = function useModal() {
4
+ var modalContext = useContext(ModalContext);
5
+
6
+ if (modalContext == null) {
7
+ throw Error('@atlaskit/modal-dialog: Modal context unavailable – this component needs to be a child of ModalDialog.');
8
+ }
9
+
10
+ return modalContext;
11
+ };
@@ -0,0 +1,7 @@
1
+ export { default } from './modal-wrapper';
2
+ export { default as ModalTransition } from './modal-transition';
3
+ export { default as ModalHeader } from './modal-header';
4
+ export { default as ModalTitle } from './modal-title';
5
+ export { default as ModalBody } from './modal-body';
6
+ export { default as ModalFooter } from './modal-footer';
7
+ export { useModal } from './hooks';
@@ -0,0 +1,131 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
+ import _typeof from "@babel/runtime/helpers/typeof";
4
+
5
+ /** @jsx jsx */
6
+ import { useMemo } from 'react';
7
+ import { css, jsx } from '@emotion/core';
8
+ import { useUID } from 'react-uid';
9
+ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
10
+ import useAutoFocus from '@atlaskit/ds-lib/use-auto-focus';
11
+ import FadeIn from '@atlaskit/motion/fade-in';
12
+ import { N0, N30A, N60A } from '@atlaskit/theme/colors';
13
+ import { borderRadius, textColor } from '../constants';
14
+ import { ModalContext, ScrollContext } from '../context';
15
+ import useOnMotionFinish from '../hooks/use-on-motion-finish';
16
+ import { dialogHeight, dialogWidth } from '../utils';
17
+ import Positioner from './positioner';
18
+ var dialogStyles = css({
19
+ display: 'flex',
20
+ width: '100%',
21
+ maxWidth: '100vw',
22
+ height: '100%',
23
+ minHeight: 0,
24
+ maxHeight: '100vh',
25
+ flex: '1 1 auto',
26
+ flexDirection: 'column',
27
+ backgroundColor: N0,
28
+ color: textColor,
29
+ pointerEvents: 'auto',
30
+ '@media (min-width: 480px)': {
31
+ width: 'var(--modal-dialog-width)',
32
+ maxWidth: 'inherit',
33
+ marginRight: 'inherit',
34
+ marginLeft: 'inherit',
35
+ borderRadius: borderRadius,
36
+ boxShadow: "0 0 0 1px ".concat(N30A, ", 0 2px 1px ").concat(N30A, ", 0 0 20px -6px ").concat(N60A)
37
+ },
38
+
39
+ /**
40
+ * This is to support scrolling if the modal's children are wrapped in
41
+ * a form.
42
+ */
43
+ // eslint-disable-next-line @repo/internal/styles/no-nested-styles
44
+ '& > form:only-child': {
45
+ display: 'inherit',
46
+ maxHeight: 'inherit',
47
+ flexDirection: 'inherit'
48
+ }
49
+ });
50
+ var viewportScrollStyles = css({
51
+ /**
52
+ * This ensures that the element fills the viewport on mobile
53
+ * while also allowing it to overflow if its height is larger than
54
+ * the viewport.
55
+ */
56
+ minHeight: '100vh',
57
+ maxHeight: 'none',
58
+ '@media (min-width: 480px)': {
59
+ minHeight: 'var(--modal-dialog-height)'
60
+ }
61
+ });
62
+ var bodyScrollStyles = css({
63
+ '@media (min-width: 480px)': {
64
+ height: 'var(--modal-dialog-height)',
65
+ maxHeight: 'inherit'
66
+ }
67
+ });
68
+
69
+ var ModalDialog = function ModalDialog(props) {
70
+ var _props$width = props.width,
71
+ width = _props$width === void 0 ? 'medium' : _props$width,
72
+ _props$shouldScrollIn = props.shouldScrollInViewport,
73
+ shouldScrollInViewport = _props$shouldScrollIn === void 0 ? false : _props$shouldScrollIn,
74
+ autoFocus = props.autoFocus,
75
+ stackIndex = props.stackIndex,
76
+ onClose = props.onClose,
77
+ onCloseComplete = props.onCloseComplete,
78
+ onOpenComplete = props.onOpenComplete,
79
+ height = props.height,
80
+ children = props.children,
81
+ testId = props.testId;
82
+ var id = useUID();
83
+ var titleId = "modal-dialog-title-".concat(id);
84
+ useAutoFocus(_typeof(autoFocus) === 'object' ? autoFocus : undefined, // When a user supplies a ref to focus we enable this hook
85
+ _typeof(autoFocus) === 'object');
86
+
87
+ var _useOnMotionFinish = useOnMotionFinish({
88
+ onOpenComplete: onOpenComplete,
89
+ onCloseComplete: onCloseComplete
90
+ }),
91
+ _useOnMotionFinish2 = _slicedToArray(_useOnMotionFinish, 2),
92
+ motionRef = _useOnMotionFinish2[0],
93
+ onMotionFinish = _useOnMotionFinish2[1];
94
+
95
+ var modalDialogContext = useMemo(function () {
96
+ return {
97
+ testId: testId,
98
+ titleId: titleId,
99
+ onClose: onClose
100
+ };
101
+ }, [testId, titleId, onClose]);
102
+ return jsx(Positioner, {
103
+ stackIndex: stackIndex,
104
+ shouldScrollInViewport: shouldScrollInViewport,
105
+ testId: testId
106
+ }, jsx(ModalContext.Provider, {
107
+ value: modalDialogContext
108
+ }, jsx(ScrollContext.Provider, {
109
+ value: shouldScrollInViewport
110
+ }, jsx(FadeIn, {
111
+ entranceDirection: "bottom",
112
+ onFinish: onMotionFinish
113
+ }, function (bottomFadeInProps) {
114
+ return jsx("section", _extends({}, bottomFadeInProps, {
115
+ ref: mergeRefs([bottomFadeInProps.ref, motionRef]),
116
+ style: {
117
+ '--modal-dialog-width': dialogWidth(width),
118
+ '--modal-dialog-height': dialogHeight(height)
119
+ },
120
+ css: [dialogStyles, shouldScrollInViewport ? viewportScrollStyles : bodyScrollStyles],
121
+ role: "dialog",
122
+ "aria-labelledby": titleId,
123
+ "data-testid": testId,
124
+ "data-modal-stack": stackIndex,
125
+ tabIndex: -1,
126
+ "aria-modal": true
127
+ }), children);
128
+ }))));
129
+ };
130
+
131
+ export default ModalDialog;