@react-ui-org/react-ui 0.58.0 → 0.59.1

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 (92) hide show
  1. package/README.md +2 -11
  2. package/dist/react-ui.css +17 -17
  3. package/dist/react-ui.development.css +1230 -1053
  4. package/dist/react-ui.development.js +126 -66
  5. package/dist/react-ui.js +1 -1
  6. package/package.json +5 -5
  7. package/src/components/Alert/Alert.jsx +4 -4
  8. package/src/components/Alert/README.md +1 -27
  9. package/src/components/Alert/_settings.scss +1 -2
  10. package/src/components/Badge/Badge.jsx +2 -2
  11. package/src/components/Button/Button.jsx +2 -2
  12. package/src/components/ButtonGroup/ButtonGroup.jsx +2 -2
  13. package/src/components/Card/Card.jsx +6 -6
  14. package/src/components/Card/Card.module.scss +2 -2
  15. package/src/components/Card/CardBody.jsx +1 -1
  16. package/src/components/Card/CardFooter.jsx +1 -1
  17. package/src/components/Card/README.md +12 -30
  18. package/src/components/Card/_settings.scss +1 -2
  19. package/src/components/Card/_theme.scss +2 -0
  20. package/src/components/CheckboxField/CheckboxField.jsx +2 -2
  21. package/src/components/FileInputField/FileInputField.jsx +147 -21
  22. package/src/components/FileInputField/FileInputField.module.scss +87 -1
  23. package/src/components/FileInputField/README.md +83 -2
  24. package/src/components/FileInputField/_settings.scss +15 -0
  25. package/src/components/FormLayout/FormLayout.jsx +2 -2
  26. package/src/components/FormLayout/FormLayoutCustomField.jsx +2 -2
  27. package/src/components/FormLayout/README.md +8 -6
  28. package/src/components/Grid/Grid.jsx +1 -1
  29. package/src/components/Grid/Grid.module.scss +2 -2
  30. package/src/components/Grid/GridSpan.jsx +1 -1
  31. package/src/components/InputGroup/InputGroup.jsx +2 -2
  32. package/src/components/InputGroup/InputGroup.module.scss +3 -3
  33. package/src/components/InputGroup/README.md +1 -1
  34. package/src/components/Modal/Modal.jsx +117 -45
  35. package/src/components/Modal/Modal.module.scss +34 -18
  36. package/src/components/Modal/ModalBody.jsx +2 -2
  37. package/src/components/Modal/ModalBody.module.scss +18 -0
  38. package/src/components/Modal/ModalCloseButton.jsx +1 -1
  39. package/src/components/Modal/ModalContent.jsx +1 -1
  40. package/src/components/Modal/ModalFooter.jsx +2 -2
  41. package/src/components/Modal/ModalFooter.module.scss +6 -2
  42. package/src/components/Modal/ModalHeader.jsx +2 -2
  43. package/src/components/Modal/ModalHeader.module.scss +8 -1
  44. package/src/components/Modal/ModalTitle.jsx +1 -1
  45. package/src/components/Modal/README.md +391 -171
  46. package/src/components/Modal/_animations.scss +9 -0
  47. package/src/components/Modal/_helpers/dialogOnCancelHandler.js +28 -0
  48. package/src/components/Modal/_helpers/dialogOnClickHandler.js +46 -0
  49. package/src/components/Modal/_helpers/dialogOnCloseHandler.js +28 -0
  50. package/src/components/Modal/_helpers/dialogOnKeyDownHandler.js +62 -0
  51. package/src/components/Modal/_helpers/getPositionClassName.js +1 -1
  52. package/src/components/Modal/_hooks/useModalFocus.js +24 -91
  53. package/src/components/Modal/_settings.scss +4 -3
  54. package/src/components/Modal/_theme.scss +1 -0
  55. package/src/components/Paper/Paper.jsx +2 -2
  56. package/src/components/Popover/Popover.jsx +2 -2
  57. package/src/components/Popover/PopoverWrapper.jsx +1 -1
  58. package/src/components/Radio/Radio.jsx +2 -2
  59. package/src/components/ScrollView/ScrollView.jsx +2 -2
  60. package/src/components/SelectField/SelectField.jsx +2 -2
  61. package/src/components/Table/Table.jsx +1 -1
  62. package/src/components/Tabs/Tabs.jsx +1 -1
  63. package/src/components/Tabs/TabsItem.jsx +2 -2
  64. package/src/components/Text/Text.jsx +2 -2
  65. package/src/components/TextArea/TextArea.jsx +2 -2
  66. package/src/components/TextField/TextField.jsx +2 -2
  67. package/src/components/TextLink/TextLink.jsx +1 -1
  68. package/src/components/Toggle/Toggle.jsx +2 -2
  69. package/src/components/Toolbar/Toolbar.jsx +2 -2
  70. package/src/components/Toolbar/ToolbarGroup.jsx +2 -2
  71. package/src/components/Toolbar/ToolbarItem.jsx +2 -2
  72. package/src/helpers/classNames/README.md +65 -0
  73. package/src/helpers/classNames/classNames.js +11 -0
  74. package/src/helpers/classNames/index.js +1 -0
  75. package/src/helpers/transferProps/README.md +46 -0
  76. package/src/helpers/transferProps/index.js +1 -0
  77. package/src/index.js +3 -3
  78. package/src/styles/elements/_links.scss +2 -14
  79. package/src/styles/generic/_focus.scss +1 -1
  80. package/src/styles/theme/_form-fields.scss +5 -5
  81. package/src/styles/tools/_accessibility.scss +3 -5
  82. package/src/styles/tools/_collections.scss +3 -20
  83. package/src/styles/tools/_links.scss +17 -0
  84. package/src/styles/tools/form-fields/_box-field-elements.scss +21 -9
  85. package/src/styles/tools/form-fields/_box-field-layout.scss +2 -2
  86. package/src/styles/tools/form-fields/_box-field-sizes.scss +6 -10
  87. package/src/styles/tools/form-fields/_variants.scss +10 -10
  88. package/src/theme.scss +53 -3
  89. package/src/translations/en.js +5 -0
  90. package/src/styles/settings/_z-indexes.scss +0 -2
  91. package/src/utils/classNames.js +0 -8
  92. /package/src/{utils → helpers/transferProps}/transferProps.js +0 -0
