@react-ui-org/react-ui 0.59.2 → 0.60.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 (33) hide show
  1. package/dist/react-ui.css +3 -3
  2. package/dist/react-ui.development.css +6 -0
  3. package/dist/react-ui.development.js +41 -31
  4. package/dist/react-ui.js +1 -1
  5. package/package.json +1 -1
  6. package/src/components/ButtonGroup/ButtonGroup.jsx +1 -1
  7. package/src/components/Card/Card.module.scss +1 -0
  8. package/src/components/Card/CardFooter.jsx +1 -1
  9. package/src/components/Card/README.md +2 -0
  10. package/src/components/Card/_theme.scss +2 -0
  11. package/src/components/FileInputField/FileInputField.jsx +14 -3
  12. package/src/components/FileInputField/FileInputField.module.scss +4 -0
  13. package/src/components/FormLayout/FormLayout.jsx +1 -1
  14. package/src/components/FormLayout/FormLayoutCustomField.jsx +1 -1
  15. package/src/components/Grid/Grid.jsx +1 -1
  16. package/src/components/Grid/GridSpan.jsx +1 -1
  17. package/src/components/InputGroup/InputGroup.jsx +1 -1
  18. package/src/components/Modal/Modal.jsx +14 -1
  19. package/src/components/Modal/ModalBody.jsx +1 -1
  20. package/src/components/Modal/ModalContent.jsx +1 -1
  21. package/src/components/Modal/_helpers/dialogOnClickHandler.js +6 -0
  22. package/src/components/ScrollView/ScrollView.jsx +39 -9
  23. package/src/components/ScrollView/_hooks/useScrollPositionHook.js +4 -4
  24. package/src/components/Text/Text.jsx +1 -1
  25. package/src/components/Toolbar/Toolbar.jsx +1 -1
  26. package/src/components/Toolbar/ToolbarGroup.jsx +1 -1
  27. package/src/components/Toolbar/ToolbarItem.jsx +1 -1
  28. package/src/helpers/isChildrenEmpty/README.md +57 -0
  29. package/src/helpers/isChildrenEmpty/index.js +1 -0
  30. package/src/index.js +1 -0
  31. package/src/theme.scss +2 -0
  32. package/src/translations/en.js +1 -0
  33. /package/src/{components/_helpers → helpers/isChildrenEmpty}/isChildrenEmpty.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.59.2",
