@lumx/react 3.1.4 → 3.2.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 (144) hide show
  1. package/_internal/types.d.ts +16 -5
  2. package/index.d.ts +51 -4
  3. package/index.js +621 -417
  4. package/index.js.map +1 -1
  5. package/package.json +3 -3
  6. package/src/components/alert-dialog/AlertDialog.stories.tsx +96 -165
  7. package/src/components/alert-dialog/AlertDialog.test.tsx +15 -23
  8. package/src/components/avatar/Avatar.stories.tsx +91 -62
  9. package/src/components/avatar/Avatar.test.tsx +9 -24
  10. package/src/components/badge/Badge.stories.tsx +63 -38
  11. package/src/components/button/Button.stories.tsx +147 -139
  12. package/src/components/button/IconButton.stories.tsx +45 -0
  13. package/src/components/checkbox/Checkbox.stories.tsx +37 -30
  14. package/src/components/chip/Chip.stories.tsx +77 -15
  15. package/src/components/comment-block/CommentBlock.stories.tsx +90 -25
  16. package/src/components/comment-block/CommentBlock.test.tsx +12 -17
  17. package/src/components/date-picker/DatePickerField.stories.tsx +52 -83
  18. package/src/components/dialog/Dialog.stories.tsx +132 -240
  19. package/src/components/dialog/Dialog.test.tsx +6 -30
  20. package/src/components/dialog/Dialog.tsx +17 -3
  21. package/src/components/dropdown/Dropdown.stories.tsx +1 -186
  22. package/src/components/flag/Flag.stories.tsx +33 -18
  23. package/src/components/flag/Flag.test.tsx +1 -8
  24. package/src/components/flex-box/FlexBox.stories.tsx +151 -238
  25. package/src/components/flex-box/FlexBox.test.tsx +9 -49
  26. package/src/components/generic-block/GenericBlock.stories.jsx +1 -1
  27. package/src/components/grid-column/GridColumn.stories.tsx +46 -0
  28. package/src/components/heading/Heading.stories.tsx +57 -95
  29. package/src/components/icon/Icon.stories.tsx +67 -70
  30. package/src/components/image-block/ImageBlock.stories.tsx +103 -47
  31. package/src/components/image-block/ImageBlock.test.tsx +12 -17
  32. package/src/components/inline-list/InlineList.stories.tsx +45 -29
  33. package/src/components/input-helper/InputHelper.stories.tsx +31 -25
  34. package/src/components/input-label/InputLabel.stories.tsx +33 -10
  35. package/src/components/lightbox/Lightbox.stories.tsx +39 -77
  36. package/src/components/lightbox/Lightbox.test.tsx +12 -17
  37. package/src/components/link/Link.stories.tsx +98 -128
  38. package/src/components/link-preview/LinkPreview.stories.tsx +48 -75
  39. package/src/components/list/List.stories.tsx +59 -84
  40. package/src/components/list/List.test.tsx +8 -17
  41. package/src/components/list/ListDivider.stories.tsx +9 -4
  42. package/src/components/list/ListDivider.test.tsx +12 -17
  43. package/src/components/list/ListItem.stories.tsx +97 -59
  44. package/src/components/list/ListItem.test.tsx +12 -17
  45. package/src/components/list/ListSubheader.stories.tsx +8 -5
  46. package/src/components/list/ListSubheader.test.tsx +12 -18
  47. package/src/components/message/Message.stories.tsx +51 -22
  48. package/src/components/mosaic/Mosaic.stories.tsx +78 -74
  49. package/src/components/mosaic/Mosaic.test.tsx +0 -31
  50. package/src/components/navigation/Navigation.stories.tsx +67 -0
  51. package/src/components/navigation/Navigation.test.tsx +58 -0
  52. package/src/components/navigation/Navigation.tsx +62 -0
  53. package/src/components/navigation/NavigationItem.test.tsx +37 -0
  54. package/src/components/navigation/NavigationItem.tsx +89 -0
  55. package/src/components/navigation/NavigationSection.test.tsx +126 -0
  56. package/src/components/navigation/NavigationSection.tsx +109 -0
  57. package/src/components/navigation/context.tsx +6 -0
  58. package/src/components/navigation/index.ts +1 -0
  59. package/src/components/notification/Notifications.stories.tsx +52 -47
  60. package/src/components/popover/Popover.stories.tsx +68 -201
  61. package/src/components/popover-dialog/PopoverDialog.stories.tsx +26 -65
  62. package/src/components/post-block/PostBlock.test.tsx +12 -17
  63. package/src/components/progress/ProgressCircular.stories.tsx +24 -12
  64. package/src/components/progress/ProgressLinear.stories.tsx +6 -2
  65. package/src/components/radio-button/RadioButton.stories.tsx +35 -24
  66. package/src/components/select/Select.stories.tsx +19 -23
  67. package/src/components/skeleton/SkeletonCircle.stories.tsx +37 -21
  68. package/src/components/skeleton/SkeletonCircle.test.tsx +12 -17
  69. package/src/components/skeleton/SkeletonRectangle.stories.tsx +74 -99
  70. package/src/components/skeleton/SkeletonRectangle.test.tsx +12 -17
  71. package/src/components/skeleton/SkeletonTypography.test.tsx +12 -17
  72. package/src/components/slider/Slider.stories.tsx +41 -25
  73. package/src/components/slider/Slider.test.tsx +12 -18
  74. package/src/components/slideshow/Slideshow.stories.tsx +31 -61
  75. package/src/components/slideshow/Slideshow.test.tsx +15 -23
  76. package/src/components/slideshow/SlideshowControls.stories.tsx +4 -6
  77. package/src/components/switch/Switch.stories.tsx +35 -32
  78. package/src/components/table/Table.test.tsx +12 -17
  79. package/src/components/tabs/Tabs.stories.tsx +4 -3
  80. package/src/components/text/Text.stories.tsx +130 -0
  81. package/src/components/text-field/TextField.stories.tsx +114 -148
  82. package/src/components/thumbnail/Thumbnail.stories.tsx +106 -255
  83. package/src/components/thumbnail/Thumbnail.test.tsx +12 -35
  84. package/src/components/tooltip/Tooltip.stories.tsx +51 -136
  85. package/src/components/user-block/UserBlock.stories.tsx +67 -56
  86. package/src/components/user-block/UserBlock.test.tsx +1 -5
  87. package/src/index.ts +1 -0
  88. package/src/stories/controls/color.ts +6 -0
  89. package/src/stories/controls/element.ts +6 -0
  90. package/src/stories/controls/focusPoint.ts +1 -0
  91. package/src/stories/controls/icons.ts +6 -0
  92. package/src/stories/{knobs → controls}/image.ts +6 -16
  93. package/src/stories/controls/selectArgType.ts +4 -0
  94. package/src/stories/controls/theme.ts +3 -0
  95. package/src/stories/controls/typography.ts +5 -0
  96. package/src/stories/controls/withUndefined.ts +1 -0
  97. package/src/stories/decorators/withChromaticForceScreenSize.tsx +8 -0
  98. package/src/stories/decorators/withCombinations.tsx +99 -0
  99. package/src/stories/decorators/withNestedProps.tsx +23 -0
  100. package/src/stories/{withResizableBox.tsx → decorators/withResizableBox.tsx} +6 -10
  101. package/src/stories/decorators/withValueOnChange.tsx +18 -0
  102. package/src/stories/decorators/withWrapper.tsx +19 -0
  103. package/src/stories/utils/CustomLink.tsx +8 -2
  104. package/src/stories/{knobs → utils}/lorem.ts +9 -9
  105. package/src/testing/utils/commonTestsSuiteRTL.ts +2 -3
  106. package/src/testing/utils/index.ts +0 -2
  107. package/src/untypped-modules.d.ts +0 -2
  108. package/src/utils/MaterialThemeSwitcher/MaterialThemeSwitcher.tsx +1 -1
  109. package/src/utils/ThemeContext.ts +4 -0
  110. package/src/utils/forwardRefPolymorphic.ts +9 -0
  111. package/src/utils/type.ts +28 -4
  112. package/src/components/alert-dialog/__snapshots__/AlertDialog.test.tsx.snap +0 -551
  113. package/src/components/avatar/__snapshots__/Avatar.test.tsx.snap +0 -681
  114. package/src/components/comment-block/__snapshots__/CommentBlock.test.tsx.snap +0 -92
  115. package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +0 -960
  116. package/src/components/expansion-panel/ExpansionPanel.stories.tsx +0 -65
  117. package/src/components/flag/__snapshots__/Flag.test.tsx.snap +0 -133
  118. package/src/components/flex-box/__snapshots__/FlexBox.test.tsx.snap +0 -492
  119. package/src/components/grid-column/GridColumn.stories.jsx +0 -56
  120. package/src/components/image-block/__snapshots__/ImageBlock.test.tsx.snap +0 -64
  121. package/src/components/lightbox/__snapshots__/Lightbox.test.tsx.snap +0 -194
  122. package/src/components/list/__snapshots__/List.test.tsx.snap +0 -360
  123. package/src/components/list/__snapshots__/ListDivider.test.tsx.snap +0 -7
  124. package/src/components/list/__snapshots__/ListItem.test.tsx.snap +0 -160
  125. package/src/components/list/__snapshots__/ListSubheader.test.tsx.snap +0 -9
  126. package/src/components/mosaic/__snapshots__/Mosaic.test.tsx.snap +0 -357
  127. package/src/components/post-block/__snapshots__/PostBlock.test.tsx.snap +0 -139
  128. package/src/components/skeleton/__snapshots__/SkeletonCircle.test.tsx.snap +0 -54
  129. package/src/components/skeleton/__snapshots__/SkeletonRectangle.test.tsx.snap +0 -177
  130. package/src/components/skeleton/__snapshots__/SkeletonTypography.test.tsx.snap +0 -174
  131. package/src/components/slider/__snapshots__/Slider.test.tsx.snap +0 -122
  132. package/src/components/slideshow/__snapshots__/Slideshow.test.tsx.snap +0 -157
  133. package/src/components/table/__snapshots__/Table.test.tsx.snap +0 -263
  134. package/src/components/text/Text.stories.jsx +0 -75
  135. package/src/components/thumbnail/__snapshots__/Thumbnail.test.tsx.snap +0 -130
  136. package/src/components/user-block/__snapshots__/UserBlock.test.tsx.snap +0 -362
  137. package/src/stories/chromaticForceScreenSize.tsx +0 -7
  138. package/src/stories/knobs/buttonKnob.ts +0 -9
  139. package/src/stories/knobs/emphasisKnob.ts +0 -8
  140. package/src/stories/knobs/enumKnob.ts +0 -14
  141. package/src/stories/knobs/focusKnob.ts +0 -3
  142. package/src/stories/knobs/sizeKnob.ts +0 -5
  143. package/src/stories/knobs/thumbnailsKnob.ts +0 -9
  144. package/src/testing/utils/itShouldRenderStories.tsx +0 -103