@@ -1,8 +1,16 @@
1
+ // 1. The drop zone is constructed as a button to support keyboard operation.
2
+ // 2. Prevent pointer events on all children of the root element to not to trigger drag events on children.
3
+
1
4
  @use "../../styles/tools/form-fields/box-field-elements";
2
5
  @use "../../styles/tools/form-fields/box-field-layout";
6
+ @use "../../styles/tools/form-fields/box-field-sizes";
3
7
  @use "../../styles/tools/form-fields/foundation";
4
8
  @use "../../styles/tools/form-fields/variants";
5
9
  @use "../../styles/tools/accessibility";
10
+ @use "../../styles/tools/links";
11
+ @use "../../styles/tools/transition";
12
+ @use "../../styles/tools/reset";
13
+ @use "settings";
6
14
 
7
15
  @layer components.file-input-field {
8
16
  // Foundation
@@ -18,6 +26,54 @@
18
26
  @include box-field-elements.input-container();
19
27
  }
20
28
 
29
+ .input {
30
+ @include accessibility.hide-text();
31
+ }
32
+
33
+ .dropZone {
34
+ --rui-local-color: #{settings.$drop-zone-color};
35
+ --rui-local-border-color: #{settings.$drop-zone-border-color};
36
+ --rui-local-background: #{settings.$drop-zone-background-color};
37
+
38
+ @include reset.button(); // 1.
39
+ @include box-field-elements.base();
40
+
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: start;
44
+ font-weight: settings.$drop-zone-font-weight;
45
+ font-size: var(--rui-local-font-size);
46
+ line-height: settings.$drop-zone-line-height;
47
+ font-family: settings.$drop-zone-font-family;
48
+ border-style: dashed;
49
+ }
50
+
51
+ .isRootDragging .dropZone {
52
+ --rui-local-border-color: #{settings.$drop-zone-dragging-border-color};
53
+ }
54
+
55
+ .isRootDisabled .dropZone {
56
+ cursor: settings.$drop-zone-disabled-cursor;
57
+ }
58
+
59
+ .root:not(.isRootDisabled, .isRootDragging) .dropZone:hover {
60
+ --rui-local-border-color: #{settings.$drop-zone-hover-border-color};
61
+ }
62
+
63
+ .root:not(.isRootDisabled, .isRootDragging) .dropZone:active {
64
+ --rui-local-border-color: #{settings.$drop-zone-active-border-color};
65
+ }
66
+
67
+ .dropZoneLink {
68
+ @include links.base();
69
+
70
+ &::before {
71
+ content: "";
72
+ position: absolute;
73
+ inset: 0;
74
+ }
75
+ }
76
+
21
77
  .helpText,
