@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.
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';