@react-ui-org/react-ui 0.59.3 → 0.61.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 (59) hide show
  1. package/.env.playwright +9 -0
  2. package/.env.playwright.dist +9 -0
  3. package/.eslintrc-ts +40 -0
  4. package/README.md +18 -1
  5. package/dist/react-ui.css +4 -4
  6. package/dist/react-ui.development.css +8 -0
  7. package/dist/react-ui.development.js +57 -47
  8. package/dist/react-ui.js +1 -1
  9. package/jest.config-ts.js +34 -0
  10. package/package.json +25 -6
  11. package/playwright-ct.config.ts +68 -0
  12. package/src/components/Alert/Alert.jsx +2 -2
  13. package/src/components/Button/Button.jsx +5 -5
  14. package/src/components/Button/README.md +1 -1
  15. package/src/components/ButtonGroup/ButtonGroup.jsx +2 -2
  16. package/src/components/Card/Card.module.scss +1 -0
  17. package/src/components/Card/CardFooter.jsx +2 -2
  18. package/src/components/Card/README.md +2 -0
  19. package/src/components/Card/_theme.scss +2 -0
  20. package/src/components/CheckboxField/CheckboxField.jsx +3 -3
  21. package/src/components/FileInputField/FileInputField.jsx +21 -8
  22. package/src/components/FileInputField/FileInputField.module.scss +4 -0
  23. package/src/components/FormLayout/FormLayout.jsx +2 -2
  24. package/src/components/FormLayout/FormLayoutCustomField.jsx +5 -5
  25. package/src/components/Grid/Grid.jsx +2 -2
  26. package/src/components/Grid/GridSpan.jsx +2 -2
  27. package/src/components/InputGroup/InputGroup.jsx +25 -5
  28. package/src/components/InputGroup/InputGroup.module.scss +2 -1
  29. package/src/components/InputGroup/README.md +69 -14
  30. package/src/components/Modal/Modal.jsx +26 -8
  31. package/src/components/Modal/ModalBody.jsx +2 -2
  32. package/src/components/Modal/ModalContent.jsx +2 -2
  33. package/src/components/Modal/_helpers/dialogOnClickHandler.js +6 -0
  34. package/src/components/Popover/Popover.jsx +5 -5
  35. package/src/components/Radio/Radio.jsx +3 -3
  36. package/src/components/ScrollView/ScrollView.jsx +42 -12
  37. package/src/components/ScrollView/_hooks/useScrollPositionHook.js +4 -4
  38. package/src/components/SelectField/SelectField.jsx +9 -6
  39. package/src/components/Table/Table.jsx +1 -1
  40. package/src/components/Table/_components/TableBodyCell/TableBodyCell.jsx +1 -1
  41. package/src/components/Table/_components/TableHeaderCell/TableHeaderCell.jsx +1 -1
  42. package/src/components/Tabs/TabsItem.jsx +3 -3
  43. package/src/components/Text/Text.jsx +2 -2
  44. package/src/components/TextArea/TextArea.jsx +3 -3
  45. package/src/components/TextField/TextField.jsx +11 -8
  46. package/src/components/Toggle/Toggle.jsx +3 -3
  47. package/src/components/Toolbar/Toolbar.jsx +2 -2
  48. package/src/components/Toolbar/ToolbarGroup.jsx +2 -2
  49. package/src/components/Toolbar/ToolbarItem.jsx +2 -2
  50. package/src/helpers/isChildrenEmpty/README.md +57 -0
  51. package/src/helpers/isChildrenEmpty/index.js +1 -0
  52. package/src/index.js +1 -0
  53. package/src/providers/globalProps/GlobalPropsProvider.jsx +1 -1
  54. package/src/providers/translations/TranslationsProvider.jsx +1 -1
  55. package/src/styles/settings/_breakpoints.scss +2 -0
  56. package/src/theme.scss +2 -0
  57. package/src/translations/en.js +1 -0
  58. package/tsconfig.json +27 -0
  59. /package/src/{components/_helpers → helpers/isChildrenEmpty}/isChildrenEmpty.js +0 -0
