@react-ui-org/react-ui 0.57.0 → 0.59.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 (118) hide show
  1. package/.nvmrc +1 -1
  2. package/README.md +2 -11
  3. package/dist/react-ui.css +19 -19
  4. package/dist/react-ui.development.css +1351 -963
  5. package/dist/react-ui.development.js +187 -87
  6. package/dist/react-ui.js +1 -1
  7. package/package.json +16 -5
  8. package/src/components/Alert/Alert.jsx +7 -9
  9. package/src/components/Alert/Alert.module.scss +3 -3
  10. package/src/components/Alert/README.md +18 -32
  11. package/src/components/Alert/_settings.scss +1 -2
  12. package/src/components/Badge/Badge.jsx +3 -3
  13. package/src/components/Button/Button.jsx +3 -3
  14. package/src/components/ButtonGroup/ButtonGroup.jsx +3 -3
  15. package/src/components/Card/Card.jsx +7 -7
  16. package/src/components/Card/Card.module.scss +8 -7
  17. package/src/components/Card/CardBody.jsx +2 -2
  18. package/src/components/Card/CardFooter.jsx +2 -2
  19. package/src/components/Card/README.md +20 -17
  20. package/src/components/Card/_settings.scss +1 -2
  21. package/src/components/Card/_theme.scss +1 -0
  22. package/src/components/CheckboxField/CheckboxField.jsx +11 -5
  23. package/src/components/CheckboxField/README.md +110 -5
  24. package/src/components/FileInputField/FileInputField.jsx +148 -22
  25. package/src/components/FileInputField/FileInputField.module.scss +87 -1
  26. package/src/components/FileInputField/README.md +83 -2
  27. package/src/components/FileInputField/_settings.scss +15 -0
  28. package/src/components/FormLayout/FormLayout.jsx +3 -3
  29. package/src/components/FormLayout/FormLayoutCustomField.jsx +3 -3
  30. package/src/components/FormLayout/README.md +1 -0
  31. package/src/components/Grid/Grid.jsx +2 -2
  32. package/src/components/Grid/Grid.module.scss +2 -2
  33. package/src/components/Grid/GridSpan.jsx +2 -2
  34. package/src/components/InputGroup/InputGroup.jsx +4 -4
  35. package/src/components/InputGroup/InputGroup.module.scss +12 -8
  36. package/src/components/InputGroup/README.md +1 -1
  37. package/src/components/Modal/Modal.jsx +118 -46
  38. package/src/components/Modal/Modal.module.scss +34 -18
  39. package/src/components/Modal/ModalBody.jsx +3 -3
  40. package/src/components/Modal/ModalBody.module.scss +18 -0
  41. package/src/components/Modal/ModalCloseButton.jsx +4 -6
  42. package/src/components/Modal/ModalContent.jsx +2 -2
  43. package/src/components/Modal/ModalFooter.jsx +3 -3
  44. package/src/components/Modal/ModalFooter.module.scss +6 -2
  45. package/src/components/Modal/ModalHeader.jsx +3 -3
  46. package/src/components/Modal/ModalHeader.module.scss +8 -1
  47. package/src/components/Modal/ModalTitle.jsx +2 -2
  48. package/src/components/Modal/README.md +407 -187
  49. package/src/components/Modal/_animations.scss +9 -0
  50. package/src/components/Modal/_helpers/dialogOnCancelHandler.js +28 -0
  51. package/src/components/Modal/_helpers/dialogOnClickHandler.js +46 -0
  52. package/src/components/Modal/_helpers/dialogOnCloseHandler.js +28 -0
  53. package/src/components/Modal/_helpers/dialogOnKeyDownHandler.js +62 -0
  54. package/src/components/Modal/_helpers/getPositionClassName.js +1 -1
  55. package/src/components/Modal/_hooks/useModalFocus.js +24 -91
  56. package/src/components/Modal/_settings.scss +4 -3
  57. package/src/components/Modal/_theme.scss +1 -0
  58. package/src/components/Paper/Paper.jsx +3 -3
  59. package/src/components/Popover/Popover.jsx +60 -15
  60. package/src/components/Popover/Popover.module.scss +37 -9
  61. package/src/components/Popover/PopoverWrapper.jsx +2 -2
  62. package/src/components/Popover/README.md +60 -3
  63. package/src/components/Popover/_helpers/cleanPlacementStyle.js +20 -0
  64. package/src/components/Radio/README.md +103 -0
  65. package/src/components/Radio/Radio.jsx +11 -5
  66. package/src/components/Radio/Radio.module.scss +4 -0
  67. package/src/components/ScrollView/ScrollView.jsx +5 -7
  68. package/src/components/SelectField/README.md +103 -0
  69. package/src/components/SelectField/SelectField.jsx +11 -5
  70. package/src/components/Table/Table.jsx +2 -2
  71. package/src/components/Tabs/Tabs.jsx +2 -2
  72. package/src/components/Tabs/TabsItem.jsx +3 -3
  73. package/src/components/Text/Text.jsx +3 -3
  74. package/src/components/TextArea/TextArea.jsx +3 -3
  75. package/src/components/TextField/README.md +14 -2
  76. package/src/components/TextField/TextField.jsx +3 -3
  77. package/src/components/TextLink/README.md +10 -3
  78. package/src/components/TextLink/TextLink.jsx +2 -2
  79. package/src/components/TextLink/_theme.scss +3 -3
  80. package/src/components/Toggle/README.md +83 -1
  81. package/src/components/Toggle/Toggle.jsx +11 -5
  82. package/src/components/Toolbar/Toolbar.jsx +3 -3
  83. package/src/components/Toolbar/ToolbarGroup.jsx +3 -3
  84. package/src/components/Toolbar/ToolbarItem.jsx +3 -3
  85. package/src/components/_helpers/resolveContextOrProp.js +6 -3
  86. package/src/helpers/classNames/README.md +65 -0
  87. package/src/helpers/classNames/classNames.js +11 -0
  88. package/src/helpers/classNames/index.js +1 -0
  89. package/src/helpers/transferProps/README.md +46 -0
  90. package/src/helpers/transferProps/index.js +1 -0
  91. package/src/index.js +6 -5
  92. package/src/providers/globalProps/GlobalPropsContext.jsx +5 -0
  93. package/src/providers/globalProps/GlobalPropsProvider.jsx +33 -0
  94. package/src/providers/globalProps/index.js +3 -0
  95. package/src/{provider → providers/globalProps}/withGlobalProps.jsx +16 -16
  96. package/src/providers/translations/TranslationsContext.jsx +6 -0
  97. package/src/providers/translations/TranslationsProvider.jsx +33 -0
  98. package/src/providers/translations/index.js +2 -0
  99. package/src/styles/elements/_links.scss +2 -9
  100. package/src/styles/generic/_focus.scss +1 -1
  101. package/src/styles/theme/_form-fields.scss +19 -0
  102. package/src/styles/theme/_links.scss +4 -3
  103. package/src/styles/tools/_accessibility.scss +3 -5
  104. package/src/styles/tools/_collections.scss +62 -5
  105. package/src/styles/tools/_links.scss +17 -0
  106. package/src/styles/tools/form-fields/_box-field-elements.scss +21 -9
  107. package/src/styles/tools/form-fields/_box-field-layout.scss +2 -2
  108. package/src/styles/tools/form-fields/_box-field-sizes.scss +6 -10
  109. package/src/styles/tools/form-fields/_foundation.scss +6 -4
  110. package/src/styles/tools/form-fields/_variants.scss +12 -8
  111. package/src/theme.scss +53 -2
  112. package/src/translations/en.js +5 -0
  113. package/src/provider/RUIContext.jsx +0 -9
  114. package/src/provider/RUIProvider.jsx +0 -42
  115. package/src/provider/index.js +0 -3
  116. package/src/styles/settings/_z-indexes.scss +0 -2
  117. package/src/utils/classNames.js +0 -8
  118. /package/src/{utils → helpers/transferProps}/transferProps.js +0 -0
