@react-ui-org/react-ui 0.59.3 → 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 +8 -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.3",
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);
@@ -198,15 +198,20 @@ export const FileInputField = React.forwardRef((props, ref) => {
198
198
  type="button"
199
199
  >
200
200
  <Text lines={1}>
201
- {!selectedFileNames.length && (
201
+ {isDragging && (
202
+ <span className={styles.dropFileHereText}>
203
+ {translations.FileInputField.dropFileHere}
204
+ </span>
205
+ )}
206
+ {!isDragging && !selectedFileNames.length && (
202
207
  <>
203
208
  <span className={styles.dropZoneLink}>{translations.FileInputField.browse}</span>
204
209
  {' '}
205
210
  {translations.FileInputField.drop}
206
211
  </>
207
212
  )}
208
- {selectedFileNames.length === 1 && selectedFileNames[0]}
209
- {selectedFileNames.length > 1 && (
213
+ {!isDragging && selectedFileNames.length === 1 && selectedFileNames[0]}
214
+ {!isDragging && selectedFileNames.length > 1 && (
210
215
  <>
211
216
  {selectedFileNames.length}
212
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: {