@khanacademy/wonder-blocks-modal 2.3.4 → 2.3.6
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/CHANGELOG.md +16 -0
- package/dist/es/index.js +18 -69
- package/dist/index.js +180 -164
- package/package.json +5 -5
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +209 -209
- package/src/components/__docs__/modal-dialog.stories.js +308 -0
- package/src/components/__docs__/modal-footer.stories.js +337 -0
- package/src/components/__docs__/modal-header.argtypes.js +76 -0
- package/src/components/__docs__/modal-header.stories.js +294 -0
- package/src/components/__docs__/modal-launcher.argtypes.js +78 -0
- package/src/components/__docs__/modal-launcher.stories.js +512 -0
- package/src/components/__docs__/modal-panel.stories.js +414 -0
- package/src/components/__docs__/one-pane-dialog.argtypes.js +102 -0
- package/src/components/__docs__/one-pane-dialog.stories.js +582 -0
- package/src/components/__tests__/focus-trap.test.js +101 -0
- package/src/components/focus-trap.js +47 -98
- package/src/components/modal-footer.js +8 -0
- package/src/components/one-pane-dialog.js +26 -1
- package/src/components/one-pane-dialog.stories.js +0 -248
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {StyleSheet} from "aphrodite";
|
|
4
|
+
|
|
5
|
+
import Button from "@khanacademy/wonder-blocks-button";
|
|
6
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
7
|
+
import {ActionMenu, ActionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
8
|
+
import {
|
|
9
|
+
LabeledTextField,
|
|
10
|
+
RadioGroup,
|
|
11
|
+
Choice,
|
|
12
|
+
} from "@khanacademy/wonder-blocks-form";
|
|
13
|
+
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
14
|
+
import {ModalLauncher, OnePaneDialog} from "@khanacademy/wonder-blocks-modal";
|
|
15
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
16
|
+
import {Body, Title} from "@khanacademy/wonder-blocks-typography";
|
|
17
|
+
|
|
18
|
+
import type {StoryComponentType} from "@storybook/react";
|
|
19
|
+
|
|
20
|
+
import type {ModalElement} from "../../util/types.js";
|
|
21
|
+
import ModalLauncherArgTypes from "./modal-launcher.argtypes.js";
|
|
22
|
+
|
|
23
|
+
import ComponentInfo from "../../../../../.storybook/components/component-info.js";
|
|
24
|
+
import {name, version} from "../../../package.json";
|
|
25
|
+
|
|
26
|
+
const customViewports = {
|
|
27
|
+
phone: {
|
|
28
|
+
name: "phone",
|
|
29
|
+
styles: {
|
|
30
|
+
width: "320px",
|
|
31
|
+
height: "568px",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
tablet: {
|
|
35
|
+
name: "tablet",
|
|
36
|
+
styles: {
|
|
37
|
+
width: "640px",
|
|
38
|
+
height: "960px",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
desktop: {
|
|
42
|
+
name: "desktop",
|
|
43
|
+
styles: {
|
|
44
|
+
width: "1024px",
|
|
45
|
+
height: "768px",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const DefaultModal = (): ModalElement => (
|
|
51
|
+
<OnePaneDialog
|
|
52
|
+
title="Single-line title"
|
|
53
|
+
content={
|
|
54
|
+
<View>
|
|
55
|
+
<Body>
|
|
56
|
+
{`Lorem ipsum dolor sit amet, consectetur
|
|
57
|
+
adipiscing elit, sed do eiusmod tempor incididunt
|
|
58
|
+
ut labore et dolore magna aliqua. Ut enim ad minim
|
|
59
|
+
veniam, quis nostrud exercitation ullamco laboris
|
|
60
|
+
nisi ut aliquip ex ea commodo consequat. Duis aute
|
|
61
|
+
irure dolor in reprehenderit in voluptate velit
|
|
62
|
+
esse cillum dolore eu fugiat nulla pariatur.
|
|
63
|
+
Excepteur sint occaecat cupidatat non proident,
|
|
64
|
+
sunt in culpa qui officia deserunt mollit anim id
|
|
65
|
+
est.`}
|
|
66
|
+
</Body>
|
|
67
|
+
</View>
|
|
68
|
+
}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export default {
|
|
73
|
+
title: "Modal/ModalLauncher",
|
|
74
|
+
component: ModalLauncher,
|
|
75
|
+
decorators: [
|
|
76
|
+
(Story: StoryComponentType): React.Element<typeof View> => (
|
|
77
|
+
<View style={styles.example}>
|
|
78
|
+
<Story />
|
|
79
|
+
</View>
|
|
80
|
+
),
|
|
81
|
+
],
|
|
82
|
+
parameters: {
|
|
83
|
+
componentSubtitle: ((
|
|
84
|
+
<ComponentInfo name={name} version={version} />
|
|
85
|
+
): any),
|
|
86
|
+
docs: {
|
|
87
|
+
description: {
|
|
88
|
+
component: null,
|
|
89
|
+
},
|
|
90
|
+
source: {
|
|
91
|
+
// See https://github.com/storybookjs/storybook/issues/12596
|
|
92
|
+
excludeDecorators: true,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
viewport: {
|
|
96
|
+
viewports: customViewports,
|
|
97
|
+
defaultViewport: "desktop",
|
|
98
|
+
},
|
|
99
|
+
chromatic: {
|
|
100
|
+
viewports: [320, 640, 1024],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
argTypes: ModalLauncherArgTypes,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const Default: StoryComponentType = (args) => (
|
|
107
|
+
<ModalLauncher modal={DefaultModal} {...args}>
|
|
108
|
+
{({openModal}) => (
|
|
109
|
+
<Button onClick={openModal}>Click me to open the modal</Button>
|
|
110
|
+
)}
|
|
111
|
+
</ModalLauncher>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
Default.parameters = {
|
|
115
|
+
chromatic: {
|
|
116
|
+
// All the examples for ModalLauncher are behavior based, not visual.
|
|
117
|
+
disableSnapshot: true,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const Simple: StoryComponentType = () => (
|
|
122
|
+
<ModalLauncher modal={DefaultModal}>
|
|
123
|
+
{({openModal}) => (
|
|
124
|
+
<Button onClick={openModal}>Click me to open the modal</Button>
|
|
125
|
+
)}
|
|
126
|
+
</ModalLauncher>
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
Simple.parameters = {
|
|
130
|
+
chromatic: {
|
|
131
|
+
// All the examples for ModalLauncher are behavior based, not visual.
|
|
132
|
+
disableSnapshot: true,
|
|
133
|
+
},
|
|
134
|
+
docs: {
|
|
135
|
+
storyDescription: `This is a basic modal launcher. Its child, the
|
|
136
|
+
button, has access to the \`openModal\` function via the
|
|
137
|
+
function-as-child pattern. It passes this into its \`onClick\`
|
|
138
|
+
function, which causes the modal to launch when the button
|
|
139
|
+
is clicked.`,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const WithCustomCloseButton: StoryComponentType = () => {
|
|
144
|
+
type MyModalProps = {|
|
|
145
|
+
closeModal: () => void,
|
|
146
|
+
|};
|
|
147
|
+
|
|
148
|
+
const ModalWithCloseButton = ({closeModal}: MyModalProps): ModalElement => (
|
|
149
|
+
<OnePaneDialog
|
|
150
|
+
title="Single-line title"
|
|
151
|
+
content={
|
|
152
|
+
<View>
|
|
153
|
+
<Body>
|
|
154
|
+
{`Lorem ipsum dolor sit amet, consectetur
|
|
155
|
+
adipiscing elit, sed do eiusmod tempor incididunt
|
|
156
|
+
ut labore et dolore magna aliqua. Ut enim ad minim
|
|
157
|
+
veniam, quis nostrud exercitation ullamco laboris
|
|
158
|
+
nisi ut aliquip ex ea commodo consequat. Duis aute
|
|
159
|
+
irure dolor in reprehenderit in voluptate velit
|
|
160
|
+
esse cillum dolore eu fugiat nulla pariatur.
|
|
161
|
+
Excepteur sint occaecat cupidatat non proident,
|
|
162
|
+
sunt in culpa qui officia deserunt mollit anim id
|
|
163
|
+
est.`}
|
|
164
|
+
</Body>
|
|
165
|
+
</View>
|
|
166
|
+
}
|
|
167
|
+
// No "X" close button in the top right corner
|
|
168
|
+
closeButtonVisible={false}
|
|
169
|
+
footer={<Button onClick={closeModal}>Close</Button>}
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<ModalLauncher modal={ModalWithCloseButton}>
|
|
175
|
+
{({openModal}) => (
|
|
176
|
+
<Button onClick={openModal}>Click me to open the modal</Button>
|
|
177
|
+
)}
|
|
178
|
+
</ModalLauncher>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
WithCustomCloseButton.parameters = {
|
|
183
|
+
chromatic: {
|
|
184
|
+
// All the examples for ModalLauncher are behavior based, not visual.
|
|
185
|
+
disableSnapshot: true,
|
|
186
|
+
},
|
|
187
|
+
docs: {
|
|
188
|
+
storyDescription: `This is an example of a modal that uses
|
|
189
|
+
a close button other than the default "X" button in the top
|
|
190
|
+
right corner. Here, the default "X" close button is not rendered
|
|
191
|
+
because the \`closeButtonVisible\` prop on the \`<OnePaneDialog>\`
|
|
192
|
+
is set to false. Instead, a custom close button has been added
|
|
193
|
+
to the modal footer. The \`modal\` prop on \`<ModalLauncher>\`
|
|
194
|
+
can either be a plain modal, or it can be a function that takes
|
|
195
|
+
a \`closeModal\` function as a parameter and returns a modal.
|
|
196
|
+
The latter is what we do in this case. Then the \`closeModal\`
|
|
197
|
+
function is passed into the \`onClick\` prop on the button
|
|
198
|
+
in the footer.`,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const WithBackdropDismissDisabled: StoryComponentType = () => (
|
|
203
|
+
<ModalLauncher modal={DefaultModal} backdropDismissEnabled={false}>
|
|
204
|
+
{({openModal}) => (
|
|
205
|
+
<Button onClick={openModal}>Click me to open the modal</Button>
|
|
206
|
+
)}
|
|
207
|
+
</ModalLauncher>
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
WithBackdropDismissDisabled.parameters = {
|
|
211
|
+
chromatic: {
|
|
212
|
+
// All the examples for ModalLauncher are behavior based, not visual.
|
|
213
|
+
disableSnapshot: true,
|
|
214
|
+
},
|
|
215
|
+
docs: {
|
|
216
|
+
storyDescription: `This is an example in which the modal _cannot_
|
|
217
|
+
be dismissed by clicking in in the backdrop. This is done by
|
|
218
|
+
setting the \`backdropDismissEnabled\` prop on the
|
|
219
|
+
\`<ModalLauncher>\` element to false.`,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const TriggeringProgrammatically: StoryComponentType = () => {
|
|
224
|
+
const [opened, setOpened] = React.useState(false);
|
|
225
|
+
|
|
226
|
+
const handleOpen = () => {
|
|
227
|
+
setOpened(true);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const handleClose = () => {
|
|
231
|
+
setOpened(false);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<View>
|
|
236
|
+
<ActionMenu menuText="actions">
|
|
237
|
+
<ActionItem label="Open modal" onClick={handleOpen} />
|
|
238
|
+
</ActionMenu>
|
|
239
|
+
|
|
240
|
+
<ModalLauncher
|
|
241
|
+
onClose={handleClose}
|
|
242
|
+
opened={opened}
|
|
243
|
+
modal={({closeModal}) => (
|
|
244
|
+
<OnePaneDialog
|
|
245
|
+
title="Triggered from action menu"
|
|
246
|
+
content={
|
|
247
|
+
<View>
|
|
248
|
+
<Title>Hello, world</Title>
|
|
249
|
+
</View>
|
|
250
|
+
}
|
|
251
|
+
footer={
|
|
252
|
+
<Button onClick={closeModal}>Close Modal</Button>
|
|
253
|
+
}
|
|
254
|
+
/>
|
|
255
|
+
)}
|
|
256
|
+
// Note that this modal launcher has no children.
|
|
257
|
+
/>
|
|
258
|
+
</View>
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
TriggeringProgrammatically.parameters = {
|
|
263
|
+
chromatic: {
|
|
264
|
+
// All the examples for ModalLauncher are behavior based, not visual.
|
|
265
|
+
disableSnapshot: true,
|
|
266
|
+
},
|
|
267
|
+
docs: {
|
|
268
|
+
storyDescription: `Sometimes you'll want to trigger a modal
|
|
269
|
+
programmatically. This can be done by rendering \`<ModalLauncher>\`
|
|
270
|
+
without any children and instead setting its \`opened\` prop to
|
|
271
|
+
true. In this situation, \`ModalLauncher\` is a controlled
|
|
272
|
+
component which means you'll also have to update \`opened\` to
|
|
273
|
+
false in response to the \`onClose\` callback being triggered.
|
|
274
|
+
It is necessary to use this method in this example, as
|
|
275
|
+
\`ActionMenu\` cannot have a \`ModalLauncher\` element as a child,
|
|
276
|
+
(it can only have \`Item\` elements as children), so launching a
|
|
277
|
+
modal from a dropdown must be done programatically.`,
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export const WithClosedFocusId: StoryComponentType = () => {
|
|
282
|
+
const [opened, setOpened] = React.useState(false);
|
|
283
|
+
|
|
284
|
+
const handleOpen = () => {
|
|
285
|
+
setOpened(true);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const handleClose = () => {
|
|
289
|
+
setOpened(false);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<View style={{gap: 20}}>
|
|
294
|
+
<Button>Top of page (should not receive focus)</Button>
|
|
295
|
+
<Button id="button-to-focus-on">Focus here after close</Button>
|
|
296
|
+
<ActionMenu menuText="actions">
|
|
297
|
+
<ActionItem label="Open modal" onClick={() => handleOpen()} />
|
|
298
|
+
</ActionMenu>
|
|
299
|
+
<ModalLauncher
|
|
300
|
+
onClose={() => handleClose()}
|
|
301
|
+
opened={opened}
|
|
302
|
+
closedFocusId="button-to-focus-on"
|
|
303
|
+
modal={DefaultModal}
|
|
304
|
+
/>
|
|
305
|
+
</View>
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
WithClosedFocusId.parameters = {
|
|
310
|
+
chromatic: {
|
|
311
|
+
// All the examples for ModalLauncher are behavior based, not visual.
|
|
312
|
+
disableSnapshot: true,
|
|
313
|
+
},
|
|
314
|
+
docs: {
|
|
315
|
+
storyDescription: `You can use the \`closedFocusId\` prop on the
|
|
316
|
+
\`ModalLauncher\` to specify where to set the focus after the
|
|
317
|
+
modal has been closed. Imagine the following situation:
|
|
318
|
+
clicking on a dropdown menu option to open a modal
|
|
319
|
+
causes the dropdown to close, and so all of the dropdown options
|
|
320
|
+
are removed from the DOM. This can be a problem because by
|
|
321
|
+
default, the focus shifts to the previously focused element after
|
|
322
|
+
a modal is closed; in this case, the element that opened the modal
|
|
323
|
+
cannot receive focus since it no longer exists in the DOM,
|
|
324
|
+
so when you close the modal, it doesn't know where to focus on the
|
|
325
|
+
page. When the previously focused element no longer exists,
|
|
326
|
+
the focus shifts to the page body, which causes a jump to
|
|
327
|
+
the top of the page. This can make it diffcult to find the original
|
|
328
|
+
dropdown. A solution to this is to use the \`closedFocusId\` prop\
|
|
329
|
+
to specify where to set the focus after the modal has been closed.
|
|
330
|
+
In this example, \`closedFocusId\` is set to the ID of the button
|
|
331
|
+
labeled "Focus here after close." If the focus shifts to the button
|
|
332
|
+
labeled "Top of page (should not receieve focus)," then the focus
|
|
333
|
+
is on the page body, and the \`closedFocusId\` did not work.`,
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
export const WithInitialFocusId: StoryComponentType = () => {
|
|
338
|
+
const [value, setValue] = React.useState("Previously stored value");
|
|
339
|
+
const [value2, setValue2] = React.useState("");
|
|
340
|
+
|
|
341
|
+
const modalInitialFocus = ({closeModal}) => (
|
|
342
|
+
<OnePaneDialog
|
|
343
|
+
title="Single-line title"
|
|
344
|
+
content={
|
|
345
|
+
<>
|
|
346
|
+
<LabeledTextField
|
|
347
|
+
label="Label"
|
|
348
|
+
value={value}
|
|
349
|
+
onChange={setValue}
|
|
350
|
+
/>
|
|
351
|
+
<Strut size={Spacing.large_24} />
|
|
352
|
+
<LabeledTextField
|
|
353
|
+
label="Label 2"
|
|
354
|
+
value={value2}
|
|
355
|
+
onChange={setValue2}
|
|
356
|
+
id="text-field-to-be-focused"
|
|
357
|
+
/>
|
|
358
|
+
</>
|
|
359
|
+
}
|
|
360
|
+
footer={
|
|
361
|
+
<>
|
|
362
|
+
<Button kind="tertiary" onClick={closeModal}>
|
|
363
|
+
Cancel
|
|
364
|
+
</Button>
|
|
365
|
+
<Strut size={Spacing.medium_16} />
|
|
366
|
+
<Button onClick={closeModal}>Submit</Button>
|
|
367
|
+
</>
|
|
368
|
+
}
|
|
369
|
+
/>
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<ModalLauncher
|
|
374
|
+
modal={modalInitialFocus}
|
|
375
|
+
initialFocusId="text-field-to-be-focused-field"
|
|
376
|
+
>
|
|
377
|
+
{({openModal}) => (
|
|
378
|
+
<Button onClick={openModal}>
|
|
379
|
+
Open modal with initial focus
|
|
380
|
+
</Button>
|
|
381
|
+
)}
|
|
382
|
+
</ModalLauncher>
|
|
383
|
+
);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
WithInitialFocusId.parameters = {
|
|
387
|
+
chromatic: {
|
|
388
|
+
// All the examples for ModalLauncher are behavior based, not visual.
|
|
389
|
+
disableSnapshot: true,
|
|
390
|
+
},
|
|
391
|
+
docs: {
|
|
392
|
+
storyDescription: `Sometimes, you may want a specific element inside
|
|
393
|
+
the modal to receive focus first. This can be done using the
|
|
394
|
+
\`initialFocusId\` prop on the \`<ModalLauncher>\` element.
|
|
395
|
+
Just pass in the ID of the element that should receive focus,
|
|
396
|
+
and it will automatically receieve focus once the modal opens.
|
|
397
|
+
In this example, the top text input would have received the focus
|
|
398
|
+
by default, but the bottom text field receives focus instead
|
|
399
|
+
since its ID is passed into the \`initialFocusId\` prop.`,
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Focus trap navigation
|
|
405
|
+
*/
|
|
406
|
+
const SubModal = () => (
|
|
407
|
+
<OnePaneDialog
|
|
408
|
+
title="Submodal"
|
|
409
|
+
content={
|
|
410
|
+
<View style={{gap: Spacing.medium_16}}>
|
|
411
|
+
<Body>
|
|
412
|
+
This modal demonstrates how the focus trap works when a
|
|
413
|
+
modal is opened from another modal.
|
|
414
|
+
</Body>
|
|
415
|
+
<Body>
|
|
416
|
+
Try navigating this modal with the keyboard and then close
|
|
417
|
+
it. The focus should be restored to the button that opened
|
|
418
|
+
the modal.
|
|
419
|
+
</Body>
|
|
420
|
+
<LabeledTextField label="Label" value="" onChange={() => {}} />
|
|
421
|
+
<Button>A focusable element</Button>
|
|
422
|
+
</View>
|
|
423
|
+
}
|
|
424
|
+
/>
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
export const FocusTrap: StoryComponentType = () => {
|
|
428
|
+
const [selectedValue, setSelectedValue] = React.useState(null);
|
|
429
|
+
|
|
430
|
+
const modalInitialFocus = ({closeModal}) => (
|
|
431
|
+
<OnePaneDialog
|
|
432
|
+
title="Testing the focus trap on multiple modals"
|
|
433
|
+
closeButtonVisible={false}
|
|
434
|
+
content={
|
|
435
|
+
<>
|
|
436
|
+
<Body>
|
|
437
|
+
This modal demonstrates how the focus trap works with
|
|
438
|
+
form elements (or focusable elements). Also demonstrates
|
|
439
|
+
how the focus trap is moved to the next modal when it is
|
|
440
|
+
opened (focus/tap on the `Open another modal` button).
|
|
441
|
+
</Body>
|
|
442
|
+
<Strut size={Spacing.large_24} />
|
|
443
|
+
<RadioGroup
|
|
444
|
+
label="A RadioGroup component inside a modal"
|
|
445
|
+
description="Some description"
|
|
446
|
+
groupName="some-group-name"
|
|
447
|
+
onChange={setSelectedValue}
|
|
448
|
+
selectedValue={selectedValue ?? ""}
|
|
449
|
+
>
|
|
450
|
+
<Choice label="Choice 1" value="some-choice-value" />
|
|
451
|
+
<Choice label="Choice 2" value="some-choice-value-2" />
|
|
452
|
+
</RadioGroup>
|
|
453
|
+
</>
|
|
454
|
+
}
|
|
455
|
+
footer={
|
|
456
|
+
<>
|
|
457
|
+
<ModalLauncher modal={SubModal}>
|
|
458
|
+
{({openModal}) => (
|
|
459
|
+
<Button kind="secondary" onClick={openModal}>
|
|
460
|
+
Open another modal
|
|
461
|
+
</Button>
|
|
462
|
+
)}
|
|
463
|
+
</ModalLauncher>
|
|
464
|
+
<Strut size={Spacing.medium_16} />
|
|
465
|
+
<Button onClick={closeModal} disabled={!selectedValue}>
|
|
466
|
+
Next
|
|
467
|
+
</Button>
|
|
468
|
+
</>
|
|
469
|
+
}
|
|
470
|
+
/>
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
return (
|
|
474
|
+
<ModalLauncher modal={modalInitialFocus}>
|
|
475
|
+
{({openModal}) => (
|
|
476
|
+
<Button onClick={openModal}>Open modal with RadioGroup</Button>
|
|
477
|
+
)}
|
|
478
|
+
</ModalLauncher>
|
|
479
|
+
);
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
FocusTrap.storyName = "Navigation with focus trap";
|
|
483
|
+
|
|
484
|
+
FocusTrap.parameters = {
|
|
485
|
+
chromatic: {
|
|
486
|
+
// All the examples for ModalLauncher are behavior based, not visual.
|
|
487
|
+
disableSnapshot: true,
|
|
488
|
+
},
|
|
489
|
+
docs: {
|
|
490
|
+
storyDescription:
|
|
491
|
+
`All modals have a focus trap, which means that the
|
|
492
|
+
focus is locked inside the modal. This is done to prevent the user
|
|
493
|
+
from tabbing out of the modal and losing their place. The focus
|
|
494
|
+
trap is also used to ensure that the focus is restored to the
|
|
495
|
+
correct element when the modal is closed. In this example, the
|
|
496
|
+
focus is trapped inside the modal, and the focus is restored to the
|
|
497
|
+
button that opened the modal when the modal is closed.\n\n` +
|
|
498
|
+
`Also, this example includes a sub-modal that is opened from the
|
|
499
|
+
first modal so we can test how the focus trap works when multiple
|
|
500
|
+
modals are open.`,
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const styles = StyleSheet.create({
|
|
505
|
+
example: {
|
|
506
|
+
alignItems: "center",
|
|
507
|
+
justifyContent: "center",
|
|
508
|
+
},
|
|
509
|
+
row: {
|
|
510
|
+
flexDirection: "row",
|
|
511
|
+
},
|
|
512
|
+
});
|