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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +2 -11
  2. package/dist/react-ui.css +17 -17
  3. package/dist/react-ui.development.css +1230 -1053
  4. package/dist/react-ui.development.js +126 -66
  5. package/dist/react-ui.js +1 -1
  6. package/package.json +5 -5
  7. package/src/components/Alert/Alert.jsx +4 -4
  8. package/src/components/Alert/README.md +1 -27
  9. package/src/components/Alert/_settings.scss +1 -2
  10. package/src/components/Badge/Badge.jsx +2 -2
  11. package/src/components/Button/Button.jsx +2 -2
  12. package/src/components/ButtonGroup/ButtonGroup.jsx +2 -2
  13. package/src/components/Card/Card.jsx +6 -6
  14. package/src/components/Card/Card.module.scss +2 -2
  15. package/src/components/Card/CardBody.jsx +1 -1
  16. package/src/components/Card/CardFooter.jsx +1 -1
  17. package/src/components/Card/README.md +12 -30
  18. package/src/components/Card/_settings.scss +1 -2
  19. package/src/components/Card/_theme.scss +2 -0
  20. package/src/components/CheckboxField/CheckboxField.jsx +2 -2
  21. package/src/components/FileInputField/FileInputField.jsx +147 -21
  22. package/src/components/FileInputField/FileInputField.module.scss +87 -1
  23. package/src/components/FileInputField/README.md +83 -2
  24. package/src/components/FileInputField/_settings.scss +15 -0
  25. package/src/components/FormLayout/FormLayout.jsx +2 -2
  26. package/src/components/FormLayout/FormLayoutCustomField.jsx +2 -2
  27. package/src/components/FormLayout/README.md +8 -6
  28. package/src/components/Grid/Grid.jsx +1 -1
  29. package/src/components/Grid/Grid.module.scss +2 -2
  30. package/src/components/Grid/GridSpan.jsx +1 -1
  31. package/src/components/InputGroup/InputGroup.jsx +2 -2
  32. package/src/components/InputGroup/InputGroup.module.scss +3 -3
  33. package/src/components/InputGroup/README.md +1 -1
  34. package/src/components/Modal/Modal.jsx +117 -45
  35. package/src/components/Modal/Modal.module.scss +34 -18
  36. package/src/components/Modal/ModalBody.jsx +2 -2
  37. package/src/components/Modal/ModalBody.module.scss +18 -0
  38. package/src/components/Modal/ModalCloseButton.jsx +1 -1
  39. package/src/components/Modal/ModalContent.jsx +1 -1
  40. package/src/components/Modal/ModalFooter.jsx +2 -2
  41. package/src/components/Modal/ModalFooter.module.scss +6 -2
  42. package/src/components/Modal/ModalHeader.jsx +2 -2
  43. package/src/components/Modal/ModalHeader.module.scss +8 -1
  44. package/src/components/Modal/ModalTitle.jsx +1 -1
  45. package/src/components/Modal/README.md +391 -171
  46. package/src/components/Modal/_animations.scss +9 -0
  47. package/src/components/Modal/_helpers/dialogOnCancelHandler.js +28 -0
  48. package/src/components/Modal/_helpers/dialogOnClickHandler.js +46 -0
  49. package/src/components/Modal/_helpers/dialogOnCloseHandler.js +28 -0
  50. package/src/components/Modal/_helpers/dialogOnKeyDownHandler.js +62 -0
  51. package/src/components/Modal/_helpers/getPositionClassName.js +1 -1
  52. package/src/components/Modal/_hooks/useModalFocus.js +24 -91
  53. package/src/components/Modal/_settings.scss +4 -3
  54. package/src/components/Modal/_theme.scss +1 -0
  55. package/src/components/Paper/Paper.jsx +2 -2
  56. package/src/components/Popover/Popover.jsx +2 -2
  57. package/src/components/Popover/PopoverWrapper.jsx +1 -1
  58. package/src/components/Radio/Radio.jsx +2 -2
  59. package/src/components/ScrollView/ScrollView.jsx +2 -2
  60. package/src/components/SelectField/SelectField.jsx +2 -2
  61. package/src/components/Table/Table.jsx +1 -1
  62. package/src/components/Tabs/Tabs.jsx +1 -1
  63. package/src/components/Tabs/TabsItem.jsx +2 -2
  64. package/src/components/Text/Text.jsx +2 -2
  65. package/src/components/TextArea/TextArea.jsx +2 -2
  66. package/src/components/TextField/TextField.jsx +2 -2
  67. package/src/components/TextLink/TextLink.jsx +1 -1
  68. package/src/components/Toggle/Toggle.jsx +2 -2
  69. package/src/components/Toolbar/Toolbar.jsx +2 -2
  70. package/src/components/Toolbar/ToolbarGroup.jsx +2 -2
  71. package/src/components/Toolbar/ToolbarItem.jsx +2 -2
  72. package/src/helpers/classNames/README.md +65 -0
  73. package/src/helpers/classNames/classNames.js +11 -0
  74. package/src/helpers/classNames/index.js +1 -0
  75. package/src/helpers/transferProps/README.md +46 -0
  76. package/src/helpers/transferProps/index.js +1 -0
  77. package/src/index.js +3 -3
  78. package/src/styles/elements/_links.scss +2 -14
  79. package/src/styles/generic/_focus.scss +1 -1
  80. package/src/styles/theme/_form-fields.scss +5 -5
  81. package/src/styles/tools/_accessibility.scss +3 -5
  82. package/src/styles/tools/_collections.scss +3 -20
  83. package/src/styles/tools/_links.scss +17 -0
  84. package/src/styles/tools/form-fields/_box-field-elements.scss +21 -9
  85. package/src/styles/tools/form-fields/_box-field-layout.scss +2 -2
  86. package/src/styles/tools/form-fields/_box-field-sizes.scss +6 -10
  87. package/src/styles/tools/form-fields/_variants.scss +10 -10
  88. package/src/theme.scss +53 -3
  89. package/src/translations/en.js +5 -0
  90. package/src/styles/settings/_z-indexes.scss +0 -2
  91. package/src/utils/classNames.js +0 -8
  92. /package/src/{utils → helpers/transferProps}/transferProps.js +0 -0