@@ -0,0 +1,34 @@
1
+
2
+ module.exports = {
3
+ extensionsToTreatAsEsm: [
4
+ '.ts',
5
+ '.tsx',
6
+ ],
7
+ moduleFileExtensions: [
8
+ 'js',
9
+ 'jsx',
10
+ 'ts',
11
+ 'tsx',
12
+ ],
13
+ moduleNameMapper: {
14
+ '\\.scss$': 'identity-obj-proxy',
15
+ '\\.svg$': '<rootDir>/tests/jest/mocks/svgrMock.jsx',
16
+ },
17
+ preset: 'ts-jest',
18
+ setupFiles: [
19
+ '<rootDir>/tests/jest/setupJest.js',
20
+ ],
21
+ setupFilesAfterEnv: [
22
+ ],
23
+ testEnvironment: '@happy-dom/jest-environment',
24
+ testMatch: [
25
+ '**/*.test.{ts,tsx}',
26
+ ],
27
+ transform: {
28
+ '^.+\\.(ts|tsx)$': [
29
+ 'ts-jest',
30
+ { useESM: true },
31
+ ],
32
+ },
33
+ verbose: true,
34
+ };
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.59.3",
4
+ "version": "0.61.0",
5
5
  "keywords": [
6
6
  "react",
7
7
  "ui",
@@ -47,17 +47,24 @@
47
47
  "copy": "npm run copy:css && npm run copy:js",
48
48
  "copy:css": "cp src/docs/_assets/generated/react-ui.css dist && cp src/docs/_assets/generated/react-ui.development.css dist",
49
49
  "copy:js": "cp src/docs/_assets/generated/react-ui.js dist && cp src/docs/_assets/generated/react-ui.development.js dist",
50
- "eslint": "eslint --ext js,jsx src",
51
- "jest": "jest src --coverage",
50
+ "eslint": "npm run eslint:ts && npm run eslint:js",
51
+ "eslint:js": "eslint --ext js,jsx src",
52
+ "eslint:ts": "eslint --ext ts,tsx -c .eslintrc-ts src tests",
52
53
  "lint": "npm run eslint && npm run markdownlint && npm run stylelint",
53
- "markdownlint": "markdownlint-cli2 \"CONTRIBUTING.md\" \"README.md\" \"RELEASING.md\" \"src/**/*.md\"",
54
+ "markdownlint": "markdownlint-cli2 \"README.md\" \"src/**/*.md\"",
54
55
  "postbuild": "npm run copy",
55
- "postinstall": "cp -n .env.dist .env || true",
56
+ "postinstall": "cp -n .env.dist .env && cp -n .env.playwright.dist .env.playwright || true",
56
57
  "precopy": "rm -rf dist && mkdir dist",
57
58
  "prepublishOnly": "npm run build",
58
59
  "start": "webpack --watch --mode=development",
59
60
  "stylelint": "stylelint \"src/**/*.{css,scss}\" \"!src/docs/_assets/generated/**\" --config stylelint.config.js",
60
- "test": "npm run jest"
61
+ "test": "npm run test:jest",
62
+ "test:jest": "npm run test:jest:ts && npm run test:jest:js",
63
+ "test:jest:js": "jest --coverage",
64
+ "test:jest:ts": "jest -c jest.config-ts.js --coverage",
65
+ "test:playwright-ct:all": "playwright test -c playwright-ct.config.ts",
66
+ "test:playwright-ct:all-with-update": "playwright test -c playwright-ct.config.ts --update-snapshots",
67
+ "test:playwright-ct:show-report": "playwright show-report --host=0.0.0.0 --port=9323"
61
68
  },
