@react-ui-org/react-ui 0.51.0 → 0.52.1
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/lib.development.js +141 -33
- package/dist/lib.js +1 -1
- package/dist/react-ui.css +40 -0
- package/dist/react-ui.js +1 -0
- package/package.json +1 -1
- package/src/lib/components/Button/Button.jsx +17 -9
- package/src/lib/components/Button/_base.scss +21 -12
- package/src/lib/components/Button/_priorities.scss +1 -18
- package/src/lib/components/Button/_theme.scss +0 -10
- package/src/lib/components/ButtonGroup/ButtonGroup.jsx +5 -3
- package/src/lib/components/ButtonGroup/ButtonGroup.scss +26 -1
- package/src/lib/components/ButtonGroup/README.mdx +11 -1
- package/src/lib/components/ButtonGroup/_theme.scss +13 -0
- package/src/lib/components/FormLayout/README.mdx +5 -0
- package/src/lib/components/InputGroup/InputGroup.jsx +170 -0
- package/src/lib/components/InputGroup/InputGroup.scss +92 -0
- package/src/lib/components/InputGroup/InputGroupContext.js +3 -0
- package/src/lib/components/InputGroup/README.mdx +278 -0
- package/src/lib/components/InputGroup/_theme.scss +2 -0
- package/src/lib/components/InputGroup/index.js +2 -0
- package/src/lib/components/Modal/Modal.jsx +58 -97
- package/src/lib/components/Modal/README.mdx +288 -15
- package/src/lib/components/Modal/_helpers/getPositionClassName.js +7 -0
- package/src/lib/components/Modal/_helpers/getSizeClassName.js +19 -0
- package/src/lib/components/Modal/_hooks/useModalFocus.js +126 -0
- package/src/lib/components/Modal/_hooks/useModalScrollPrevention.js +35 -0
- package/src/lib/components/Modal/_settings.scss +1 -1
- package/src/lib/components/Radio/README.mdx +9 -1
- package/src/lib/components/Radio/Radio.jsx +39 -31
- package/src/lib/components/Radio/Radio.scss +12 -2
- package/src/lib/components/SelectField/SelectField.jsx +21 -8
- package/src/lib/components/SelectField/SelectField.scss +5 -0
- package/src/lib/components/TextField/TextField.jsx +21 -8
- package/src/lib/components/TextField/TextField.scss +5 -0
- package/src/lib/index.js +1 -0
- package/src/lib/styles/theme/_borders.scss +2 -1
- package/src/lib/styles/tools/form-fields/_box-field-elements.scss +19 -2
- package/src/lib/styles/tools/form-fields/_box-field-layout.scss +26 -14
- package/src/lib/styles/tools/form-fields/_box-field-sizes.scss +11 -8
- package/src/lib/styles/tools/form-fields/_foundation.scss +7 -0
- package/src/lib/theme.scss +23 -11
- /package/src/lib/components/{Button/helpers → _helpers}/getRootPriorityClassName.js +0 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
import { useEffect } from 'react';
|
2
|
+
|
3
|
+
export const useModalFocus = (
|
4
|
+
autoFocus,
|
5
|
+
childrenWrapperRef,
|
6
|
+
primaryButtonRef,
|
7
|
+
closeButtonRef,
|
8
|
+
) => {
|
9
|
+
useEffect(
|
10
|
+
() => {
|
11
|
+
// Following code finds all focusable elements and among them first not disabled form
|
12
|
+
// field element (input, textarea or select) or primary button and focuses it. This is
|
13
|
+
// necessary to have focus on one of those elements to be able to submit the form
|
14
|
+
// 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
|
16
|
+
// (Modal itself) is focused.
|
17
|
+
|
18
|
+
const childrenWrapperElement = childrenWrapperRef.current;
|
19
|
+
|
20
|
+
if (childrenWrapperElement == null) {
|
21
|
+
return () => {};
|
22
|
+
}
|
23
|
+
|
24
|
+
const childrenFocusableElements = Array.from(
|
25
|
+
childrenWrapperElement.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),
|
26
|
+
);
|
27
|
+
|
28
|
+
const firstFocusableElement = childrenFocusableElements[0];
|
29
|
+
const lastFocusableElement = childrenFocusableElements[childrenFocusableElements.length - 1];
|
30
|
+
|
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
|
+
}
|
95
|
+
|
96
|
+
if (!e.shiftKey && window.document.activeElement === lastFocusableElement) {
|
97
|
+
firstFocusableElement.focus();
|
98
|
+
e.preventDefault();
|
99
|
+
return;
|
100
|
+
}
|
101
|
+
|
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
|
+
};
|
112
|
+
|
113
|
+
resolveFocusBeforeListener();
|
114
|
+
|
115
|
+
window.document.addEventListener('keydown', keyPressHandler, false);
|
116
|
+
|
117
|
+
return () => window.document.removeEventListener('keydown', keyPressHandler, false);
|
118
|
+
},
|
119
|
+
[
|
120
|
+
autoFocus,
|
121
|
+
childrenWrapperRef,
|
122
|
+
primaryButtonRef,
|
123
|
+
closeButtonRef,
|
124
|
+
],
|
125
|
+
);
|
126
|
+
};
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
2
|
+
|
3
|
+
export const useModalScrollPrevention = (preventScrollUnderneath) => {
|
4
|
+
useLayoutEffect(
|
5
|
+
() => {
|
6
|
+
if (preventScrollUnderneath === 'off') {
|
7
|
+
return () => {};
|
8
|
+
}
|
9
|
+
|
10
|
+
if (preventScrollUnderneath === 'default') {
|
11
|
+
const scrollbarWidth = Math.abs(window.innerWidth - window.document.documentElement.clientWidth);
|
12
|
+
const prevOverflow = window.document.body.style.overflow;
|
13
|
+
const prevPaddingRight = window.document.body.style.paddingRight;
|
14
|
+
|
15
|
+
window.document.body.style.overflow = 'hidden';
|
16
|
+
|
17
|
+
if (Number.isNaN(parseInt(prevPaddingRight, 10))) {
|
18
|
+
window.document.body.style.paddingRight = `${scrollbarWidth}px`;
|
19
|
+
} else {
|
20
|
+
window.document.body.style.paddingRight = `calc(${prevPaddingRight} + ${scrollbarWidth}px)`;
|
21
|
+
}
|
22
|
+
|
23
|
+
return () => {
|
24
|
+
window.document.body.style.overflow = prevOverflow;
|
25
|
+
window.document.body.style.paddingRight = prevPaddingRight;
|
26
|
+
};
|
27
|
+
}
|
28
|
+
|
29
|
+
preventScrollUnderneath?.start();
|
30
|
+
|
31
|
+
return preventScrollUnderneath?.reset;
|
32
|
+
},
|
33
|
+
[preventScrollUnderneath],
|
34
|
+
);
|
35
|
+
};
|
@@ -3,7 +3,7 @@
|
|
3
3
|
@use "../../styles/theme/borders";
|
4
4
|
@use "../../styles/theme/typography";
|
5
5
|
|
6
|
-
$border-radius: borders.$radius;
|
6
|
+
$border-radius: borders.$radius-2;
|
7
7
|
$z-index: z-indexes.$modal;
|
8
8
|
$backdrop-z-index: z-indexes.$modal-backdrop;
|
9
9
|
$title-font-size: map.get(typography.$font-size-values, 2);
|
@@ -77,7 +77,12 @@ See [API](#api) for all available options.
|
|
77
77
|
- Use **clear, calm error messages** when there's a problem with what they
|
78
78
|
entered.
|
79
79
|
|
80
|
-
|
80
|
+
- In the background, Radio uses the [`fieldset`][fieldset] element. Not only it
|
81
|
+
improves the [accessibility] of the group, it also allows you to make use of
|
82
|
+
its built-in features like disabling all nested inputs or pairing the group
|
83
|
+
with a form outside. Consult [the MDN docs][fieldset] to learn more.
|
84
|
+
|
85
|
+
📖 [Read more about checkboxes and radios at Nielsen Norman Group.][nng-radio]
|
81
86
|
|
82
87
|
## Invisible Label
|
83
88
|
|
@@ -308,5 +313,8 @@ options. On top of that, the following options are available for Radio.
|
|
308
313
|
| `--rui-FormField--check__input--radio__border-radius` | Input corner radius |
|
309
314
|
| `--rui-FormField--check__input--radio--checked__background-image` | Checked input background image (inline, URL, …) |
|
310
315
|
|
316
|
+
[nng-radio]: https://www.nngroup.com/articles/checkboxes-vs-radio-buttons/
|
317
|
+
[fieldset]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset
|
318
|
+
[accessibility]: https://www.w3.org/WAI/tutorials/forms/grouping/
|
311
319
|
[React synthetic events]: https://reactjs.org/docs/events.html
|
312
320
|
[radio]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio#additional_attributes
|
@@ -25,7 +25,8 @@ export const Radio = ({
|
|
25
25
|
const context = useContext(FormLayoutContext);
|
26
26
|
|
27
27
|
return (
|
28
|
-
<
|
28
|
+
<fieldset
|
29
|
+
{...transferProps(restProps)}
|
29
30
|
className={classNames(
|
30
31
|
styles.root,
|
31
32
|
context && styles.isRootInFormLayout,
|
@@ -36,53 +37,59 @@ export const Radio = ({
|
|
36
37
|
required && styles.isRootRequired,
|
37
38
|
getRootValidationStateClassName(validationState, styles),
|
38
39
|
)}
|
40
|
+
disabled={disabled}
|
39
41
|
id={id}
|
40
42
|
>
|
43
|
+
<legend
|
44
|
+
className={styles.legend}
|
45
|
+
id={id && `${id}__label`}
|
46
|
+
>
|
47
|
+
{label}
|
48
|
+
</legend>
|
41
49
|
<div
|
50
|
+
aria-hidden
|
42
51
|
className={classNames(
|
43
52
|
styles.label,
|
44
53
|
!isLabelVisible && styles.isLabelHidden,
|
45
54
|
)}
|
46
|
-
id={id && `${id}
|
55
|
+
id={id && `${id}__displayLabel`}
|
47
56
|
>
|
48
57
|
{label}
|
49
58
|
</div>
|
50
59
|
<div className={styles.field}>
|
51
|
-
<
|
60
|
+
<div className={styles.options}>
|
52
61
|
{
|
53
62
|
options.map((option) => {
|
54
63
|
const key = option.key ?? option.value;
|
55
64
|
return (
|
56
|
-
<
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
65
|
+
<label
|
66
|
+
className={styles.option}
|
67
|
+
htmlFor={id && `${id}__item__${key}`}
|
68
|
+
id={id && `${id}__item__${key}__label`}
|
69
|
+
key={key}
|
70
|
+
>
|
71
|
+
<input
|
72
|
+
className={styles.input}
|
73
|
+
checked={restProps.onChange
|
74
|
+
? (value === option.value) || false
|
75
|
+
: undefined}
|
76
|
+
disabled={disabled || option.disabled}
|
77
|
+
id={id && `${id}__item__${key}`}
|
78
|
+
name={id}
|
79
|
+
type="radio"
|
80
|
+
value={option.value}
|
81
|
+
/>
|
82
|
+
<span
|
83
|
+
className={styles.optionLabel}
|
84
|
+
id={id && `${id}__item__${key}__labelText`}
|
61
85
|
>
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
checked={restProps.onChange
|
66
|
-
? (value === option.value) || false
|
67
|
-
: undefined}
|
68
|
-
disabled={disabled || option.disabled}
|
69
|
-
id={id && `${id}__item__${key}`}
|
70
|
-
name={id}
|
71
|
-
type="radio"
|
72
|
-
value={option.value}
|
73
|
-
/>
|
74
|
-
<span
|
75
|
-
className={styles.optionLabel}
|
76
|
-
id={id && `${id}__item__${key}__labelText`}
|
77
|
-
>
|
78
|
-
{ option.label }
|
79
|
-
</span>
|
80
|
-
</label>
|
81
|
-
</li>
|
86
|
+
{ option.label }
|
87
|
+
</span>
|
88
|
+
</label>
|
82
89
|
);
|
83
90
|
})
|
84
91
|
}
|
85
|
-
</
|
92
|
+
</div>
|
86
93
|
{helpText && (
|
87
94
|
<div
|
88
95
|
className={styles.helpText}
|
@@ -100,7 +107,7 @@ export const Radio = ({
|
|
100
107
|
</div>
|
101
108
|
)}
|
102
109
|
</div>
|
103
|
-
</
|
110
|
+
</fieldset>
|
104
111
|
);
|
105
112
|
};
|
106
113
|
|
@@ -129,7 +136,8 @@ Radio.propTypes = {
|
|
129
136
|
* ID of the root HTML element.
|
130
137
|
*
|
131
138
|
* Also serves as base for ids of nested elements:
|
132
|
-
* * `<ID>
|
139
|
+
* * `<ID>__label`
|
140
|
+
* * `<ID>__displayLabel`
|
133
141
|
* * `<ID>__helpText`
|
134
142
|
* * `<ID>__validationText`
|
135
143
|
*
|
@@ -1,3 +1,6 @@
|
|
1
|
+
// 1. Legends are tricky to style, let's use a `div` instead.
|
2
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset#styling_with_css
|
3
|
+
|
1
4
|
@use "../../styles/tools/form-fields/box-field-elements";
|
2
5
|
@use "../../styles/tools/form-fields/box-field-layout";
|
3
6
|
@use "../../styles/tools/form-fields/foundation";
|
@@ -11,15 +14,22 @@
|
|
11
14
|
// Foundation
|
12
15
|
.root {
|
13
16
|
@include foundation.root();
|
17
|
+
@include foundation.fieldset();
|
14
18
|
@include variants.visual(check);
|
15
19
|
}
|
16
20
|
|
21
|
+
// 1.
|
22
|
+
.legend {
|
23
|
+
@include accessibility.hide-text();
|
24
|
+
}
|
25
|
+
|
26
|
+
// 1.
|
17
27
|
.label,
|
18
28
|
.optionLabel {
|
19
29
|
@include foundation.label();
|
20
30
|
}
|
21
31
|
|
22
|
-
.
|
32
|
+
.options {
|
23
33
|
@include reset.list();
|
24
34
|
}
|
25
35
|
|
@@ -74,5 +84,5 @@
|
|
74
84
|
}
|
75
85
|
|
76
86
|
.isRootInFormLayout {
|
77
|
-
@include box-field-layout.in-form-layout();
|
87
|
+
@include box-field-layout.in-form-layout($is-fieldset: true);
|
78
88
|
}
|
@@ -7,6 +7,7 @@ import { getRootValidationStateClassName } from '../_helpers/getRootValidationSt
|
|
7
7
|
import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
|
8
8
|
import { transferProps } from '../_helpers/transferProps';
|
9
9
|
import { FormLayoutContext } from '../FormLayout';
|
10
|
+
import { InputGroupContext } from '../InputGroup/InputGroupContext';
|
10
11
|
import { Option } from './_components/Option';
|
11
12
|
import styles from './SelectField.scss';
|
12
13
|
|
@@ -28,20 +29,25 @@ export const SelectField = React.forwardRef((props, ref) => {
|
|
28
29
|
...restProps
|
29
30
|
} = props;
|
30
31
|
|
31
|
-
const
|
32
|
+
const formLayoutContext = useContext(FormLayoutContext);
|
33
|
+
const inputGroupContext = useContext(InputGroupContext);
|
32
34
|
|
33
35
|
return (
|
34
36
|
<label
|
35
37
|
className={classNames(
|
36
38
|
styles.root,
|
37
39
|
fullWidth && styles.isRootFullWidth,
|
38
|
-
|
39
|
-
resolveContextOrProp(
|
40
|
+
formLayoutContext && styles.isRootInFormLayout,
|
41
|
+
resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled) && styles.isRootDisabled,
|
42
|
+
resolveContextOrProp(formLayoutContext && formLayoutContext.layout, layout) === 'horizontal'
|
40
43
|
? styles.isRootLayoutHorizontal
|
41
44
|
: styles.isRootLayoutVertical,
|
42
|
-
|
45
|
+
inputGroupContext && styles.isRootGrouped,
|
43
46
|
required && styles.isRootRequired,
|
44
|
-
getRootSizeClassName(
|
47
|
+
getRootSizeClassName(
|
48
|
+
resolveContextOrProp(inputGroupContext && inputGroupContext.size, size),
|
49
|
+
styles,
|
50
|
+
),
|
45
51
|
getRootValidationStateClassName(validationState, styles),
|
46
52
|
variant === 'filled' ? styles.isRootVariantFilled : styles.isRootVariantOutline,
|
47
53
|
)}
|
@@ -51,7 +57,7 @@ export const SelectField = React.forwardRef((props, ref) => {
|
|
51
57
|
<div
|
52
58
|
className={classNames(
|
53
59
|
styles.label,
|
54
|
-
!isLabelVisible && styles.isLabelHidden,
|
60
|
+
(!isLabelVisible || inputGroupContext) && styles.isLabelHidden,
|
55
61
|
)}
|
56
62
|
id={id && `${id}__labelText`}
|
57
63
|
>
|
@@ -62,7 +68,7 @@ export const SelectField = React.forwardRef((props, ref) => {
|
|
62
68
|
<select
|
63
69
|
{...transferProps(restProps)}
|
64
70
|
className={styles.input}
|
65
|
-
disabled={disabled}
|
71
|
+
disabled={resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled)}
|
66
72
|
id={id}
|
67
73
|
ref={ref}
|
68
74
|
required={required}
|
@@ -110,7 +116,7 @@ export const SelectField = React.forwardRef((props, ref) => {
|
|
110
116
|
{helpText}
|
111
117
|
</div>
|
112
118
|
)}
|
113
|
-
{validationText && (
|
119
|
+
{(validationText && !inputGroupContext) && (
|
114
120
|
<div
|
115
121
|
className={styles.validationText}
|
116
122
|
id={id && `${id}__validationText`}
|
@@ -169,6 +175,8 @@ SelectField.propTypes = {
|
|
169
175
|
/**
|
170
176
|
* If `false`, the label will be visually hidden (but remains accessible by assistive
|
171
177
|
* technologies).
|
178
|
+
*
|
179
|
+
* Automatically set to `false` when the component is rendered within `InputGroup` component.
|
172
180
|
*/
|
173
181
|
isLabelVisible: PropTypes.bool,
|
174
182
|
/**
|
@@ -224,6 +232,8 @@ SelectField.propTypes = {
|
|
224
232
|
required: PropTypes.bool,
|
225
233
|
/**
|
226
234
|
* Size of the field.
|
235
|
+
*
|
236
|
+
* Ignored if the component is rendered within `InputGroup` component as the value is inherited in such case.
|
227
237
|
*/
|
228
238
|
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
229
239
|
/**
|
@@ -232,6 +242,9 @@ SelectField.propTypes = {
|
|
232
242
|
validationState: PropTypes.oneOf(['invalid', 'valid', 'warning']),
|
233
243
|
/**
|
234
244
|
* Validation message to be displayed.
|
245
|
+
*
|
246
|
+
* Validation text is never rendered when the component is placed into `InputGroup`. Instead, the `InputGroup`
|
247
|
+
* component itself renders all validation texts of its nested components.
|
235
248
|
*/
|
236
249
|
validationText: PropTypes.node,
|
237
250
|
/**
|
@@ -7,6 +7,7 @@ import { getRootValidationStateClassName } from '../_helpers/getRootValidationSt
|
|
7
7
|
import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
|
8
8
|
import { transferProps } from '../_helpers/transferProps';
|
9
9
|
import { FormLayoutContext } from '../FormLayout';
|
10
|
+
import { InputGroupContext } from '../InputGroup/InputGroupContext';
|
10
11
|
import styles from './TextField.scss';
|
11
12
|
|
12
13
|
const SMALL_INPUT_SIZE = 10;
|
@@ -29,7 +30,8 @@ export const TextField = React.forwardRef((props, ref) => {
|
|
29
30
|
variant,
|
30
31
|
...restProps
|
31
32
|
} = props;
|
32
|
-
const
|
33
|
+
const formLayoutContext = useContext(FormLayoutContext);
|
34
|
+
const inputGroupContext = useContext(InputGroupContext);
|
33
35
|
const hasSmallInput = (inputSize !== null) && (inputSize <= SMALL_INPUT_SIZE);
|
34
36
|
|
35
37
|
return (
|
@@ -39,13 +41,17 @@ export const TextField = React.forwardRef((props, ref) => {
|
|
39
41
|
fullWidth && styles.isRootFullWidth,
|
40
42
|
hasSmallInput && styles.hasRootSmallInput,
|
41
43
|
inputSize && styles.hasRootCustomInputSize,
|
42
|
-
|
43
|
-
resolveContextOrProp(
|
44
|
+
formLayoutContext && styles.isRootInFormLayout,
|
45
|
+
resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled) && styles.isRootDisabled,
|
46
|
+
resolveContextOrProp(formLayoutContext && formLayoutContext.layout, layout) === 'horizontal'
|
44
47
|
? styles.isRootLayoutHorizontal
|
45
48
|
: styles.isRootLayoutVertical,
|
46
|
-
|
49
|
+
inputGroupContext && styles.isRootGrouped,
|
47
50
|
required && styles.isRootRequired,
|
48
|
-
getRootSizeClassName(
|
51
|
+
getRootSizeClassName(
|
52
|
+
resolveContextOrProp(inputGroupContext && inputGroupContext.size, size),
|
53
|
+
styles,
|
54
|
+
),
|
49
55
|
getRootValidationStateClassName(validationState, styles),
|
50
56
|
variant === 'filled' ? styles.isRootVariantFilled : styles.isRootVariantOutline,
|
51
57
|
)}
|
@@ -56,7 +62,7 @@ export const TextField = React.forwardRef((props, ref) => {
|
|
56
62
|
<div
|
57
63
|
className={classNames(
|
58
64
|
styles.label,
|
59
|
-
!isLabelVisible && styles.isLabelHidden,
|
65
|
+
(!isLabelVisible || inputGroupContext) && styles.isLabelHidden,
|
60
66
|
)}
|
61
67
|
id={id && `${id}__labelText`}
|
62
68
|
>
|
@@ -67,7 +73,7 @@ export const TextField = React.forwardRef((props, ref) => {
|
|
67
73
|
<input
|
68
74
|
{...transferProps(restProps)}
|
69
75
|
className={styles.input}
|
70
|
-
disabled={disabled}
|
76
|
+
disabled={resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled)}
|
71
77
|
id={id}
|
72
78
|
ref={ref}
|
73
79
|
required={required}
|
@@ -86,7 +92,7 @@ export const TextField = React.forwardRef((props, ref) => {
|
|
86
92
|
{helpText}
|
87
93
|
</div>
|
88
94
|
)}
|
89
|
-
{validationText && (
|
95
|
+
{(validationText && !inputGroupContext) && (
|
90
96
|
<div
|
91
97
|
className={styles.validationText}
|
92
98
|
id={id && `${id}__validationText`}
|
@@ -143,6 +149,8 @@ TextField.propTypes = {
|
|
143
149
|
/**
|
144
150
|
* If `false`, the label will be visually hidden (but remains accessible by assistive
|
145
151
|
* technologies).
|
152
|
+
*
|
153
|
+
* Automatically set to `false` when the component is rendered within `InputGroup` component.
|
146
154
|
*/
|
147
155
|
isLabelVisible: PropTypes.bool,
|
148
156
|
/**
|
@@ -162,6 +170,8 @@ TextField.propTypes = {
|
|
162
170
|
required: PropTypes.bool,
|
163
171
|
/**
|
164
172
|
* Size of the field.
|
173
|
+
*
|
174
|
+
* Ignored if the component is rendered within `InputGroup` component as the value is inherited in such case.
|
165
175
|
*/
|
166
176
|
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
167
177
|
/**
|
@@ -174,6 +184,9 @@ TextField.propTypes = {
|
|
174
184
|
validationState: PropTypes.oneOf(['invalid', 'valid', 'warning']),
|
175
185
|
/**
|
176
186
|
* Validation message to be displayed.
|
187
|
+
*
|
188
|
+
* Validation text is never rendered when the component is placed into `InputGroup`. Instead, the `InputGroup`
|
189
|
+
* component itself renders all validation texts of its nested components.
|
177
190
|
*/
|
178
191
|
validationText: PropTypes.node,
|
179
192
|
/**
|
package/src/lib/index.js
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
// result width across browsers.
|
4
4
|
// 3. Let inputs properly fit various layout scenarios.
|
5
5
|
// 4. Leave out space for SelectField caret.
|
6
|
+
// 5. Use a block-level display mode to prevent extra white space below grouped inputs in Safari.
|
6
7
|
|
7
8
|
@use "../../settings/form-fields" as settings;
|
8
9
|
@use "../../theme/form-fields" as theme;
|
@@ -93,8 +94,8 @@
|
|
93
94
|
align-items: center;
|
94
95
|
justify-content: center;
|
95
96
|
width: calc(#{settings.$box-field-caret-size} - 2 * #{theme.$box-border-width});
|
96
|
-
border-
|
97
|
-
border-
|
97
|
+
border-start-end-radius: theme.$box-border-radius;
|
98
|
+
border-end-end-radius: theme.$box-border-radius;
|
98
99
|
pointer-events: none;
|
99
100
|
}
|
100
101
|
|
@@ -122,3 +123,19 @@
|
|
122
123
|
transform: scaleX(1);
|
123
124
|
}
|
124
125
|
}
|
126
|
+
|
127
|
+
@mixin in-group-layout() {
|
128
|
+
.inputContainer {
|
129
|
+
display: block; // 5.
|
130
|
+
}
|
131
|
+
|
132
|
+
&:not(:first-child) .input {
|
133
|
+
border-start-start-radius: var(--rui-local-inner-border-radius);
|
134
|
+
border-end-start-radius: var(--rui-local-inner-border-radius);
|
135
|
+
}
|
136
|
+
|
137
|
+
&:not(:last-child) .input {
|
138
|
+
border-start-end-radius: var(--rui-local-inner-border-radius);
|
139
|
+
border-end-end-radius: var(--rui-local-inner-border-radius);
|
140
|
+
}
|
141
|
+
}
|
@@ -26,8 +26,12 @@
|
|
26
26
|
// Reverted for full-width fields.
|
27
27
|
//
|
28
28
|
// 8. Grid settings are inherited from horizontal FormLayout and applied using `subgrid`.
|
29
|
-
// A fallback is supplied to browsers that don't support `subgrid` yet.
|
30
|
-
//
|
29
|
+
// A fallback is supplied to browsers that don't support `subgrid` yet.
|
30
|
+
//
|
31
|
+
// Chrome 117+ supports `subgrid` but it doesn't work for `<fieldset>`. This is why we always
|
32
|
+
// use the fallback for `<fieldset>`.
|
33
|
+
//
|
34
|
+
// https://bugs.chromium.org/p/chromium/issues/detail?id=1473242
|
31
35
|
// https://github.com/react-ui-org/react-ui/issues/232
|
32
36
|
//
|
33
37
|
// 9. Help texts and validation messages can take up full width of FormLayout. There is no reason
|
@@ -180,7 +184,7 @@
|
|
180
184
|
}
|
181
185
|
}
|
182
186
|
|
183
|
-
@mixin in-form-layout() {
|
187
|
+
@mixin in-form-layout($is-fieldset: false) {
|
184
188
|
justify-self: start; // 12.
|
185
189
|
|
186
190
|
.field {
|
@@ -192,19 +196,27 @@
|
|
192
196
|
width: auto; // 11.
|
193
197
|
}
|
194
198
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
199
|
+
// 8.
|
200
|
+
@if $is-fieldset {
|
201
|
+
&.isRootLayoutHorizontal,
|
202
|
+
&.isRootLayoutHorizontal.hasRootSmallInput {
|
203
|
+
display: contents;
|
204
|
+
}
|
205
|
+
} @else {
|
206
|
+
&.isRootLayoutHorizontal,
|
207
|
+
&.isRootLayoutHorizontal.hasRootSmallInput {
|
208
|
+
grid: inherit;
|
209
|
+
grid-template-columns: subgrid;
|
210
|
+
grid-column: span 2;
|
211
|
+
|
212
|
+
@supports not (grid-template-columns: subgrid) {
|
213
|
+
display: contents;
|
214
|
+
}
|
203
215
|
}
|
204
|
-
}
|
205
216
|
|
206
|
-
|
207
|
-
|
217
|
+
&.isRootLayoutHorizontal.isRootFullWidth {
|
218
|
+
grid-template-columns: subgrid;
|
219
|
+
}
|
208
220
|
}
|
209
221
|
|
210
222
|
&.isRootLayoutHorizontal .label,
|