@cleartrip/ct-design-modal 4.0.0-TEST.3 → 5.0.0

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 (78) hide show
  1. package/README.md +100 -0
  2. package/dist/Backdrop/Backdrop.d.ts +8 -4
  3. package/dist/Backdrop/Backdrop.d.ts.map +1 -1
  4. package/dist/Backdrop/style.d.ts +18 -0
  5. package/dist/Backdrop/style.d.ts.map +1 -0
  6. package/dist/Backdrop/type.d.ts +9 -8
  7. package/dist/Backdrop/type.d.ts.map +1 -1
  8. package/dist/Modal.d.ts +4 -4
  9. package/dist/Modal.d.ts.map +1 -1
  10. package/dist/Modal.native.d.ts +5 -0
  11. package/dist/Modal.native.d.ts.map +1 -0
  12. package/dist/ModalAction/ModalAction.d.ts +7 -5
  13. package/dist/ModalAction/ModalAction.d.ts.map +1 -1
  14. package/dist/ModalContainer/ModalContainer.d.ts +7 -7
  15. package/dist/ModalContainer/ModalContainer.d.ts.map +1 -1
  16. package/dist/ModalContent/ModalContent.d.ts +9 -6
  17. package/dist/ModalContent/ModalContent.d.ts.map +1 -1
  18. package/dist/ModalContext.d.ts +0 -1
  19. package/dist/ModalContext.d.ts.map +1 -1
  20. package/dist/ModalTitle/ModalTitle.d.ts +12 -5
  21. package/dist/ModalTitle/ModalTitle.d.ts.map +1 -1
  22. package/dist/ModalsUsableComponents/ModalWithCloseOverlay.d.ts +3 -2
  23. package/dist/ModalsUsableComponents/ModalWithCloseOverlay.d.ts.map +1 -1
  24. package/dist/ModalsUsableComponents/type.d.ts +10 -4
  25. package/dist/ModalsUsableComponents/type.d.ts.map +1 -1
  26. package/dist/constants.d.ts +28 -0
  27. package/dist/constants.d.ts.map +1 -0
  28. package/dist/ct-design-modal.browser.cjs.js +1 -1
  29. package/dist/ct-design-modal.browser.cjs.js.map +1 -1
  30. package/dist/ct-design-modal.browser.esm.js +1 -1
  31. package/dist/ct-design-modal.browser.esm.js.map +1 -1
  32. package/dist/ct-design-modal.cjs.js +233 -130
  33. package/dist/ct-design-modal.cjs.js.map +1 -1
  34. package/dist/ct-design-modal.esm.js +235 -132
  35. package/dist/ct-design-modal.esm.js.map +1 -1
  36. package/dist/ct-design-modal.umd.js +1881 -140
  37. package/dist/ct-design-modal.umd.js.map +1 -1
  38. package/dist/index.d.ts +4 -2
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.native.d.ts +5 -0
  41. package/dist/index.native.d.ts.map +1 -0
  42. package/dist/modalUtils.d.ts +2 -0
  43. package/dist/modalUtils.d.ts.map +1 -0
  44. package/dist/style.d.ts +18 -0
  45. package/dist/style.d.ts.map +1 -0
  46. package/dist/style.native.d.ts +29 -0
  47. package/dist/style.native.d.ts.map +1 -0
  48. package/dist/type.d.ts +38 -40
  49. package/dist/type.d.ts.map +1 -1
  50. package/package.json +38 -17
  51. package/src/Backdrop/Backdrop.tsx +39 -0
  52. package/src/Backdrop/index.ts +1 -0
  53. package/src/Backdrop/style.ts +18 -0
  54. package/src/Backdrop/type.ts +17 -0
  55. package/src/Handler.ts +78 -0
  56. package/src/Modal.native.tsx +210 -0
  57. package/src/Modal.tsx +234 -0
  58. package/src/ModalAction/ModalAction.tsx +42 -0
  59. package/src/ModalAction/index.ts +1 -0
  60. package/src/ModalContainer/ModalContainer.tsx +40 -0
  61. package/src/ModalContainer/index.ts +1 -0
  62. package/src/ModalContent/ModalContent.tsx +44 -0
  63. package/src/ModalContent/index.ts +1 -0
  64. package/src/ModalContext.ts +14 -0
  65. package/src/ModalTitle/ModalTitle.tsx +70 -0
  66. package/src/ModalTitle/index.ts +1 -0
  67. package/src/ModalsUsableComponents/ModalWithCloseOverlay.tsx +92 -0
  68. package/src/ModalsUsableComponents/index.ts +2 -0
  69. package/src/ModalsUsableComponents/type.ts +40 -0
  70. package/src/constants.ts +31 -0
  71. package/src/index.native.ts +4 -0
  72. package/src/index.ts +10 -0
  73. package/src/modalUtils.ts +1 -0
  74. package/src/style.native.ts +83 -0
  75. package/src/style.ts +44 -0
  76. package/src/type.ts +72 -0
  77. package/dist/ModalsUsableComponents/style.d.ts +0 -11
  78. package/dist/ModalsUsableComponents/style.d.ts.map +0 -1