@@ -0,0 +1,46 @@
1
+ // Disable coverage for the following function
2
+ /* istanbul ignore next */
3
+
4
+ /**
5
+ * Handles the click event of the dialog which is fired when the user clicks on the dialog or on its descendants.
6
+ *
7
+ * This handler is used to close the dialog when the user clicks on the backdrop, if it is allowed to close
8
+ * on backdrop click and the close button is not disabled.
9
+ *
10
+ * @param e
11
+ * @param closeButtonRef
12
+ * @param dialogRef
13
+ * @param allowCloseOnBackdropClick
14
+ */
15
+ export const dialogOnClickHandler = (
16
+ e,
17
+ closeButtonRef,
18
+ dialogRef,
19
+ allowCloseOnBackdropClick,
20
+ ) => {
21
+ // If it is not allowed to close modal on backdrop click, do nothing.
22
+ if (!allowCloseOnBackdropClick) {
23
+ return;
24
+ }
25
+
26
+ // Detection of the click on the backdrop is based on the following conditions:
27
+ // 1. The click target is the dialog itself. This prevents detection of clicks on the dialog's children.
28
+ // 2. The click is outside the dialog's boundaries.
29
+ const dialogRect = dialogRef.current.getBoundingClientRect();
30
+ const isClickedOnBackdrop = dialogRef.current === e.target && (
31
+ e.clientX < dialogRect.left
32
+ || e.clientX > dialogRect.right
33
+ || e.clientY < dialogRect.top
34
+ || e.clientY > dialogRect.bottom
35
+ );
36
+
37
+ // If user does not click on the backdrop, do nothing.
38
+ if (!isClickedOnBackdrop) {
39
+ return;
40
+ }
41
+
42
+ // If the close button is not disabled, close the modal.
43
+ if (closeButtonRef?.current != null && closeButtonRef?.current?.disabled === false) {
44
+ closeButtonRef.current.click();
45
+ }
46
+ };
@@ -0,0 +1,28 @@
1
+ // Disable coverage for the following function
2
+ /* istanbul ignore next */
3
+
4
+ /**
5
+ * Handles the close event of the dialog which is fired when the user presses the Escape key or triggers close event
6
+ * by native dialog mechanism.
7
+ *
8
+ * It prevents the default behaviour of the native dialog and closes the dialog manually by clicking the close button,
9
+ * if the close button is not disabled.
10
+ *
11
+ * @param e
12
+ * @param closeButtonRef
13
+ * @param onCloseHandler
14
+ */
15
+ export const dialogOnCloseHandler = (e, closeButtonRef, onCloseHandler = undefined) => {
16
+ // Prevent the default behaviour of the event as we want to close dialog manually.
17
+ e.preventDefault();
18
+
19
+ // If the close button is not disabled, close the modal.
20
+ if (closeButtonRef?.current != null && closeButtonRef?.current?.disabled === false) {
21
+ closeButtonRef.current.click();
22
+ }
23
+
24
+ // This is a custom handler that is passed as a prop to the Modal component
25
+ if (onCloseHandler) {
26
+ onCloseHandler(e);
27
+ }
28
+ };
@@ -0,0 +1,62 @@
1
+ // Disable coverage for the following function
2
+ /* istanbul ignore next */
3
+
4
+ /**
5
+ * Handles the keydown event of the dialog which is fired when the user presses a key within the dialog.
6
+ *
7
+ * This handler is used to stop propagation of the Escape key press, if it is not allowed to close
8
+ * on Escape key and the close button is disabled.
9
+ *
10
+ * It is also used to trigger the primary action when the user presses the Enter key, if it is allowed to trigger
11
+ * the primary action on Enter key and the primary button is not disabled. This applies only when the focused
12
+ * element is an input or select as other elements should not trigger the primary action. Textarea is omitted
13
+ * as Enter key is used for new line.
14
+ *
15
+ * @param e
16
+ * @param closeButtonRef
17
+ * @param primaryButtonRef
18
+ * @param allowCloseOnEscapeKey
19
+ * @param allowPrimaryActionOnEnterKey
20
+ */
21
+ export const dialogOnKeyDownHandler = (
22
+ e,
23
+ closeButtonRef,
24
+ primaryButtonRef,
25
+ allowCloseOnEscapeKey,
26
+ allowPrimaryActionOnEnterKey,
27
+ ) => {
28
+ if (e.key === 'Escape') {
29
+ // Prevent closing the modal using the Escape key when one of the following conditions is met:
30
+ // 1. The close button is not present
31
+ // 2. The close button is disabled
32
+ // 3. `allowCloseOnEscapeKey` is set to `false`
33
+ //
34
+ // ⚠️ Else-if statement calling `closeButtonRef.current.click()` is necessary due to missing support
35
+ // of close event in happy-dom library. When this is fixed, the `else` statement can be removed
36
+ // as the `closeButtonRef.current.click()` will be handled by `dialogOnCancelHandler.js`.
37
+ if (
38
+ closeButtonRef?.current == null
39
+ || closeButtonRef?.current?.disabled === true
40
+ || !allowCloseOnEscapeKey
41
+ ) {
42
+ e.preventDefault();
43
+ } else if (process?.env?.NODE_ENV === 'test') {
44
+ closeButtonRef.current.click();
45
+ }
46
+ }
47
+
48
+ // Trigger the primary action when the Enter key is pressed and the following conditions are met:
49
+ // 1. The primary button is present
50
+ // 2. The primary button is not disabled
51
+ // 3. `allowPrimaryActionOnEnterKey` is set to `true`
52
+ // 4. The focused element is an input or select (text area is omitted as Enter key is used for new line)
53
+ if (
54
+ e.key === 'Enter'
55
+ && primaryButtonRef?.current != null
56
+ && primaryButtonRef?.current?.disabled === false
57
+ && allowPrimaryActionOnEnterKey
58
+ && ['INPUT', 'SELECT'].includes(e.target.nodeName)
59
+ ) {
60
+ primaryButtonRef.current.click();
61
+ }
62
+ };
@@ -3,5 +3,5 @@ export const getPositionClassName = (modalPosition, styles) => {
3
3
  return styles.isRootPositionTop;
4
4
  }