4
+ "version": "0.60.0",
5
5
  "keywords": [
6
6
  "react",
7
7
  "ui",
@@ -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
 
@@ -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 = ({
@@ -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);
@@ -19,6 +19,8 @@ import { Text } from '../Text';
19
19
  import { FormLayoutContext } from '../FormLayout';
20
20
  import styles from './FileInputField.module.scss';
21
21
 
22
+ /* istanbul ignore file */
23
+
22
24
  export const FileInputField = React.forwardRef((props, ref) => {
23
25
  const {
24
26
  disabled,
@@ -47,6 +49,10 @@ export const FileInputField = React.forwardRef((props, ref) => {
47
49
  const internalInputRef = useRef();
48
50
 
49
51
  const handleReset = useCallback((event) => {
52
+ if (internalInputRef.current) {
53
+ internalInputRef.current.value = '';
54
+ }
55
+
50
56
  setSelectedFileNames([]);
51
57
  onFilesChanged([], event);
52
58
  }, [onFilesChanged]);
@@ -192,15 +198,20 @@ export const FileInputField = React.forwardRef((props, ref) => {
192
198
  type="button"
193
199
  >
194
200
  <Text lines={1}>
195
- {!selectedFileNames.length && (
201
+ {isDragging && (
202
+ <span className={styles.dropFileHereText}>
203
+ {translations.FileInputField.dropFileHere}
204
+ </span>
205
+ )}
206
+ {!isDragging && !selectedFileNames.length && (
196
207
  <>
197
208
  <span className={styles.dropZoneLink}>{translations.FileInputField.browse}</span>
198
209
  {' '}
199
210
  {translations.FileInputField.drop}
200
211
  </>
201
212
  )}
202
- {selectedFileNames.length === 1 && selectedFileNames[0]}
203
- {selectedFileNames.length > 1 && (
213
+ {!isDragging && selectedFileNames.length === 1 && selectedFileNames[0]}
214
+ {!isDragging && selectedFileNames.length > 1 && (
204
215
  <>
205
216
  {selectedFileNames.length}
206
217
  {' '}
@@ -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
 
@@ -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
 
@@ -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
 
@@ -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
 
@@ -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';
@@ -61,6 +61,7 @@ export const Modal = ({
61
61
  ...restProps
62
62
  }) => {
63
63
  const internalDialogRef = useRef();
64
+ const mouseDownTarget = useRef(null);
64
65
 
65
66
  useEffect(() => {
66
67
  internalDialogRef.current.showModal();
@@ -79,7 +80,13 @@ export const Modal = ({
79
80
  [closeButtonRef, restProps.onCancel],
80
81
  );
81
82
  const onClick = useCallback(
82
- (e) => dialogOnClickHandler(e, closeButtonRef, internalDialogRef, allowCloseOnBackdropClick),
83
+ (e) => dialogOnClickHandler(
84
+ e,
85
+ closeButtonRef,
86
+ internalDialogRef,
87
+ allowCloseOnBackdropClick,
88
+ mouseDownTarget.current,
89
+ ),
83
90
  [allowCloseOnBackdropClick, closeButtonRef, internalDialogRef],
84
91
  );
85
92
  const onClose = useCallback(
@@ -101,11 +108,17 @@ export const Modal = ({
101
108
  primaryButtonRef,
102
109
  ],
103
110
  );
111
+
112
+ const onMouseDown = useCallback((e) => {
113
+ mouseDownTarget.current = e.target;
114
+ }, []);
115
+
104
116
  const events = {
105
117
  onCancel,
106
118
  onClick,
107
119
  onClose,
108
120
  onKeyDown,
121
+ onMouseDown,
109
122
  };
110
123
 
111
124
  if (portalId === null) {
@@ -3,7 +3,7 @@ import React 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 { getScrollingClassName } from './_helpers/getScrollingClassName';
8
8
  import styles from './ModalBody.module.scss';
9
9
 
@@ -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 styles from './ModalContent.module.scss';
7
7
 
8
8
  export const ModalContent = ({
@@ -17,12 +17,18 @@ export const dialogOnClickHandler = (
17
17
  closeButtonRef,
18
18
  dialogRef,
19
19
  allowCloseOnBackdropClick,
20
+ mouseDownTarget,
20
21
  ) => {
21
22
  // If it is not allowed to close modal on backdrop click, do nothing.
22
23
  if (!allowCloseOnBackdropClick) {
23
24
  return;
24
25
  }
25
26
 
27
+ // If the click started on the inside of the dialog, do nothing.
28
+ if (e.target !== mouseDownTarget) {
29
+ return;
30
+ }
31
+
26
32
  // Detection of the click on the backdrop is based on the following conditions:
27
33
  // 1. The click target is the dialog itself. This prevents detection of clicks on the dialog's children.
28
34
  // 2. The click is outside the dialog's boundaries.
@@ -5,6 +5,7 @@ import React, {
5
5
  useLayoutEffect,
6
6
  useRef,
7
7
  useState,
8
+ useCallback,
8
9
  } from 'react';
9
10
  import { TranslationsContext } from '../../providers/translations';
10
11
  import { withGlobalProps } from '../../providers/globalProps';
@@ -58,20 +59,26 @@ export const ScrollView = React.forwardRef((props, ref) => {
58
59
  const blankRef = useRef(null);
59
60
  const scrollViewViewportEl = ref ?? blankRef;
60
61
 
61
- const handleScrollViewState = (currentPosition) => {
62
+ const handleScrollViewState = useCallback((currentPosition) => {
62
63
  const isScrolledAtStartActive = currentPosition[scrollPositionStart]
63
64
  <= -1 * EDGE_DETECTION_INACCURACY_PX;
64
65
  const isScrolledAtEndActive = currentPosition[scrollPositionEnd]
65
66
  >= EDGE_DETECTION_INACCURACY_PX;
66
67
 
67
- if (isScrolledAtStartActive !== isScrolledAtStart) {
68
- setIsScrolledAtStart(isScrolledAtStartActive);
69
- }
68
+ setIsScrolledAtStart((prevIsScrolledAtStart) => {
69
+ if (isScrolledAtStartActive !== prevIsScrolledAtStart) {
70
+ return isScrolledAtStartActive;
71
+ }
72
+ return prevIsScrolledAtStart;
73
+ });
70
74
 
71
- if (isScrolledAtEndActive !== isScrolledAtEnd) {
72
- setIsScrolledAtEnd(isScrolledAtEndActive);
73
- }
74
- };
75
+ setIsScrolledAtEnd((prevIsScrolledAtEnd) => {
76
+ if (isScrolledAtEndActive !== prevIsScrolledAtEnd) {
77
+ return isScrolledAtEndActive;
78
+ }
79
+ return prevIsScrolledAtEnd;
80
+ });
81
+ }, [scrollPositionStart, scrollPositionEnd]);
75
82
 
76
83
  /**
77
84
  * It handles scroll event fired on `scrollViewViewportEl` element. If autoScroll is in progress,
@@ -146,7 +153,6 @@ export const ScrollView = React.forwardRef((props, ref) => {
146
153
 
147
154
  useScrollPosition(
148
155
  (currentPosition) => (handleScrollViewState(currentPosition)),
149
- [isScrolledAtStart, isScrolledAtEnd],
150
156
  scrollViewContentEl,
151
157
  scrollViewViewportEl,
152
158
  debounce,
@@ -163,6 +169,30 @@ export const ScrollView = React.forwardRef((props, ref) => {
163
169
  [autoScroll, autoScrollChildrenKeys, autoScrollChildrenLength],
164
170
  );
165
171
 
172
+ // ResizeObserver to detect when content or viewport dimensions change due to style changes
173
+ useLayoutEffect(() => {
174
+ const contentElement = scrollViewContentEl.current;
175
+ const viewportElement = scrollViewViewportEl.current;
176
+
177
+ if (!contentElement || !viewportElement) {
178
+ return () => {};
179
+ }
180
+
181
+ const resizeObserver = new ResizeObserver(() => {
182
+ handleScrollViewState(
183
+ getElementsPositionDifference(scrollViewContentEl, scrollViewViewportEl),
184
+ );
185
+ });
186
+
187
+ // Observe both content and viewport for dimension changes
188
+ resizeObserver.observe(contentElement);
189
+ resizeObserver.observe(viewportElement);
190
+
191
+ return () => {
192
+ resizeObserver.disconnect();
193
+ };
194
+ }, [scrollViewContentEl, scrollViewViewportEl, handleScrollViewState]);
195
+
166
196
  const arrowHandler = (contentEl, viewportEl, scrollViewDirection, shiftDirection, step) => {
167
197
  const offset = shiftDirection === 'next' ? step : -1 * step;
168
198
  const differenceX = scrollViewDirection === 'horizontal' ? offset : 0;
@@ -1,10 +1,10 @@
1
1
  import {
2
- useLayoutEffect,
3
2
  useRef,
3
+ useEffect,
4
4
  } from 'react';
5
5
  import { getElementsPositionDifference } from '../_helpers/getElementsPositionDifference';
6
6
 
7
- export const useScrollPosition = (effect, dependencies, contentEl, viewportEl, wait) => {
7
+ export const useScrollPosition = (effect, contentEl, viewportEl, wait) => {
8
8
  const throttleTimeout = useRef(null);
9
9
 
10
10
  const callBack = (wasDelayed = false) => {
@@ -15,7 +15,7 @@ export const useScrollPosition = (effect, dependencies, contentEl, viewportEl, w
15
15
  }
16
16
  };
17
17
 
18
- useLayoutEffect(() => {
18
+ useEffect(() => {
19
19
  const viewport = viewportEl.current;
20
20
 
21
21
  const handleScroll = () => {
@@ -34,7 +34,7 @@ export const useScrollPosition = (effect, dependencies, contentEl, viewportEl, w
34
34
  clearTimeout(throttleTimeout.current);
35
35
  viewport.removeEventListener('scroll', handleScroll);
36
36
  };
37
- }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
37
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
38
38
  };
39
39
 
40
40
  export default useScrollPosition;
@@ -3,7 +3,7 @@ import React 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 { getRootClampClassName } from './_helpers/getRootClampClassName';
8
8
  import { getRootHyphensClassName } from './_helpers/getRootHyphensClassName';
9
9
  import { getRootWordWrappingClassName } from './_helpers/getRootWordWrappingClassName';
@@ -3,7 +3,7 @@ import React 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 { getAlignClassName } from './_helpers/getAlignClassName';
8
8
  import { getJustifyClassName } from './_helpers/getJustifyClassName';
9
9
  import styles from './Toolbar.module.scss';
@@ -3,7 +3,7 @@ import React 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 { getAlignClassName } from './_helpers/getAlignClassName';
8
8
  import styles from './Toolbar.module.scss';
9
9
 
@@ -3,7 +3,7 @@ import React from 'react';
3
3
  import { classNames } from '../../helpers/classNames/classNames';
4
4
  import { transferProps } from '../../helpers/transferProps';
5
5
  import { withGlobalProps } from '../../providers/globalProps';
6
- import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
6
+ import { isChildrenEmpty } from '../../helpers/isChildrenEmpty/isChildrenEmpty';
7
7
  import styles from './Toolbar.module.scss';
8
8
 
9
9
  export const ToolbarItem = ({
@@ -0,0 +1,57 @@
1
+ # isChildrenEmpty
2
+
3
+ The `isChildrenEmpty` helper function determines whether the given children
4
+ value should be considered "empty".
5
+
6
+ It is useful in React when conditionally rendering components based on
7
+ whether children contain meaningful content.
8
+
9
+ ## Usage
10
+
11
+ To use `isChildrenEmpty` helper, you need to import it first:
12
+
13
+ ```js
14
+ import { isChildrenEmpty } from '@react-ui-org/react-ui';
15
+ ```
16
+
17
+ Then use it:
18
+
19
+ ```docoff-react-preview
20
+
21
+ React.createElement(() => {
22
+ const children = null;
23
+ const isEmpty = isChildrenEmpty(children);
24
+
25
+ if (isEmpty === false) {
26
+ return (
27
+ <div>{children}</div>
28
+ );
29
+ }
30
+
31
+ return (
32
+ <div>Children not provided</div>
33
+ );
34
+ });
35
+ ```
36
+
37
+ ```docoff-react-preview
38
+ React.createElement(() => {
39
+ const children = (
40
+ <>
41
+ <h1>Title</h1>
42
+ <p>Content</p>
43
+ </>
44
+ );
45
+ const isEmpty = isChildrenEmpty(children);
46
+
47
+ if (isEmpty === false) {
48
+ return (
49
+ <div>{children}</div>
50
+ );
51
+ }
52
+
53
+ return (
54
+ <div>Children not provided</div>
55
+ );
56
+ });
57
+ ```
@@ -0,0 +1 @@
1
+ export { isChildrenEmpty } from './isChildrenEmpty';
package/src/index.js CHANGED
@@ -64,3 +64,4 @@ export { TranslationsProvider } from './providers/translations';
64
64
  // Helpers
65
65
  export { classNames } from './helpers/classNames';
66
66
  export { transferProps } from './helpers/transferProps';
67
+ export { isChildrenEmpty } from './helpers/isChildrenEmpty';
package/src/theme.scss CHANGED
@@ -832,6 +832,8 @@
832
832
  --rui-Card--raised__box-shadow: var(--rui-shadow-layer-1);
833
833
  --rui-Card--disabled__background-color: var(--rui-color-background-disabled);
834
834
  --rui-Card--disabled__opacity: var(--rui-ratio-disabled-opacity);
835
+ --rui-Card--disabled__border-width: var(--rui-dimension-border-width-1);
836
+ --rui-Card--disabled__border-color: var(--rui-color-border-primary);
835
837
 
836
838
  // Card: variant: success
837
839
  --rui-Card--success__color: var(--rui-color-text-primary);
@@ -5,6 +5,7 @@ export default {
5
5
  FileInputField: {
6
6
  browse: 'Browse',
7
7
  drop: 'or drop file here',
8
+ dropFileHere: 'Drop file here',
8
9
  filesSelected: 'files selected',
9
10
  },
10
11
  ModalCloseButton: {