22
78
  .validationText {
23
79
  @include foundation.help-text();
@@ -28,6 +84,18 @@
28
84
  }
29
85
 
30
86
  // States
87
+ .isRootDisabled {
88
+ --rui-local-color: #{settings.$drop-zone-disabled-color};
89
+ --rui-local-border-color: #{settings.$drop-zone-disabled-border-color};
90
+ --rui-local-background: #{settings.$drop-zone-disabled-background-color};
91
+
92
+ @include variants.disabled-state();
93
+ }
94
+
95
+ .isRootDisabled .dropZoneLink {
96
+ cursor: inherit;
97
+ }
98
+
31
99
  .isRootStateInvalid {
32
100
  @include variants.validation(invalid);
33
101
  }
@@ -56,10 +124,28 @@
56
124
  }
57
125
 
58
126
  .isRootFullWidth {
59
- @include box-field-layout.full-width();
127
+ @include box-field-layout.full-width($input-element-selector: ".dropZone");
60
128
  }
61
129
 
62
130
  .isRootInFormLayout {
63
131
  @include box-field-layout.in-form-layout();
64
132
  }
133
+
134
+ // Sizes
135
+ .isRootSizeSmall {
136
+ @include box-field-sizes.size(small);
137
+ }
138
+
139
+ .isRootSizeMedium {
140
+ @include box-field-sizes.size(medium);
141
+ }
142
+
143
+ .isRootSizeLarge {
144
+ @include box-field-sizes.size(large);
145
+ }
146
+
147
+ // Groups
148
+ .isRootGrouped {
149
+ @include box-field-elements.in-group-layout($input-element-selector: ".dropZone");
150
+ }
65
151
  }
@@ -13,7 +13,7 @@ import { FileInputField } from '@react-ui-org/react-ui';
13
13
  And use it:
14
14
 
15
15
  ```docoff-react-preview
16
- <FileInputField label="Attachment" />
16
+ <FileInputField id="my-file" label="Attachment" onFilesChanged={() => {}} />
17
17
  ```
18
18
 
