@react-ui-org/react-ui 0.51.0 → 0.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/lib.development.js +141 -33
  2. package/dist/lib.js +1 -1
  3. package/dist/react-ui.css +40 -0
  4. package/dist/react-ui.js +1 -0
  5. package/package.json +1 -1
  6. package/src/lib/components/Button/Button.jsx +17 -9
  7. package/src/lib/components/Button/_base.scss +21 -12
  8. package/src/lib/components/Button/_priorities.scss +1 -18
  9. package/src/lib/components/Button/_theme.scss +0 -10
  10. package/src/lib/components/ButtonGroup/ButtonGroup.jsx +5 -3
  11. package/src/lib/components/ButtonGroup/ButtonGroup.scss +26 -1
  12. package/src/lib/components/ButtonGroup/README.mdx +11 -1
  13. package/src/lib/components/ButtonGroup/_theme.scss +13 -0
  14. package/src/lib/components/FormLayout/README.mdx +5 -0
  15. package/src/lib/components/InputGroup/InputGroup.jsx +170 -0
  16. package/src/lib/components/InputGroup/InputGroup.scss +92 -0
  17. package/src/lib/components/InputGroup/InputGroupContext.js +3 -0
  18. package/src/lib/components/InputGroup/README.mdx +278 -0
  19. package/src/lib/components/InputGroup/_theme.scss +2 -0
  20. package/src/lib/components/InputGroup/index.js +2 -0
  21. package/src/lib/components/Modal/Modal.jsx +58 -97
  22. package/src/lib/components/Modal/README.mdx +288 -15
  23. package/src/lib/components/Modal/_helpers/getPositionClassName.js +7 -0
  24. package/src/lib/components/Modal/_helpers/getSizeClassName.js +19 -0
  25. package/src/lib/components/Modal/_hooks/useModalFocus.js +126 -0
  26. package/src/lib/components/Modal/_hooks/useModalScrollPrevention.js +35 -0
  27. package/src/lib/components/Modal/_settings.scss +1 -1
  28. package/src/lib/components/Radio/README.mdx +9 -1
  29. package/src/lib/components/Radio/Radio.jsx +39 -31
  30. package/src/lib/components/Radio/Radio.scss +12 -2
  31. package/src/lib/components/SelectField/SelectField.jsx +21 -8
  32. package/src/lib/components/SelectField/SelectField.scss +5 -0
  33. package/src/lib/components/TextField/TextField.jsx +21 -8
  34. package/src/lib/components/TextField/TextField.scss +5 -0
  35. package/src/lib/index.js +1 -0
  36. package/src/lib/styles/theme/_borders.scss +2 -1
  37. package/src/lib/styles/tools/form-fields/_box-field-elements.scss +19 -2
  38. package/src/lib/styles/tools/form-fields/_box-field-layout.scss +26 -14
  39. package/src/lib/styles/tools/form-fields/_box-field-sizes.scss +11 -8
  40. package/src/lib/styles/tools/form-fields/_foundation.scss +7 -0
  41. package/src/lib/theme.scss +23 -11
  42. /package/src/lib/components/{Button/helpers → _helpers}/getRootPriorityClassName.js +0 -0