package/package.json CHANGED
@@ -1,39 +1,58 @@
1
1
  {
2
2
  "name": "@cleartrip/ct-design-modal",
3
- "version": "4.0.0-TEST.3",
3
+ "version": "5.0.0",
4
4
  "description": "Modal Component",
5
5
  "types": "dist/index.d.ts",
6
- "main": "dist/ct-design-modal.cjs.js",
6
+ "main": "./dist/ct-design-modal.cjs.js",
7
+ "react-native": "src/index.native.ts",
7
8
  "jsnext:main": "dist/ct-design-modal.esm.js",
8
9
  "module": "dist/ct-design-modal.esm.js",
9
10
  "sideEffects": false,
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/ct-design-modal.esm.js",
15
+ "default": "./dist/ct-design-modal.cjs.js"
16
+ }
17
+ },
10
18
  "browser": {
11
19
  "./dist/ct-design-modal.esm.js": "./dist/ct-design-modal.browser.esm.js",
12
20
  "./dist/ct-design-modal.cjs.js": "./dist/ct-design-modal.browser.cjs.js"
13
21
  },
14
22
  "files": [
15
- "dist"
23
+ "dist",
24
+ "src"
16
25
  ],
17
26
  "dependencies": {
18
- "@cleartrip/ct-design-types": "3.20.0",
19
- "@cleartrip/ct-design-container": "3.20.0",
20
- "@cleartrip/ct-design-use-merge-refs": "3.20.0",
21
- "@cleartrip/ct-design-icons": "39.0.0-TEST.3",
22
- "@cleartrip/ct-design-typography": "3.21.0",
23
- "@cleartrip/ct-design-transition": "4.0.0-TEST.3",
24
- "@cleartrip/ct-design-theme": "3.20.0",
25
- "@cleartrip/ct-design-portal": "3.20.0",
26
- "@cleartrip/ct-design-focus-lock": "3.20.0",
27
- "@cleartrip/ct-design-box": "3.20.0",
28
- "@cleartrip/ct-design-button": "3.20.0"
27
+ "@emotion/react": "^11.14.0",
28
+ "@emotion/styled": "^11.14.0",
29
+ "@cleartrip/ct-design-container": "5.0.0",
30
+ "@cleartrip/ct-design-types": "5.0.0",
31
+ "@cleartrip/ct-design-transition": "5.0.0",
32
+ "@cleartrip/ct-design-focus-lock": "5.0.0",
33
+ "@cleartrip/ct-design-use-merge-refs": "5.0.0",
34
+ "@cleartrip/ct-design-icons": "5.0.0",
35
+ "@cleartrip/ct-design-typography": "5.0.0",
36
+ "@cleartrip/ct-design-box": "5.0.0",
37
+ "@cleartrip/ct-design-theme": "5.0.0",
38
+ "@cleartrip/ct-design-button": "5.0.0",
39
+ "@cleartrip/ct-design-style-manager": "5.0.0",
40
+ "@cleartrip/ct-design-overlay": "5.0.0",
41
+ "@cleartrip/ct-design-portal": "5.0.0"
29
42
  },
