@react-ui-org/react-ui 0.58.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 (92) hide show
  1. package/README.md +2 -11
  2. package/dist/react-ui.css +17 -17
  3. package/dist/react-ui.development.css +1228 -1051
  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 +0 -26
  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 +2 -21
  18. package/src/components/Card/_settings.scss +1 -2
  19. package/src/components/Card/_theme.scss +1 -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 +1 -0
  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 +51 -1
  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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-ui-org/react-ui",
3
3
  "description": "React UI is a themeable UI library for React apps.",
4
- "version": "0.58.0",
4
+ "version": "0.59.0",
5
5
  "keywords": [
6
6
  "react",
7
7
  "ui",
@@ -56,7 +56,7 @@
56
56
  "precopy": "rm -rf dist && mkdir dist",
57
57
  "prepublishOnly": "npm run build",
58
58
  "start": "webpack --watch --mode=development",
59
- "stylelint": "stylelint \"src/**/*.scss\" --config stylelint.config.js",
59
+ "stylelint": "stylelint \"src/**/*.{css,scss}\" \"!src/docs/_assets/generated/**\" --config stylelint.config.js",
60
60
  "test": "npm run jest"
61
61
  },
62
62
  "dependencies": {
@@ -76,10 +76,11 @@
76
76
  "@babel/preset-env": "^7.24.7",
77
77
  "@babel/preset-react": "^7.24.7",
78
78
  "@babel/register": "^7.24.6",
79
+ "@happy-dom/jest-environment": "^16.6.0",
79
80
  "@stylistic/stylelint-config": "^1.0.1",
80
81
  "@svgr/webpack": "^8.1.0",
81
- "@testing-library/jest-dom": "^6.4.6",
82
- "@testing-library/react": "^16.0.0",
82
+ "@testing-library/jest-dom": "^6.6.3",
83
+ "@testing-library/react": "^16.1.0",
83
84
  "@testing-library/user-event": "^14.5.2",
84
85
  "@visionappscz/eslint-config-visionapps": "^1.7.0",
85
86
  "@visionappscz/stylelint-config": "^4.0.0",
@@ -97,7 +98,6 @@
97
98
  "eslint-plugin-react-hooks": "^4.6.2",
98
99
  "identity-obj-proxy": "^3.0.0",
99
100
  "jest": "^29.7.0",
100
- "jest-environment-jsdom": "^29.7.0",
101
101
  "markdownlint-cli2": "^0.13.0",
102
102
  "mini-css-extract-plugin": "^2.9.0",
103
103
  "postcss": "^8.4.39",
@@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
2
2
  import React, { useContext } from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { TranslationsContext } from '../../providers/translations';
5
- import { classNames } from '../../utils/classNames';
6
- import { transferProps } from '../../utils/transferProps';
5
+ import { classNames } from '../../helpers/classNames/classNames';
6
+ import { transferProps } from '../../helpers/transferProps';
7
7
  import { getRootColorClassName } from '../_helpers/getRootColorClassName';
8
8
  import styles from './Alert.module.scss';
9
9
 
@@ -69,9 +69,9 @@ Alert.propTypes = {
69
69
  children: PropTypes.node.isRequired,
70
70
  /**
71
71
  * Color variant to clarify importance and meaning of the alert. Implements
72
- * [Feedback and Neutral color collections](/docs/foundation/collections#colors).
72
+ * [Feedback color collection](/docs/foundation/collections#colors).
73
73
  */
74
- color: PropTypes.oneOf(['success', 'warning', 'danger', 'help', 'info', 'note', 'light', 'dark']),
74
+ color: PropTypes.oneOf(['success', 'warning', 'danger', 'help', 'info', 'note']),
75
75
  /**
76
76
  * Optional element to be displayed next to the alert body.
77
77
  */
@@ -111,32 +111,6 @@ Neutral informative alert.
111
111
  </Alert>
112
112
  ```
113
113
 
114
- ### Light
115
-
116
- Light alert variant.
117
-
118
- ```docoff-react-preview
119
- <docoff-placeholder dark>
120
- <Alert color="light">
121
- <strong>Light alert:</strong> Stands out on dark backgrounds.
122
- {' '}
123
- <TextLink href="/" label="This is a link" />
124
- </Alert>
125
- </docoff-placeholder>
126
- ```
127
-
128
- ### Dark
129
-
130
- Dark alert variant.
131
-
132
- ```docoff-react-preview
133
- <Alert color="dark">
134
- <strong>Dark alert:</strong> Stands out on light backgrounds.
135
- {' '}
136
- <TextLink href="/" label="This is a link" />
137
- </Alert>
138
- ```
139
-
140
114
  ## Alerts with Icons
141
115
 
142
116
  An icon can (and should) accompany the message.
@@ -1,4 +1,3 @@
1
- @use "sass:list";
2
1
  @use "sass:map";
3
2
  @use "../../styles/settings/collections";
4
3
  @use "../../styles/theme/typography";
@@ -8,5 +7,5 @@ $font-size: map.get(typography.$font-size-values, 1);
8
7
  $line-height: typography.$line-height-base;
9
8
  $min-height: calc(#{$font-size} * #{$line-height} + 2 * #{theme.$padding});
10
9
 
11
- $colors: list.join(collections.$feedback-colors, collections.$neutral-colors);
10
+ $colors: collections.$feedback-colors;
12
11
  $themeable-properties: color, foreground-color, background-color;
@@ -1,8 +1,8 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React 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 { getRootColorClassName } from '../_helpers/getRootColorClassName';
7
7
  import { getRootPriorityClassName } from '../_helpers/getRootPriorityClassName';
8
8
  import styles from './Badge.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 { getRootColorClassName } from '../_helpers/getRootColorClassName';
7
7
  import { getRootPriorityClassName } from '../_helpers/getRootPriorityClassName';
8
8
  import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
@@ -3,8 +3,8 @@ import React, {
3
3
  useMemo,
4
4
  } from 'react';
5
5
  import { withGlobalProps } from '../../providers/globalProps';
6
- import { classNames } from '../../utils/classNames';
7
- import { transferProps } from '../../utils/transferProps';
6
+ import { classNames } from '../../helpers/classNames/classNames';
7
+ import { transferProps } from '../../helpers/transferProps';
8
8
  import { getRootPriorityClassName } from '../_helpers/getRootPriorityClassName';
9
9
  import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
10
10
  import styles from './ButtonGroup.module.scss';
@@ -1,8 +1,8 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React 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 { getRootColorClassName } from '../_helpers/getRootColorClassName';
7
7
  import styles from './Card.module.scss';
8
8
 
@@ -18,7 +18,7 @@ export const Card = ({
18
18
  {...transferProps(restProps)}
19
19
  className={classNames(
20
20
  styles.root,
21
- getRootColorClassName(color, styles),
21
+ color && getRootColorClassName(color, styles),
22
22
  dense && styles.isRootDense,
23
23
  raised && styles.isRootRaised,
24
24
  disabled && styles.isRootDisabled,
@@ -29,7 +29,7 @@ export const Card = ({
29
29
  );
30
30
 
31
31
  Card.defaultProps = {
32
- color: 'light',
32
+ color: undefined,
33
33
  dense: false,
34
34
  disabled: false,
35
35
  raised: false,
@@ -45,9 +45,9 @@ Card.propTypes = {
45
45
  children: PropTypes.node.isRequired,
46
46
  /**
47
47
  * Color to clarify importance and meaning of the card. Implements
48
- * [Feedback and Neutral color collections](/docs/foundation/collections#colors).
48
+ * [Feedback color collection](/docs/foundation/collections#colors).
49
49
  */
50
- color: PropTypes.oneOf(['success', 'warning', 'danger', 'help', 'info', 'note', 'light', 'dark']),
50
+ color: PropTypes.oneOf(['success', 'warning', 'danger', 'help', 'info', 'note']),
51
51
  /**
52
52
  * Make the card more compact.
53
53
  */
@@ -12,9 +12,9 @@
12
12
  flex-direction: column;
13
13
  min-width: 0; // 1.
14
14
  color: var(--rui-local-color);
15
- border: theme.$border-width solid var(--rui-local-border-color);
15
+ border: theme.$border-width solid var(--rui-local-border-color, transparent);
16
16
  border-radius: theme.$border-radius;
17
- background-color: var(--rui-local-background-color);
17
+ background-color: var(--rui-local-background-color, theme.$background-color);
18
18
  }
19
19
 
20
20
  .body {
@@ -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 styles from './Card.module.scss';
6
6
 
7
7
  export const CardBody = ({
@@ -1,6 +1,6 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
- import { transferProps } from '../../utils/transferProps';
3
+ import { transferProps } from '../../helpers/transferProps';
4
4
  import { withGlobalProps } from '../../providers/globalProps';
5
5
  import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
6
6
  import styles from './Card.module.scss';
@@ -148,7 +148,7 @@ for card content.
148
148
  ## Color Variants
149
149
 
150
150
  To cover all possible needs of your project, Card is available in colors from
151
- [Feedback and Neutral color collections](/docs/foundation/collections#colors).
151
+ [Feedback color collection](/docs/foundation/collections#colors).
152
152
 
153
153
  ```docoff-react-preview
154
154
  <Card color="success">
@@ -211,26 +211,6 @@ To cover all possible needs of your project, Card is available in colors from
211
211
  <Button label="Read more" priority="outline" color="note" />
212
212
  </CardFooter>
213
213
  </Card>
214
- <Card>
215
- <CardBody>
216
- Hello! I&apos;m light (default) variant of card.
217
- {' '}
218
- <TextLink href="/" label="This is a link" />
219
- </CardBody>
220
- <CardFooter>
221
- <Button label="Read more" priority="outline" color="dark" />
222
- </CardFooter>
223
- </Card>
224
- <Card color="dark">
225
- <CardBody>
226
- Hello! I&apos;m dark variant of card.
227
- {' '}
228
- <TextLink href="/" label="This is a link" />
229
- </CardBody>
230
- <CardFooter>
231
- <Button label="Read more" priority="outline" color="light" />
232
- </CardFooter>
233
- </Card>
234
214
  ```
235
215
 
236
216
  ## States
@@ -314,6 +294,7 @@ Separate your card actions with CardFooter. See
314
294
  | `--rui-Card__padding` | Padding shared by card header, body and footer |
315
295
  | `--rui-Card__border-width` | Border width |
316
296
  | `--rui-Card__border-radius` | Corner radius |
297
+ | `--rui-Card__background-color` | Card background color |
317
298
  | `--rui-Card--dense__padding` | Padding of dense card |
318
299
  | `--rui-Card--raised__box-shadow` | Box shadow of raised card |
319
300
  | `--rui-Card--disabled__background-color` | Card background color in disabled state |
@@ -1,5 +1,4 @@
1
- @use "sass:list";
2
1
  @use "../../styles/settings/collections";
3
2
 
4
- $colors: list.join(collections.$feedback-colors, collections.$neutral-colors);
3
+ $colors: collections.$feedback-colors;
5
4
  $themeable-properties: color, border-color, background-color;
@@ -1,6 +1,7 @@
1
1
  $padding: var(--rui-Card__padding);
2
2
  $border-width: var(--rui-Card__border-width);
3
3
  $border-radius: var(--rui-Card__border-radius);
4
+ $background-color: var(--rui-Card__background-color);
4
5
 
5
6
  $dense-padding: var(--rui-Card--dense__padding);
6
7
  $raised-box-shadow: var(--rui-Card--raised__box-shadow);
@@ -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 { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
7
7
  import { FormLayoutContext } from '../FormLayout';
8
8
  import styles from './CheckboxField.module.scss';
@@ -1,10 +1,19 @@
1
1
  import PropTypes from 'prop-types';
2
- import React, { useContext } from 'react';
2
+ import React, {
3
+ useContext,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
3
8
  import { withGlobalProps } from '../../providers/globalProps';
4
- import { classNames } from '../../utils/classNames';
5
- import { transferProps } from '../../utils/transferProps';
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
  }