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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. package/.nvmrc +1 -1
  2. package/dist/react-ui.css +14 -14
  3. package/dist/react-ui.development.css +225 -14
  4. package/dist/react-ui.development.js +102 -62
  5. package/dist/react-ui.js +1 -1
  6. package/package.json +12 -1
  7. package/src/components/Alert/Alert.jsx +3 -5
  8. package/src/components/Alert/Alert.module.scss +3 -3
  9. package/src/components/Alert/README.md +22 -10
  10. package/src/components/Badge/Badge.jsx +1 -1
  11. package/src/components/Button/Button.jsx +1 -1
  12. package/src/components/ButtonGroup/ButtonGroup.jsx +1 -1
  13. package/src/components/Card/Card.jsx +1 -1
  14. package/src/components/Card/Card.module.scss +6 -5
  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 +22 -0
  18. package/src/components/CheckboxField/CheckboxField.jsx +9 -3
  19. package/src/components/CheckboxField/README.md +110 -5
  20. package/src/components/FileInputField/FileInputField.jsx +1 -1
  21. package/src/components/FormLayout/FormLayout.jsx +1 -1
  22. package/src/components/FormLayout/FormLayoutCustomField.jsx +1 -1
  23. package/src/components/Grid/Grid.jsx +1 -1
  24. package/src/components/Grid/GridSpan.jsx +1 -1
  25. package/src/components/InputGroup/InputGroup.jsx +2 -2
  26. package/src/components/InputGroup/InputGroup.module.scss +9 -5
  27. package/src/components/Modal/Modal.jsx +1 -1
  28. package/src/components/Modal/ModalBody.jsx +1 -1
  29. package/src/components/Modal/ModalCloseButton.jsx +3 -5
  30. package/src/components/Modal/ModalContent.jsx +1 -1
  31. package/src/components/Modal/ModalFooter.jsx +1 -1
  32. package/src/components/Modal/ModalHeader.jsx +1 -1
  33. package/src/components/Modal/ModalTitle.jsx +1 -1
  34. package/src/components/Modal/README.md +18 -18
  35. package/src/components/Paper/Paper.jsx +1 -1
  36. package/src/components/Popover/Popover.jsx +58 -13
  37. package/src/components/Popover/Popover.module.scss +37 -9
  38. package/src/components/Popover/PopoverWrapper.jsx +1 -1
  39. package/src/components/Popover/README.md +60 -3
  40. package/src/components/Popover/_helpers/cleanPlacementStyle.js +20 -0
  41. package/src/components/Radio/README.md +103 -0
  42. package/src/components/Radio/Radio.jsx +9 -3
  43. package/src/components/Radio/Radio.module.scss +4 -0
  44. package/src/components/ScrollView/ScrollView.jsx +3 -5
  45. package/src/components/SelectField/README.md +103 -0
  46. package/src/components/SelectField/SelectField.jsx +9 -3
  47. package/src/components/Table/Table.jsx +1 -1
  48. package/src/components/Tabs/Tabs.jsx +1 -1
  49. package/src/components/Tabs/TabsItem.jsx +1 -1
  50. package/src/components/Text/Text.jsx +1 -1
  51. package/src/components/TextArea/TextArea.jsx +1 -1
  52. package/src/components/TextField/README.md +14 -2
  53. package/src/components/TextField/TextField.jsx +1 -1
  54. package/src/components/TextLink/README.md +10 -3
  55. package/src/components/TextLink/TextLink.jsx +1 -1
  56. package/src/components/TextLink/_theme.scss +3 -3
  57. package/src/components/Toggle/README.md +83 -1
  58. package/src/components/Toggle/Toggle.jsx +9 -3
  59. package/src/components/Toolbar/Toolbar.jsx +1 -1
  60. package/src/components/Toolbar/ToolbarGroup.jsx +1 -1
  61. package/src/components/Toolbar/ToolbarItem.jsx +1 -1
  62. package/src/components/_helpers/resolveContextOrProp.js +6 -3
  63. package/src/index.js +3 -2
  64. package/src/providers/globalProps/GlobalPropsContext.jsx +5 -0
  65. package/src/providers/globalProps/GlobalPropsProvider.jsx +33 -0
  66. package/src/providers/globalProps/index.js +3 -0
  67. package/src/{provider → providers/globalProps}/withGlobalProps.jsx +16 -16
  68. package/src/providers/translations/TranslationsContext.jsx +6 -0
  69. package/src/providers/translations/TranslationsProvider.jsx +33 -0
  70. package/src/providers/translations/index.js +2 -0
  71. package/src/styles/elements/_links.scss +7 -2
  72. package/src/styles/theme/_form-fields.scss +19 -0
  73. package/src/styles/theme/_links.scss +4 -3
  74. package/src/styles/tools/_collections.scss +79 -5
  75. package/src/styles/tools/form-fields/_foundation.scss +6 -4
  76. package/src/styles/tools/form-fields/_variants.scss +5 -1
  77. package/src/theme.scss +2 -1
  78. package/src/provider/RUIContext.jsx +0 -9
  79. package/src/provider/RUIProvider.jsx +0 -42
  80. package/src/provider/index.js +0 -3