30
43
  "devDependencies": {
31
- "@cleartrip/ct-design-icons": "39.0.0-TEST.3"
44
+ "@emotion/babel-plugin": "^11.12.0",
45
+ "@cleartrip/ct-design-icons": "5.0.0"
32
46
  },
33
47
  "peerDependencies": {
34
48
  "react": ">=16.8.0",
35
49
  "react-dom": ">=16.8.0",
36
- "styled-components": "^5.3.6"
50
+ "react-native-reanimated": ">=3.0.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "react-native-reanimated": {
54
+ "optional": true
55
+ }
37
56
  },
38
57
  "publishConfig": {
39
58
  "access": "public"
@@ -44,6 +63,8 @@
44
63
  "test": "echo \"Error: no test specified\" && exit 1",
45
64
  "watch-package": "rollup -c -w",
46
65
  "build-package": "rollup -c",
47
- "build-package:clean": "rm -rf dist && rollup -c"
66
+ "build-package:clean": "rm -rf dist && rollup -c",
67
+ "publish-package:local": "yalc publish --no-scripts",
68
+ "publish-package:local:registry": "pnpm publish --registry http://localhost:4873 --no-git-checks --access public"
48
69
  }
49
70
  }
@@ -0,0 +1,39 @@
1
+ import { forwardRef } from 'react';
2
+
3
+ import { css } from '@emotion/css';
4
+ import { Transition } from '@cleartrip/ct-design-transition';
5
+ import { useStyles, useWebMergeStyles } from '@cleartrip/ct-design-style-manager';
6
+
7
+ import { ModalVariant } from '../constants';
8
+ import { getBackdropDynamicStyles, staticBackdropStyles } from './style';
9
+ import { BackdropProps } from './type';
10
+
11
+ const webStyle = css({
12
+ position: 'fixed',
13
+ });
14
+
15
+ const Backdrop = forwardRef<HTMLDivElement, BackdropProps>(
16
+ (
17
+ { children, open, transitionDuration, styleConfig, blur, variant = ModalVariant.NEUTRAL, ...rest },
18
+ forwardedRef,
19
+ ) => {
20
+ const dynamicStyles = useStyles(() => ({ root: getBackdropDynamicStyles({ blur, variant }) }), [blur, variant]);
21
+
22
+ const className = useWebMergeStyles(
23
+ [staticBackdropStyles.root, dynamicStyles.root, webStyle, ...(styleConfig?.root ?? [])],
24
+ [dynamicStyles.root, styleConfig?.root],
25
+ );
26
+
27
+ return (
28
+ <Transition in={open} timeout={transitionDuration} type='fade'>
29
+ <div ref={forwardedRef} className={className} {...rest}>
30
+ {children}
31
+ </div>
32
+ </Transition>
33
+ );
34
+ },
35
+ );
36
+
37
+ Backdrop.displayName = 'Backdrop';
38
+
39
+ export default Backdrop;
@@ -0,0 +1 @@
1
+ export { default } from './Backdrop';
@@ -0,0 +1,18 @@
1
+ import { makeStyles } from '@cleartrip/ct-design-style-manager';
2
+ import { ModalVariant } from '../constants';
3
+
4
+ export const staticBackdropStyles = makeStyles(() => ({
5
+ root: {
6
+ display: 'flex',
7
+ alignItems: 'center',
8
+ justifyContent: 'center',
9
+ zIndex: -1,
10
+ inset: 0,
11
+ },
12
+ }));
13
+
14
+ export const getBackdropDynamicStyles = ({ blur, variant }: { blur?: boolean; variant?: `${ModalVariant}` }) => ({
15
+ backgroundColor: blur ? 'rgba(26, 26, 26, 0.6)' : '',
16
+ // @ts-ignore
17
+ backdropFilter: variant !== ModalVariant.DARK && blur ? 'blur(1px)' : 'none',
18
+ });
@@ -0,0 +1,17 @@
1
+ import type { HTMLAttributes } from 'react';
2
+ import type { Styles } from '@cleartrip/ct-design-types';
3
+ import type { TransitionProps } from '@cleartrip/ct-design-transition';
4
+ import { ModalVariant } from '../constants';
5
+
6
+ export interface BackdropStyleConfigProps {
7
+ root?: Styles[];
8
+ }
9
+
10
+ export type BackdropProps = HTMLAttributes<HTMLDivElement> & {
11
+ open?: boolean;
12
+ transitionDuration?: TransitionProps['timeout'];
13
+ blur?: boolean;
14
+ /** Dark variant needs a different backdrop treatment (no blur filter). */
15
+ variant?: `${ModalVariant}`;
16
+ styleConfig?: BackdropStyleConfigProps;
17
+ };
package/src/Handler.ts ADDED
@@ -0,0 +1,78 @@
1
+ type Modal = { ref: HTMLElement | null; prevStyle?: CSSStyleDeclaration };
2
+
3
+ type Parent = {
4
+ modals: Modal[];
5
+ parentNode: Modal;
6
+ };
7
+
8
+ export default class Handler {
9
+ // track all the modals mounted
10
+ private parent: Parent[];
11
+
12
+ constructor() {
13
+ this.parent = [];
14
+ }
15
+
16
+ static handleStyle(parentNode: Modal) {
17
+ if (parentNode.ref) {
18
+ parentNode.prevStyle = { ...parentNode.ref.style };
19
+ // Apply styles here
20
+ parentNode.ref.style.overflow = 'hidden';
21
+ }
22
+ }
23
+
24
+ static restoreStyle(parentNode: Modal) {
25
+ if (parentNode.ref && parentNode.prevStyle) {
26
+ // remove all styles here
27
+
28
+ /**
29
+ * Case:- In safari browsers undefined is not setting the css for overflow styly so added the "" as a fallback.
30
+ */
31
+ parentNode.ref.style.overflow = parentNode.prevStyle.overflow || '';
32
+ }
33
+ }
34
+
35
+ mount(modal: Modal, parentNode: Modal = { ref: null }) {
36
+ // add modal with parent
37
+ if (parentNode.ref) {
38
+ const parentIndex = this.parent.findIndex((node) => node.parentNode === parentNode);
39
+ if (parentIndex !== -1) {
40
+ this.parent[parentIndex].modals.push(modal);
41
+ } else {
42
+ this.parent.push({
43
+ parentNode,
44
+ modals: [modal],
45
+ });
46
+ }
47
+ Handler.handleStyle(parentNode);
48
+ } else {
49
+ // get top modal, if not get the body;
50
+ const parent = this.getTopModal();
51
+ parent.modals.push(modal);
52
+ Handler.handleStyle(parent.parentNode);
53
+ if (parent.parentNode.ref !== document.body) {
54
+ this.parent.push({
55
+ parentNode: modal,
56
+ modals: [],
57
+ });
58
+ } else {
59
+ this.parent.push(parent);
60
+ }
61
+ }
62
+ }
63
+
64
+ getTopModal(): Parent {
65
+ if (this.parent.length) {
66
+ return this.parent[this.parent.length - 1];
67
+ } else {
68
+ return { parentNode: { ref: document.body, prevStyle: document.body.style }, modals: [] };
69
+ }
70
+ }
71
+
72
+ remove(modal: Modal) {
73
+ const parentNode = this.parent.find((node) => node.modals.find((_modal) => _modal.ref === modal.ref));
74
+ if (parentNode) {
75
+ Handler.restoreStyle(parentNode.parentNode);
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,210 @@
1
+ import { cloneElement, forwardRef, isValidElement, memo, ReactElement, useCallback, useEffect, useState } from 'react';
2
+
3
+ import { Keyboard } from 'react-native';
4
+ import Animated, { Easing, SharedValue, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
5
+ import { Container, ContainerRef } from '@cleartrip/ct-design-container';
6
+ import { Overlay } from '@cleartrip/ct-design-overlay';
7
+ import { Portal } from '@cleartrip/ct-design-portal';
8
+ import { makeStyles, useStyles } from '@cleartrip/ct-design-style-manager';
9
+ import { Cross, ModalBack } from '@cleartrip/ct-design-icons';
10
+
11
+ import { ActionButtonVariantsType, IChildProps, IModalProps, ModalVariantTypes } from './type';
12
+ import { ActionButtonAlignment, ActionButtonVariant, ModalPlacement, ModalSize, ModalVariant } from './constants';
13
+ import { getActionButtonAlignment, getModalChildContainerStyle } from './style.native';
14
+ import { MODAL_ANIMATION_DURATION } from './modalUtils';
15
+
16
+ const staticStyles = makeStyles((theme) => ({
17
+ modal: {
18
+ position: 'absolute',
19
+ height: '100%',
20
+ margin: 'auto',
21
+ width: '100%',
22
+ justifyContent: 'center',
23
+ alignItems: 'center',
24
+ },
25
+ modalContentContainer: {
26
+ flex: 1,
27
+ width: '100%',
28
+ height: '100%',
29
+ },
30
+ modalContent: {
31
+ position: 'relative',
32
+ overflow: 'scroll',
33
+ maxWidth: '90%',
34
+ maxHeight: '90%',
35
+ width: '90%',
36
+ margin: 'auto',
37
+ borderRadius: theme?.border?.radius?.[2],
38
+ justifyContent: 'center',
39
+ },
40
+ overlayStyles: {
41
+ position: 'absolute',
42
+ left: 0,
43
+ right: 0,
44
+ justifyContent: 'center',
45
+ alignItems: 'center',
46
+ top: 0,
47
+ zIndex: -1,
48
+ bottom: 0,
49
+ flex: 1,
50
+ },
51
+ modalBody: {
52
+ backgroundColor: theme?.color?.background?.neutral,
53
+ borderRadius: theme?.border?.radius?.[12],
54
+ },
55
+ actionButtonWrapper: {
56
+ top: 0,
57
+ height: 45,
58
+ },
59
+ actionButton: {
60
+ marginLeft: 'auto',
61
+ alignItems: 'center',
62
+ backgroundColor: theme?.color?.background?.neutral,
63
+ borderRadius: theme?.border?.radius?.[16],
64
+ justifyContent: 'center',
65
+ height: theme?.size?.[8],
66
+ width: theme?.size?.[8],
67
+ },
68
+ }));
69
+
70
+ const getActionIcon = ({
71
+ actionButtonType,
72
+ variant,
73
+ }: {
74
+ actionButtonType: ActionButtonVariantsType;
75
+ variant: ModalVariantTypes;
76
+ }): ReactElement => {
77
+ if (actionButtonType === ActionButtonVariant.BACK) {
78
+ return <ModalBack />;
79
+ }
80
+
81
+ return <Cross height={24} width={24} crossColor={variant === ModalVariant.DARK ? 'black' : '#1A1A1A'} />;
82
+ };
83
+
84
+ const Modal = forwardRef<ContainerRef, IModalProps>(
85
+ (
86
+ {
87
+ actionButtonAlignment = ActionButtonAlignment.RIGHT,
88
+ actionButtonType = ActionButtonVariant.CLOSE,
89
+ allowBackdropClose = true,
90
+ animationDuration = MODAL_ANIMATION_DURATION,
91
+ backDropComponent,
92
+ blur = true,
93
+ children,
94
+ onClose,
95
+ open = false,
96
+ placement = ModalPlacement.CENTER,
97
+ size = ModalSize.MEDIUM,
98
+ styleConfig,
99
+ variant = ModalVariant.NEUTRAL,
100
+ },
101
+ ref,
102
+ ) => {
103
+ const [isModalVisible, setIsModalVisible] = useState<boolean>(open);
104
+ const CloseIcon: ReactElement = getActionIcon({ actionButtonType, variant });
105
+ const opacity: SharedValue<number> = useSharedValue(0);
106
+
107
+ const {
108
+ root: customRootStyles = [],
109
+ backdropStyles: customOverlayStyles = [],
110
+ modalContentStyles: customModalContentStyles = [],
111
+ } = styleConfig || {};
112
+
113
+ const modalContentAnimatedStyle = useAnimatedStyle(() => ({
114
+ opacity: opacity.value,
115
+ }));
116
+
117
+ const actionButtonAlignmentStyles = useStyles(
118
+ () => ({
119
+ root: getActionButtonAlignment({ actionButtonAlignment }),
120
+ }),
121
+ [actionButtonAlignment],
122
+ );
123
+
124
+ const dynamicModalContainerStyle = useStyles(
125
+ () => ({
126
+ root: { justifyContent: placement === 'center' ? 'center' : 'flex-end', zIndex: 1000 },
127
+ }),
128
+ [placement],
129
+ );
130
+
131
+ const dynamicModalContentStyles = useStyles(
132
+ (theme) => ({
133
+ childContainer: getModalChildContainerStyle({ theme, size, blur }),
134
+ }),
135
+ [size, blur],
136
+ );
137
+
138
+ const handleBackdropClose = (): void => {
139
+ if (allowBackdropClose) {
140
+ onClose?.();
141
+ setIsModalVisible(false);
142
+ }
143
+ };
144
+
145
+ const getModalAnimation = useCallback((): void => {
146
+ opacity.value = withTiming(opacity.value === 0 ? 1 : 0, {
147
+ duration: animationDuration,
148
+ easing: Easing.bezier(0.4, 0, 0.2, 1),
149
+ });
150
+ }, [animationDuration, opacity]);
151
+
152
+ useEffect(() => {
153
+ setIsModalVisible(open);
154
+ getModalAnimation();
155
+ if (Keyboard.isVisible()) Keyboard.dismiss();
156
+ return () => {
157
+ opacity.value = 0;
158
+ };
159
+ }, [open, animationDuration, opacity, getModalAnimation]);
160
+
161
+ if (!isModalVisible) {
162
+ return null;
163
+ }
164
+
165
+ return (
166
+ <Portal id='global_modal'>
167
+ <Container
168
+ styleConfig={{
169
+ root: [staticStyles.modal, dynamicModalContainerStyle.root, ...customRootStyles],
170
+ }}
171
+ ref={ref}
172
+ >
173
+ <Animated.View style={[modalContentAnimatedStyle, staticStyles.overlayStyles]}>
174
+ {backDropComponent || (
175
+ <Container styleConfig={{ root: [staticStyles.modalContentContainer] }} onClick={handleBackdropClose}>
176
+ <Overlay blur={blur} styleConfig={{ root: [...customOverlayStyles] }} />
177
+ </Container>
178
+ )}
179
+ </Animated.View>
180
+
181
+ <Container
182
+ styleConfig={{
183
+ root: [staticStyles.modalContent, dynamicModalContentStyles?.childContainer, ...customModalContentStyles],
184
+ }}
185
+ >
186
+ {actionButtonType !== ActionButtonVariant.NONE ? (
187
+ <Container styleConfig={{ root: [staticStyles.actionButtonWrapper] }}>
188
+ <Container
189
+ styleConfig={{
190
+ root: [staticStyles.actionButton, actionButtonAlignmentStyles.root],
191
+ }}
192
+ onClick={onClose}
193
+ >
194
+ {CloseIcon}
195
+ </Container>
196
+ </Container>
197
+ ) : null}
198
+
199
+ <Container styleConfig={{ root: [staticStyles.modalBody] }}>
200
+ {isValidElement<IChildProps>(children) && cloneElement(children, { onClose, ...(children.props || {}) })}
201
+ </Container>
202
+ </Container>
203
+ </Container>
204
+ </Portal>
205
+ );
206
+ },
207
+ );
208
+
209
+ Modal.displayName = 'Modal';
210
+ export default memo(Modal);
package/src/Modal.tsx ADDED
@@ -0,0 +1,234 @@
1
+ import React, { forwardRef, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { CSSProperties } from 'react';
3
+ import { getModalSizeStyles } from './style';
4
+ import { IModalProps } from './type';
5
+ import { ModalPlacement, ModalSize, ModalVariant } from './constants';
6
+ import { Portal } from '@cleartrip/ct-design-portal';
7
+ import { FocusLock } from '@cleartrip/ct-design-focus-lock';
8
+ import { Transition } from '@cleartrip/ct-design-transition';
9
+ import Backdrop from './Backdrop';
10
+ import useMergeRefs from '@cleartrip/ct-design-use-merge-refs';
11
+ import { Container, ContainerRef } from '@cleartrip/ct-design-container';
12
+ import ModalContext from './ModalContext';
13
+ import Handler from './Handler';
14
+ import { makeStyles, useStyles, useWebMergeStyles } from '@cleartrip/ct-design-style-manager';
15
+ import { css } from '@emotion/css';
16
+
17
+ const staticModalStyles = makeStyles(() => ({
18
+ modalContainer: {
19
+ position: 'relative',
20
+ display: 'flex',
21
+ flexDirection: 'column',
22
+ margin: 'auto',
23
+ height: '100%',
24
+ width: '100%',
25
+ alignItems: 'center',
26
+ },
27
+ outerContainer: {
28
+ position: 'absolute',
29
+ top: 0,
30
+ left: 0,
31
+ right: 0,
32
+ bottom: 0,
33
+ },
34
+ }));
35
+
36
+ const handler = new Handler();
37
+ const DEFAULT_ANIMATION_TIME = 350;
38
+
39
+ const Modal = forwardRef<ContainerRef, IModalProps>(
40
+ (
41
+ {
42
+ children,
43
+ insidePortal = true,
44
+ open = false,
45
+ onClose,
46
+ onKeyDown,
47
+ hideCrossIcon,
48
+ footerAlignment,
49
+ size = 'MEDIUM',
50
+ allowBackdropClose = true,
51
+ blur = true,
52
+ backDropComponent,
53
+ animationDuration = DEFAULT_ANIMATION_TIME,
54
+ placement = 'center',
55
+ bottomMargin,
56
+ variant,
57
+ styleConfig = {},
58
+ ...rest
59
+ },
60
+ forwardedRef,
61
+ ) => {
62
+ const { backdropStyles = [], modalContentStyles = [], root: rootStyles = [] } = styleConfig;
63
+ const overflowY: CSSProperties['overflowY'] = 'auto';
64
+ const isFullScreen = size === ModalSize.FULL_SCREEN;
65
+
66
+ const dynamicStyles = useStyles(
67
+ (theme) => ({
68
+ modalContainer: {
69
+ position: 'relative',
70
+ zIndex: theme.zIndex.modal,
71
+ overflowY,
72
+ display: 'flex',
73
+ flexDirection: 'column',
74
+ margin: 'auto',
75
+ height: '100%',
76
+ width: '100%',
77
+ justifyContent: placement === ModalPlacement.BOTTOM ? 'flex-end' : ModalPlacement.CENTER,
78
+ alignItems: ModalPlacement.CENTER,
79
+ paddingBottom: placement === ModalPlacement.BOTTOM ? bottomMargin || 188 : 0,
80
+ },
81
+ outerContainer: {
82
+ zIndex: theme.zIndex.modal,
83
+ /**
84
+ * Web only properties
85
+ */
86
+ ...(isFullScreen ? { height: '100vh' as unknown as number, width: '100vw' as unknown as number } : {}),
87
+ },
88
+ childContainer: {
89
+ boxSizing: 'content-box',
90
+ borderRadius: size === ModalSize.FULL_SCREEN ? 0 : theme.spacing['3'],
91
+ borderBottomLeftRadius: size === ModalSize.FULL_SCREEN ? 0 : theme.spacing['3'],
92
+ borderBottomRightRadius: size === ModalSize.FULL_SCREEN ? 0 : theme.spacing['3'],
93
+ backgroundColor:
94
+ variant === ModalVariant.DARK ? theme.color.background.defaultDarkest : theme.color.background.neutral,
95
+ overflowY,
96
+ maxHeight:
97
+ size === ModalSize.FULL_SCREEN
98
+ ? ('100vh' as unknown as number)
99
+ : (`calc(100vh - 64px)` as unknown as number),
100
+ boxShadow: blur
101
+ ? `${theme.elevation.modalBlurLight}, ${theme.elevation.modalBlurMedium}, ${theme.elevation.modalBlurHeavy}`
102
+ : 'none',
103
+ },
104
+ }),
105
+ [size, overflowY, placement, bottomMargin, isFullScreen, blur, variant],
106
+ );
107
+
108
+ const webDynamicStyles = useMemo(() => {
109
+ const styles = getModalSizeStyles(size);
110
+ return css(styles);
111
+ }, [size]);
112
+
113
+ const modalContainerClass = useWebMergeStyles(
114
+ [staticModalStyles.modalContainer, dynamicStyles.modalContainer, webDynamicStyles, ...rootStyles],
115
+ [dynamicStyles.modalContainer, rootStyles, webDynamicStyles],
116
+ );
117
+ const modalRef = useRef<HTMLDivElement | null>(null);
118
+ const containerRef = useRef<{ modalRef?: typeof modalRef.current }>({});
119
+ const [isOpen, setIsOpen] = useState(open);
120
+
121
+ const handleRef = useMergeRefs(forwardedRef, modalRef);
122
+
123
+ useLayoutEffect(() => {
124
+ let timeoutId: NodeJS.Timeout | null = null;
125
+ if (!open) {
126
+ // to add closing animation
127
+ timeoutId = setTimeout(() => setIsOpen(false), animationDuration);
128
+ } else {
129
+ setIsOpen(true);
130
+ }
131
+
132
+ return () => {
133
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
134
+ timeoutId && clearTimeout(timeoutId);
135
+ };
136
+ }, [open, animationDuration]);
137
+
138
+ useEffect(() => {
139
+ if (modalRef.current) {
140
+ modalRef.current.scrollTop = 0;
141
+ }
142
+ if (open) {
143
+ containerRef.current.modalRef = modalRef.current;
144
+ handler.mount({ ref: containerRef.current.modalRef });
145
+ }
146
+
147
+ return () => {
148
+ if (open) {
149
+ handler.remove({ ref: containerRef.current.modalRef || null });
150
+ // @ts-ignore
151
+ // eslint-disable-next-line react-hooks/exhaustive-deps
152
+ containerRef.current.modalRef = modalRef.current;
153
+ }
154
+ };
155
+ }, [open]);
156
+
157
+ const handleBackdropClick = (event: React.MouseEvent<HTMLDivElement>) => {
158
+ if (event.target !== event.currentTarget) {
159
+ return;
160
+ }
161
+ if (allowBackdropClose && onClose) {
162
+ onClose();
163
+ }
164
+ };
165
+
166
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
167
+ if (onKeyDown) {
168
+ onKeyDown(event);
169
+ }
170
+ if (event.key !== 'Escape') {
171
+ return;
172
+ }
173
+
174
+ event.stopPropagation();
175
+ if (onClose) {
176
+ onClose();
177
+ }
178
+ };
179
+
180
+ if (!isOpen || !React.isValidElement(children)) {
181
+ return null;
182
+ }
183
+
184
+ // TODO Focus trap breaks height sequence
185
+ const modalElement = (
186
+ <FocusLock>
187
+ <Container
188
+ styleConfig={{
189
+ root: [staticModalStyles.outerContainer, dynamicStyles.outerContainer],
190
+ }}
191
+ {...rest}
192
+ >
193
+ <div className={modalContainerClass} onKeyDown={handleKeyDown} ref={handleRef}>
194
+ {backDropComponent || (
195
+ <Backdrop
196
+ open={open}
197
+ blur={blur}
198
+ transitionDuration={350}
199
+ onClick={handleBackdropClick}
200
+ variant={variant}
201
+ styleConfig={{ root: backdropStyles }}
202
+ />
203
+ )}
204
+ <Transition
205
+ type={isFullScreen ? 'slide' : 'fade'}
206
+ slideDirection='up'
207
+ in={open}
208
+ timeout={animationDuration}
209
+ >
210
+ <Container
211
+ styleConfig={{
212
+ root: [dynamicStyles.childContainer, ...modalContentStyles],
213
+ }}
214
+ >
215
+ <ModalContext.Provider value={{ footerAlignment, hideCrossIcon, onClose, size }}>
216
+ {React.cloneElement(children, { ...children.props, onClose })}
217
+ </ModalContext.Provider>
218
+ </Container>
219
+ </Transition>
220
+ </div>
221
+ </Container>
222
+ </FocusLock>
223
+ );
224
+
225
+ if (insidePortal) {
226
+ return <Portal id='global_modal'>{modalElement}</Portal>;
227
+ }
228
+
229
+ return modalElement;
230
+ },
231
+ );
232
+
233
+ Modal.displayName = 'Modal';
234
+ export default Modal;