5
5
 
6
- return styles.isRootPositionCenter;
6
+ return null;
7
7
  };
@@ -2,9 +2,8 @@ import { useEffect } from 'react';
2
2
 
3
3
  export const useModalFocus = (
4
4
  autoFocus,
5
- childrenWrapperRef,
5
+ dialogRef,
6
6
  primaryButtonRef,
7
- closeButtonRef,
8
7
  ) => {
9
8
  useEffect(
10
9
  () => {
@@ -12,115 +11,49 @@ export const useModalFocus = (
12
11
  // field element (input, textarea or select) or primary button and focuses it. This is
13
12
  // necessary to have focus on one of those elements to be able to submit the form
14
13
  // by pressing Enter key. If there are neither, it tries to focus any other focusable
15
- // elements. In case there are none or `autoFocus` is disabled, childrenWrapperElement
14
+ // elements. In case there are none or `autoFocus` is disabled, dialogElement
16
15
  // (Modal itself) is focused.
17
16
 
18
- const childrenWrapperElement = childrenWrapperRef.current;
17
+ const dialogElement = dialogRef.current;
19
18
 
20
- if (childrenWrapperElement == null) {
19
+ if (dialogElement == null) {
21
20
  return () => {};
22
21
  }
23
22
 
24
23
  const childrenFocusableElements = Array.from(
25
- childrenWrapperElement.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),
24
+ dialogElement.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),
26
25
  );
27
26
 
28
27
  const firstFocusableElement = childrenFocusableElements[0];
29
- const lastFocusableElement = childrenFocusableElements[childrenFocusableElements.length - 1];
30
28
 
31
- const resolveFocusBeforeListener = () => {
32
- if (!autoFocus || childrenFocusableElements.length === 0) {
33
- childrenWrapperElement.tabIndex = -1;
34
- childrenWrapperElement.focus();
35
- return;
36
- }
37
-
38
- const firstFormFieldEl = childrenFocusableElements.find(
39
- (element) => ['INPUT', 'TEXTAREA', 'SELECT'].includes(element.nodeName) && !element.disabled,
40
- );
41
-
42
- if (firstFormFieldEl) {
43
- firstFormFieldEl.focus();
44
- return;
45
- }
46
-
47
- if (primaryButtonRef?.current != null) {
48
- primaryButtonRef.current.focus();
49
- return;
50
- }
51
-
52
- firstFocusableElement.focus();
53
- };
54
-
55
- const keyPressHandler = (e) => {
56
- if (e.key === 'Escape' && closeButtonRef?.current != null) {
57
- closeButtonRef.current.click();
58
- return;
59
- }
60
-
61
- if (
62
- e.key === 'Enter'
63
- && e.target.nodeName !== 'BUTTON'
64
- && e.target.nodeName !== 'TEXTAREA'
65
- && e.target.nodeName !== 'A'
66
- && primaryButtonRef?.current != null
67
- ) {
68
- primaryButtonRef.current.click();
69
- return;
70
- }
71
-
72
- // Following code traps focus inside Modal
73
-
74
- if (e.key !== 'Tab') {
75
- return;
76
- }
77
-
78
- if (childrenFocusableElements.length === 0) {
79
- childrenWrapperElement.focus();
80
- e.preventDefault();
81
- return;
82
- }
83
-
84
- if (
85
- ![
86
- ...childrenFocusableElements,
87
- childrenWrapperElement,
88
- ]
89
- .includes(window.document.activeElement)
90
- ) {
91
- firstFocusableElement.focus();
92
- e.preventDefault();
93
- return;
94
- }
29
+ if (!autoFocus || childrenFocusableElements.length === 0) {
30
+ dialogElement.tabIndex = -1;
31
+ dialogElement.focus();
32
+ return () => {};
33
+ }
95
34
 
96
- if (!e.shiftKey && window.document.activeElement === lastFocusableElement) {
97
- firstFocusableElement.focus();
98
- e.preventDefault();
99
- return;
100
- }
35
+ const firstFormFieldEl = childrenFocusableElements.find(
36
+ (element) => ['INPUT', 'TEXTAREA', 'SELECT'].includes(element.nodeName) && !element.disabled,
37
+ );
101
38
 
102
- if (e.shiftKey
103
- && (
104
- window.document.activeElement === firstFocusableElement
105
- || window.document.activeElement === childrenWrapperElement
106
- )
107
- ) {
108
- lastFocusableElement.focus();
109
- e.preventDefault();
110
- }
111
- };
39
+ if (firstFormFieldEl) {
40
+ firstFormFieldEl.focus();
41
+ return () => {};
42
+ }
112
43
 
113
- resolveFocusBeforeListener();
44
+ if (primaryButtonRef?.current != null && primaryButtonRef?.current?.disabled === false) {
45
+ primaryButtonRef.current.focus();
46
+ return () => {};
47
+ }
114
48
 
115
- window.document.addEventListener('keydown', keyPressHandler, false);
49
+ firstFocusableElement.focus();
116
50
 
117
- return () => window.document.removeEventListener('keydown', keyPressHandler, false);
51
+ return () => {};
118
52
  },
119
53
  [
120
54
  autoFocus,
121
- childrenWrapperRef,
55
+ dialogRef,
122
56
  primaryButtonRef,
123
- closeButtonRef,
124
57
  ],
125
58
  );
126
59
  };