19
19
  See [API](#api) for all available options.
@@ -48,12 +48,37 @@ layout perspective, FileInputFields work just like any other form fields.
48
48
 
49
49
  ## Sizes
50
50
 
51
+ Aside from the default (medium) size, two additional sizes are available: small
52
+ and large.
53
+
54
+ ```docoff-react-preview
55
+ <FileInputField
56
+ id="my-file-small"
57
+ label="Attachment"
58
+ onFilesChanged={() => {}}
59
+ size="small"
60
+ />
61
+ <FileInputField
62
+ id="my-file-medium"
63
+ label="Attachment"
64
+ onFilesChanged={() => {}}
65
+ />
66
+ <FileInputField
67
+ id="my-file-large"
68
+ label="Attachment"
69
+ onFilesChanged={() => {}}
70
+ size="large"
71
+ />
72
+ ```
73
+
51
74
  Full-width fields span the full width of a parent:
52
75
 
53
76
  ```docoff-react-preview
54
77
  <FileInputField
55
78
  fullWidth
79
+ id="my-file"
56
80
  label="First name"
81
+ onFilesChanged={() => {}}
57
82
  />
58
83
  ```
59
84
 
@@ -68,8 +93,10 @@ dangerous to hide labels from users in most cases. Keep in mind you should
68
93
 
69
94
  ```docoff-react-preview
70
95
  <FileInputField
96
+ id="my-file"
71
97
  isLabelVisible={false}
72
98
  label="Attachment"
99
+ onFilesChanged={() => {}}
73
100
  />
74
101
  ```
75
102
 
@@ -81,14 +108,18 @@ supports this kind of layout as well.
81
108
 
82
109
  ```docoff-react-preview
83
110
  <FileInputField
111
+ id="my-file-horizontal"
84
112
  label="Attachment"
85
113
  layout="horizontal"
114
+ onFilesChanged={() => {}}
86
115
  />
87
116
  <FileInputField
88
117
  fullWidth
118
+ id="my-file-horizontal-full-width"
89
119
  isLabelVisible={false}
90
120
  label="Attachment"
91
121
  layout="horizontal"
122
+ onFilesChanged={() => {}}
92
123
  />
93
124
  ```
94
125
 
@@ -100,18 +131,24 @@ filled.
100
131
  ```docoff-react-preview
101
132
  <FileInputField
102
133
  helpText="Choose one or more files to upload."
134
+ id="my-file-help-text"
103
135
  label="Attachment"
136
+ onFilesChanged={() => {}}
104
137
  />
105
138
  <FileInputField
106
139
  helpText="Choose one or more files to upload."
140
+ id="my-file-help-text-horizontal"
107
141
  label="Attachment"
108
142
  layout="horizontal"
143
+ onFilesChanged={() => {}}
109
144
  />
110
145
  <FileInputField
111
146
  fullWidth
112
147
  helpText="Choose one or more files to upload."
148
+ id="my-file-help-text-horizontal-full-width"
113
149
  label="Attachment"
114
150
  layout="horizontal"
151
+ onFilesChanged={() => {}}
115
152
  />
116
153
  ```
117
154
 
@@ -126,17 +163,23 @@ have.
126
163
 
127
164
  ```docoff-react-preview
128
165
  <FileInputField
166
+ id="my-file-valid"
129
167
  label="Attachment"
168
+ onFilesChanged={() => {}}
130
169
  validationState="valid"
131
170
  validationText="Looks good!"
132
171
  />
133
172
  <FileInputField
173
+ id="my-file-invalid"
134
174
  label="Attachment"
175
+ onFilesChanged={() => {}}
135
176
  validationState="invalid"
136
177
  validationText="Your file is too big. Please select something smaller."
137
178
  />
138
179
  <FileInputField
180
+ id="my-file-warning"
139
181
  label="Attachment"
182
+ onFilesChanged={() => {}}
140
183
  validationState="warning"
141
184
  validationText={`
142
185
  You selected more than 10 files.
@@ -152,7 +195,44 @@ It's possible to disable the whole input.
152
195
  ```docoff-react-preview
153
196
  <FileInputField
154
197
  disabled
198
+ id="my-file"
155
199
  label="Attachment"
200
+ onFilesChanged={() => {}}
201
+ />
202
+ ```
203
+
204
+ ## Handling Files
205
+
206
+ Files selected by the user are handled by providing a custom function to the
207
+ `onFilesChanged` prop. The `onFilesChanged` function is then called on the
208
+ `change` event of the `input` element and on the `drop` event of the root
209
+ `div` element.
210
+
211
+ ```docoff-react-preview
212
+ <FileInputField
213
+ id="my-file"
214
+ label="Attachment"
215
+ onFilesChanged={(files, event) => {
216
+ // Do something with the files…
217
+ console.log('Files selected:', files);
218
+ }}
219
+ />
220
+ ```
221
+
222
+ ### Multiple Files
223
+
224
+ By default, users can select only one file. To allow selecting multiple files,
225
+ set the `multiple` prop to `true`.
226
+
227
+ ```docoff-react-preview
228
+ <FileInputField
229
+ id="my-files"
230
+ label="Attachment"
231
+ multiple
232
+ onFilesChanged={(files, event) => {
233
+ // Do something with the files…
234
+ console.log('Files selected:', files);
235
+ }}
156
236
  />
157
237
  ```
158
238
 
@@ -172,8 +252,9 @@ to improve its accessibility.
172
252
  Choose up to 10 files. Allowed extensions are .pdf, .jpg, .jpeg, or .png.
173
253
  Size limit is 10 MB.
174
254
  `}
255
+ id="my-file"
175
256
  label="Attachment"
176
- multiple
257
+ onFilesChanged={() => {}}
177
258
  />
178
259
  ```
179
260
 
@@ -0,0 +1,15 @@
1
+ @use "../../styles/theme/typography";
2
+
3
+ $drop-zone-color: var(--rui-color-text-primary);
4
+ $drop-zone-disabled-color: var(--rui-color-text-primary-disabled);
5
+ $drop-zone-border-color: var(--rui-color-border-primary);
6
+ $drop-zone-hover-border-color: var(--rui-color-border-primary-hover);
7
+ $drop-zone-active-border-color: var(--rui-color-border-primary-active);
8
+ $drop-zone-dragging-border-color: var(--rui-color-border-primary-active);
9
+ $drop-zone-disabled-border-color: var(--rui-color-border-primary);
10
+ $drop-zone-background-color: var(--rui-color-background-basic);
11
+ $drop-zone-disabled-background-color: var(--rui-color-background-disabled);
12
+ $drop-zone-disabled-cursor: var(--rui-cursor-not-allowed);
13
+ $drop-zone-font-weight: typography.$font-weight-base;
14
+ $drop-zone-line-height: typography.$line-height-base;
15
+ $drop-zone-font-family: typography.$font-family-base;
@@ -1,8 +1,8 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React, { useMemo } from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
- import { classNames } from '../../utils/classNames';
5
- import { transferProps } from '../../utils/transferProps';
4
+ import { classNames } from '../../helpers/classNames/classNames';
5
+ import { transferProps } from '../../helpers/transferProps';
6
6
  import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
7
7
  import { FormLayoutContext } from './FormLayoutContext';
8
8
  import styles from './FormLayout.module.scss';
@@ -1,8 +1,8 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React, { useContext } from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
- import { classNames } from '../../utils/classNames';
5
- import { transferProps } from '../../utils/transferProps';
4
+ import { classNames } from '../../helpers/classNames/classNames';
5
+ import { transferProps } from '../../helpers/transferProps';
6
6
  import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
7
7
  import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
8
8
  import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
@@ -300,7 +300,7 @@ This is a demo of all components supported by FormLayout.
300
300
 
301
301
  ```docoff-react-preview
302
302
  React.createElement(() => {
303
- const [fieldLayout, setFieldLayout] = React.useState('vertical');
303
+ const [fieldLayout, setFieldLayout] = React.useState('horizontal');
304
304
  const [fruit, setFruit] = React.useState('apple');
305
305
  const [isDeliveryAddress, setIsDeliveryAddress] = React.useState(true);
306
306
  const [receiveNewsletter, setReceiveNewsletter] = React.useState(true);
@@ -323,16 +323,16 @@ React.createElement(() => {
323
323
  <Toolbar>
324
324
  <ToolbarItem>
325
325
  <ButtonGroup>
326
- <Button
327
- color={fieldLayout === 'vertical' ? 'selected' : 'secondary'}
328
- label="Vertical layout"
329
- onClick={() => setFieldLayout('vertical')}
330
- />
331
326
  <Button
332
327
  color={fieldLayout === 'horizontal' ? 'selected' : 'secondary'}
333
328
  label="Horizontal layout"
334
329
  onClick={() => setFieldLayout('horizontal')}
335
330
  />
331
+ <Button
332
+ color={fieldLayout === 'vertical' ? 'selected' : 'secondary'}
333
+ label="Vertical layout"
334
+ onClick={() => setFieldLayout('vertical')}
335
+ />
336
336
  </ButtonGroup>
337
337
  </ToolbarItem>
338
338
  </Toolbar>
@@ -388,7 +388,9 @@ React.createElement(() => {
388
388
  rows={3}
389
389
  />
390
390
  <FileInputField
391
+ id="my-file"
391
392
  label="Attachment"
393
+ onFilesChanged={() => {}}
392
394
  />
393
395
  <Toggle
394
396
  checked={receiveNewsletter}
@@ -1,7 +1,7 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
- import { transferProps } from '../../utils/transferProps';
4
+ import { transferProps } from '../../helpers/transferProps';
5
5
  import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
6
6
  import { generateResponsiveCustomProperties } from './_helpers/generateResponsiveCustomProperties';
7
7
  import styles from './Grid.module.scss';
@@ -20,8 +20,8 @@
20
20
  //
21
21
  // 2. Apply custom property value that is defined within current breakpoint, see 1.
22
22
  //
23
- // 3. Intentionally use longhand properties because the custom property fallback mechanism evaluates `initial` values as
24
- // empty. That makes the other value of the shorthand property unexpectedly used for both axes.
23
+ // 3. Intentionally use longhand properties because the custom property fallback mechanism evaluates `initial` values
24
+ // as empty. That makes the other value of the shorthand property unexpectedly used for both axes.
25
25
 
26
26
  @use "sass:map";
27
27
  @use "../../styles/tools/spacing";
@@ -1,7 +1,7 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
- import { transferProps } from '../../utils/transferProps';
4
+ import { transferProps } from '../../helpers/transferProps';
5
5
  import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
6
6
  import { generateResponsiveCustomProperties } from './_helpers/generateResponsiveCustomProperties';
7
7
  import styles from './Grid.module.scss';
@@ -4,8 +4,8 @@ import React, {
4
4
  useMemo,
5
5
  } from 'react';
6
6
  import { withGlobalProps } from '../../providers/globalProps';
7
- import { classNames } from '../../utils/classNames';
8
- import { transferProps } from '../../utils/transferProps';
7
+ import { classNames } from '../../helpers/classNames/classNames';
8
+ import { transferProps } from '../../helpers/transferProps';
9
9
  import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
10
10
  import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
11
11
  import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
@@ -84,14 +84,14 @@
84
84
 
85
85
  // Sizes
86
86
  .isRootSizeSmall {
87
- @include box-field-sizes.size(small, $has-input: false);
87
+ @include box-field-sizes.size(small);
88
88
  }
89
89
 
90
90
  .isRootSizeMedium {
91
- @include box-field-sizes.size(medium, $has-input: false);
91
+ @include box-field-sizes.size(medium);
92
92
  }
93
93
 
94
94
  .isRootSizeLarge {
95
- @include box-field-sizes.size(large, $has-input: false);
95
+ @include box-field-sizes.size(large);
96
96
  }
97
97
  }
@@ -145,7 +145,7 @@ supports this kind of layout as well.
145
145
  label="Horizontal layout"
146
146
  layout="horizontal"
147
147
  >
148
- <TextField label="Label" />
148
+ <FileInputField id="my-file" label="Attachment" onFilesChanged={() => {}} />
149
149
  <Button label="Submit" />
150
150
  </InputGroup>
151
151
  ```
@@ -1,9 +1,19 @@
1
1
  import PropTypes from 'prop-types';
2
- import React, { useRef } from 'react';
2
+ import React, {
3
+ useCallback,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useRef,
7
+ } from 'react';
3
8
  import { createPortal } from 'react-dom';
9
+ import { classNames } from '../../helpers/classNames';
10
+ import { transferProps } from '../../helpers/transferProps';
4
11
  import { withGlobalProps } from '../../providers/globalProps';
5
- import { classNames } from '../../utils/classNames';
6
- import { transferProps } from '../../utils/transferProps';
12
+ import { getRootColorClassName } from '../_helpers/getRootColorClassName';
13
+ import { dialogOnCancelHandler } from './_helpers/dialogOnCancelHandler';
14
+ import { dialogOnClickHandler } from './_helpers/dialogOnClickHandler';
15
+ import { dialogOnCloseHandler } from './_helpers/dialogOnCloseHandler';
16
+ import { dialogOnKeyDownHandler } from './_helpers/dialogOnKeyDownHandler';
7
17
  import { getPositionClassName } from './_helpers/getPositionClassName';
8
18
  import { getSizeClassName } from './_helpers/getSizeClassName';
9
19
  import { useModalFocus } from './_hooks/useModalFocus';
@@ -12,44 +22,37 @@ import styles from './Modal.module.scss';
12
22
 
13
23
  const preRender = (
14
24
  children,
15
- childrenWrapperRef,
16
- closeButtonRef,
25
+ color,
26
+ dialogRef,
17
27
  position,
18
- restProps,
19
28
  size,
29
+ events,
30
+ restProps,
20
31
  ) => (
21
- <div
22
- className={styles.backdrop}
23
- onClick={(e) => {
24
- e.preventDefault();
25
- if (closeButtonRef?.current != null) {
26
- closeButtonRef.current.click();
27
- }
28
- }}
29
- role="presentation"
32
+ <dialog
33
+ {...transferProps(restProps)}
34
+ {...transferProps(events)}
35
+ className={classNames(
36
+ styles.root,
37
+ color && getRootColorClassName(color, styles),
38
+ getSizeClassName(size, styles),
39
+ getPositionClassName(position, styles),
40
+ )}
41
+ ref={dialogRef}
30
42
  >
31
- <div
32
- {...transferProps(restProps)}
33
- className={classNames(
34
- styles.root,
35
- getSizeClassName(size, styles),
36
- getPositionClassName(position, styles),
37
- )}
38
- onClick={(e) => {
39
- e.stopPropagation();
40
- }}
41
- ref={childrenWrapperRef}
42
- role="presentation"
43
- >
44
- {children}
45
- </div>
46
- </div>
43
+ {children}
44
+ </dialog>
47
45
  );
48
46
 
49
47
  export const Modal = ({
48
+ allowCloseOnBackdropClick,
49
+ allowCloseOnEscapeKey,
50
+ allowPrimaryActionOnEnterKey,
50
51
  autoFocus,
51
52
  children,
52
53
  closeButtonRef,
54
+ color,
55
+ dialogRef,
53
56
  portalId,
54
57
  position,
55
58
  preventScrollUnderneath,
@@ -57,45 +60,89 @@ export const Modal = ({
57
60
  size,
58
61
  ...restProps
59
62
  }) => {
60
- const childrenWrapperRef = useRef();
63
+ const internalDialogRef = useRef();
61
64
 
62
- useModalFocus(
63
- autoFocus,
64
- childrenWrapperRef,
65
- primaryButtonRef,
66
- closeButtonRef,
67
- );
65
+ useEffect(() => {
66
+ internalDialogRef.current.showModal();
67
+ }, []);
68
68
 
69
+ // We need to have a reference to the dialog element to be able to call its methods,
70
+ // but at the same time we want to expose this reference to the parent component for
71
+ // case someone wants to call dialog methods from outside the component.
72
+ useImperativeHandle(dialogRef, () => internalDialogRef.current);
73
+
74
+ useModalFocus(autoFocus, internalDialogRef, primaryButtonRef);
69
75
  useModalScrollPrevention(preventScrollUnderneath);
70
76
 
77
+ const onCancel = useCallback(
78
+ (e) => dialogOnCancelHandler(e, closeButtonRef, restProps.onCancel),
79
+ [closeButtonRef, restProps.onCancel],
80
+ );
81
+ const onClick = useCallback(
82
+ (e) => dialogOnClickHandler(e, closeButtonRef, internalDialogRef, allowCloseOnBackdropClick),
83
+ [allowCloseOnBackdropClick, closeButtonRef, internalDialogRef],
84
+ );
85
+ const onClose = useCallback(
86
+ (e) => dialogOnCloseHandler(e, closeButtonRef, restProps.onClose),
87
+ [closeButtonRef, restProps.onClose],
88
+ );
89
+ const onKeyDown = useCallback(
90
+ (e) => dialogOnKeyDownHandler(
91
+ e,
92
+ closeButtonRef,
93
+ primaryButtonRef,
94
+ allowCloseOnEscapeKey,
95
+ allowPrimaryActionOnEnterKey,
96
+ ),
97
+ [
98
+ allowCloseOnEscapeKey,
99
+ allowPrimaryActionOnEnterKey,
100
+ closeButtonRef,
101
+ primaryButtonRef,
102
+ ],
103
+ );
104
+ const events = {
105
+ onCancel,
106
+ onClick,
107
+ onClose,
108
+ onKeyDown,
109
+ };
110
+
71
111
  if (portalId === null) {
72
112
  return preRender(
73
113
  children,
74
- childrenWrapperRef,
75
- closeButtonRef,
114
+ color,
115
+ internalDialogRef,
76
116
  position,
77
- restProps,
78
117
  size,
118
+ events,
119
+ restProps,
79
120
  );
80
121
  }
81
122
 
82
123
  return createPortal(
83
124
  preRender(
84
125
  children,
85
- childrenWrapperRef,
86
- closeButtonRef,
126
+ color,
127
+ internalDialogRef,
87
128
  position,
88
- restProps,
89
129
  size,
130
+ events,
131
+ restProps,
90
132
  ),
91
133
  document.getElementById(portalId),
92
134
  );
93
135
  };
94
136
 
95
137
  Modal.defaultProps = {
138
+ allowCloseOnBackdropClick: true,
139
+ allowCloseOnEscapeKey: true,
140
+ allowPrimaryActionOnEnterKey: true,
96
141
  autoFocus: true,
97
142
  children: null,
98
143
  closeButtonRef: null,
144
+ color: undefined,
145
+ dialogRef: null,
99
146
  portalId: null,
100
147
  position: 'center',
101
148
  preventScrollUnderneath: window.document.body,
@@ -104,6 +151,18 @@ Modal.defaultProps = {
104
151
  };
105
152
 
106
153
  Modal.propTypes = {
154
+ /**
155
+ * If `true`, the `Modal` can be closed by clicking on the backdrop.
156
+ */
157
+ allowCloseOnBackdropClick: PropTypes.bool,
158
+ /**
159
+ * If `true`, the `Modal` can be closed by pressing the Escape key.
160
+ */
161
+ allowCloseOnEscapeKey: PropTypes.bool,
162
+ /**
163
+ * If `true`, the `Modal` can be submitted by pressing the Enter key.
164
+ */
165
+ allowPrimaryActionOnEnterKey: PropTypes.bool,
107
166
  /**
108
167
  * If `true`, focus the first input element in the `Modal`, or primary button (referenced by the `primaryButtonRef`
109
168
  * prop), or other focusable element when the `Modal` is opened. If there are none or `autoFocus` is set to `false`,
@@ -121,12 +180,25 @@ Modal.propTypes = {
121
180
  */
122
181
  children: PropTypes.node,
123
182
  /**
124
- * Reference to close button element. It is used to close modal when Escape key is pressed or the backdrop is clicked.
183
+ * Reference to close button element. It is used to close modal when Escape key is pressed
184
+ * or the backdrop is clicked.
125
185
  */
126
186
  closeButtonRef: PropTypes.shape({
127
187
  // eslint-disable-next-line react/forbid-prop-types
128
188
  current: PropTypes.any,
129
189
  }),
190
+ /**
191
+ * Color to clarify importance and meaning of the modal. Implements
192
+ * [Feedback color collection](/docs/foundation/collections#colors).
193
+ */
194
+ color: PropTypes.oneOf(['success', 'warning', 'danger', 'help', 'info', 'note']),
195
+ /**
196
+ * Reference to dialog element
197
+ */
198
+ dialogRef: PropTypes.shape({
199
+ // eslint-disable-next-line react/forbid-prop-types
200
+ current: PropTypes.any,
201
+ }),
130
202
  /**
131
203
  * If set, modal is rendered in the React Portal with that ID.
132
204
  */