@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.
- package/.nvmrc +1 -1
- package/README.md +2 -11
- package/dist/react-ui.css +19 -19
- package/dist/react-ui.development.css +1351 -963
- package/dist/react-ui.development.js +187 -87
- package/dist/react-ui.js +1 -1
- package/package.json +16 -5
- package/src/components/Alert/Alert.jsx +7 -9
- package/src/components/Alert/Alert.module.scss +3 -3
- package/src/components/Alert/README.md +18 -32
- package/src/components/Alert/_settings.scss +1 -2
- package/src/components/Badge/Badge.jsx +3 -3
- package/src/components/Button/Button.jsx +3 -3
- package/src/components/ButtonGroup/ButtonGroup.jsx +3 -3
- package/src/components/Card/Card.jsx +7 -7
- package/src/components/Card/Card.module.scss +8 -7
- package/src/components/Card/CardBody.jsx +2 -2
- package/src/components/Card/CardFooter.jsx +2 -2
- package/src/components/Card/README.md +20 -17
- package/src/components/Card/_settings.scss +1 -2
- package/src/components/Card/_theme.scss +1 -0
- package/src/components/CheckboxField/CheckboxField.jsx +11 -5
- package/src/components/CheckboxField/README.md +110 -5
- package/src/components/FileInputField/FileInputField.jsx +148 -22
- package/src/components/FileInputField/FileInputField.module.scss +87 -1
- package/src/components/FileInputField/README.md +83 -2
- package/src/components/FileInputField/_settings.scss +15 -0
- package/src/components/FormLayout/FormLayout.jsx +3 -3
- package/src/components/FormLayout/FormLayoutCustomField.jsx +3 -3
- package/src/components/FormLayout/README.md +1 -0
- package/src/components/Grid/Grid.jsx +2 -2
- package/src/components/Grid/Grid.module.scss +2 -2
- package/src/components/Grid/GridSpan.jsx +2 -2
- package/src/components/InputGroup/InputGroup.jsx +4 -4
- package/src/components/InputGroup/InputGroup.module.scss +12 -8
- package/src/components/InputGroup/README.md +1 -1
- package/src/components/Modal/Modal.jsx +118 -46
- package/src/components/Modal/Modal.module.scss +34 -18
- package/src/components/Modal/ModalBody.jsx +3 -3
- package/src/components/Modal/ModalBody.module.scss +18 -0
- package/src/components/Modal/ModalCloseButton.jsx +4 -6
- package/src/components/Modal/ModalContent.jsx +2 -2
- package/src/components/Modal/ModalFooter.jsx +3 -3
- package/src/components/Modal/ModalFooter.module.scss +6 -2
- package/src/components/Modal/ModalHeader.jsx +3 -3
- package/src/components/Modal/ModalHeader.module.scss +8 -1
- package/src/components/Modal/ModalTitle.jsx +2 -2
- package/src/components/Modal/README.md +407 -187
- package/src/components/Modal/_animations.scss +9 -0
- package/src/components/Modal/_helpers/dialogOnCancelHandler.js +28 -0
- package/src/components/Modal/_helpers/dialogOnClickHandler.js +46 -0
- package/src/components/Modal/_helpers/dialogOnCloseHandler.js +28 -0
- package/src/components/Modal/_helpers/dialogOnKeyDownHandler.js +62 -0
- package/src/components/Modal/_helpers/getPositionClassName.js +1 -1
- package/src/components/Modal/_hooks/useModalFocus.js +24 -91
- package/src/components/Modal/_settings.scss +4 -3
- package/src/components/Modal/_theme.scss +1 -0
- package/src/components/Paper/Paper.jsx +3 -3
- package/src/components/Popover/Popover.jsx +60 -15
- package/src/components/Popover/Popover.module.scss +37 -9
- package/src/components/Popover/PopoverWrapper.jsx +2 -2
- package/src/components/Popover/README.md +60 -3
- package/src/components/Popover/_helpers/cleanPlacementStyle.js +20 -0
- package/src/components/Radio/README.md +103 -0
- package/src/components/Radio/Radio.jsx +11 -5
- package/src/components/Radio/Radio.module.scss +4 -0
- package/src/components/ScrollView/ScrollView.jsx +5 -7
- package/src/components/SelectField/README.md +103 -0
- package/src/components/SelectField/SelectField.jsx +11 -5
- package/src/components/Table/Table.jsx +2 -2
- package/src/components/Tabs/Tabs.jsx +2 -2
- package/src/components/Tabs/TabsItem.jsx +3 -3
- package/src/components/Text/Text.jsx +3 -3
- package/src/components/TextArea/TextArea.jsx +3 -3
- package/src/components/TextField/README.md +14 -2
- package/src/components/TextField/TextField.jsx +3 -3
- package/src/components/TextLink/README.md +10 -3
- package/src/components/TextLink/TextLink.jsx +2 -2
- package/src/components/TextLink/_theme.scss +3 -3
- package/src/components/Toggle/README.md +83 -1
- package/src/components/Toggle/Toggle.jsx +11 -5
- package/src/components/Toolbar/Toolbar.jsx +3 -3
- package/src/components/Toolbar/ToolbarGroup.jsx +3 -3
- package/src/components/Toolbar/ToolbarItem.jsx +3 -3
- package/src/components/_helpers/resolveContextOrProp.js +6 -3
- package/src/helpers/classNames/README.md +65 -0
- package/src/helpers/classNames/classNames.js +11 -0
- package/src/helpers/classNames/index.js +1 -0
- package/src/helpers/transferProps/README.md +46 -0
- package/src/helpers/transferProps/index.js +1 -0
- package/src/index.js +6 -5
- package/src/providers/globalProps/GlobalPropsContext.jsx +5 -0
- package/src/providers/globalProps/GlobalPropsProvider.jsx +33 -0
- package/src/providers/globalProps/index.js +3 -0
- package/src/{provider → providers/globalProps}/withGlobalProps.jsx +16 -16
- package/src/providers/translations/TranslationsContext.jsx +6 -0
- package/src/providers/translations/TranslationsProvider.jsx +33 -0
- package/src/providers/translations/index.js +2 -0
- package/src/styles/elements/_links.scss +2 -9
- package/src/styles/generic/_focus.scss +1 -1
- package/src/styles/theme/_form-fields.scss +19 -0
- package/src/styles/theme/_links.scss +4 -3
- package/src/styles/tools/_accessibility.scss +3 -5
- package/src/styles/tools/_collections.scss +62 -5
- package/src/styles/tools/_links.scss +17 -0
- package/src/styles/tools/form-fields/_box-field-elements.scss +21 -9
- package/src/styles/tools/form-fields/_box-field-layout.scss +2 -2
- package/src/styles/tools/form-fields/_box-field-sizes.scss +6 -10
- package/src/styles/tools/form-fields/_foundation.scss +6 -4
- package/src/styles/tools/form-fields/_variants.scss +12 -8
- package/src/theme.scss +53 -2
- package/src/translations/en.js +5 -0
- package/src/provider/RUIContext.jsx +0 -9
- package/src/provider/RUIProvider.jsx +0 -42
- package/src/provider/index.js +0 -3
- package/src/styles/settings/_z-indexes.scss +0 -2
- package/src/utils/classNames.js +0 -8
- /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=
|
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=
|
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=
|
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=
|
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=
|
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, {
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
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
|
-
<
|
103
|
+
<div
|
30
104
|
className={classNames(
|
31
105
|
styles.root,
|
32
106
|
fullWidth && styles.isRootFullWidth,
|
33
|
-
|
34
|
-
resolveContextOrProp(
|
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
|
-
|
42
|
-
|
121
|
+
id={`${id}__root`}
|
122
|
+
onDragLeave={!disabled ? handleDragLeave : undefined}
|
123
|
+
onDragOver={!disabled ? handleDragOver : undefined}
|
124
|
+
onDrop={!disabled ? handleDrop : undefined}
|
43
125
|
>
|
44
|
-
<
|
126
|
+
<label
|
45
127
|
className={classNames(
|
46
128
|
styles.label,
|
47
|
-
!isLabelVisible && styles.isLabelHidden,
|
129
|
+
(!isLabelVisible || inputGroupContext) && styles.isLabelHidden,
|
48
130
|
)}
|
49
|
-
|
131
|
+
htmlFor={id}
|
132
|
+
id={`${id}__labelText`}
|
50
133
|
>
|
51
134
|
{label}
|
52
|
-
</
|
135
|
+
</label>
|
53
136
|
<div className={styles.field}>
|
54
137
|
<div className={styles.inputContainer}>
|
55
138
|
<input
|
56
139
|
{...transferProps(restProps)}
|
57
|
-
|
140
|
+
className={styles.input}
|
141
|
+
disabled={resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled)}
|
58
142
|
id={id}
|
59
|
-
|
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={
|
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={
|
186
|
+
id={`${id}__validationText`}
|
76
187
|
>
|
77
188
|
{validationText}
|
78
189
|
</div>
|
79
190
|
)}
|
80
191
|
</div>
|
81
|
-
</
|
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
|
-
|
257
|
+
onFilesChanged={() => {}}
|
177
258
|
/>
|
178
259
|
```
|
179
260
|
|