@@ -0,0 +1,170 @@
1
+ import PropTypes from 'prop-types';
2
+ import React, { useContext } from 'react';
3
+ import { Text } from '../Text';
4
+ import { withGlobalProps } from '../../provider';
5
+ import { classNames } from '../../utils/classNames';
6
+ import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
7
+ import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
8
+ import { isChildrenEmpty } from '../_helpers/isChildrenEmpty';
9
+ import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
10
+ import { transferProps } from '../_helpers/transferProps';
11
+ import { FormLayoutContext } from '../FormLayout';
12
+ import { InputGroupContext } from './InputGroupContext';
13
+ import styles from './InputGroup.scss';
14
+
15
+ export const InputGroup = ({
16
+ children,
17
+ disabled,
18
+ id,
19
+ isLabelVisible,
20
+ label,
21
+ layout,
22
+ size,
23
+ validationTexts,
24
+ ...restProps
25
+ }) => {
26
+ const formLayoutContext = useContext(FormLayoutContext);
27
+
28
+ if (isChildrenEmpty(children)) {
29
+ return null;
30
+ }
31
+
32
+ const validationState = children.reduce(
33
+ (state, child) => {
34
+ if (state === 'invalid' || (state === 'warning' && child.props.validationState === 'valid')) {
35
+ return state;
36
+ }
37
+ return child.props.validationState ?? state;
38
+ },
39
+ null,
40
+ );
41
+
42
+ return (
43
+ <fieldset
44
+ {...transferProps(restProps)}
45
+ id={id}
46
+ className={classNames(
47
+ styles.root,
48
+ formLayoutContext && styles.isRootInFormLayout,
49
+ resolveContextOrProp(formLayoutContext && formLayoutContext.layout, layout) === 'horizontal'
50
+ ? styles.isRootLayoutHorizontal
51
+ : styles.isRootLayoutVertical,
52
+ disabled && styles.isRootDisabled,
53
+ getRootSizeClassName(size, styles),
54
+ getRootValidationStateClassName(validationState, styles),
55
+ )}
56
+ disabled={disabled}
57
+ >
58
+ <legend
59
+ className={styles.legend}
60
+ id={id && `${id}__label`}
61
+ >
62
+ {label}
63
+ </legend>
64
+ <div
65
+ aria-hidden
66
+ className={classNames(
67
+ styles.label,
68
+ !isLabelVisible && styles.isLabelHidden,
69
+ )}
70
+ id={id && `${id}__displayLabel`}
71
+ >
72
+ {label}
73
+ </div>
74
+ <div className={styles.field}>
75
+ <div
76
+ className={styles.inputGroup}
77
+ id={id && `${id}__group`}
78
+ >
79
+ <InputGroupContext.Provider
80
+ value={{
81
+ disabled,
82
+ layout,
83
+ size,
84
+ }}
85
+ >
86
+ {children}
87
+ </InputGroupContext.Provider>
88
+ </div>
89
+ {validationTexts && (
90
+ <ul
91
+ className={styles.validationText}
92
+ id={id && `${id}__validationTexts`}
93
+ >
94
+ {validationTexts.map((validationText) => (
95
+ <li key={validationText}>
96
+ <Text blockLevel>
97
+ {validationText}
98
+ </Text>
99
+ </li>
100
+ ))}
101
+ </ul>
102
+ )}
103
+ </div>
104
+ </fieldset>
105
+ );
106
+ };
107
+
108
+ InputGroup.defaultProps = {
109
+ children: null,
110
+ disabled: false,
111
+ id: undefined,
112
+ isLabelVisible: true,
113
+ layout: 'vertical',
114
+ size: 'medium',
115
+ validationTexts: null,
116
+ };
117
+
118
+ InputGroup.propTypes = {
119
+ /**
120
+ * Supported elements to be grouped:
121
+ * * `Button`
122
+ * * `SelectField`
123
+ * * `TextField`
124
+ *
125
+ * If none are provided nothing is rendered.
126
+ */
127
+ children: PropTypes.node,
128
+ /**
129
+ * If `true`, the whole input group with all nested inputs and buttons will be disabled.
130
+ */
131
+ disabled: PropTypes.bool,
132
+ /**
133
+ * ID of the root HTML element.
134
+ *
135
+ * Also serves as base for ids of nested elements:
136
+ * * `<ID>__label`
137
+ * * `<ID>__displayLabel`
138
+ * * `<ID>__group`
139
+ * * `<ID>__validationTexts`
140
+ */
141
+ id: PropTypes.string,
142
+ /**
143
+ * If `false`, the label will be visually hidden (but remains accessible by assistive
144
+ * technologies).
145
+ */
146
+ isLabelVisible: PropTypes.bool,
147
+ /**
148
+ * Input group label.
149
+ */
150
+ label: PropTypes.string.isRequired,
151
+ /**
152
+ * Layout of the group.
153
+ *
154
+ * Ignored if the component is rendered within `FormLayout` component
155
+ * as the value is inherited in such case.
156
+ */
157
+ layout: PropTypes.oneOf(['horizontal', 'vertical']),
158
+ /**
159
+ * Size of the `children` elements.
160
+ */
161
+ size: PropTypes.oneOf(['small', 'medium', 'large']),
162
+ /**
163
+ * An array of validation messages to be displayed.
164
+ */
165
+ validationTexts: PropTypes.node,
166
+ };
167
+
168
+ export const InputGroupWithGlobalProps = withGlobalProps(InputGroup, 'InputGroup');
169
+
170
+ export default InputGroupWithGlobalProps;
@@ -0,0 +1,92 @@
1
+ // 1. The class name is intentionally singular because it's targeted by other mixins too.
2
+ // 2. Use a block-level display mode to prevent extra white space below grouped inputs in Safari.
3
+ // 3. Prevent individual inputs from overlapping inside narrow containers.
4
+ // 4. Legends are tricky to style, let's use a `div` instead.
5
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset#styling_with_css
6
+
7
+ @use "../../styles/tools/form-fields/box-field-elements";
8
+ @use "../../styles/tools/form-fields/box-field-layout";
9
+ @use "../../styles/tools/form-fields/box-field-sizes";
10
+ @use "../../styles/tools/form-fields/foundation";
11
+ @use "../../styles/tools/form-fields/variants";
12
+ @use "../../styles/tools/accessibility";
13
+ @use "../../styles/tools/reset";
14
+ @use "theme";
15
+
16
+ .root {
17
+ @include foundation.root();
18
+ @include foundation.fieldset();
19
+ }
20
+
21
+ // 4.
22
+ .legend {
23
+ @include accessibility.hide-text();
24
+ }
25
+
26
+ // 4.
27
+ .label {
28
+ @include foundation.label();
29
+ }
30
+
31
+ .inputGroup {
32
+ --rui-local-inner-border-radius: #{theme.$inner-border-radius};
33
+
34
+ display: flex; // 2.
35
+ gap: theme.$gap;
36
+ }
37
+
38
+ // 1.
39
+ .validationText {
40
+ @include reset.list();
41
+ @include foundation.help-text();
42
+ }
43
+
44
+ // States
45
+ .isRootStateInvalid {
46
+ @include variants.validation(invalid);
47
+ }
48
+
49
+ .isRootStateValid {
50
+ @include variants.validation(valid);
51
+ }
52
+
53
+ .isRootStateWarning {
54
+ @include variants.validation(warning);
55
+ }
56
+
57
+ // Invisible label
58
+ .isLabelHidden {
59
+ @include accessibility.hide-text();
60
+ }
61
+
62
+ // Layouts
63
+ .isRootLayoutVertical,
64
+ .isRootLayoutHorizontal {
65
+ @include box-field-layout.vertical();
66
+ }
67
+
68
+ .isRootLayoutVertical .field,
69
+ .isRootLayoutHorizontal .field {
70
+ max-width: none; // 3.
71
+ }
72
+
73
+ .isRootLayoutHorizontal {
74
+ @include box-field-layout.horizontal();
75
+ }
76
+
77
+ .isRootInFormLayout {
78
+ @include box-field-layout.in-form-layout($is-fieldset: true);
79
+ }
80
+
81
+ // Sizes
82
+ .isRootSizeSmall {
83
+ @include box-field-sizes.size(small, $has-input: false);
84
+ }
85
+
86
+ .isRootSizeMedium {
87
+ @include box-field-sizes.size(medium, $has-input: false);
88
+ }
89
+
90
+ .isRootSizeLarge {
91
+ @include box-field-sizes.size(large, $has-input: false);
92
+ }
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+
3
+ export const InputGroupContext = React.createContext(null);
@@ -0,0 +1,278 @@
1
+ ---
2
+ name: InputGroup
3
+ menu: 'Layouts'
4
+ route: /components/input-group
5
+ ---
6
+
7
+ # InputGroup
8
+
9
+ InputGroup visually groups related form fields and actions together.
10
+
11
+ import {
12
+ Playground,
13
+ Props,
14
+ } from 'docz'
15
+ import Icon from '../../../docs/_components/Icon'
16
+ import {
17
+ Button,
18
+ InputGroup,
19
+ SelectField,
20
+ TextField,
21
+ } from '../..'
22
+
23
+ ## Basic Usage
24
+
25
+ To implement the InputGroup component, you need to import it first:
26
+
27
+ ```js
28
+ import { InputGroup } from '@react-ui-org/react-ui';
29
+ ```
30
+
31
+ And use it:
32
+
33
+ <Playground>
34
+ {() => {
35
+ const [fruit, setFruit] = React.useState('apple');
36
+ const options = [
37
+ {
38
+ label: 'Apple',
39
+ value: 'apple',
40
+ },
41
+ {
42
+ label: 'Pear',
43
+ value: 'pear',
44
+ },
45
+ {
46
+ label: 'Cherry',
47
+ value: 'cherry',
48
+ },
49
+ ];
50
+ return (
51
+ <InputGroup label="Your favourite fruit">
52
+ <SelectField
53
+ label="Your favourite fruit"
54
+ onChange={(e) => setFruit(e.target.value)}
55
+ options={options}
56
+ value={fruit}
57
+ />
58
+ <TextField
59
+ label="Variety"
60
+ placeholder="Eg. Golden delicious"
61
+ />
62
+ <Button label="Submit" />
63
+ </InputGroup>
64
+ );
65
+ }}
66
+ </Playground>
67
+
68
+ See [API](#api) for all available options.
69
+
70
+ ## General Guidelines
71
+
72
+ - Use input group to group **related fields and actions** that a user can take.
73
+ Input fields and buttons should not be grouped just to save space on the
74
+ screen.
75
+
76
+ - While the number of child inputs is not limited, keep in mind the layout of
77
+ InputGroup is currently **not responsive: the inputs do not shrink nor wrap**.
78
+ Make sure your inputs fit their container, especially on small screens.
79
+
80
+ - In the background, InputGroup uses the [`fieldset`][fieldset] element. Not
81
+ only it improves the [accessibility] of the group, it also allows you to make
82
+ use of its built-in features like disabling all nested inputs or pairing the
83
+ group with a form outside. Consult [the MDN docs][fieldset] to learn more.
84
+
85
+ - InputGroup currently **supports grouping of**
86
+ [TextField](/components/text-field), [SelectField](/components/select-field),
87
+ and [Button](/components/button) components.
88
+
89
+ - To group [Buttons](/components/button) only, use the
90
+ [ButtonGroup](/components/button-group) component which is designed
91
+ specifically for that purpose.
92
+
93
+ ## Sizes
94
+
95
+ All existing field and button sizes are also available on the input group level:
96
+ small, medium, and large.
97
+
98
+ <Playground>
99
+ <InputGroup
100
+ label="Small size"
101
+ size="small"
102
+ >
103
+ <TextField label="Input" />
104
+ <Button label="Submit" />
105
+ </InputGroup>
106
+ <InputGroup label="Medium size">
107
+ <TextField label="Input" />
108
+ <Button label="Submit" />
109
+ </InputGroup>
110
+ <InputGroup
111
+ label="Large size"
112
+ size="large"
113
+ >
114
+ <TextField label="Input" />
115
+ <Button label="Submit" />
116
+ </InputGroup>
117
+ </Playground>
118
+
119
+ ### Shared Property
120
+
121
+ You can set the `size` property directly on InputGroup to be shared for all
122
+ fields and buttons inside the group. This property is then passed over to
123
+ individual elements. At the same time, it **cannot be overridden** on the
124
+ fields' or buttons' level. While technically possible, from the design point of
125
+ view it's undesirable to group elements of totally different types or sizes.
126
+
127
+ ## Invisible Label
128
+
129
+ In some cases, it may be convenient to visually hide the group label. The label
130
+ remains accessible to assistive technologies. Labels of individual inputs are
131
+ always visually hidden.
132
+
133
+ While it may be acceptable for login screens with just a few fields or for other
134
+ simple forms, it's dangerous to hide labels from users in most cases. Keep in
135
+ mind you should **provide another visual clue** so users know what to fill into
136
+ the input.
137
+
138
+ <Playground>
139
+ <InputGroup
140
+ isLabelVisible={false}
141
+ label="First and last name"
142
+ >
143
+ <TextField
144
+ label="First name"
145
+ placeholder="Eg. John"
146
+ />
147
+ <TextField
148
+ label="Last name"
149
+ placeholder="Eg. Doe"
150
+ />
151
+ <Button label="Submit" />
152
+ </InputGroup>
153
+ </Playground>
154
+
155
+ ## Horizontal layout
156
+
157
+ The default vertical layout is very easy to use and work with. However, there
158
+ are situations where horizontal layout suits better — and that's why React UI
159
+ supports this kind of layout as well.
160
+
161
+ <Playground>
162
+ <InputGroup
163
+ label="Horizontal layout"
164
+ layout="horizontal"
165
+ >
166
+ <TextField label="Label" />
167
+ <Button label="Submit" />
168
+ </InputGroup>
169
+ </Playground>
170
+
171
+ ## States
172
+
173
+ ### Disabled State
174
+
175
+ Disables all fields and buttons inside the group.
176
+
177
+ <Playground>
178
+ <InputGroup disabled label="Disabled group">
179
+ <TextField label="Label" />
180
+ <Button label="Submit" />
181
+ </InputGroup>
182
+ </Playground>
183
+
184
+ ### Validation States
185
+
186
+ Validation states visually present the result of validation of the grouped
187
+ inputs. Input group's validation state is taken from its child inputs. You
188
+ should always **provide validation messages for states other than valid**
189
+ directly through `validationTexts` prop so users know what happened and what
190
+ action they should take or what options they have. These messages are not
191
+ semantically tied to the `children` elements, the connection should be expressed
192
+ in textual form in the actual message. The individual `children` elements must
193
+ not show any `validationText`, they only show their respective `validationState`.
194
+ Validation messages passed to input elements' `validationText` prop will be
195
+ ignored.
196
+
197
+ <Playground>
198
+ <InputGroup
199
+ label="First and last name"
200
+ validationTexts={[
201
+ "First name must be filled in.",
202
+ "Last name must be filled in.",
203
+ ]}
204
+ >
205
+ <TextField
206
+ label="First name"
207
+ placeholder="Eg. John"
208
+ validationState="invalid"
209
+ />
210
+ <TextField
211
+ label="Last name"
212
+ placeholder="Eg. Doe"
213
+ validationState="invalid"
214
+ />
215
+ <Button label="Submit" />
216
+ </InputGroup>
217
+ <InputGroup
218
+ label="First and last name"
219
+ validationTexts={[
220
+ "Last name should not include any digits.",
221
+ ]}
222
+ >
223
+ <TextField
224
+ label="First name"
225
+ placeholder="Eg. John"
226
+ value="John"
227
+ />
228
+ <TextField
229
+ label="Last name"
230
+ placeholder="Eg. Doe"
231
+ validationState="warning"
232
+ value="123Doe"
233
+ />
234
+ <Button label="Submit" />
235
+ </InputGroup>
236
+ <InputGroup label="First and last name">
237
+ <TextField
238
+ label="First name"
239
+ placeholder="Eg. John"
240
+ validationState="valid"
241
+ value="John"
242
+ />
243
+ <TextField
244
+ label="Last name"
245
+ placeholder="Eg. Doe"
246
+ validationState="valid"
247
+ value="Doe"
248
+ />
249
+ <Button label="Submit" />
250
+ </InputGroup>
251
+ </Playground>
252
+
253
+ ## Forwarding HTML Attributes
254
+
255
+ In addition to the options below in the [component's API](#api) section, you
256
+ can specify [React synthetic events] or **any HTML attribute you like.** All
257
+ attributes that don't interfere with the API are forwarded to the `<div>` HTML
258
+ element which wraps elements to be grouped. This enables making the component
259
+ interactive and helps to improve its accessibility.
260
+
261
+ 👉 Refer to the MDN reference for the full list of supported attributes of the
262
+ [div] element.
263
+
264
+ ## API
265
+
266
+ <Props table of={InputGroup} />
267
+
268
+ ## Theming
269
+
270
+ | Custom Property | Description |
271
+ |--------------------------------------------------------------------|------------------------------------------------|
272
+ | `--rui-InputGroup__gap` | Gap between elements |
273
+ | `--rui-InputGroup__inner-border-radius` | Inner border radius of elements |
274
+
275
+ [fieldset]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset
276
+ [accessibility]: https://www.w3.org/WAI/tutorials/forms/grouping/
277
+ [React synthetic events]: https://reactjs.org/docs/events.html
278
+ [div]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes
@@ -0,0 +1,2 @@
1
+ $gap: var(--rui-InputGroup__gap);
2
+ $inner-border-radius: var(--rui-InputGroup__inner-border-radius);
@@ -0,0 +1,2 @@
1
+ export { default as InputGroup } from './InputGroup';
2
+ export { InputGroupContext } from './InputGroupContext';