@@ -18,7 +18,13 @@ React.createElement(() => {
18
18
  return (
19
19
  <CheckboxField
20
20
  checked={agree}
21
- label="I agree"
21
+ label={(
22
+ <>
23
+ I have read and agree with
24
+ {' '}
25
+ <TextLink href="#" label="terms and conditions" />
26
+ </>
27
+ )}
22
28
  onChange={() => setAgree(!agree)}
23
29
  />
24
30
  );
@@ -132,20 +138,44 @@ React.createElement(() => {
132
138
  <>
133
139
  <CheckboxField
134
140
  checked={agree}
135
- label="I have read and agree with terms and conditions"
141
+ label={(
142
+ <>
143
+ I have read and agree with
144
+ {' '}
145
+ <TextLink href="#" label="terms and conditions" />
146
+ </>
147
+ )}
136
148
  onChange={() => setAgree(!agree)}
137
149
  validationState="valid"
138
150
  />
139
151
  <CheckboxField
140
152
  checked={agree}
141
- label="I have read and agree with terms and conditions"
153
+ label={(
154
+ <>
155
+ I have read and agree with
156
+ {' '}
157
+ <TextLink href="#" label="terms and conditions" />
158
+ </>
159
+ )}
142
160
  onChange={() => setAgree(!agree)}
143
161
  validationState="warning"
144
- validationText="Please wait 10 minutes until we verify your data."
162
+ validationText={(
163
+ <>
164
+ Please wait 10 minutes until we verify your data.
165
+ {' '}
166
+ <TextLink href="#" label="Cancel" />
167
+ </>
168
+ )}
145
169
  />
146
170
  <CheckboxField
147
171
  checked={agree}
148
- label="I have read and agree with terms and conditions"
172
+ label={(
173
+ <>
174
+ I have read and agree with
175
+ {' '}
176
+ <TextLink href="#" label="terms and conditions" />
177
+ </>
178
+ )}
149
179
  onChange={() => setAgree(!agree)}
150
180
  required
151
181
  validationState="invalid"
@@ -156,6 +186,81 @@ React.createElement(() => {
156
186
  });
157
187
  ```
158
188
 
189
+ ### Required State
190
+
191
+ The required state indicates that the input is mandatory. Required fields
192
+ display an asterisk `*` after the label by default.
193
+
194
+ ```docoff-react-preview
195
+ React.createElement(() => {
196
+ const [agree, setAgree] = React.useState(true);
197
+ return (
198
+ <CheckboxField
199
+ checked={agree}
200
+ label="I agree"
201
+ onChange={() => setAgree(!agree)}
202
+ required
203
+ />
204
+ );
205
+ });
206
+ ```
207
+
208
+ #### Styling the Required State
209
+
210
+ All form fields in React UI can be
211
+ [styled](/docs/customize/theming/forms/#required-state)
212
+ to indicate the required state.
213
+
214
+ However, you may find yourself in a situation where a form field is valid in
215
+ both checked and unchecked states, for example to turn on or off a feature.
216
+ If your project uses the label color as the primary means to indicate the
217
+ required state of input fields and the usual asterisk `*` is omitted, you may
218
+ want to keep the label color consistent for both states to avoid confusion.
219
+
220
+ For this edge case, there is the `renderAsRequired` prop:
221
+
222
+ ```docoff-react-preview
223
+ React.createElement(() => {
224
+ const [optional, setOptional] = React.useState(false);
225
+ const [renderAsRequired, setRenderAsRequired] = React.useState(false);
226
+ return (
227
+ <React.Fragment>
228
+ <style>
229
+ {`
230
+ .example {
231
+ display: flex;
232
+ flex-wrap: wrap;
233
+ gap: 1rem 0.5rem;
234
+ }
235
+
236
+ .example--themed-form-fields {
237
+ --rui-FormField__label__color: var(--rui-color-text-secondary);
238
+ --rui-FormField--required__label__color: var(--rui-color-text-primary);
239
+ --rui-FormField--required__sign: '';
240
+ }
241
+ `}
242
+ </style>
243
+ <div class="example example--themed-form-fields">
244
+ <CheckboxField
245
+ checked={optional}
246
+ label="This field is optional"
247
+ onChange={() => setOptional(!optional)}
248
+ />
249
+ <CheckboxField
250
+ checked={renderAsRequired}
251
+ label="This field is optional but looks like required"
252
+ onChange={() => setRenderAsRequired(!renderAsRequired)}
253
+ renderAsRequired
254
+ />
255
+ </div>
256
+ </React.Fragment>
257
+ );
258
+ });
259
+ ```
260
+
261
+ It renders the field as if it was required, but doesn't add the `required`
262
+ attribute to the actual input.
263
+
159
264
  ### Disabled State
160
265
 
161
266
  Disabled state makes the input unavailable.
@@ -1,10 +1,19 @@
1
1
  import PropTypes from 'prop-types';
2
- import React, { useContext } from 'react';
3
- import { withGlobalProps } from '../../provider';
4
- import { classNames } from '../../utils/classNames';
5
- import { transferProps } from '../../utils/transferProps';
2
+ import React, {
3
+ useContext,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
8
+ import { withGlobalProps } from '../../providers/globalProps';
9
+ import { classNames } from '../../helpers/classNames';
10
+ import { transferProps } from '../../helpers/transferProps';
11
+ import { TranslationsContext } from '../../providers/translations';
12
+ import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
6
13
  import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
7
14
  import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
15
+ import { InputGroupContext } from '../InputGroup';
16
+ import { Text } from '../Text';
8
17
  import { FormLayoutContext } from '../FormLayout';
9
18
  import styles from './FileInputField.module.scss';
10
19
 
@@ -17,54 +26,156 @@ export const FileInputField = React.forwardRef((props, ref) => {
17
26
  isLabelVisible,
18
27
  label,
19
28
  layout,
29
+ multiple,
30
+ onFilesChanged,
20
31
  required,
32
+ size,
21
33
  validationState,
22
34
  validationText,
23
35
  ...restProps
24
36
  } = props;
25
37
 
26
- const context = useContext(FormLayoutContext);
38
+ const internalInputRef = useRef();
39
+
40
+ // We need to have a reference to the input element to be able to call its methods,
41
+ // but at the same time we want to expose this reference to the parent component for
42
+ // case someone wants to call input methods from outside the component.
43
+ useImperativeHandle(ref, () => internalInputRef.current);
44
+
45
+ const formLayoutContext = useContext(FormLayoutContext);
46
+ const inputGroupContext = useContext(InputGroupContext);
47
+ const translations = useContext(TranslationsContext);
48
+
49
+ const [selectedFileNames, setSelectedFileNames] = useState([]);
50
+ const [isDragging, setIsDragging] = useState(false);
51
+
52
+ const handleFileChange = (files, event) => {
53
+ if (files.length === 0) {
54
+ setSelectedFileNames([]);
55
+ return;
56
+ }
57
+
58
+ // Mimic the native behavior of the `input` element: if multiple files are selected and the input
59
+ // does not accept multiple files, no files are processed.
60
+ if (files.length > 1 && !multiple) {
61
+ setSelectedFileNames([]);
62
+ return;
63
+ }
64
+
65
+ const fileNames = [];
66
+
67
+ [...files].forEach((file) => {
68
+ fileNames.push(file.name);
69
+ });
70
+
71
+ setSelectedFileNames(fileNames);
72
+ onFilesChanged(files, event);
73
+ };
74
+
75
+ const handleInputChange = (event) => {
76
+ handleFileChange(event.target.files, event);
77
+ };
78
+
79
+ const handleClick = () => {
80
+ internalInputRef?.current.click();
81
+ };
82
+
83
+ const handleDrop = (event) => {
84
+ event.preventDefault();
85
+ handleFileChange(event.dataTransfer.files, event);
86
+ setIsDragging(false);
87
+ };
88
+
89
+ const handleDragOver = (event) => {
90
+ if (!isDragging) {
91
+ setIsDragging(true);
92
+ }
93
+ event.preventDefault();
94
+ };
95
+
96
+ const handleDragLeave = () => {
97
+ if (isDragging) {
98
+ setIsDragging(false);
99
+ }
100
+ };
27
101
 
28
102
  return (
29
- <label
103
+ <div
30
104
  className={classNames(
31
105
  styles.root,
32
106
  fullWidth && styles.isRootFullWidth,
33
- context && styles.isRootInFormLayout,
34
- resolveContextOrProp(context && context.layout, layout) === 'horizontal'
107
+ formLayoutContext && styles.isRootInFormLayout,
108
+ resolveContextOrProp(formLayoutContext && formLayoutContext.layout, layout) === 'horizontal'
35
109
  ? styles.isRootLayoutHorizontal
36
110
  : styles.isRootLayoutVertical,
37
- disabled && styles.isRootDisabled,
111
+ resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled) && styles.isRootDisabled,
112
+ inputGroupContext && styles.isRootGrouped,
113
+ isDragging && styles.isRootDragging,
38
114
  required && styles.isRootRequired,
115
+ getRootSizeClassName(
116
+ resolveContextOrProp(inputGroupContext && inputGroupContext.size, size),
117
+ styles,
118
+ ),
39
119
  getRootValidationStateClassName(validationState, styles),
40
120
  )}
41
- htmlFor={id}
42
- id={id && `${id}__label`}
121
+ id={`${id}__root`}
122
+ onDragLeave={!disabled ? handleDragLeave : undefined}
123
+ onDragOver={!disabled ? handleDragOver : undefined}
124
+ onDrop={!disabled ? handleDrop : undefined}
43
125
  >
44
- <div
126
+ <label
45
127
  className={classNames(
46
128
  styles.label,
47
- !isLabelVisible && styles.isLabelHidden,
129
+ (!isLabelVisible || inputGroupContext) && styles.isLabelHidden,
48
130
  )}
49
- id={id && `${id}__labelText`}
131
+ htmlFor={id}
132
+ id={`${id}__labelText`}
50
133
  >
51
134
  {label}
52
- </div>
135
+ </label>
53
136
  <div className={styles.field}>
54
137
  <div className={styles.inputContainer}>
55
138
  <input
56
139
  {...transferProps(restProps)}
57
- disabled={disabled}
140
+ className={styles.input}
141
+ disabled={resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled)}
58
142
  id={id}
59
- ref={ref}
143
+ multiple={multiple}
144
+ onChange={handleInputChange}
145
+ ref={internalInputRef}
60
146
  required={required}
147
+ tabIndex={-1}
61
148
  type="file"
62
149
  />
150
+ <button
151
+ className={styles.dropZone}
152
+ disabled={resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled)}
153
+ onClick={handleClick}
154
+ type="button"
155
+ >
156
+ <Text lines={1}>
157
+ {!selectedFileNames.length && (
158
+ <>
159
+ <span className={styles.dropZoneLink}>{translations.FileInputField.browse}</span>
160
+ {' '}
161
+ {translations.FileInputField.drop}
162
+ </>
163
+ )}
164
+ {selectedFileNames.length === 1 && selectedFileNames[0]}
165
+ {selectedFileNames.length > 1 && (
166
+ <>
167
+ {selectedFileNames.length}
168
+ {' '}
169
+ {translations.FileInputField.filesSelected}
170
+ </>
171
+ )}
172
+ </Text>
173
+ </button>
63
174
  </div>
64
175
  {helpText && (
65
176
  <div
66
177
  className={styles.helpText}
67
- id={id && `${id}__helpText`}
178
+ id={`${id}__helpText`}
68
179
  >
69
180
  {helpText}
70
181
  </div>
@@ -72,13 +183,13 @@ export const FileInputField = React.forwardRef((props, ref) => {
72
183
  {validationText && (
73
184
  <div
74
185
  className={styles.validationText}
75
- id={id && `${id}__validationText`}
186
+ id={`${id}__validationText`}
76
187
  >
77
188
  {validationText}
78
189
  </div>
79
190
  )}
80
191
  </div>
81
- </label>
192
+ </div>
82
193
  );
83
194
  });
84
195
 
@@ -86,10 +197,11 @@ FileInputField.defaultProps = {
86
197
  disabled: false,
87
198
  fullWidth: false,
88
199
  helpText: null,
89
- id: undefined,
90
200
  isLabelVisible: true,
91
201
  layout: 'vertical',
202
+ multiple: false,
92
203
  required: false,
204
+ size: 'medium',
93
205
  validationState: null,
94
206
  validationText: null,
95
207
  };
@@ -116,7 +228,7 @@ FileInputField.propTypes = {
116
228
  * * `<ID>__helpText`
117
229
  * * `<ID>__validationText`
118
230
  */
119
- id: PropTypes.string,
231
+ id: PropTypes.string.isRequired,
120
232
  /**
121
233
  * If `false`, the label will be visually hidden (but remains accessible by assistive
122
234
  * technologies).
@@ -134,10 +246,24 @@ FileInputField.propTypes = {
134
246
  *
135
247
  */
136
248
  layout: PropTypes.oneOf(['horizontal', 'vertical']),
249
+ /**
250
+ * If `true`, the input will accept multiple files.
251
+ */
252
+ multiple: PropTypes.bool,
253
+ /**
254
+ * Callback fired when the value of the input changes.
255
+ */
256
+ onFilesChanged: PropTypes.func.isRequired,
137
257
  /**
138
258
  * If `true`, the input will be required.
139
259
  */
140
260
  required: PropTypes.bool,
261
+ /**
262
+ * Size of the field.
263
+ *
264
+ * Ignored if the component is rendered within `InputGroup` component as the value is inherited in such case.
265
+ */
266
+ size: PropTypes.oneOf(['small', 'medium', 'large']),
141
267
  /**
142
268
  * Alter the field to provide feedback based on validation result.
143
269
  */
@@ -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