62
69
  "dependencies": {
63
70
  "normalize.css": "^8.0.1"
@@ -77,11 +84,18 @@
77
84
  "@babel/preset-react": "^7.24.7",
78
85
  "@babel/register": "^7.24.6",
79
86
  "@happy-dom/jest-environment": "^16.6.0",
87
+ "@playwright/experimental-ct-react": "^1.50.1",
80
88
  "@stylistic/stylelint-config": "^1.0.1",
81
89
  "@svgr/webpack": "^8.1.0",
82
90
  "@testing-library/jest-dom": "^6.6.3",
83
91
  "@testing-library/react": "^16.1.0",
84
92
  "@testing-library/user-event": "^14.5.2",
93
+ "@types/jest": "^29.5.14",
94
+ "@types/node": "^22.13.5",
95
+ "@types/react": "^19.0.10",
96
+ "@types/react-dom": "^19.0.4",
97
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
98
+ "@typescript-eslint/parser": "^7.18.0",
85
99
  "@visionappscz/eslint-config-visionapps": "^1.7.0",
86
100
  "@visionappscz/stylelint-config": "^4.0.0",
87
101
  "autoprefixer": "^10.4.19",
@@ -89,13 +103,17 @@
89
103
  "babel-loader": "^9.1.3",
90
104
  "core-js": "^3.37.1",
91
105
  "css-loader": "^7.1.2",
106
+ "dotenv": "^16.4.7",
92
107
  "eslint": "^8.57.0",
93
108
  "eslint-config-airbnb": "^19.0.4",
109
+ "eslint-config-airbnb-typescript": "^18.0.0",
110
+ "eslint-plugin-deprecation": "^3.0.0",
94
111
  "eslint-plugin-import": "^2.29.1",
95
112
  "eslint-plugin-jsx-a11y": "^6.9.0",
96
113
  "eslint-plugin-markdown": "^3.0.1",
97
114
  "eslint-plugin-react": "^7.34.3",
98
115
  "eslint-plugin-react-hooks": "^4.6.2",
116
+ "eslint-plugin-typescript-sort-keys": "^3.3.0",
99
117
  "identity-obj-proxy": "^3.0.0",
100
118
  "jest": "^29.7.0",
101
119
  "markdownlint-cli2": "^0.13.0",
@@ -110,6 +128,7 @@
110
128
  "stylelint": "^16.7.0",
111
129
  "stylelint-webpack-plugin": "^5.0.1",
112
130
  "terser-webpack-plugin": "^5.3.10",
131
+ "ts-jest": "^29.2.6",
113
132
  "webpack": "^5.92.1",
114
133
  "webpack-cli": "^5.1.4",
115
134
  "webpack-visualizer-plugin2": "^1.1.0"
@@ -0,0 +1,68 @@
1
+ import {
2
+ defineConfig,
3
+ devices,
4
+ } from '@playwright/experimental-ct-react';
5
+ import { parseEnvironment } from './tests/playwright/env/parseEnvironment';
6
+
7
+ const environment = parseEnvironment();
8
+
9
+ const isCI = !!process.env.CI;
10
+
11
+ /**
12
+ * @see https://playwright.dev/docs/test-configuration
13
+ */
14
+ module.exports = defineConfig({
15
+ // Fail the build on CI if you accidentally left test.only in the source code
16
+ forbidOnly: isCI,
17
+ // Run tests in files in parallel
18
+ fullyParallel: true,
19
+ // Directory where the output generated by Playwright is stored
20
+ outputDir: './tests/playwright/.temp/playwright-ct-output/',
21
+ // Run tests for following projects
22
+ projects: [
23
+ {
24
+ name: 'chromium',
25
+ use: { ...devices['Desktop Chrome'] },
26
+ },
27
+ ],
28
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
29
+ reporter: 'html',
30
+ // Retry failed tests once to eliminate flakiness
31
+ retries: 1,
32
+ // Directory where the snapshots are located
33
+ // (must be same as testDir to save snapshots in place where tests are located)
34
+ snapshotDir: './src/',
35
+ // Directory where the tests are located
36
+ testDir: './src/',
37
+ // Pattern to search for test files
38
+ testMatch: '*/*.spec.{ts,tsx}',
39
+ // Timeout for each test
40
+ timeout: 10 * 1000,
41
+ // Do not update snapshots automatically
42
+ updateSnapshots: 'none',
43
+ // Configuration for Playwright component testing
44
+ use: {
45
+ // Directory where the cache is stored
46
+ ctCacheDir: './tests/playwright/.temp/playwright-ct-cache',
47
+ // Port to use for Playwright component endpoint
48
+ ctPort: environment.CT_PORT,
49
+ // Directory where the templates are stored
50
+ ctTemplateDir: './tests/playwright/templates',
51
+ // Launch options for Playwright
52
+ launchOptions: {
53
+ args: [
54
+ '--use-gl=egl',
55
+ // # Must be set to `new` to enable new version of Chromium Headless to obtain correct snapshots
56
+ '--headless=new',
57
+ ],
58
+ ignoreDefaultArgs: [
59
+ // # Must be ignored to enable new version of Chromium Headless to obtain correct snapshots
60
+ '--headless',
61
+ ],
62
+ },
63
+ // Collect trace on all retries
64
+ trace: 'on-all-retries',
65
+ },
66
+ // Number of workers to use
67
+ workers: environment.WORKERS,
68
+ });
@@ -57,9 +57,9 @@ export const Alert = ({
57
57
 
58
58
  Alert.defaultProps = {
59
59
  color: 'note',
60
- icon: null,
60
+ icon: undefined,
61
61
  id: undefined,
62
- onClose: null,
62
+ onClose: undefined,
63
63
  };
64
64
 
65
65
  Alert.propTypes = {
@@ -101,18 +101,18 @@ export const Button = React.forwardRef((props, ref) => {
101
101
  });
102
102
 
103
103
  Button.defaultProps = {
104
- afterLabel: null,
105
- beforeLabel: null,
104
+ afterLabel: undefined,
105
+ beforeLabel: undefined,
106
106
  block: false,
107
107
  color: 'primary',
108
108
  disabled: false,
109
- endCorner: null,
110
- feedbackIcon: null,
109
+ endCorner: undefined,
110
+ feedbackIcon: undefined,
111
111
  id: undefined,
112
112
  labelVisibility: 'xs',
113
113
  priority: 'filled',
114
114
  size: 'medium',
115
- startCorner: null,
115
+ startCorner: undefined,
116
116
  type: 'button',
117
117
  };
118
118
 
@@ -28,7 +28,7 @@ See [API](#api) for all available options.
28
28
  - **Don't overwhelm your UI** with too many high-emphasis actions. There should
29
29
  always be one but chances are that having more of them is not necessary.
30
30
 
31
- - Ensure the **button action is well recognazible** across your target audience.
31
+ - Ensure the **button action is well recognizable** across your target audience.
32
32
  This is especially important when using the button [with an icon only](#icon-buttons).
33
33
 
34
34
  ## Priorities
@@ -6,7 +6,7 @@ import { withGlobalProps } from '../../providers/globalProps';
6
6
  import { classNames } from '../../helpers/classNames/classNames';
7
7
  import { transferProps } from '../../helpers/transferProps';
8
8
  import { getRootPriorityClassName } from '../_helpers/getRootPriorityClassName';
9
- import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
9
+ import { isChildrenEmpty } from '../../helpers/isChildrenEmpty/isChildrenEmpty';
10
10
  import styles from './ButtonGroup.module.scss';
11
11
  import { ButtonGroupContext } from './ButtonGroupContext';
12
12
 
@@ -48,7 +48,7 @@ export const ButtonGroup = ({
48
48
 
49
49
  ButtonGroup.defaultProps = {
50
50
  block: false,
51
- children: null,
51
+ children: undefined,
52
52
  disabled: false,
53
53
  priority: 'filled',
54
54
  size: 'medium',
@@ -35,6 +35,7 @@
35
35
  }
36
36
 
37
37
  .isRootDisabled {
38
+ border: theme.$disabled-border-width solid var(--rui-local-border-color, theme.$disabled-border-color);
38
39
  background-color: theme.$disabled-background-color;
39
40
  opacity: theme.$disabled-opacity;
40
41
  }
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
3
  import { transferProps } from '../../helpers/transferProps';
4
4
  import { withGlobalProps } from '../../providers/globalProps';
5
- import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
5
+ import { isChildrenEmpty } from '../../helpers/isChildrenEmpty/isChildrenEmpty';
6
6
  import styles from './Card.module.scss';
7
7
 
8
8
  export const CardFooter = ({
@@ -24,7 +24,7 @@ export const CardFooter = ({
24
24
  };
25
25
 
26
26
  CardFooter.defaultProps = {
27
- children: null,
27
+ children: undefined,
28
28
  };
29
29
 
30
30
  CardFooter.propTypes = {
@@ -300,6 +300,8 @@ Separate your card actions with CardFooter. See
300
300
  | `--rui-Card--raised__box-shadow` | Box shadow of raised card |
301
301
  | `--rui-Card--disabled__background-color` | Card background color in disabled state |
302
302
  | `--rui-Card--disabled__opacity` | Card opacity in disabled state |
303
+ | `--rui-Card--disabled__border-width` | Card border width in disabled state |
304
+ | `--rui-Card--disabled__border-color` | Card border color in disabled state |
303
305
 
304
306
  ### Theming Variants
305
307
 
@@ -9,3 +9,5 @@ $raised-box-shadow: var(--rui-Card--raised__box-shadow);
9
9
 
10
10
  $disabled-background-color: var(--rui-Card--disabled__background-color);
11
11
  $disabled-opacity: var(--rui-Card--disabled__opacity);
12
+ $disabled-border-width: var(--rui-Card--disabled__border-width);
13
+ $disabled-border-color: var(--rui-Card--disabled__border-color);
@@ -79,14 +79,14 @@ export const CheckboxField = React.forwardRef((props, ref) => {
79
79
 
80
80
  CheckboxField.defaultProps = {
81
81
  disabled: false,
82
- helpText: null,
82
+ helpText: undefined,
83
83
  id: undefined,
84
84
  isLabelVisible: true,
85
85
  labelPosition: 'after',
86
86
  renderAsRequired: false,
87
87
  required: false,
88
- validationState: null,
89
- validationText: null,
88
+ validationState: undefined,
89
+ validationText: undefined,
90
90
  };
91
91
 
92
92
  CheckboxField.propTypes = {
@@ -78,6 +78,7 @@ export const FileInputField = React.forwardRef((props, ref) => {
78
78
  const handleFileChange = (files, event) => {
79
79
  if (files.length === 0) {
80
80
  setSelectedFileNames([]);
81
+ onFilesChanged([], event);
81
82
  return;
82
83
  }
83
84
 
@@ -85,6 +86,7 @@ export const FileInputField = React.forwardRef((props, ref) => {
85
86
  // does not accept multiple files, no files are processed.
86
87
  if (files.length > 1 && !multiple) {
87
88
  setSelectedFileNames([]);
89
+ onFilesChanged([], event);
88
90
  return;
89
91
  }
90
92
 
@@ -198,15 +200,20 @@ export const FileInputField = React.forwardRef((props, ref) => {
198
200
  type="button"
199
201
  >
200
202
  <Text lines={1}>
201
- {!selectedFileNames.length && (
203
+ {isDragging && (
204
+ <span className={styles.dropFileHereText}>
205
+ {translations.FileInputField.dropFileHere}
206
+ </span>
207
+ )}
208
+ {!isDragging && !selectedFileNames.length && (
202
209
  <>
203
210
  <span className={styles.dropZoneLink}>{translations.FileInputField.browse}</span>
204
211
  {' '}
205
212
  {translations.FileInputField.drop}
206
213
  </>
207
214
  )}
208
- {selectedFileNames.length === 1 && selectedFileNames[0]}
209
- {selectedFileNames.length > 1 && (
215
+ {!isDragging && selectedFileNames.length === 1 && selectedFileNames[0]}
216
+ {!isDragging && selectedFileNames.length > 1 && (
210
217
  <>
211
218
  {selectedFileNames.length}
212
219
  {' '}
@@ -216,7 +223,7 @@ export const FileInputField = React.forwardRef((props, ref) => {
216
223
  </Text>
217
224
  </button>
218
225
  </div>
219
- {helpText && (
226
+ {(helpText && !inputGroupContext) && (
220
227
  <div
221
228
  className={styles.helpText}
222
229
  id={`${id}__helpText`}
@@ -224,7 +231,7 @@ export const FileInputField = React.forwardRef((props, ref) => {
224
231
  {helpText}
225
232
  </div>
226
233
  )}
227
- {validationText && (
234
+ {(validationText && !inputGroupContext) && (
228
235
  <div
229
236
  className={styles.validationText}
230
237
  id={`${id}__validationText`}
@@ -240,14 +247,14 @@ export const FileInputField = React.forwardRef((props, ref) => {
240
247
  FileInputField.defaultProps = {
241
248
  disabled: false,
242
249
  fullWidth: false,
243
- helpText: null,
250
+ helpText: undefined,
244
251
  isLabelVisible: true,
245
252
  layout: 'vertical',
246
253
  multiple: false,
247
254
  required: false,
248
255
  size: 'medium',
249
- validationState: null,
250
- validationText: null,
256
+ validationState: undefined,
257
+ validationText: undefined,
251
258
  };
252
259
 
253
260
  FileInputField.propTypes = {
@@ -261,6 +268,9 @@ FileInputField.propTypes = {
261
268
  fullWidth: PropTypes.bool,
262
269
  /**
263
270
  * Optional help text.
271
+ *
272
+ * Help text is never rendered when the component is placed into `InputGroup`.
273
+ * If a help text is needed, it must be defined on the `InputGroup` component instead.
264
274
  */
265
275
  helpText: PropTypes.node,
266
276
  /**
@@ -314,6 +324,9 @@ FileInputField.propTypes = {
314
324
  validationState: PropTypes.oneOf(['invalid', 'valid', 'warning']),
315
325
  /**
316
326
  * Validation message to be displayed.
327
+ *
328
+ * Validation text is never rendered when the component is placed into `InputGroup`.
329
+ * If a validation text is needed, it must be defined on the `InputGroup` component instead.
317
330
  */
318
331
  validationText: PropTypes.node,
319
332
  };
@@ -64,6 +64,10 @@
64
64
  --rui-local-border-color: #{settings.$drop-zone-active-border-color};
65
65
  }
66
66
 
67
+ .dropFileHereText {
68
+ color: var(--rui-local-link-color, var(--rui-color-text-link));
69
+ }
70
+
67
71
  .dropZoneLink {
68
72
  @include links.base();
69
73
 
@@ -3,7 +3,7 @@ import React, { useMemo } from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { classNames } from '../../helpers/classNames/classNames';
5
5
  import { transferProps } from '../../helpers/transferProps';
6
- import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
6
+ import { isChildrenEmpty } from '../../helpers/isChildrenEmpty/isChildrenEmpty';
7
7
  import { FormLayoutContext } from './FormLayoutContext';
8
8
  import styles from './FormLayout.module.scss';
9
9
 
@@ -68,7 +68,7 @@ export const FormLayout = ({
68
68
 
69
69
  FormLayout.defaultProps = {
70
70
  autoWidth: false,
71
- children: null,
71
+ children: undefined,
72
72
  fieldLayout: 'vertical',
73
73
  labelWidth: 'default',
74
74
  };
@@ -5,7 +5,7 @@ import { classNames } from '../../helpers/classNames/classNames';
5
5
  import { transferProps } from '../../helpers/transferProps';
6
6
  import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
7
7
  import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
8
- import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
8
+ import { isChildrenEmpty } from '../../helpers/isChildrenEmpty/isChildrenEmpty';
9
9
  import { FormLayoutContext } from './FormLayoutContext';
10
10
  import styles from './FormLayoutCustomField.module.scss';
11
11
 
@@ -80,15 +80,15 @@ export const FormLayoutCustomField = ({
80
80
  };
81
81
 
82
82
  FormLayoutCustomField.defaultProps = {
83
- children: null,
83
+ children: undefined,
84
84
  disabled: false,
85
85
  fullWidth: false,
86
86
  id: undefined,
87
- innerFieldSize: null,
88
- label: null,
87
+ innerFieldSize: undefined,
88
+ label: undefined,
89
89
  labelForId: undefined,
90
90
  required: false,
91
- validationState: null,
91
+ validationState: undefined,
92
92
  };
93
93
 
94
94
  FormLayoutCustomField.propTypes = {
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { transferProps } from '../../helpers/transferProps';
5
- import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
5
+ import { isChildrenEmpty } from '../../helpers/isChildrenEmpty/isChildrenEmpty';
6
6
  import { generateResponsiveCustomProperties } from './_helpers/generateResponsiveCustomProperties';
7
7
  import styles from './Grid.module.scss';
8
8
 
@@ -54,7 +54,7 @@ Grid.defaultProps = {
54
54
  alignContent: undefined,
55
55
  alignItems: undefined,
56
56
  autoFlow: undefined,
57
- children: null,
57
+ children: undefined,
58
58
  columnGap: 4,
59
59
  columns: '1fr',
60
60
  justifyContent: undefined,
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { transferProps } from '../../helpers/transferProps';
5
- import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
5
+ import { isChildrenEmpty } from '../../helpers/isChildrenEmpty/isChildrenEmpty';
6
6
  import { generateResponsiveCustomProperties } from './_helpers/generateResponsiveCustomProperties';
7
7
  import styles from './Grid.module.scss';
8
8
 
@@ -35,7 +35,7 @@ export const GridSpan = ({
35
35
  /* eslint-disable sort-keys */
36
36
 
37
37
  GridSpan.defaultProps = {
38
- children: null,
38
+ children: undefined,
39
39
  columns: 1,
40
40
  rows: 1,
41
41
  tag: 'div',
@@ -8,7 +8,7 @@ import { classNames } from '../../helpers/classNames/classNames';
8
8
  import { transferProps } from '../../helpers/transferProps';
9
9
  import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
10
10
  import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
11
- import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
11
+ import { isChildrenEmpty } from '../../helpers/isChildrenEmpty/isChildrenEmpty';
12
12
  import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
13
13
  import { FormLayoutContext } from '../FormLayout';
14
14
  import { Text } from '../Text';
@@ -18,6 +18,7 @@ import styles from './InputGroup.module.scss';
18
18
  export const InputGroup = ({
19
19
  children,
20
20
  disabled,
21
+ helpTexts,
21
22
  id,
22
23
  isLabelVisible,
23
24
  label,
@@ -89,7 +90,21 @@ export const InputGroup = ({
89
90
  {children}
90
91
  </InputGroupContext.Provider>
91
92
  </div>
92
- {validationTexts && (
93
+ {helpTexts && helpTexts.length > 0 && (
94
+ <ul
95
+ className={styles.helpText}
96
+ id={id && `${id}__helpTexts`}
97
+ >
98
+ {helpTexts.map((helpText) => (
99
+ <li key={helpText}>
100
+ <Text blockLevel>
101
+ {helpText}
102
+ </Text>
103
+ </li>
104
+ ))}
105
+ </ul>
106
+ )}
107
+ {validationTexts && validationTexts.length > 0 && (
93
108
  <ul
94
109
  className={styles.validationText}
95
110
  id={id && `${id}__validationTexts`}
@@ -109,14 +124,15 @@ export const InputGroup = ({
109
124
  };
110
125
 
111
126
  InputGroup.defaultProps = {
112
- children: null,
127
+ children: undefined,
113
128
  disabled: false,
129
+ helpTexts: undefined,
114
130
  id: undefined,
115
131
  isLabelVisible: true,
116
132
  layout: 'vertical',
117
133
  required: false,
118
134
  size: 'medium',
119
- validationTexts: null,
135
+ validationTexts: undefined,
120
136
  };
121
137
 
122
138
  InputGroup.propTypes = {
@@ -133,6 +149,10 @@ InputGroup.propTypes = {
133
149
  * If `true`, the whole input group with all nested inputs and buttons will be disabled.
134
150
  */
135
151
  disabled: PropTypes.bool,
152
+ /**
153
+ * An array of help texts to be displayed.
154
+ */
155
+ helpTexts: PropTypes.arrayOf(PropTypes.node),
136
156
  /**
137
157
  * ID of the root HTML element.
138
158
  *
@@ -171,7 +191,7 @@ InputGroup.propTypes = {
171
191
  /**
172
192
  * An array of validation messages to be displayed.
173
193
  */
174
- validationTexts: PropTypes.node,
194
+ validationTexts: PropTypes.arrayOf(PropTypes.node),
175
195
  };
176
196
 
177
197
  export const InputGroupWithGlobalProps = withGlobalProps(InputGroup, 'InputGroup');
@@ -1,4 +1,4 @@
1
- // 1. The class name is intentionally singular because it's targeted by other mixins too.
1
+ // 1. The class names are intentionally singular because they are also targeted by other mixins.
2
2
  // 2. Use a block-level display mode to prevent extra white space below grouped inputs in Safari.
3
3
  // 3. Let wide input groups honor the minimum input width and overflow horizontally without wrapping and distorting
4
4
  // the inputs.
@@ -39,6 +39,7 @@
39
39
  }
40
40
 
41
41
  // 1.
42
+ .helpText,
42
43
  .validationText {
43
44
  @include reset.list();
44
45
  @include foundation.help-text();