@@ -1,9 +1,7 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React, { useContext } from 'react';
3
- import {
4
- RUIContext,
5
- withGlobalProps,
6
- } from '../../provider';
3
+ import { TranslationsContext } from '../../providers/translations';
4
+ import { withGlobalProps } from '../../providers/globalProps';
7
5
  import { transferProps } from '../../utils/transferProps';
8
6
  import styles from './ModalCloseButton.module.scss';
9
7
 
@@ -13,7 +11,7 @@ export const ModalCloseButton = React.forwardRef((props, ref) => {
13
11
  ...restProps
14
12
  } = props;
15
13
 
16
- const { translations } = useContext(RUIContext);
14
+ const translations = useContext(TranslationsContext);
17
15
 
18
16
  return (
19
17
  <button
@@ -1,6 +1,6 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
- import { withGlobalProps } from '../../provider';
3
+ import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { transferProps } from '../../utils/transferProps';
5
5
  import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
6
6
  import styles from './ModalContent.module.scss';
@@ -1,6 +1,6 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
- import { withGlobalProps } from '../../provider';
3
+ import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { classNames } from '../../utils/classNames';
5
5
  import { transferProps } from '../../utils/transferProps';
6
6
  import { getJustifyClassName } from './_helpers/getJustifyClassName';
@@ -1,6 +1,6 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
- import { withGlobalProps } from '../../provider';
3
+ import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { classNames } from '../../utils/classNames';
5
5
  import { transferProps } from '../../utils/transferProps';
6
6
  import { getJustifyClassName } from './_helpers/getJustifyClassName';
@@ -1,6 +1,6 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
- import { withGlobalProps } from '../../provider';
3
+ import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { transferProps } from '../../utils/transferProps';
5
5
  import styles from './ModalTitle.module.scss';
6
6
 
@@ -30,7 +30,7 @@ React.createElement(() => {
30
30
  React UI docs. You may not need it in your application.
31
31
  */}
32
32
  return (
33
- <RUIProvider globalProps={{
33
+ <GlobalPropsProvider globalProps={{
34
34
  Modal: { preventScrollUnderneath: window.document.documentElement }
35
35
  }}>
36
36
  <Button
@@ -72,7 +72,7 @@ React.createElement(() => {
72
72
  </Modal>
73
73
  )}
74
74
  </div>
75
- </RUIProvider>
75
+ </GlobalPropsProvider>
76
76
  );
77
77
  });
78
78
  ```
@@ -126,7 +126,7 @@ React.createElement(() => {
126
126
  React UI docs. You may not need it in your application.
127
127
  */}
128
128
  return (
129
- <RUIProvider globalProps={{
129
+ <GlobalPropsProvider globalProps={{
130
130
  Modal: { preventScrollUnderneath: window.document.documentElement }
131
131
  }}>
132
132
  <Button
@@ -247,7 +247,7 @@ React.createElement(() => {
247
247
  </Modal>
248
248
  )}
249
249
  </div>
250
- </RUIProvider>
250
+ </GlobalPropsProvider>
251
251
  );
252
252
  });
253
253
  ```
@@ -281,7 +281,7 @@ React.createElement(() => {
281
281
  React UI docs. You may not need it in your application.
282
282
  */}
283
283
  return (
284
- <RUIProvider globalProps={{
284
+ <GlobalPropsProvider globalProps={{
285
285
  Modal: { preventScrollUnderneath: window.document.documentElement }
286
286
  }}>
287
287
  <Button
@@ -374,7 +374,7 @@ React.createElement(() => {
374
374
  </Modal>
375
375
  )}
376
376
  </div>
377
- </RUIProvider>
377
+ </GlobalPropsProvider>
378
378
  );
379
379
  });
380
380
  ```
@@ -415,7 +415,7 @@ React.createElement(() => {
415
415
  React UI docs. You may not need it in your application.
416
416
  */}
417
417
  return (
418
- <RUIProvider globalProps={{
418
+ <GlobalPropsProvider globalProps={{
419
419
  Modal: { preventScrollUnderneath: window.document.documentElement }
420
420
  }}>
421
421
  <Button
@@ -517,7 +517,7 @@ React.createElement(() => {
517
517
  </Modal>
518
518
  )}
519
519
  </div>
520
- </RUIProvider>
520
+ </GlobalPropsProvider>
521
521
  );
522
522
  });
523
523
  ```
@@ -538,7 +538,7 @@ React.createElement(() => {
538
538
  React UI docs. You may not need it in your application.
539
539
  */}
540
540
  return (
541
- <RUIProvider globalProps={{
541
+ <GlobalPropsProvider globalProps={{
542
542
  Modal: { preventScrollUnderneath: window.document.documentElement }
543
543
  }}>
544
544
  <Button
@@ -605,7 +605,7 @@ React.createElement(() => {
605
605
  </Modal>
606
606
  )}
607
607
  </div>
608
- </RUIProvider>
608
+ </GlobalPropsProvider>
609
609
  );
610
610
  });
611
611
  ```
@@ -622,7 +622,7 @@ React.createElement(() => {
622
622
  React UI docs. You may not need it in your application.
623
623
  */}
624
624
  return (
625
- <RUIProvider globalProps={{
625
+ <GlobalPropsProvider globalProps={{
626
626
  Modal: { preventScrollUnderneath: window.document.documentElement }
627
627
  }}>
628
628
  <Button
@@ -665,7 +665,7 @@ React.createElement(() => {
665
665
  </Modal>
666
666
  )}
667
667
  </div>
668
- </RUIProvider>
668
+ </GlobalPropsProvider>
669
669
  );
670
670
  });
671
671
  ```
@@ -690,7 +690,7 @@ React.createElement(() => {
690
690
  React UI docs. You may not need it in your application.
691
691
  */}
692
692
  return (
693
- <RUIProvider globalProps={{
693
+ <GlobalPropsProvider globalProps={{
694
694
  Modal: { preventScrollUnderneath: window.document.documentElement }
695
695
  }}>
696
696
  <Button
@@ -734,7 +734,7 @@ React.createElement(() => {
734
734
  </Modal>
735
735
  )}
736
736
  </div>
737
- </RUIProvider>
737
+ </GlobalPropsProvider>
738
738
  );
739
739
  });
740
740
  ```
@@ -754,7 +754,7 @@ React.createElement(() => {
754
754
  React UI docs. You may not need it in your application.
755
755
  */}
756
756
  return (
757
- <RUIProvider globalProps={{
757
+ <GlobalPropsProvider globalProps={{
758
758
  Modal: { preventScrollUnderneath: window.document.documentElement }
759
759
  }}>
760
760
  <Button
@@ -807,7 +807,7 @@ React.createElement(() => {
807
807
  </Modal>
808
808
  )}
809
809
  </div>
810
- </RUIProvider>
810
+ </GlobalPropsProvider>
811
811
  );
812
812
  });
813
813
  ```
@@ -936,7 +936,7 @@ React.createElement(() => {
936
936
  React UI docs. You may not need it in your application.
937
937
  */}
938
938
  return (
939
- <RUIProvider globalProps={{
939
+ <GlobalPropsProvider globalProps={{
940
940
  Modal: { preventScrollUnderneath: window.document.documentElement }
941
941
  }}>
942
942
  <Button
@@ -999,7 +999,7 @@ React.createElement(() => {
999
999
  </Modal>
1000
1000
  )}
1001
1001
  </div>
1002
- </RUIProvider>
1002
+ </GlobalPropsProvider>
1003
1003
  );
1004
1004
  });
1005
1005
  ```
@@ -1,6 +1,6 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
- import { withGlobalProps } from '../../provider';
3
+ import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { classNames } from '../../utils/classNames';
5
5
  import { transferProps } from '../../utils/transferProps';
6
6
  import styles from './Paper.module.scss';
@@ -1,9 +1,10 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
3
  import { createPortal } from 'react-dom';
4
- import { withGlobalProps } from '../../provider';
4
+ import { withGlobalProps } from '../../providers/globalProps';
5
5
  import { classNames } from '../../utils/classNames';
6
6
  import { transferProps } from '../../utils/transferProps';
7
+ import cleanPlacementStyle from './_helpers/cleanPlacementStyle';
7
8
  import getRootSideClassName from './_helpers/getRootSideClassName';
8
9
  import getRootAlignmentClassName from './_helpers/getRootAlignmentClassName';
9
10
  import styles from './Popover.module.scss';
@@ -12,24 +13,42 @@ export const Popover = React.forwardRef((props, ref) => {
12
13
  const {
13
14
  placement,
14
15
  children,
16
+ placementStyle,
17
+ popoverTargetId,
15
18
  portalId,
16
19
  ...restProps
17
20
  } = props;
18
21
 
19
22
  const PopoverEl = (
20
- <div
21
- {...transferProps(restProps)}
22
- className={classNames(
23
- styles.root,
24
- ref && styles.isRootControlled,
25
- getRootSideClassName(placement, styles),
26
- getRootAlignmentClassName(placement, styles),
23
+ <>
24
+ {/**
25
+ * This hack is needed because the default behavior of the Popover API is to place the popover into a
26
+ * top-layer. It is currently not possible to position an element in the top-layer relative to a normal element.
27
+ * This will create a hidden browser popover, then with CSS it will open and close the RUI popover.
28
+ */}
29
+ {!!popoverTargetId && (
30
+ <div
31
+ className={styles.helper}
32
+ id={popoverTargetId}
33
+ popover="auto"
34
+ />
27
35
  )}
28
- ref={ref}
29
- >
30
- {children}
31
- <span className={styles.arrow} />
32
- </div>
36
+ <div
37
+ {...transferProps(restProps)}
38
+ className={classNames(
39
+ styles.root,
40
+ ref && styles.isRootControlled,
41
+ popoverTargetId && styles.controlledPopover,
42
+ getRootSideClassName(placement, styles),
43
+ getRootAlignmentClassName(placement, styles),
44
+ )}
45
+ ref={ref}
46
+ style={placementStyle ? cleanPlacementStyle(placementStyle) : null}
47
+ >
48
+ {children}
49
+ <span className={styles.arrow} />
50
+ </div>
51
+ </>
33
52
  );
34
53
 
35
54
  if (portalId === null) {
@@ -41,6 +60,8 @@ export const Popover = React.forwardRef((props, ref) => {
41
60
 
42
61
  Popover.defaultProps = {
43
62
  placement: 'bottom',
63
+ placementStyle: null,
64
+ popoverTargetId: null,
44
65
  portalId: null,
45
66
  };
46
67
 
@@ -67,6 +88,30 @@ Popover.propTypes = {
67
88
  'left-start',
68
89
  'left-end',
69
90
  ]),
91
+ /**
92
+ * Used for positioning the popover with a library like Floating UI. It is filtered,
93
+ * then passed to the popover as the `style` prop.
94
+ */
95
+ placementStyle: PropTypes.shape({
96
+ bottom: PropTypes.string,
97
+ inset: PropTypes.string,
98
+ 'inset-block-end': PropTypes.string,
99
+ 'inset-block-start': PropTypes.string,
100
+ 'inset-inline-end': PropTypes.string,
101
+ 'inset-inline-start': PropTypes.string,
102
+ left: PropTypes.string,
103
+ position: PropTypes.string,
104
+ right: PropTypes.string,
105
+ top: PropTypes.string,
106
+ 'transform-origin': PropTypes.string,
107
+ translate: PropTypes.string,
108
+ }),
109
+ /**
110
+ * If set, the popover will become controlled, meaning it will be hidden by default and will need a trigger to open.
111
+ * This sets the ID of the internal helper element for the popover.
112
+ * Assign the same ID to `popovertarget` of a trigger to make it open and close.
113
+ */
114
+ popoverTargetId: PropTypes.string,
70
115
  /**
71
116
  * If set, popover is rendered in the React Portal with that ID.
72
117
  */
@@ -1,6 +1,12 @@
1
- // 1. Reset positioning for controlled variant.
2
- // 2. Shift Popover so there is space for the arrow between Popover and reference element.
3
- // 3. Add top offset in case it's not defined by external library.
1
+ // 1. Hide the popover by default. This is needed because the popover is
2
+ // controlled via CSS with the help of the helper popover. The popover can't
3
+ // be displayed directly, because relative positioning doesn't work with
4
+ // elements on the top-layer, so this CSS hack is needed.
5
+ // 2. Hide the popover helper element.
6
+ // 3. If the popover helper is open, show the actual popover.
7
+ // 4. Reset positioning for controlled variant.
8
+ // 5. Shift Popover so there is space for the arrow between Popover and reference element.
9
+ // 6. Add top offset in case it's not defined by external library.
4
10
 
5
11
  @use "theme";
6
12
 
@@ -49,6 +55,28 @@
49
55
  }
50
56
  }
51
57
 
58
+ // Controlled popover
59
+ .controlledPopover {
60
+ display: none; // 1.
61
+ }
62
+
63
+ .helper {
64
+ position: fixed; // 2.
65
+ inset: unset;
66
+ top: 0;
67
+ right: 0;
68
+ width: auto;
69
+ height: auto;
70
+ padding: 0;
71
+ border: none;
72
+ background: transparent;
73
+ pointer-events: none;
74
+ }
75
+
76
+ .helper:popover-open ~ .controlledPopover {
77
+ display: block; // 3.
78
+ }
79
+
52
80
  // Sides
53
81
  .isRootAtTop {
54
82
  bottom: calc(100% + #{theme.$arrow-gap} - #{theme.$arrow-safe-rendering-overlap});
@@ -212,27 +240,27 @@
212
240
  .isRootControlled.isRootAtBottom,
213
241
  .isRootControlled.isRootAtLeft,
214
242
  .isRootControlled.isRootAtRight {
215
- inset: unset; // 1.
243
+ inset: unset; // 4.
216
244
  }
217
245
 
218
246
  .isRootControlled.isRootAtTop {
219
- transform: translate(0, calc(-1 * #{theme.$arrow-height})); // 2.
247
+ transform: translate(0, calc(-1 * #{theme.$arrow-height})); // 5.
220
248
  }
221
249
 
222
250
  .isRootControlled.isRootAtBottom {
223
- transform: translate(0, #{theme.$arrow-height}); // 2.
251
+ transform: translate(0, #{theme.$arrow-height}); // 5.
224
252
  }
225
253
 
226
254
  .isRootControlled.isRootAtLeft {
227
- transform: translate(calc(-1 * #{theme.$arrow-height}), 0); // 2.
255
+ transform: translate(calc(-1 * #{theme.$arrow-height}), 0); // 5.
228
256
  }
229
257
 
230
258
  .isRootControlled.isRootAtRight {
231
- transform: translate(#{theme.$arrow-height}, 0); // 2.
259
+ transform: translate(#{theme.$arrow-height}, 0); // 5.
232
260
  }
233
261
 
234
262
  .isRootControlled.isRootAtLeft.isRootAtStart,
235
263
  .isRootControlled.isRootAtRight.isRootAtStart {
236
- top: 0; // 3.
264
+ top: 0; // 6.
237
265
  }
238
266
  }
@@ -1,6 +1,6 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
- import { withGlobalProps } from '../../provider';
3
+ import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { transferProps } from '../../utils/transferProps';
5
5
  import styles from './PopoverWrapper.module.scss';
6
6
 
@@ -162,6 +162,29 @@ automatically, including smart position updates to ensure Popover visibility,
162
162
  we recommend to involve an external library designed specifically for this
163
163
  purpose.
164
164
 
165
+ To position the popover, you need to provide the `placementStyle` prop with the
166
+ style you want to apply to the popover. This prop should only be used to
167
+ position the popover. The allowed props are:
168
+
169
+ - `position`
170
+ - `inset`
171
+ - `inset-inline-start`
172
+ - `inset-inline-end`
173
+ - `inset-block-start`
174
+ - `inset-block-end`
175
+ - `top`
176
+ - `right`
177
+ - `bottom`
178
+ - `left`
179
+ - `translate`
180
+ - `transform-origin`
181
+
182
+ ⚠️ [`inset`][mdn-inset] is a shorthand for `top right bottom left`, not for
183
+ `inset-*` properties.
184
+
185
+ As opposed to `top right bottom left` and the `inset` shorthand, `inset-*`
186
+ properties are writing-direction aware.
187
+
165
188
  ℹ️ The following example is using external library [Floating UI]. To use
166
189
  Floating UI, install it first:
167
190
 
@@ -267,10 +290,10 @@ React.createElement(() => {
267
290
  <Popover
268
291
  id="my-advanced-popover"
269
292
  placement={finalPlacement}
270
- style={{
293
+ placementStyle={{
271
294
  position: strategy,
272
- top: y ? y : '',
273
- left: x ? x : '',
295
+ top: `${y}px`,
296
+ left: `${x}px`,
274
297
  }}
275
298
  ref={floating}
276
299
  >
@@ -284,6 +307,39 @@ React.createElement(() => {
284
307
  });
285
308
  ```
286
309
 
310
+ ## Controlled Popover
311
+
312
+ Popover API can be used to control visibility of Popover component. You need to
313
+ set `id` on the trigger element and matching `popoverTargetId` attribute on the
314
+ Popover component. This leverages the browser's Popover API to control the
315
+ popover, automatically closing it when the trigger or the backdrop is pressed.
316
+
317
+ ```docoff-react-preview
318
+ React.createElement(() => {
319
+ // All inline styles in this example are for demonstration purposes only.
320
+ return (
321
+ <div
322
+ style={{
323
+ display: 'grid',
324
+ placeContent: 'center',
325
+ minWidth: '20rem',
326
+ minHeight: '10rem',
327
+ }}
328
+ >
329
+ <PopoverWrapper>
330
+ <Button
331
+ label="Want to see a popover? Click me!"
332
+ popovertarget="my-popover-helper"
333
+ />
334
+ <Popover id="my-popover" popoverTargetId="my-popover-helper">
335
+ Hello there!
336
+ </Popover>
337
+ </PopoverWrapper>
338
+ </div>
339
+ );
340
+ });
341
+ ```
342
+
287
343
  ## Forwarding HTML Attributes
288
344
 
289
345
  In addition to the options below in the [component's API](#api) section, you
@@ -326,5 +382,6 @@ which enables [Advanced Positioning](#advanced-positioning).
326
382
 
327
383
  [div-attributes]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes
328
384
  [Floating UI]: https://floating-ui.com/docs/react-dom
385
+ [mdn-inset]: https://developer.mozilla.org/en-US/docs/Web/CSS/inset
329
386
  [React common props]: https://react.dev/reference/react-dom/components/common#common-props
330
387
  [ref]: https://reactjs.org/docs/refs-and-the-dom.html
@@ -0,0 +1,20 @@
1
+ export default (placementStyle) => {
2
+ const validProps = [
3
+ 'position',
4
+ 'inset',
5
+ 'inset-inline-start',
6
+ 'inset-inline-end',
7
+ 'inset-block-start',
8
+ 'inset-block-end',
9
+ 'top',
10
+ 'right',
11
+ 'bottom',
12
+ 'left',
13
+ 'translate',
14
+ 'transform-origin',
15
+ ];
16
+
17
+ return Object.fromEntries(
18
+ Object.entries(placementStyle).filter(([prop]) => validProps.includes(prop)),
19
+ );
20
+ };
@@ -237,6 +237,109 @@ have.
237
237
  })
238
238
  ```
239
239
 
240
+ ### Required State
241
+
242
+ The required state indicates that the input is mandatory.
243
+
244
+ ```docoff-react-preview
245
+ React.createElement(() => {
246
+ const [fruit, setFruit] = React.useState('apple');
247
+ return (
248
+ <Radio
249
+ label="Your favourite fruit"
250
+ onChange={(e) => setFruit(e.target.value)}
251
+ options={[
252
+ {
253
+ label: 'Apple',
254
+ value: 'apple',
255
+ },
256
+ {
257
+ label: 'Banana',
258
+ value: 'banana',
259
+ },
260
+ {
261
+ label: 'Grapefruit',
262
+ value: 'grapefruit',
263
+ },
264
+ ]}
265
+ value={fruit}
266
+ required
267
+ />
268
+ );
269
+ })
270
+ ```
271
+
272
+ #### Styling the Required State
273
+
274
+ All form fields in React UI can be
275
+ [styled](/docs/customize/theming/forms/#required-state)
276
+ to indicate the required state.
277
+
278
+ However, you may find yourself in a situation where a form field is valid in
279
+ both selected and unselected states, for example to turn on or off a feature.
280
+ If your project uses the label color as the primary means to indicate the
281
+ required state of input fields and the usual asterisk `*` is omitted, you may
282
+ want to keep the label color consistent for both states to avoid confusion.
283
+
284
+ For this edge case, there is the `renderAsRequired` prop:
285
+
286
+ ```docoff-react-preview
287
+ React.createElement(() => {
288
+ const [fruit, setFruit] = React.useState('apple');
289
+ const options = [
290
+ {
291
+ label: 'Apple',
292
+ value: 'apple',
293
+ },
294
+ {
295
+ label: 'Banana',
296
+ value: 'banana',
297
+ },
298
+ {
299
+ label: 'Grapefruit',
300
+ value: 'grapefruit',
301
+ },
302
+ ];
303
+ return (
304
+ <React.Fragment>
305
+ <style>
306
+ {`
307
+ .example {
308
+ display: flex;
309
+ flex-wrap: wrap;
310
+ gap: 1rem 0.5rem;
311
+ }
312
+
313
+ .example--themed-form-fields {
314
+ --rui-FormField__label__color: var(--rui-color-text-secondary);
315
+ --rui-FormField--required__label__color: var(--rui-color-text-primary);
316
+ --rui-FormField--required__sign: '';
317
+ }
318
+ `}
319
+ </style>
320
+ <div class="example example--themed-form-fields">
321
+ <Radio
322
+ label="This field is optional"
323
+ onChange={(e) => setFruit(e.target.value)}
324
+ options={options}
325
+ value={fruit}
326
+ />
327
+ <Radio
328
+ label="This field is optional but looks like required"
329
+ onChange={(e) => setFruit(e.target.value)}
330
+ options={options}
331
+ value={fruit}
332
+ renderAsRequired
333
+ />
334
+ </div>
335
+ </React.Fragment>
336
+ );
337
+ })
338
+ ```
339
+
340
+ It renders the field as if it was required, but doesn't add the `required`
341
+ attribute to the actual input.
342
+
240
343
  ### Disabled State
241
344
 
242
345
  It's possible to disable just some options or the whole set.
@@ -1,6 +1,6 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React, { useContext } from 'react';
3
- import { withGlobalProps } from '../../provider';
3
+ import { withGlobalProps } from '../../providers/globalProps';
4
4
  import { classNames } from '../../utils/classNames';
5
5
  import { transferProps } from '../../utils/transferProps';
6
6
  import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
@@ -16,6 +16,7 @@ export const Radio = ({
16
16
  label,
17
17
  layout,
18
18
  options,
19
+ renderAsRequired,
19
20
  required,
20
21
  validationState,
21
22
  validationText,
@@ -33,7 +34,7 @@ export const Radio = ({
33
34
  ? styles.isRootLayoutHorizontal
34
35
  : styles.isRootLayoutVertical,
35
36
  disabled && styles.isRootDisabled,
36
- required && styles.isRootRequired,
37
+ (renderAsRequired || required) && styles.isRootRequired,
37
38
  getRootValidationStateClassName(validationState, styles),
38
39
  )}
39
40
  disabled={disabled}
@@ -116,6 +117,7 @@ Radio.defaultProps = {
116
117
  id: undefined,
117
118
  isLabelVisible: true,
118
119
  layout: 'vertical',
120
+ renderAsRequired: false,
119
121
  required: false,
120
122
  validationState: null,
121
123
  validationText: null,
@@ -181,7 +183,11 @@ Radio.propTypes = {
181
183
  ]),
182
184
  })).isRequired,
183
185
  /**
184
- * If `true`, the input will be required.
186
+ * If `true`, the input will be rendered as if it was required.
187
+ */
188
+ renderAsRequired: PropTypes.bool,
189
+ /**
190
+ * If `true`, the input will be made and rendered as required, regardless of the `renderAsRequired` prop.
185
191
  */
186
192
  required: PropTypes.bool,
187
193
  /**