@react-ui-org/react-ui 0.50.2 → 0.52.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/lib.development.js +157 -49
- package/dist/lib.js +1 -1
- package/package.json +1 -1
- package/src/lib/components/Alert/Alert.jsx +1 -3
- package/src/lib/components/Alert/Alert.scss +1 -9
- package/src/lib/components/Alert/README.mdx +0 -20
- package/src/lib/components/Alert/_settings.scss +1 -1
- package/src/lib/components/Alert/_theme.scss +0 -10
- package/src/lib/components/Badge/Badge.jsx +1 -3
- package/src/lib/components/Badge/Badge.scss +25 -44
- package/src/lib/components/Badge/README.mdx +6 -14
- package/src/lib/components/Button/Button.jsx +20 -10
- package/src/lib/components/Button/README.mdx +8 -3
- package/src/lib/components/Button/_base.scss +21 -12
- package/src/lib/components/Button/_priorities.scss +13 -18
- package/src/lib/components/Button/_settings.scss +1 -1
- 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 +85 -59
- package/src/lib/components/ButtonGroup/_theme.scss +13 -0
- package/src/lib/components/Card/Card.jsx +1 -3
- package/src/lib/components/Card/Card.scss +0 -9
- package/src/lib/components/Card/README.mdx +0 -16
- package/src/lib/components/Card/_theme.scss +0 -10
- package/src/lib/components/FormLayout/README.mdx +22 -8
- package/src/lib/components/Grid/_helpers/generateResponsiveCustomProperties.js +1 -1
- 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/ModalCloseButton.scss +2 -2
- package/src/lib/components/Modal/README.mdx +392 -128
- 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 +2 -2
- package/src/lib/components/Popover/README.mdx +7 -4
- 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 +11 -1
- package/src/lib/components/ScrollView/README.mdx +2 -2
- package/src/lib/components/SelectField/SelectField.jsx +21 -8
- package/src/lib/components/SelectField/SelectField.scss +5 -0
- package/src/lib/components/Table/_components/TableCell.scss +6 -5
- package/src/lib/components/Table/_components/TableHeaderCell/TableHeaderCell.jsx +8 -5
- package/src/lib/components/Table/_settings.scss +5 -6
- package/src/lib/components/Text/README.mdx +14 -8
- package/src/lib/components/TextField/TextField.jsx +21 -8
- package/src/lib/components/TextField/TextField.scss +5 -0
- package/src/lib/components/TextLink/README.mdx +8 -6
- package/src/lib/components/TextLink/TextLink.scss +5 -0
- package/src/lib/components/TextLink/_theme.scss +2 -0
- package/src/lib/components/Toolbar/README.mdx +19 -11
- package/src/lib/components/_helpers/getRootColorClassName.js +4 -0
- package/src/lib/index.js +1 -0
- package/src/lib/styles/elements/_code.scss +1 -3
- package/src/lib/styles/elements/_page.scss +1 -0
- package/src/lib/styles/elements/_rulers.scss +1 -3
- package/src/lib/styles/elements/_small.scss +1 -1
- package/src/lib/styles/settings/_form-fields.scss +1 -1
- package/src/lib/styles/settings/_utilities.scss +46 -14
- package/src/lib/styles/theme/_accessibility.scss +4 -4
- package/src/lib/styles/theme/_borders.scss +3 -2
- package/src/lib/styles/theme/_code.scss +2 -2
- package/src/lib/styles/theme/_links.scss +6 -4
- package/src/lib/styles/theme/_lists.scss +1 -1
- package/src/lib/styles/theme/_page.scss +2 -2
- package/src/lib/styles/theme/_spacing.scss +11 -11
- package/src/lib/styles/theme/_typography.scss +19 -18
- package/src/lib/styles/theme-constants/_colors.scss +23 -23
- package/src/lib/styles/tools/_spacing.scss +1 -1
- package/src/lib/styles/tools/form-fields/_box-field-elements.scss +19 -2
- 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 +650 -567
- package/src/lib/styles/theme/_colors.scss +0 -65
- /package/src/lib/components/{Button/helpers → _helpers}/getRootPriorityClassName.js +0 -0
@@ -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();
|
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,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
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import PropTypes from 'prop-types';
|
2
|
-
import React, {
|
3
|
-
useEffect,
|
4
|
-
useRef,
|
5
|
-
} from 'react';
|
2
|
+
import React, { useRef } from 'react';
|
6
3
|
import { createPortal } from 'react-dom';
|
7
4
|
import { withGlobalProps } from '../../provider';
|
8
5
|
import { transferProps } from '../_helpers/transferProps';
|
9
6
|
import { classNames } from '../../utils/classNames';
|
7
|
+
import { getPositionClassName } from './_helpers/getPositionClassName';
|
8
|
+
import { getSizeClassName } from './_helpers/getSizeClassName';
|
9
|
+
import { useModalFocus } from './_hooks/useModalFocus';
|
10
|
+
import { useModalScrollPrevention } from './_hooks/useModalScrollPrevention';
|
10
11
|
import styles from './Modal.scss';
|
11
12
|
|
12
13
|
const preRender = (
|
@@ -16,63 +17,34 @@ const preRender = (
|
|
16
17
|
position,
|
17
18
|
restProps,
|
18
19
|
size,
|
19
|
-
) =>
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
}
|
28
|
-
|
29
|
-
|
30
|
-
return styles.isRootSizeLarge;
|
31
|
-
}
|
32
|
-
|
33
|
-
if (modalSize === 'fullscreen') {
|
34
|
-
return styles.isRootSizeFullscreen;
|
35
|
-
}
|
36
|
-
|
37
|
-
return styles.isRootSizeAuto;
|
38
|
-
};
|
39
|
-
|
40
|
-
const positionClass = (modalPosition) => {
|
41
|
-
if (modalPosition === 'top') {
|
42
|
-
return styles.isRootPositionTop;
|
43
|
-
}
|
44
|
-
|
45
|
-
return styles.isRootPositionCenter;
|
46
|
-
};
|
47
|
-
|
48
|
-
return (
|
20
|
+
) => (
|
21
|
+
<div
|
22
|
+
className={styles.backdrop}
|
23
|
+
onClick={(e) => {
|
24
|
+
e.preventDefault();
|
25
|
+
if (closeButtonRef?.current != null) {
|
26
|
+
closeButtonRef.current.click();
|
27
|
+
}
|
28
|
+
}}
|
29
|
+
role="presentation"
|
30
|
+
>
|
49
31
|
<div
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
32
|
+
{...transferProps(restProps)}
|
33
|
+
className={classNames(
|
34
|
+
styles.root,
|
35
|
+
getSizeClassName(size, styles),
|
36
|
+
getPositionClassName(position, styles),
|
37
|
+
)}
|
38
|
+
onClick={(e) => {
|
39
|
+
e.stopPropagation();
|
55
40
|
}}
|
56
41
|
role="presentation"
|
42
|
+
ref={childrenWrapperRef}
|
57
43
|
>
|
58
|
-
|
59
|
-
{...transferProps(restProps)}
|
60
|
-
className={classNames(
|
61
|
-
styles.root,
|
62
|
-
sizeClass(size),
|
63
|
-
positionClass(position),
|
64
|
-
)}
|
65
|
-
onClick={(e) => {
|
66
|
-
e.stopPropagation();
|
67
|
-
}}
|
68
|
-
role="presentation"
|
69
|
-
ref={childrenWrapperRef}
|
70
|
-
>
|
71
|
-
{children}
|
72
|
-
</div>
|
44
|
+
{children}
|
73
45
|
</div>
|
74
|
-
|
75
|
-
|
46
|
+
</div>
|
47
|
+
);
|
76
48
|
|
77
49
|
export const Modal = ({
|
78
50
|
autoFocus,
|
@@ -80,52 +52,21 @@ export const Modal = ({
|
|
80
52
|
closeButtonRef,
|
81
53
|
portalId,
|
82
54
|
position,
|
55
|
+
preventScrollUnderneath,
|
83
56
|
primaryButtonRef,
|
84
57
|
size,
|
85
58
|
...restProps
|
86
59
|
}) => {
|
87
60
|
const childrenWrapperRef = useRef();
|
88
61
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
primaryButtonRef.current.click();
|
96
|
-
}
|
97
|
-
};
|
98
|
-
|
99
|
-
useEffect(() => {
|
100
|
-
window.document.addEventListener('keydown', keyPressHandler, false);
|
101
|
-
const removeKeyPressHandler = () => {
|
102
|
-
window.document.removeEventListener('keydown', keyPressHandler, false);
|
103
|
-
};
|
104
|
-
|
105
|
-
// If `autoFocus` is set to `true`, following code finds first form field element
|
106
|
-
// (input, textarea or select) or primary button and auto focuses it. This is necessary
|
107
|
-
// to have focus on one of those elements to be able to submit form by pressing Enter key.
|
108
|
-
if (autoFocus) {
|
109
|
-
if (childrenWrapperRef?.current != null) {
|
110
|
-
const childrenWrapperElement = childrenWrapperRef.current;
|
111
|
-
const childrenElements = childrenWrapperElement.querySelectorAll('*');
|
112
|
-
const formFieldEl = Array.from(childrenElements).find(
|
113
|
-
(element) => ['INPUT', 'TEXTAREA', 'SELECT'].includes(element.nodeName) && !element.disabled,
|
114
|
-
);
|
115
|
-
|
116
|
-
if (formFieldEl) {
|
117
|
-
formFieldEl.focus();
|
118
|
-
return removeKeyPressHandler;
|
119
|
-
}
|
120
|
-
}
|
121
|
-
|
122
|
-
if (primaryButtonRef?.current != null) {
|
123
|
-
primaryButtonRef.current.focus();
|
124
|
-
}
|
125
|
-
}
|
62
|
+
useModalFocus(
|
63
|
+
autoFocus,
|
64
|
+
childrenWrapperRef,
|
65
|
+
primaryButtonRef,
|
66
|
+
closeButtonRef,
|
67
|
+
);
|
126
68
|
|
127
|
-
|
128
|
-
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
69
|
+
useModalScrollPrevention(preventScrollUnderneath);
|
129
70
|
|
130
71
|
if (portalId === null) {
|
131
72
|
return preRender(
|
@@ -157,14 +98,16 @@ Modal.defaultProps = {
|
|
157
98
|
closeButtonRef: null,
|
158
99
|
portalId: null,
|
159
100
|
position: 'center',
|
101
|
+
preventScrollUnderneath: 'default',
|
160
102
|
primaryButtonRef: null,
|
161
103
|
size: 'medium',
|
162
104
|
};
|
163
105
|
|
164
106
|
Modal.propTypes = {
|
165
107
|
/**
|
166
|
-
* If `true`, focus the first input element in the
|
167
|
-
* when the
|
108
|
+
* If `true`, focus the first input element in the `Modal`, or primary button (referenced by the `primaryButtonRef`
|
109
|
+
* prop), or other focusable element when the `Modal` is opened. If there are none or `autoFocus` is set to `false`,
|
110
|
+
* focus the Modal itself.
|
168
111
|
*/
|
169
112
|
autoFocus: PropTypes.bool,
|
170
113
|
/**
|
@@ -192,6 +135,24 @@ Modal.propTypes = {
|
|
192
135
|
* Vertical position of the modal inside browser window.
|
193
136
|
*/
|
194
137
|
position: PropTypes.oneOf(['top', 'center']),
|
138
|
+
/**
|
139
|
+
* Mode in which Modal prevents scroll of elements bellow:
|
140
|
+
* * `default` - Modal prevents scroll on the `body` element
|
141
|
+
* * `off` - Modal does not prevent any scroll
|
142
|
+
* * object
|
143
|
+
* * * `reset` - method called on Modal's unmount to reset scroll prevention
|
144
|
+
* * * `start` - method called on Modal's mount to custom scroll prevention
|
145
|
+
*/
|
146
|
+
preventScrollUnderneath: PropTypes.oneOfType([
|
147
|
+
PropTypes.oneOf([
|
148
|
+
'default',
|
149
|
+
'off',
|
150
|
+
]),
|
151
|
+
PropTypes.shape({
|
152
|
+
reset: PropTypes.func,
|
153
|
+
start: PropTypes.func,
|
154
|
+
}),
|
155
|
+
]),
|
195
156
|
/**
|
196
157
|
* Reference to primary button element. It is used to submit modal when Enter key is pressed and as fallback
|
197
158
|
* when `autoFocus` functionality does not find any input element to be focused.
|
@@ -8,11 +8,11 @@
|
|
8
8
|
@include reset.button();
|
9
9
|
@include accessibility.min-tap-target();
|
10
10
|
|
11
|
-
font-size: map.get(typography.$size-values,
|
11
|
+
font-size: map.get(typography.$font-size-values, 4);
|
12
12
|
line-height: 1;
|
13
13
|
color: inherit;
|
14
14
|
|
15
15
|
&:disabled {
|
16
|
-
cursor: var(--rui-
|
16
|
+
cursor: var(--rui-cursor-not-allowed);
|
17
17
|
}
|
18
18
|
}
|