@@ -1,9 +1,10 @@
1
1
  @use "sass:map";
2
- @use "../../styles/settings/z-indexes";
2
+ @use "../../styles/settings/collections";
3
3
  @use "../../styles/theme/borders";
4
4
  @use "../../styles/theme/typography";
5
5
 
6
+ $border-width: borders.$width;
6
7
  $border-radius: borders.$radius-2;
7
- $z-index: z-indexes.$modal;
8
- $backdrop-z-index: z-indexes.$modal-backdrop;
9
8
  $title-font-size: map.get(typography.$font-size-values, 2);
9
+ $colors: collections.$feedback-colors;
10
+ $themeable-properties: border-color, background-color;
@@ -10,6 +10,7 @@ $footer-gap: var(--rui-Modal__footer__gap);
10
10
  $backdrop-background: var(--rui-Modal__backdrop__background);
11
11
  $outer-spacing-xs: var(--rui-Modal__outer-spacing--xs);
12
12
  $outer-spacing-sm: var(--rui-Modal__outer-spacing--sm);
13
+ $animation-duration: var(--rui-Modal__animation__duration);
13
14
 
14
15
  $sizes: (
15
16
  auto: (
@@ -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 styles from './Paper.module.scss';
7
7
 
8
8
  export const Paper = ({
@@ -1,9 +1,9 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
3
  import { createPortal } from 'react-dom';
4
+ import { transferProps } from '../../helpers/transferProps';
5
+ import { classNames } from '../../helpers/classNames';
4
6
  import { withGlobalProps } from '../../providers/globalProps';
5
- import { classNames } from '../../utils/classNames';
6
- import { transferProps } from '../../utils/transferProps';
7
7
  import cleanPlacementStyle from './_helpers/cleanPlacementStyle';
8
8
  import getRootSideClassName from './_helpers/getRootSideClassName';
9
9
  import getRootAlignmentClassName from './_helpers/getRootAlignmentClassName';
@@ -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 './PopoverWrapper.module.scss';
6
6
 
7
7
  export const PopoverWrapper = ({
@@ -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 { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
8
8
  import { FormLayoutContext } from '../FormLayout';
@@ -8,8 +8,8 @@ import React, {
8
8
  } from 'react';
9
9
  import { TranslationsContext } from '../../providers/translations';
10
10
  import { withGlobalProps } from '../../providers/globalProps';
11
- import { classNames } from '../../utils/classNames';
12
- import { transferProps } from '../../utils/transferProps';
11
+ import { classNames } from '../../helpers/classNames/classNames';
12
+ import { transferProps } from '../../helpers/transferProps';
13
13
  import { getElementsPositionDifference } from './_helpers/getElementsPositionDifference';
14
14
  import { useLoadResize } from './_hooks/useLoadResizeHook';
15
15
  import { useScrollPosition } from './_hooks/useScrollPositionHook';
@@ -1,8 +1,8 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React, { useContext } from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
- import { classNames } from '../../utils/classNames';
5
- import { transferProps } from '../../utils/transferProps';
4
+ import { classNames } from '../../helpers/classNames/classNames';
5
+ import { transferProps } from '../../helpers/transferProps';
6
6
  import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
7
7
  import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
8
8
  import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
@@ -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 { TableHeaderCell } from './_components/TableHeaderCell';
6
6
  import { TableBodyCell } from './_components/TableBodyCell';
7
7
  import styles from './Table.module.scss';
@@ -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 './Tabs.module.scss';
6
6
 
7
7
  export const Tabs = ({
@@ -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 styles from './TabsItem.module.scss';
7
7
 
8
8
  export const TabsItem = ({
@@ -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 { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
7
7
  import { getRootClampClassName } from './_helpers/getRootClampClassName';
8
8
  import { getRootHyphensClassName } from './_helpers/getRootHyphensClassName';
@@ -1,8 +1,8 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React, { useContext } from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
- import { classNames } from '../../utils/classNames';
5
- import { transferProps } from '../../utils/transferProps';
4
+ import { classNames } from '../../helpers/classNames/classNames';
5
+ import { transferProps } from '../../helpers/transferProps';
6
6
  import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
7
7
  import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
8
8
  import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
@@ -1,8 +1,8 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React, { useContext } from 'react';
3
3
  import { withGlobalProps } from '../../providers/globalProps';
4
- import { classNames } from '../../utils/classNames';
5
- import { transferProps } from '../../utils/transferProps';
4
+ import { classNames } from '../../helpers/classNames/classNames';
5
+ import { transferProps } from '../../helpers/transferProps';
6
6
  import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
7
7
  import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
8
8
  import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
@@ -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 './TextLink.module.scss';
6
6
 
7
7
  export const TextLink = ({
@@ -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 './Toggle.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 { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
7
7
  import { getAlignClassName } from './_helpers/getAlignClassName';
8
8
  import { getJustifyClassName } from './_helpers/getJustifyClassName';
@@ -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 { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
7
7
  import { getAlignClassName } from './_helpers/getAlignClassName';
8
8
  import styles from './Toolbar.module.scss';
@@ -1,7 +1,7 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
- import { classNames } from '../../utils/classNames';
4
- import { transferProps } from '../../utils/transferProps';
3
+ import { classNames } from '../../helpers/classNames/classNames';
4
+ import { transferProps } from '../../helpers/transferProps';
5
5
  import { withGlobalProps } from '../../providers/globalProps';
6
6
  import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
7
7
  import styles from './Toolbar.module.scss';
@@ -0,0 +1,65 @@
1
+ # classNames
2
+
3
+ The `classNames` helper function simplifies creating a string passable to
4
+ the `class` / `className` attribute.
5
+
6
+ It accepts multiple arguments, filters out invalid values, and returns a single
7
+ string where the remaining parameters are joined by a space.
8
+
9
+ ## Usage
10
+
11
+ To use `classNames` helper, you need to import it first:
12
+
13
+ ```js
14
+ import { classNames } from '@react-ui-org/react-ui';
15
+ ```
16
+
17
+ And use it:
18
+
19
+ ```docoff-react-preview
20
+ <>
21
+ <div
22
+ className={classNames(
23
+ 'd-block',
24
+ new Date('2025-01-01T00:00:00') < new Date() && 'text-danger',
25
+ )}
26
+ >
27
+ {(new Date()).toLocaleDateString()}
28
+ </div>
29
+ <div
30
+ className={classNames(
31
+ 'd-block',
32
+ new Date('3000-01-01T00:00:00') < new Date() && 'text-danger',
33
+ )}
34
+ >
35
+ {(new Date()).toLocaleDateString()}
36
+ </div>
37
+ </>
38
+ ```
39
+
40
+ ## Parameter Filtering
41
+
42
+ The `classNames` function:
43
+
44
+ * filters out all values that are not strings
45
+ * filters out empty strings
46
+ * filters out whitespace only strings
47
+
48
+ <!-- markdownlint-disable MD010 -->
49
+ ```docoff-react-preview
50
+ {classNames(
51
+ 'class-1',
52
+ 'class-2 class-3',
53
+ ' ',
54
+ ' ', // non-breakable space
55
+ ' ', // tab
56
+ '',
57
+ 0,
58
+ 1,
59
+ null,
60
+ undefined,
61
+ true,
62
+ false,
63
+ )}
64
+ ```
65
+ <!-- markdownlint-enable MD010 -->
@@ -0,0 +1,11 @@
1
+ export const classNames = (...classes) => {
2
+ const filteredClassNames = classes.filter(
3
+ (className) => typeof className === 'string'
4
+ && className.trim().length > 0,
5
+ );
6
+
7
+ return filteredClassNames.length > 0
8
+ ? filteredClassNames.join(' ')
9
+ // React does not render attributes whose value is `undefined` and we do not want an empty `class` attribute in HTML
10
+ : undefined;
11
+ };
@@ -0,0 +1 @@
1
+ export { classNames } from './classNames';
@@ -0,0 +1,46 @@
1
+ # transferProps
2
+
3
+ The `transferProps` helper controls passing of props from the React component
4
+ to the HTML element.
5
+
6
+ It enables making the component interactive and helps to improve its
7
+ accessibility. However some props should never be passed to the HTML element
8
+ as it would break things. This function is used to filter them out. Among these
9
+ props are:
10
+
11
+ - `children`
12
+ - `className`
13
+ - `contentEditable`
14
+ - `dangerouslySetInnerHtml`
15
+ - `ref`
16
+ - `staticContext`
17
+ - `style`
18
+ - `suppressContentEditableWarning`
19
+
20
+ 👉 When run in development mode, the function will log the error to the console
21
+ if any invalid props are passed.
22
+
23
+ ## Basic Usage
24
+
25
+ To use `transferProps` helper, you need to import it first:
26
+
27
+ ```js
28
+ import { transferProps } from "@react-ui-org/react-ui";
29
+ ```
30
+
31
+ And use it:
32
+
33
+ ```jsx
34
+ const CustomComponent = ({
35
+ children,
36
+ id,
37
+ ...restProps
38
+ }) => (
39
+ <div
40
+ {...transferProps(restProps)}
41
+ id={id && `${id}__customComponent`}
42
+ >
43
+ {children}
44
+ </div>
45
+ );
46
+ ```
@@ -0,0 +1 @@
1
+ export { transferProps } from './transferProps';