@@ -1,3 +1,7 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks,react/jsx-key */
2
+ import React, { useRef, useState } from 'react';
3
+
4
+ import over from 'lodash/over';
1
5
  import { mdiClose } from '@lumx/icons';
2
6
  import {
3
7
  AlertDialog,
@@ -6,25 +10,28 @@ import {
6
10
  DatePickerField,
7
11
  Emphasis,
8
12
  FlexBox,
13
+ Heading,
9
14
  IconButton,
10
15
  List,
11
16
  ListItem,
12
17
  Select,
13
18
  Size,
14
19
  TextField,
15
- Theme,
16
20
  Toolbar,
17
21
  } from '@lumx/react';
18
22
  import { DIALOG_TRANSITION_DURATION } from '@lumx/core/js/constants';
19
- import { select } from '@storybook/addon-knobs';
20
- import React, { RefObject, useCallback, useRef, useState } from 'react';
21
23
  import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
22
- import { Dialog, DialogSizes } from './Dialog';
23
- import { loremIpsum } from '../../stories/knobs/lorem';
24
- import { chromaticForceScreenSize } from '../../stories/chromaticForceScreenSize';
24
+ import { getSelectArgType } from '@lumx/react/stories/controls/selectArgType';
25
+ import { withChromaticForceScreenSize } from '@lumx/react/stories/decorators/withChromaticForceScreenSize';
26
+
27
+ import { Dialog } from './Dialog';
28
+ import { loremIpsum } from '../../stories/utils/lorem';
29
+
30
+ const dialogSizes = [Size.tiny, Size.regular, Size.big, Size.huge];
25
31
 
26
32
  export default {
27
33
  title: 'LumX components/dialog/Dialog',
34
+ component: Dialog,
28
35
  parameters: {
29
36
  // Delay Chromatic snapshot to wait for dialog to open.
30
37
  chromatic: {
@@ -32,261 +39,148 @@ export default {
32
39
  delay: DIALOG_TRANSITION_DURATION,
33
40
  },
34
41
  },
35
- // Force minimum chromatic screen size to make sure the dialog appears in view.
36
- decorators: [chromaticForceScreenSize],
42
+ decorators: [
43
+ // Force minimum chromatic screen size to make sure the dialog appears in view.
44
+ withChromaticForceScreenSize(),
45
+ ],
46
+ args: Dialog.defaultProps,
47
+ argTypes: {
48
+ size: getSelectArgType(dialogSizes),
49
+ onVisibilityChange: { action: true },
50
+ children: { control: false },
51
+ },
52
+ render(props: any) {
53
+ const buttonRef = useRef<HTMLButtonElement>(null);
54
+ const [isOpen, close, open] = useBooleanState(true);
55
+ return (
56
+ <>
57
+ <Button ref={buttonRef} onClick={open}>
58
+ Open dialog
59
+ </Button>
60
+ <Dialog {...props} isOpen={isOpen} onClose={close} parentElement={buttonRef} />
61
+ </>
62
+ );
63
+ },
37
64
  };
38
65
 
39
- const header = <header className="lumx-spacing-padding lumx-typography-title">Dialog header</header>;
40
- const content = <div className="lumx-spacing-padding">{loremIpsum('short')}</div>;
41
- const longContent = <div className="lumx-spacing-padding">{loremIpsum('long')}</div>;
42
- const footer = <footer className="lumx-spacing-padding">Dialog footer</footer>;
43
-
44
- function useOpenButton(theme: Theme, defaultState = true) {
45
- const buttonRef = useRef() as RefObject<HTMLButtonElement>;
46
- const [isOpen, setOpen] = useState(defaultState);
47
- const openDialog = useCallback(() => setOpen(true), []);
48
- const closeDialog = useCallback(() => setOpen(false), []);
49
-
50
- return {
51
- button: (
52
- <Button ref={buttonRef} onClick={openDialog} theme={theme}>
53
- Open dialog
54
- </Button>
55
- ),
56
- buttonRef,
57
- openDialog,
58
- closeDialog,
59
- isOpen,
60
- };
61
- }
62
-
63
- export const SimpleDialog = ({ theme }: any) => {
64
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
65
- return (
66
- <>
67
- {button}
68
- <Dialog isOpen={isOpen} onClose={closeDialog} parentElement={buttonRef}>
69
- {content}
70
- </Dialog>
71
- </>
72
- );
66
+ /**
67
+ * Default dialog
68
+ */
69
+ export const Default = {
70
+ args: { children: loremIpsum('short') },
73
71
  };
74
72
 
75
- export const PreventDialogAutoClose = ({ theme }: any) => {
76
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
77
- return (
78
- <>
79
- {button}
80
- <Dialog preventAutoClose isOpen={isOpen} onClose={closeDialog} parentElement={buttonRef}>
81
- {content}
82
- <footer>
83
- <Toolbar
84
- after={
85
- <Button onClick={closeDialog} emphasis={Emphasis.low}>
86
- Close
87
- </Button>
88
- }
89
- />
90
- </footer>
91
- </Dialog>
92
- </>
93
- );
73
+ /**
74
+ * Loading state
75
+ */
76
+ export const Loading = {
77
+ args: { ...Default.args, isLoading: true },
94
78
  };
95
79
 
96
- export const DialogWithAlertDialog = ({ theme }: any) => {
97
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
98
- const { openDialog: openAlertDialog, closeDialog: closeAlertDialog, isOpen: isAlertDialogOpen } = useOpenButton(
99
- theme,
100
- false,
101
- );
102
-
103
- const handleSubmitDialog = () => {
104
- closeDialog();
105
- openAlertDialog();
106
- };
107
-
108
- return (
109
- <>
110
- {button}
111
- <Dialog isOpen={isOpen} onClose={closeDialog} parentElement={buttonRef}>
112
- {content}
113
- <footer>
114
- <Toolbar
115
- after={
116
- <Button onClick={handleSubmitDialog} emphasis={Emphasis.low}>
117
- Close
118
- </Button>
119
- }
120
- />
121
- </footer>
122
- </Dialog>
123
- <AlertDialog
124
- isOpen={isAlertDialogOpen}
125
- onClose={closeDialog}
126
- parentElement={buttonRef}
127
- title="Default (info)"
128
- confirmProps={{ onClick: closeAlertDialog, label: 'Confirm' }}
129
- >
130
- Consequat deserunt officia aute laborum tempor anim sint est.
131
- </AlertDialog>
132
- </>
133
- );
134
- };
135
-
136
- export const Sizes = ({ theme }: any) => {
137
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
138
- const sizes: DialogSizes[] = [Size.tiny, Size.regular, Size.big, Size.huge];
139
- return (
140
- <>
141
- {button}
142
- Use the knobs to change the dialog size!
143
- <Dialog
144
- isOpen={isOpen}
145
- onClose={closeDialog}
146
- size={select('Dialog size', sizes, Size.tiny)}
147
- parentElement={buttonRef}
148
- >
149
- {longContent}
150
- </Dialog>
151
- </>
152
- );
80
+ /**
81
+ * Basic header/footer props
82
+ */
83
+ export const WithHeaderFooter = {
84
+ args: { ...Default.args, header: 'Dialog header', footer: 'Dialog footer' },
153
85
  };
154
86
 
155
- export const LoadingDialog = ({ theme }: any) => {
156
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
157
- return (
158
- <>
159
- {button}
160
- <Dialog isOpen={isOpen} onClose={closeDialog} isLoading parentElement={buttonRef}>
161
- {content}
162
- </Dialog>
163
- </>
164
- );
87
+ /**
88
+ * More complex header/footer in children
89
+ */
90
+ export const WithHeaderFooterChildren = {
91
+ args: {
92
+ children: [
93
+ <header>
94
+ <Toolbar
95
+ label={<Heading typography="title">Dialog heading</Heading>}
96
+ after={<IconButton label="Close" emphasis="low" icon={mdiClose} />}
97
+ />
98
+ </header>,
99
+ <div className="lumx-spacing-padding-huge">{loremIpsum('short')}</div>,
100
+ <footer>
101
+ <Toolbar after={<Button>Close</Button>} />
102
+ </footer>,
103
+ ],
104
+ },
165
105
  };
166
106
 
167
- export const DialogWithHeaderFooterProps = ({ theme }: any) => {
168
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
169
- return (
170
- <>
171
- {button}
172
- <Dialog
173
- isOpen={isOpen}
174
- onClose={closeDialog}
175
- header="Header prop"
176
- footer="Footer prop"
177
- parentElement={buttonRef}
178
- >
179
- {content}
180
- </Dialog>
181
- </>
182
- );
107
+ /**
108
+ * Forcing header/footer dividers
109
+ */
110
+ export const ForceDivider = {
111
+ args: { ...WithHeaderFooter.args, forceFooterDivider: true, forceHeaderDivider: true },
183
112
  };
184
113
 
185
- export const DialogWithHeaderFooterChildren = ({ theme }: any) => {
186
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
187
- return (
188
- <>
189
- {button}
190
- <Dialog isOpen={isOpen} onClose={closeDialog} parentElement={buttonRef}>
191
- {header}
192
- {content}
193
- {footer}
194
- </Dialog>
195
- </>
196
- );
114
+ /**
115
+ * Long scrollable content
116
+ */
117
+ export const LongContent = {
118
+ args: { ...WithHeaderFooter.args, children: loremIpsum('long') },
197
119
  };
198
120
 
199
- export const DialogWithHeaderFooterPropsAndChildren = ({ theme }: any) => {
200
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
201
- return (
202
- <>
203
- {button}
204
- <Dialog
205
- isOpen={isOpen}
206
- onClose={closeDialog}
207
- header=" Header prop"
208
- footer=" Footer prop"
209
- parentElement={buttonRef}
210
- >
211
- {header}
212
- {content}
213
- {footer}
214
- </Dialog>
215
- </>
216
- );
121
+ /**
122
+ * Prevent auto close (click outside & close on escape)
123
+ */
124
+ export const PreventAutoClose = {
125
+ args: { ...Default.args, preventAutoClose: true },
217
126
  };
218
127
 
219
- export const DialogWithHeaderFooterAndDivider = ({ theme }: any) => {
220
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
221
- return (
222
- <>
223
- {button}
224
- <Dialog
225
- isOpen={isOpen}
226
- onClose={closeDialog}
227
- forceFooterDivider
228
- forceHeaderDivider
229
- parentElement={buttonRef}
230
- >
231
- {header}
232
- {content}
233
- {footer}
234
- </Dialog>
235
- </>
236
- );
128
+ /**
129
+ * Prevent close on escape
130
+ */
131
+ export const PreventCloseOnEscape = {
132
+ args: { ...Default.args, preventCloseOnEscape: true },
237
133
  };
238
134
 
239
- export const ScrollableDialog = ({ theme }: any) => {
240
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
241
- return (
242
- <>
243
- {button}
244
- <Dialog isOpen={isOpen} onClose={closeDialog} size={Size.regular} parentElement={buttonRef}>
245
- {longContent}
246
- </Dialog>
247
- </>
248
- );
135
+ /**
136
+ * Prevent close on click outside
137
+ */
138
+ export const PreventCloseOnClick = {
139
+ args: { ...Default.args, preventCloseOnClick: true },
249
140
  };
250
141
 
251
- export const WithAnimationCallbacks = ({ theme }: any) => {
252
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
253
- const handleVisibiltyCallback = (isVisible: boolean) => {
254
- alert(isVisible ? 'OPEN' : 'CLOSE');
255
- };
256
-
257
- return (
258
- <>
259
- {button}
260
- <Dialog
261
- isOpen={isOpen}
262
- onClose={closeDialog}
263
- size={Size.regular}
264
- parentElement={buttonRef}
265
- onVisibilityChange={handleVisibiltyCallback}
266
- >
267
- {content}
268
- </Dialog>
269
- </>
270
- );
271
- };
142
+ /**
143
+ * Dialog needing a confirmation before close using an AlertDialog
144
+ */
145
+ export const WithConfirmClose = () => {
146
+ const buttonRef = useRef<HTMLButtonElement>(null);
147
+ const closeButtonRef = useRef<HTMLButtonElement>(null);
148
+ const [isOpen, close, open] = useBooleanState(true);
149
+ const [isAlertDialogOpen, closeAlertDialog, openAlertDialog] = useBooleanState(false);
272
150
 
273
- export const ScrollableDialogWithHeaderAndFooter = ({ theme }: any) => {
274
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
275
151
  return (
276
152
  <>
277
- {button}
278
- <Dialog isOpen={isOpen} onClose={closeDialog} parentElement={buttonRef}>
279
- {header}
280
- {longContent}
281
- {footer}
153
+ <Button ref={buttonRef} onClick={open}>
154
+ Open dialog
155
+ </Button>
156
+ <Dialog isOpen={isOpen} onClose={openAlertDialog} parentElement={buttonRef}>
157
+ <FlexBox orientation="vertical" vAlign="center" className="lumx-spacing-padding-huge">
158
+ {loremIpsum('tiny')}
159
+ <Button ref={closeButtonRef} onClick={openAlertDialog}>
160
+ Close
161
+ </Button>
162
+ </FlexBox>
282
163
  </Dialog>
164
+ <AlertDialog
165
+ isOpen={isAlertDialogOpen}
166
+ onClose={closeAlertDialog}
167
+ parentElement={closeButtonRef}
168
+ title="Confirm close"
169
+ confirmProps={{ onClick: over([close, closeAlertDialog]), label: 'Confirm' }}
170
+ cancelProps={{ onClick: closeAlertDialog, label: 'Cancel' }}
171
+ >
172
+ {loremIpsum('tiny')}
173
+ </AlertDialog>
283
174
  </>
284
175
  );
285
176
  };
286
177
 
287
- /** Test dialog focus trap (focus is contained inside the dialog) with all kinds of focusable and non-focusable items */
288
- export const DialogFocusTrap = ({ theme }: any) => {
289
- const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
178
+ /**
179
+ * Test dialog focus trap (focus is contained inside the dialog) with all kinds of focusable and non-focusable items
180
+ */
181
+ export const FocusTrap = () => {
182
+ const buttonRef = useRef<HTMLButtonElement>(null);
183
+ const [isOpen, close, open] = useBooleanState(true);
290
184
  const [textValue, setTextValue] = useState('value');
291
185
  const [checkboxValue, setCheckboxValue] = useState(false);
292
186
  const inputRef = useRef(null);
@@ -308,14 +202,14 @@ export const DialogFocusTrap = ({ theme }: any) => {
308
202
 
309
203
  return (
310
204
  <>
311
- {button}
312
- <Dialog isOpen={isOpen} onClose={closeDialog} parentElement={buttonRef} focusElement={inputRef}>
205
+ <Button ref={buttonRef} onClick={open}>
206
+ Open dialog
207
+ </Button>
208
+ <Dialog isOpen={isOpen} onClose={close} parentElement={buttonRef} focusElement={inputRef}>
313
209
  <header>
314
210
  <Toolbar
315
211
  label={<span className="lumx-typography-title">Dialog header</span>}
316
- after={
317
- <IconButton label="Close" icon={mdiClose} onClick={closeDialog} emphasis={Emphasis.low} />
318
- }
212
+ after={<IconButton label="Close" icon={mdiClose} onClick={close} emphasis={Emphasis.low} />}
319
213
  />
320
214
  </header>
321
215
  <div className="lumx-spacing-padding-horizontal-huge lumx-spacing-padding-bottom-huge">
@@ -370,7 +264,6 @@ export const DialogFocusTrap = ({ theme }: any) => {
370
264
  locale="fr"
371
265
  label="Start date"
372
266
  placeholder="Pick a date"
373
- theme={theme}
374
267
  onChange={setDate}
375
268
  value={date}
376
269
  nextButtonProps={{ label: 'Next month' }}
@@ -380,7 +273,6 @@ export const DialogFocusTrap = ({ theme }: any) => {
380
273
  locale="fr"
381
274
  label="Start date"
382
275
  placeholder="Pick a date"
383
- theme={theme}
384
276
  onChange={setDate2}
385
277
  value={date2}
386
278
  nextButtonProps={{ label: 'Next month' }}
@@ -5,8 +5,6 @@ import { mount, shallow } from 'enzyme';
5
5
  import 'jest-enzyme';
6
6
  import React, { ReactElement } from 'react';
7
7
 
8
- import * as stories from './Dialog.stories';
9
-
10
8
  const CLASSNAME = Dialog.className as string;
11
9
 
12
10
  // Mock out the useIntersectionObserver hook since it can't work with Jest/Enzyme.
@@ -29,26 +27,6 @@ const setup = (propsOverride: SetupProps = {}, shallowRendering = true) => {
29
27
  };
30
28
 
31
29
  describe(`<${Dialog.displayName}>`, () => {
32
- // 1. Test render via snapshot.
33
- describe('Snapshots and structure', () => {
34
- // Do snapshot render test on every stories.
35
- for (const [storyName, Story] of Object.entries(stories)) {
36
- if (typeof Story !== 'function') {
37
- continue;
38
- }
39
-
40
- it(`should render story ${storyName}`, () => {
41
- const wrapper = shallow(<Story />);
42
- expect(wrapper).toMatchSnapshot();
43
- });
44
- }
45
- });
46
-
47
- // 2. Test defaultProps value and important props custom values.
48
- describe('Props', () => {
49
- // Nothing to do here.
50
- });
51
-
52
30
  // 3. Test events.
53
31
  describe('Events', () => {
54
32
  const keyDown = (key: string) => new KeyboardEvent('keydown', { key } as any);
@@ -76,16 +54,14 @@ describe(`<${Dialog.displayName}>`, () => {
76
54
  document.body.dispatchEvent(keyDown('Escape'));
77
55
  expect(onClose).not.toHaveBeenCalled();
78
56
  });
79
- });
80
57
 
81
- // 4. Test conditions (i.e. things that display or not in the UI based on props).
82
- describe('Conditions', () => {
83
- // Nothing to do here.
84
- });
58
+ it('should not trigger `onClose` when pressing `escape` key with `preventCloseOnEscape` set to `true`', () => {
59
+ const onClose = jest.fn();
60
+ setup({ isOpen: true, onClose, preventCloseOnEscape: true }, false);
85
61
 
86
- // 5. Test state.
87
- describe('State', () => {
88
- // Nothing to do here.
62
+ document.body.dispatchEvent(keyDown('Escape'));
63
+ expect(onClose).not.toHaveBeenCalled();
64
+ });
89
65
  });
90
66
 
91
67
  // Common tests suite.
@@ -43,6 +43,10 @@ export interface DialogProps extends GenericProps {
43
43
  focusElement?: RefObject<HTMLElement>;
44
44
  /** Whether to keep the dialog open on clickaway or escape press. */
45
45
  preventAutoClose?: boolean;
46
+ /** Whether to keep the dialog open on escape press. */
47
+ preventCloseOnEscape?: boolean;
48
+ /** Whether to keep the dialog open on clickaway. */
49
+ preventCloseOnClick?: boolean;
46
50
  /** Size variant. */
47
51
  size?: DialogSizes;
48
52
  /** Z-axis position. */
@@ -53,6 +57,8 @@ export interface DialogProps extends GenericProps {
53
57
  onClose?(): void;
54
58
  /** Callback called when the open animation starts and the close animation finishes. */
55
59
  onVisibilityChange?(isVisible: boolean): void;
60
+ /** whether to disable the scroll on the body or not */
61
+ disableBodyScroll?: boolean;
56
62
  }
57
63
 
58
64
  export type DialogSizes = Extract<Size, 'tiny' | 'regular' | 'big' | 'huge'>;
@@ -75,6 +81,7 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
75
81
  */
76
82
  const DEFAULT_PROPS: Partial<DialogProps> = {
77
83
  size: Size.big,
84
+ disableBodyScroll: true,
78
85
  };
79
86
 
80
87
  /**
@@ -108,6 +115,9 @@ export const Dialog: Comp<DialogProps, HTMLDivElement> = forwardRef((props, ref)
108
115
  zIndex,
109
116
  dialogProps,
110
117
  onVisibilityChange,
118
+ disableBodyScroll,
119
+ preventCloseOnClick,
120
+ preventCloseOnEscape,
111
121
  ...forwardedProps
112
122
  } = props;
113
123
 
@@ -125,8 +135,10 @@ export const Dialog: Comp<DialogProps, HTMLDivElement> = forwardRef((props, ref)
125
135
  }
126
136
  }, [isOpen, parentElement]);
127
137
 
138
+ const shouldPreventCloseOnEscape = preventAutoClose || preventCloseOnEscape;
139
+
128
140
  // eslint-disable-next-line react-hooks/rules-of-hooks
129
- useCallbackOnEscape(onClose, isOpen && !preventAutoClose);
141
+ useCallbackOnEscape(onClose, isOpen && !shouldPreventCloseOnEscape);
130
142
 
131
143
  // eslint-disable-next-line react-hooks/rules-of-hooks
132
144
  const wrapperRef = useRef<HTMLDivElement>(null);
@@ -141,7 +153,7 @@ export const Dialog: Comp<DialogProps, HTMLDivElement> = forwardRef((props, ref)
141
153
  useFocusTrap(isOpen && wrapperRef.current, focusElement?.current);
142
154
 
143
155
  // eslint-disable-next-line react-hooks/rules-of-hooks
144
- useDisableBodyScroll(isOpen && localContentRef.current);
156
+ useDisableBodyScroll(disableBodyScroll && isOpen && localContentRef.current);
145
157
 
146
158
  // eslint-disable-next-line react-hooks/rules-of-hooks
147
159
  const [sentinelTop, setSentinelTop] = useState<Element | null>(null);
@@ -175,6 +187,8 @@ export const Dialog: Comp<DialogProps, HTMLDivElement> = forwardRef((props, ref)
175
187
  // eslint-disable-next-line react-hooks/rules-of-hooks
176
188
  const isVisible = useTransitionVisibility(rootRef, Boolean(isOpen), onVisibilityChange);
177
189
 
190
+ const shouldPreventCloseOnClickAway = preventAutoClose || preventCloseOnClick;
191
+
178
192
  return isOpen || isVisible
179
193
  ? createPortal(
180
194
  <div
@@ -196,7 +210,7 @@ export const Dialog: Comp<DialogProps, HTMLDivElement> = forwardRef((props, ref)
196
210
 
197
211
  <section className={`${CLASSNAME}__container`} role="dialog" aria-modal="true" {...dialogProps}>
198
212
  <ClickAwayProvider
199
- callback={!preventAutoClose && onClose}
213
+ callback={!shouldPreventCloseOnClickAway && onClose}
200
214
  childrenRefs={clickAwayRefs}
201
215
  parentRef={rootRef}
202
216
  >