@react-ui-org/react-ui 0.51.0 → 0.52.0
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.
- package/dist/lib.development.js +140 -32
- package/dist/lib.js +1 -1
- 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 +11 -1
- 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-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
@@ -25,6 +25,7 @@ import {
|
|
25
25
|
ModalTitle,
|
26
26
|
Radio,
|
27
27
|
ScrollView,
|
28
|
+
TextArea,
|
28
29
|
TextField,
|
29
30
|
Toolbar,
|
30
31
|
ToolbarGroup,
|
@@ -116,12 +117,15 @@ See [API](#api) for all available options.
|
|
116
117
|
|
117
118
|
- Modal **automatically focuses the first non-disabled form field** by default
|
118
119
|
which allows users to confirm the modal by hitting the enter key. When no
|
119
|
-
field is found then the primary button (in the footer) is focused.
|
120
|
-
|
120
|
+
field is found then the primary button (in the footer) is focused. If there
|
121
|
+
are neither, it tries to focus any other focusable elements. In case there
|
122
|
+
are none, or [autoFocus](#autoFocus) is disabled, Modal itself is focused.
|
121
123
|
|
122
124
|
- **Avoid stacking** of modals. While it may technically work, the modal is just
|
123
125
|
not designed for that.
|
124
126
|
|
127
|
+
📖 [Read more about modals at Nielsen Norman Group.][nng-modal]
|
128
|
+
|
125
129
|
## Composition
|
126
130
|
|
127
131
|
Modal is decomposed into the following components:
|
@@ -822,24 +826,147 @@ can be closed by pressing the `Escape` key.
|
|
822
826
|
|
823
827
|
To enable it, you just need to pass a reference to the buttons using
|
824
828
|
`primaryButtonRef` and `closeButtonRef` props on Modal. The advantage of passing
|
825
|
-
|
826
|
-
not fire the event.
|
827
|
-
|
828
|
-
👉 We strongly recommend using this feature together with
|
829
|
-
[Autofocus](#autofocus) for a better user experience.
|
829
|
+
the reference to the button is that if the button is disabled, the key press
|
830
|
+
will not fire the event.
|
830
831
|
|
831
832
|
## Autofocus
|
832
833
|
|
833
834
|
Autofocus is implemented to enhance the user experience by automatically
|
834
|
-
|
835
|
+
focusing an element within the Modal.
|
835
836
|
|
836
837
|
How does it work? It tries to find `input`, `textarea`, and `select` elements
|
837
838
|
inside of Modal and moves focus onto the first non-disabled one. If none is
|
838
839
|
found and the `primaryButtonRef` prop on Modal is set, then the primary button
|
839
|
-
is focused.
|
840
|
+
is focused. If there are neither, it tries to focus any other focusable elements.
|
841
|
+
In case there are none or `autoFocus` is disabled, Modal itself is focused.
|
840
842
|
|
841
|
-
|
842
|
-
|
843
|
+
<Playground>
|
844
|
+
{() => {
|
845
|
+
const [modalOpen, setModalOpen] = React.useState(null);
|
846
|
+
const modalPrimaryButtonRef = React.useRef();
|
847
|
+
const modalCloseButtonRef = React.useRef();
|
848
|
+
return (
|
849
|
+
<>
|
850
|
+
<Button
|
851
|
+
label="Launch modal with autofocus and form"
|
852
|
+
onClick={() => setModalOpen(1)}
|
853
|
+
/>
|
854
|
+
<Button
|
855
|
+
label="Launch modal with autofocus"
|
856
|
+
onClick={() => setModalOpen(2)}
|
857
|
+
/>
|
858
|
+
<Button
|
859
|
+
label="Launch modal with autofocus disabled"
|
860
|
+
onClick={() => setModalOpen(3)}
|
861
|
+
/>
|
862
|
+
<div>
|
863
|
+
{modalOpen === 1 && (
|
864
|
+
<Modal
|
865
|
+
closeButtonRef={modalCloseButtonRef}
|
866
|
+
primaryButtonRef={modalPrimaryButtonRef}
|
867
|
+
>
|
868
|
+
<ModalHeader>
|
869
|
+
<ModalTitle>Modal with autoFocus and form</ModalTitle>
|
870
|
+
<ModalCloseButton onClick={() => setModalOpen(null)} />
|
871
|
+
</ModalHeader>
|
872
|
+
<ModalBody>
|
873
|
+
<ModalContent>
|
874
|
+
<FormLayout autoWidth fieldLayout="horizontal">
|
875
|
+
<TextField
|
876
|
+
disabled
|
877
|
+
label="A form element"
|
878
|
+
/>
|
879
|
+
<TextField label="Another form element" />
|
880
|
+
<TextArea label="Yet another one" />
|
881
|
+
</FormLayout>
|
882
|
+
</ModalContent>
|
883
|
+
</ModalBody>
|
884
|
+
<ModalFooter>
|
885
|
+
<Button
|
886
|
+
label="Submit"
|
887
|
+
onClick={() => setModalOpen(null)}
|
888
|
+
ref={modalPrimaryButtonRef}
|
889
|
+
/>
|
890
|
+
<Button
|
891
|
+
color="secondary"
|
892
|
+
label="Close"
|
893
|
+
onClick={() => setModalOpen(null)}
|
894
|
+
priority="outline"
|
895
|
+
ref={modalCloseButtonRef}
|
896
|
+
/>
|
897
|
+
</ModalFooter>
|
898
|
+
</Modal>
|
899
|
+
)}
|
900
|
+
{modalOpen === 2 && (
|
901
|
+
<Modal
|
902
|
+
closeButtonRef={modalCloseButtonRef}
|
903
|
+
primaryButtonRef={modalPrimaryButtonRef}
|
904
|
+
>
|
905
|
+
<ModalHeader>
|
906
|
+
<ModalTitle>Modal with autoFocus enabled with no form</ModalTitle>
|
907
|
+
<ModalCloseButton onClick={() => setModalOpen(null)} />
|
908
|
+
</ModalHeader>
|
909
|
+
<ModalBody>
|
910
|
+
<ModalContent>
|
911
|
+
<p>
|
912
|
+
This Modal autofocuses the primary button or any other
|
913
|
+
focusable element.
|
914
|
+
</p>
|
915
|
+
</ModalContent>
|
916
|
+
</ModalBody>
|
917
|
+
<ModalFooter>
|
918
|
+
<Button
|
919
|
+
label="Acknowledge"
|
920
|
+
onClick={() => setModalOpen(null)}
|
921
|
+
ref={modalPrimaryButtonRef}
|
922
|
+
/>
|
923
|
+
<Button
|
924
|
+
color="secondary"
|
925
|
+
label="Close"
|
926
|
+
onClick={() => setModalOpen(null)}
|
927
|
+
priority="outline"
|
928
|
+
ref={modalCloseButtonRef}
|
929
|
+
/>
|
930
|
+
</ModalFooter>
|
931
|
+
</Modal>
|
932
|
+
)}
|
933
|
+
{modalOpen === 3 && (
|
934
|
+
<Modal
|
935
|
+
autoFocus={false}
|
936
|
+
closeButtonRef={modalCloseButtonRef}
|
937
|
+
primaryButtonRef={modalPrimaryButtonRef}
|
938
|
+
>
|
939
|
+
<ModalHeader>
|
940
|
+
<ModalTitle>Modal with autoFocus disabled</ModalTitle>
|
941
|
+
</ModalHeader>
|
942
|
+
<ModalBody>
|
943
|
+
<ModalContent>
|
944
|
+
<p>
|
945
|
+
This Modal focuses the Modal element itself.
|
946
|
+
</p>
|
947
|
+
</ModalContent>
|
948
|
+
</ModalBody>
|
949
|
+
<ModalFooter>
|
950
|
+
<Button
|
951
|
+
label="Acknowledge"
|
952
|
+
onClick={() => setModalOpen(null)}
|
953
|
+
ref={modalPrimaryButtonRef}
|
954
|
+
/>
|
955
|
+
<Button
|
956
|
+
color="secondary"
|
957
|
+
label="Close"
|
958
|
+
onClick={() => setModalOpen(null)}
|
959
|
+
priority="outline"
|
960
|
+
ref={modalCloseButtonRef}
|
961
|
+
/>
|
962
|
+
</ModalFooter>
|
963
|
+
</Modal>
|
964
|
+
)}
|
965
|
+
</div>
|
966
|
+
</>
|
967
|
+
);
|
968
|
+
}}
|
969
|
+
</Playground>
|
843
970
|
|
844
971
|
## Scrolling Long Content
|
845
972
|
|
@@ -959,7 +1086,7 @@ independent of the page itself. This can be done in three ways using the
|
|
959
1086
|
<div>
|
960
1087
|
{modalOpen && (
|
961
1088
|
<Modal
|
962
|
-
autoFocus={
|
1089
|
+
autoFocus={modalScrolling !== 'none'}
|
963
1090
|
closeButtonRef={modalCloseButtonRef}
|
964
1091
|
primaryButtonRef={modalPrimaryButtonRef}
|
965
1092
|
size="small"
|
@@ -1003,9 +1130,154 @@ independent of the page itself. This can be done in three ways using the
|
|
1003
1130
|
|
1004
1131
|
### Long Content and Autofocus
|
1005
1132
|
|
1006
|
-
👉 If you
|
1007
|
-
|
1008
|
-
opened.
|
1133
|
+
👉 If you have a Modal with `scrolling` set to `none`, you may want to disable
|
1134
|
+
`autoFocus` to prevent the modal from scrolling to the end immediately after
|
1135
|
+
being opened.
|
1136
|
+
|
1137
|
+
## Prevent Scrolling Underneath the Modal
|
1138
|
+
|
1139
|
+
You can choose the mode in which Modal prevents the scroll of the page underneath.
|
1140
|
+
Default mode prevents scrolling on `<body>` element and accounts for the scrollbar
|
1141
|
+
width. If you choose `off`, there will be no scroll prevention. If you need more
|
1142
|
+
flexibility, define your methods `start` (called on Modal's mount) and `reset`
|
1143
|
+
(called on Modal unmount) wrapped by an object and handle scroll prevention
|
1144
|
+
yourself.
|
1145
|
+
|
1146
|
+
<Playground>
|
1147
|
+
{() => {
|
1148
|
+
const [modalOpen, setModalOpen] = React.useState(null);
|
1149
|
+
const modalPrimaryButtonRef = React.useRef();
|
1150
|
+
const modalCloseButtonRef = React.useRef();
|
1151
|
+
const customScrollPreventionObject = {
|
1152
|
+
start: () => {
|
1153
|
+
// YOUR CUSTOM SCROLL PREVENTING LOGIC GOES HERE
|
1154
|
+
window.document.body.style.overflowY = 'hidden'
|
1155
|
+
},
|
1156
|
+
reset: () => {
|
1157
|
+
// YOUR CUSTOM SCROLL RE-ENABLING LOGIC GOES HERE
|
1158
|
+
window.document.body.style.overflowY = 'auto'
|
1159
|
+
},
|
1160
|
+
};
|
1161
|
+
return (
|
1162
|
+
<>
|
1163
|
+
<Button
|
1164
|
+
label="Launch modal with default scroll prevention"
|
1165
|
+
onClick={() => setModalOpen(1)}
|
1166
|
+
/>
|
1167
|
+
<Button
|
1168
|
+
label="Launch modal with no scroll prevention"
|
1169
|
+
onClick={() => setModalOpen(2)}
|
1170
|
+
/>
|
1171
|
+
<Button
|
1172
|
+
label="Launch modal with custom scroll prevention"
|
1173
|
+
onClick={() => setModalOpen(3)}
|
1174
|
+
/>
|
1175
|
+
<div>
|
1176
|
+
{modalOpen === 1 && (
|
1177
|
+
<Modal
|
1178
|
+
closeButtonRef={modalCloseButtonRef}
|
1179
|
+
primaryButtonRef={modalPrimaryButtonRef}
|
1180
|
+
>
|
1181
|
+
<ModalHeader>
|
1182
|
+
<ModalTitle>Modal with default scroll prevention</ModalTitle>
|
1183
|
+
<ModalCloseButton onClick={() => setModalOpen(null)} />
|
1184
|
+
</ModalHeader>
|
1185
|
+
<ModalBody>
|
1186
|
+
<ModalContent>
|
1187
|
+
<p>
|
1188
|
+
This Modal uses default scroll prevention on the document's
|
1189
|
+
<code>body</code> element.
|
1190
|
+
</p>
|
1191
|
+
</ModalContent>
|
1192
|
+
</ModalBody>
|
1193
|
+
<ModalFooter>
|
1194
|
+
<Button
|
1195
|
+
label="Acknowledge"
|
1196
|
+
onClick={() => setModalOpen(null)}
|
1197
|
+
ref={modalPrimaryButtonRef}
|
1198
|
+
/>
|
1199
|
+
<Button
|
1200
|
+
color="secondary"
|
1201
|
+
label="Close"
|
1202
|
+
onClick={() => setModalOpen(null)}
|
1203
|
+
priority="outline"
|
1204
|
+
ref={modalCloseButtonRef}
|
1205
|
+
/>
|
1206
|
+
</ModalFooter>
|
1207
|
+
</Modal>
|
1208
|
+
)}
|
1209
|
+
{modalOpen === 2 && (
|
1210
|
+
<Modal
|
1211
|
+
closeButtonRef={modalCloseButtonRef}
|
1212
|
+
preventScrollUnderneath="off"
|
1213
|
+
primaryButtonRef={modalPrimaryButtonRef}
|
1214
|
+
>
|
1215
|
+
<ModalHeader>
|
1216
|
+
<ModalTitle>Modal with no scroll prevention</ModalTitle>
|
1217
|
+
<ModalCloseButton onClick={() => setModalOpen(null)} />
|
1218
|
+
</ModalHeader>
|
1219
|
+
<ModalBody>
|
1220
|
+
<ModalContent>
|
1221
|
+
<p>
|
1222
|
+
This Modal does not prevent scrolling.
|
1223
|
+
</p>
|
1224
|
+
</ModalContent>
|
1225
|
+
</ModalBody>
|
1226
|
+
<ModalFooter>
|
1227
|
+
<Button
|
1228
|
+
label="Acknowledge"
|
1229
|
+
onClick={() => setModalOpen(null)}
|
1230
|
+
ref={modalPrimaryButtonRef}
|
1231
|
+
/>
|
1232
|
+
<Button
|
1233
|
+
color="secondary"
|
1234
|
+
label="Close"
|
1235
|
+
onClick={() => setModalOpen(null)}
|
1236
|
+
priority="outline"
|
1237
|
+
ref={modalCloseButtonRef}
|
1238
|
+
/>
|
1239
|
+
</ModalFooter>
|
1240
|
+
</Modal>
|
1241
|
+
)}
|
1242
|
+
{modalOpen === 3 && (
|
1243
|
+
<Modal
|
1244
|
+
closeButtonRef={modalCloseButtonRef}
|
1245
|
+
preventScrollUnderneath={customScrollPreventionObject}
|
1246
|
+
primaryButtonRef={modalPrimaryButtonRef}
|
1247
|
+
>
|
1248
|
+
<ModalHeader>
|
1249
|
+
<ModalTitle>Modal with custom scroll prevention</ModalTitle>
|
1250
|
+
<ModalCloseButton onClick={() => setModalOpen(null)} />
|
1251
|
+
</ModalHeader>
|
1252
|
+
<ModalBody>
|
1253
|
+
<ModalContent>
|
1254
|
+
<p>
|
1255
|
+
This Modal uses provided custom functions to prevent scrolling
|
1256
|
+
and reset it on unmount.
|
1257
|
+
</p>
|
1258
|
+
</ModalContent>
|
1259
|
+
</ModalBody>
|
1260
|
+
<ModalFooter>
|
1261
|
+
<Button
|
1262
|
+
label="Acknowledge"
|
1263
|
+
onClick={() => setModalOpen(null)}
|
1264
|
+
ref={modalPrimaryButtonRef}
|
1265
|
+
/>
|
1266
|
+
<Button
|
1267
|
+
color="secondary"
|
1268
|
+
label="Close"
|
1269
|
+
onClick={() => setModalOpen(null)}
|
1270
|
+
priority="outline"
|
1271
|
+
ref={modalCloseButtonRef}
|
1272
|
+
/>
|
1273
|
+
</ModalFooter>
|
1274
|
+
</Modal>
|
1275
|
+
)}
|
1276
|
+
</div>
|
1277
|
+
</>
|
1278
|
+
);
|
1279
|
+
}}
|
1280
|
+
</Playground>
|
1009
1281
|
|
1010
1282
|
<!-- markdownlint-disable MD024 -->
|
1011
1283
|
|
@@ -1081,6 +1353,7 @@ accessibility.
|
|
1081
1353
|
| `--rui-Modal--fullscreen__width` | Width of fullscreen modal |
|
1082
1354
|
| `--rui-Modal--fullscreen__height` | Height of fullscreen modal |
|
1083
1355
|
|
1356
|
+
[nng-modal]: https://www.nngroup.com/articles/modal-nonmodal-dialog/
|
1084
1357
|
[React synthetic events]: https://reactjs.org/docs/events.html
|
1085
1358
|
[div]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes
|
1086
1359
|
[heading]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#attributes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
export const getSizeClassName = (modalSize, styles) => {
|
2
|
+
if (modalSize === 'small') {
|
3
|
+
return styles.isRootSizeSmall;
|
4
|
+
}
|
5
|
+
|
6
|
+
if (modalSize === 'medium') {
|
7
|
+
return styles.isRootSizeMedium;
|
8
|
+
}
|
9
|
+
|
10
|
+
if (modalSize === 'large') {
|
11
|
+
return styles.isRootSizeLarge;
|
12
|
+
}
|
13
|
+
|
14
|
+
if (modalSize === 'fullscreen') {
|
15
|
+
return styles.isRootSizeFullscreen;
|
16
|
+
}
|
17
|
+
|
18
|
+
return styles.isRootSizeAuto;
|
19
|
+
};
|
@@ -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
|