@khanacademy/wonder-blocks-modal 2.3.7 → 2.3.9
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 +14 -0
- package/docs.md +4 -491
- package/package.json +3 -3
- package/src/components/__tests__/modal-launcher.test.js +1 -1
- package/src/components/__tests__/modal-panel.test.js +6 -15
- package/src/components/__tests__/one-pane-dialog.test.js +36 -8
- package/building-blocks.md +0 -136
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +0 -3473
- package/src/__tests__/generated-snapshot.test.js +0 -846
- package/src/components/modal-launcher.md +0 -0
- package/src/components/one-pane-dialog.md +0 -250
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-modal
|
|
2
2
|
|
|
3
|
+
## 2.3.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @khanacademy/wonder-blocks-icon-button@3.4.13
|
|
8
|
+
|
|
9
|
+
## 2.3.8
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [3bae2aba]
|
|
14
|
+
- @khanacademy/wonder-blocks-icon@1.2.31
|
|
15
|
+
- @khanacademy/wonder-blocks-icon-button@3.4.12
|
|
16
|
+
|
|
3
17
|
## 2.3.7
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/docs.md
CHANGED
|
@@ -1,492 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
[here](https://deploy-preview-389--wonder-blocks.netlify.com/#modal)
|
|
1
|
+
Documentation for `@khanacademy/wonder-blocks-modal` is now in Storybook.
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Once the modal is launched, tab focus wraps inside the modal content. Pressing Tab at the end of the modal will focus the modal's first element, and pressing Shift-Tab at the start of the modal will focus the modal's last element.
|
|
9
|
-
|
|
10
|
-
```js
|
|
11
|
-
import {StyleSheet} from "aphrodite";
|
|
12
|
-
|
|
13
|
-
import {ModalLauncher, OnePaneDialog} from "@khanacademy/wonder-blocks-modal";
|
|
14
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
15
|
-
import {Body} from "@khanacademy/wonder-blocks-typography";
|
|
16
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
17
|
-
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
18
|
-
|
|
19
|
-
const styles = StyleSheet.create({
|
|
20
|
-
example: {
|
|
21
|
-
padding: Spacing.xLarge_32,
|
|
22
|
-
alignItems: "center",
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
title: {
|
|
26
|
-
marginBottom: Spacing.medium_16,
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
modalContent: {
|
|
30
|
-
margin: "0 auto",
|
|
31
|
-
maxWidth: 544,
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
above: {
|
|
35
|
-
background: "url(/modal-above.png)",
|
|
36
|
-
width: 874,
|
|
37
|
-
height: 551,
|
|
38
|
-
position: "absolute",
|
|
39
|
-
top: 40,
|
|
40
|
-
left: -140
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
below: {
|
|
44
|
-
background: "url(/modal-below.png)",
|
|
45
|
-
width: 868,
|
|
46
|
-
height: 521,
|
|
47
|
-
position: "absolute",
|
|
48
|
-
top: -100,
|
|
49
|
-
left: -300
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const onePaneDialog = ({closeModal}) => (
|
|
54
|
-
<OnePaneDialog
|
|
55
|
-
title="Title"
|
|
56
|
-
subtitle="You're reading the subtitle!"
|
|
57
|
-
above={<View style={styles.above} />}
|
|
58
|
-
below={<View style={styles.below} />}
|
|
59
|
-
testId="dialog-default-example"
|
|
60
|
-
content={
|
|
61
|
-
<View style={styles.modalContent}>
|
|
62
|
-
<Body tag="p">
|
|
63
|
-
{
|
|
64
|
-
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est."
|
|
65
|
-
}
|
|
66
|
-
</Body>
|
|
67
|
-
</View>
|
|
68
|
-
}
|
|
69
|
-
footer={
|
|
70
|
-
<Button onClick={closeModal}>
|
|
71
|
-
Close modal
|
|
72
|
-
</Button>
|
|
73
|
-
}
|
|
74
|
-
/>
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
<View style={styles.example}>
|
|
78
|
-
<ModalLauncher
|
|
79
|
-
modal={onePaneDialog}
|
|
80
|
-
testId="modal-launcher-default-example"
|
|
81
|
-
>
|
|
82
|
-
{({openModal}) => <Button onClick={openModal}>OnePaneDialog</Button>}
|
|
83
|
-
</ModalLauncher>
|
|
84
|
-
</View>;
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Example: Disabling backdrop dismission
|
|
88
|
-
|
|
89
|
-
By default, `ModalLauncher` allows you to close the modal by clicking on the overlay/backdrop window. Somethimes you might need to disable it, and to to this, you can set `backdropDismissEnabled` to `false`.
|
|
90
|
-
|
|
91
|
-
```js
|
|
92
|
-
import {StyleSheet} from "aphrodite";
|
|
93
|
-
|
|
94
|
-
import {ModalLauncher, OnePaneDialog} from "@khanacademy/wonder-blocks-modal";
|
|
95
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
96
|
-
import {Body} from "@khanacademy/wonder-blocks-typography";
|
|
97
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
98
|
-
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
99
|
-
|
|
100
|
-
const styles = StyleSheet.create({
|
|
101
|
-
example: {
|
|
102
|
-
padding: Spacing.xLarge_32,
|
|
103
|
-
alignItems: "center",
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
modalContent: {
|
|
107
|
-
margin: "0 auto",
|
|
108
|
-
maxWidth: 544,
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const exampleModal = ({closeModal}) => (
|
|
113
|
-
<OnePaneDialog
|
|
114
|
-
title="Backdrop dismission disabled"
|
|
115
|
-
content={
|
|
116
|
-
<View style={styles.modalContent}>
|
|
117
|
-
<Body tag="p">
|
|
118
|
-
{
|
|
119
|
-
"This window won't be closed if you click/tap outside of the ModalPanel. To do that, you can still press `esc` or use the close button located on the top right."
|
|
120
|
-
}
|
|
121
|
-
</Body>
|
|
122
|
-
</View>
|
|
123
|
-
}
|
|
124
|
-
/>
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
<View style={styles.example}>
|
|
128
|
-
<ModalLauncher modal={exampleModal} backdropDismissEnabled={false}>
|
|
129
|
-
{({openModal}) => <Button onClick={openModal}>Open modal</Button>}
|
|
130
|
-
</ModalLauncher>
|
|
131
|
-
</View>
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Example: Triggering programmatically
|
|
135
|
-
|
|
136
|
-
Sometimes you'll want to trigger a modal programmatically. This can be done by
|
|
137
|
-
rendering `ModalLauncher` without any children and instead setting its `opened`
|
|
138
|
-
prop to `true`. In this situation `ModalLauncher` is a controlled component
|
|
139
|
-
which means you'll also have to update `opened` to `false` in response to the
|
|
140
|
-
`onClose` callback being triggered.
|
|
141
|
-
|
|
142
|
-
```js
|
|
143
|
-
import {ModalLauncher, OnePaneDialog} from "@khanacademy/wonder-blocks-modal";
|
|
144
|
-
import {Title} from "@khanacademy/wonder-blocks-typography";
|
|
145
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
146
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
147
|
-
import {ActionMenu, ActionItem} from "@khanacademy/wonder-blocks-dropdown";
|
|
148
|
-
|
|
149
|
-
class Example extends React.Component {
|
|
150
|
-
constructor(props) {
|
|
151
|
-
super(props);
|
|
152
|
-
this.state = {
|
|
153
|
-
opened: false,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
handleOpen() {
|
|
158
|
-
console.log('opening modal');
|
|
159
|
-
this.setState({opened: true});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
handleClose() {
|
|
163
|
-
console.log('closing modal');
|
|
164
|
-
this.setState({opened: false});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
render() {
|
|
168
|
-
return <View>
|
|
169
|
-
<ActionMenu menuText="actions">
|
|
170
|
-
<ActionItem
|
|
171
|
-
label="Open modal"
|
|
172
|
-
onClick={() => this.handleOpen()}
|
|
173
|
-
/>
|
|
174
|
-
</ActionMenu>
|
|
175
|
-
<ModalLauncher
|
|
176
|
-
onClose={() => this.handleClose()}
|
|
177
|
-
opened={this.state.opened}
|
|
178
|
-
modal={({closeModal}) => (
|
|
179
|
-
<OnePaneDialog
|
|
180
|
-
title="Triggered from action menu"
|
|
181
|
-
content={
|
|
182
|
-
<View>
|
|
183
|
-
<Title>Hello, world</Title>
|
|
184
|
-
</View>
|
|
185
|
-
}
|
|
186
|
-
footer={
|
|
187
|
-
<Button onClick={closeModal}>
|
|
188
|
-
Close Modal
|
|
189
|
-
</Button>
|
|
190
|
-
}
|
|
191
|
-
/>
|
|
192
|
-
)}
|
|
193
|
-
/>
|
|
194
|
-
</View>;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
<Example />
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
**Warning:** Do not wrap items in a dropdown in a `ModalLauncher`. Instead, trigger
|
|
202
|
-
the modal programmatically by using the `ModalLauncher` as an uncontrolled component
|
|
203
|
-
as shown in the above example.
|
|
204
|
-
|
|
205
|
-
This is necessary because wrapping an item in `ModalLauncher` will result in the
|
|
206
|
-
modal disappearing as soon as the focus changes. The reason is that the change in
|
|
207
|
-
focus results in the item that in the dropdown that was clicked to be blur which
|
|
208
|
-
closes the dropdown. This results in all of its children to unmount including the
|
|
209
|
-
ModalLauncher which was wrapping the menu item.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
## Accessibility
|
|
213
|
-
|
|
214
|
-
It should follow guidelines from [W3C](https://www.w3.org/TR/wai-aria-practices/#dialog_modal).
|
|
215
|
-
|
|
216
|
-
### Keyboard Interaction
|
|
217
|
-
|
|
218
|
-
When a dialog opens, focus moves to an element inside the dialog. See notes below regarding initial focus placement.
|
|
219
|
-
|
|
220
|
-
- Tab:
|
|
221
|
-
- Moves focus to the next tabbable element inside the dialog.
|
|
222
|
-
- If focus is on the last tabbable element inside the dialog, moves focus to the first tabbable element inside the dialog.
|
|
223
|
-
- Shift + Tab:
|
|
224
|
-
- Moves focus to the previous tabbable element inside the dialog.
|
|
225
|
-
- If focus is on the first tabbable element inside the dialog, moves focus to the last tabbable element inside the dialog.
|
|
226
|
-
- Escape: Closes the dialog.
|
|
227
|
-
|
|
228
|
-
#### Initial focus placement:
|
|
229
|
-
The initial focus placement depends on the following scenarios:
|
|
230
|
-
|
|
231
|
-
1. `initialFocusId` (default): `ModalLauncher` exposes this prop as a string. The dialog will try to find this element into the DOM. If it's found, focus is initially set on this element.
|
|
232
|
-
2. focusable elements: This is the second scenario, where the dialog tries to find the first ocurrence of possible focusable elements.
|
|
233
|
-
3. Dialog: If the first two conditions are not met, then focus is initially set to the Dialog element itself.
|
|
234
|
-
|
|
235
|
-
### Example: Set initial focus on a given element inside the modal
|
|
236
|
-
|
|
237
|
-
```js
|
|
238
|
-
import {StyleSheet} from "aphrodite";
|
|
239
|
-
|
|
240
|
-
import {ModalLauncher, OnePaneDialog} from "@khanacademy/wonder-blocks-modal";
|
|
241
|
-
import {Title} from "@khanacademy/wonder-blocks-typography";
|
|
242
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
243
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
244
|
-
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
245
|
-
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
246
|
-
|
|
247
|
-
const styles = StyleSheet.create({
|
|
248
|
-
example: {
|
|
249
|
-
padding: Spacing.xLarge_32,
|
|
250
|
-
alignItems: "center",
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
const modalInitialFocus = ({closeModal}) => (
|
|
255
|
-
<OnePaneDialog
|
|
256
|
-
title="Single-line title"
|
|
257
|
-
content={
|
|
258
|
-
<View>
|
|
259
|
-
<View>
|
|
260
|
-
<label>Label</label>
|
|
261
|
-
<input type="text" />
|
|
262
|
-
<Strut size={Spacing.medium_16} />
|
|
263
|
-
<Button id="initial-focus">
|
|
264
|
-
Button to receive initial focus
|
|
265
|
-
</Button>
|
|
266
|
-
</View>
|
|
267
|
-
</View>
|
|
268
|
-
}
|
|
269
|
-
footer={
|
|
270
|
-
<React.Fragment>
|
|
271
|
-
<Button kind="tertiary" onClick={closeModal}>
|
|
272
|
-
Cancel
|
|
273
|
-
</Button>
|
|
274
|
-
<Strut size={Spacing.medium_16} />
|
|
275
|
-
<Button>
|
|
276
|
-
Submit
|
|
277
|
-
</Button>
|
|
278
|
-
</React.Fragment>
|
|
279
|
-
}
|
|
280
|
-
/>
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
<View style={styles.example}>
|
|
284
|
-
<ModalLauncher
|
|
285
|
-
onClose={() => window.alert("you closed the modal")}
|
|
286
|
-
initialFocusId="initial-focus"
|
|
287
|
-
modal={modalInitialFocus}
|
|
288
|
-
>
|
|
289
|
-
{({openModal}) => <Button onClick={openModal}>Open modal with initial focus</Button>}
|
|
290
|
-
</ModalLauncher>
|
|
291
|
-
</View>
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
### WAI-ARIA Roles
|
|
295
|
-
- The element that serves as the **dialog container** has `aria-role` defined as `dialog`.
|
|
296
|
-
- The dialog has a value set for the `aria-labelledby` property that refers to a **visible dialog title**.
|
|
297
|
-
|
|
298
|
-
## Customization
|
|
299
|
-
|
|
300
|
-
### Example: Flexible dialogs
|
|
301
|
-
|
|
302
|
-
This example illustrates how we can update the Modal's contents by wrapping it into a new component/container. **Modal** is built in a way that provides great flexibility and makes it work with different variations and/or layouts (see Custom Two-Pane Dialog example).
|
|
303
|
-
|
|
304
|
-
```js
|
|
305
|
-
import {StyleSheet} from "aphrodite";
|
|
306
|
-
|
|
307
|
-
import {ModalLauncher, OnePaneDialog} from "@khanacademy/wonder-blocks-modal";
|
|
308
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
309
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
310
|
-
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
311
|
-
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
312
|
-
import {Body, LabelLarge} from "@khanacademy/wonder-blocks-typography";
|
|
313
|
-
|
|
314
|
-
const styles = StyleSheet.create({
|
|
315
|
-
example: {
|
|
316
|
-
padding: Spacing.xLarge_32,
|
|
317
|
-
alignItems: "center",
|
|
318
|
-
},
|
|
319
|
-
row: {
|
|
320
|
-
flexDirection: "row",
|
|
321
|
-
justifyContent: "flex-end"
|
|
322
|
-
},
|
|
323
|
-
footer: {
|
|
324
|
-
alignItems: "center",
|
|
325
|
-
flexDirection: "row",
|
|
326
|
-
justifyContent: "space-between",
|
|
327
|
-
width: "100%"
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
class ExerciseModal extends React.Component {
|
|
332
|
-
render() {
|
|
333
|
-
const {current, handleNextButton, handlePrevButton, question, total} = this.props;
|
|
334
|
-
|
|
335
|
-
return (
|
|
336
|
-
<OnePaneDialog
|
|
337
|
-
title="Exercises"
|
|
338
|
-
content={
|
|
339
|
-
<View>
|
|
340
|
-
<Body>This is the current question: {question}</Body>
|
|
341
|
-
</View>
|
|
342
|
-
}
|
|
343
|
-
footer={
|
|
344
|
-
<View style={styles.footer}>
|
|
345
|
-
<LabelLarge>Step {current+1} of {total}</LabelLarge>
|
|
346
|
-
<View style={styles.row}>
|
|
347
|
-
<Button kind="tertiary" onClick={handlePrevButton}>Previous</Button>
|
|
348
|
-
<Strut size={16} />
|
|
349
|
-
<Button kind="primary" onClick={handleNextButton}>Next</Button>
|
|
350
|
-
</View>
|
|
351
|
-
</View>
|
|
352
|
-
}
|
|
353
|
-
/>
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
class ExerciseContainer extends React.Component {
|
|
359
|
-
constructor(props) {
|
|
360
|
-
super(props);
|
|
361
|
-
|
|
362
|
-
this.state = {
|
|
363
|
-
currentQuestion: 0
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
handleNextButton() {
|
|
368
|
-
this.setState({
|
|
369
|
-
currentQuestion: Math.min(this.state.currentQuestion + 1, this.props.questions.length - 1),
|
|
370
|
-
});
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
handlePrevButton() {
|
|
374
|
-
this.setState({
|
|
375
|
-
currentQuestion: Math.max(0, this.state.currentQuestion - 1),
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
render() {
|
|
380
|
-
return (
|
|
381
|
-
<ModalLauncher
|
|
382
|
-
onClose={() => console.log("you closed the modal")}
|
|
383
|
-
modal={
|
|
384
|
-
<ExerciseModal
|
|
385
|
-
question={this.props.questions[this.state.currentQuestion]}
|
|
386
|
-
current={this.state.currentQuestion}
|
|
387
|
-
total={this.props.questions.length}
|
|
388
|
-
handlePrevButton={() => this.handlePrevButton()}
|
|
389
|
-
handleNextButton={() => this.handleNextButton()}
|
|
390
|
-
/>
|
|
391
|
-
}
|
|
392
|
-
>
|
|
393
|
-
{({openModal}) => <Button onClick={openModal}>Open flexible modal</Button>}
|
|
394
|
-
</ModalLauncher>
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
<View style={styles.example}>
|
|
400
|
-
<ExerciseContainer questions={["First question", "Second question", "Last question"]} />
|
|
401
|
-
</View>
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
### Example: Dialogs with custom styles
|
|
405
|
-
|
|
406
|
-
Sometimes you'll want to customize the styling of the **Dialog** .e.g., custom width or height. You can pass in `style` which will customize the styling of the modal wrapper.
|
|
407
|
-
To use styling for different screen sizes, wrap your component with `MediaLayout` component. Please see example code below for details.
|
|
408
|
-
|
|
409
|
-
```js
|
|
410
|
-
import {StyleSheet} from "aphrodite";
|
|
411
|
-
|
|
412
|
-
import {OnePaneDialog} from "@khanacademy/wonder-blocks-modal";
|
|
413
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
414
|
-
import {Title, Body} from "@khanacademy/wonder-blocks-typography";
|
|
415
|
-
import {MediaLayout} from "@khanacademy/wonder-blocks-layout";
|
|
416
|
-
|
|
417
|
-
const styles = StyleSheet.create({
|
|
418
|
-
previewSizer: {
|
|
419
|
-
height: 512,
|
|
420
|
-
},
|
|
421
|
-
|
|
422
|
-
modalPositioner: {
|
|
423
|
-
// Checkerboard background
|
|
424
|
-
backgroundImage: "linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%)",
|
|
425
|
-
backgroundSize: "20px 20px",
|
|
426
|
-
backgroundPosition: "0 0, 0 10px, 10px -10px, -10px 0px",
|
|
427
|
-
|
|
428
|
-
display: "flex",
|
|
429
|
-
flexDirection: "row",
|
|
430
|
-
alignItems: "center",
|
|
431
|
-
justifyContent: "center",
|
|
432
|
-
|
|
433
|
-
position: "absolute",
|
|
434
|
-
left: 0,
|
|
435
|
-
right: 0,
|
|
436
|
-
top: 0,
|
|
437
|
-
bottom: 0,
|
|
438
|
-
},
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
const styleSheets = {
|
|
442
|
-
mdOrLarger: StyleSheet.create({
|
|
443
|
-
customModal: {
|
|
444
|
-
maxWidth: 300,
|
|
445
|
-
maxHeight: 200,
|
|
446
|
-
},
|
|
447
|
-
|
|
448
|
-
below: {
|
|
449
|
-
background: "url(/blue-blob.png)",
|
|
450
|
-
backgroundSize: "cover",
|
|
451
|
-
width: 294,
|
|
452
|
-
height: 306,
|
|
453
|
-
position: "absolute",
|
|
454
|
-
top: 0,
|
|
455
|
-
left: -60
|
|
456
|
-
},
|
|
457
|
-
|
|
458
|
-
above: {
|
|
459
|
-
background: "url(/asteroid.png)",
|
|
460
|
-
backgroundSize: "cover",
|
|
461
|
-
width: 418,
|
|
462
|
-
height: 260,
|
|
463
|
-
position: "absolute",
|
|
464
|
-
top: -10,
|
|
465
|
-
left: -5
|
|
466
|
-
},
|
|
467
|
-
}),
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
<View style={styles.previewSizer}>
|
|
471
|
-
<View style={styles.modalPositioner}>
|
|
472
|
-
<MediaLayout styleSheets={styleSheets}>
|
|
473
|
-
{({styles}) => (
|
|
474
|
-
<OnePaneDialog
|
|
475
|
-
style={styles.customModal}
|
|
476
|
-
title="Single-line title"
|
|
477
|
-
content={
|
|
478
|
-
<View>
|
|
479
|
-
<Body>
|
|
480
|
-
Hello World!
|
|
481
|
-
</Body>
|
|
482
|
-
</View>
|
|
483
|
-
}
|
|
484
|
-
onClose={() => alert("This would close the modal.")}
|
|
485
|
-
below={<View style={styles.below} />}
|
|
486
|
-
above={<View style={styles.above} />}
|
|
487
|
-
/>
|
|
488
|
-
)}
|
|
489
|
-
</MediaLayout>
|
|
490
|
-
</View>
|
|
491
|
-
</View>;
|
|
492
|
-
```
|
|
3
|
+
Visit the [Storybook
|
|
4
|
+
Modal](https://khan.github.io/wonder-blocks/?path=/docs/modal) docs on GitHub
|
|
5
|
+
Pages.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-modal",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.9",
|
|
4
4
|
"design": "v2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"@khanacademy/wonder-blocks-breadcrumbs": "^1.0.33",
|
|
20
20
|
"@khanacademy/wonder-blocks-color": "^1.2.0",
|
|
21
21
|
"@khanacademy/wonder-blocks-core": "^4.4.0",
|
|
22
|
-
"@khanacademy/wonder-blocks-icon": "^1.2.
|
|
23
|
-
"@khanacademy/wonder-blocks-icon-button": "^3.4.
|
|
22
|
+
"@khanacademy/wonder-blocks-icon": "^1.2.31",
|
|
23
|
+
"@khanacademy/wonder-blocks-icon-button": "^3.4.13",
|
|
24
24
|
"@khanacademy/wonder-blocks-layout": "^1.4.11",
|
|
25
25
|
"@khanacademy/wonder-blocks-spacing": "^3.0.5",
|
|
26
26
|
"@khanacademy/wonder-blocks-toolbar": "^2.1.34",
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
4
|
-
import "jest-enzyme";
|
|
5
|
-
|
|
6
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
3
|
+
import {render, screen} from "@testing-library/react";
|
|
7
4
|
|
|
8
5
|
import expectRenderError from "../../../../../utils/testing/expect-render-error.js";
|
|
9
6
|
import ModalPanel from "../modal-panel.js";
|
|
10
7
|
import ModalContext from "../modal-context.js";
|
|
11
|
-
import CloseButton from "../close-button.js";
|
|
12
8
|
|
|
13
9
|
describe("ModalPanel", () => {
|
|
14
10
|
test("ModalContext.Provider and onClose should warn", () => {
|
|
@@ -26,27 +22,22 @@ describe("ModalPanel", () => {
|
|
|
26
22
|
|
|
27
23
|
test("testId should be added to the panel wrapper", () => {
|
|
28
24
|
// Arrange
|
|
29
|
-
|
|
30
|
-
<ModalPanel content="dummy content" testId="test-id" />,
|
|
31
|
-
);
|
|
25
|
+
render(<ModalPanel content="dummy content" testId="test-id" />);
|
|
32
26
|
|
|
33
27
|
// Act
|
|
34
|
-
const mainElement = wrapper.find(View).first();
|
|
35
28
|
|
|
36
29
|
// Assert
|
|
37
|
-
expect(
|
|
30
|
+
expect(screen.getByTestId("test-id-panel")).toBeInTheDocument();
|
|
38
31
|
});
|
|
39
32
|
|
|
40
33
|
test("testId should be added to the CloseButton element", () => {
|
|
41
34
|
// Arrange
|
|
42
|
-
|
|
43
|
-
<ModalPanel content="dummy content" testId="test-id" />,
|
|
44
|
-
);
|
|
35
|
+
render(<ModalPanel content="dummy content" testId="test-id" />);
|
|
45
36
|
|
|
46
37
|
// Act
|
|
47
|
-
const closeButton =
|
|
38
|
+
const closeButton = screen.getByLabelText("Close modal");
|
|
48
39
|
|
|
49
40
|
// Assert
|
|
50
|
-
expect(closeButton.
|
|
41
|
+
expect(closeButton).toHaveAttribute("data-test-id", "test-id-close");
|
|
51
42
|
});
|
|
52
43
|
});
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
4
|
-
import "jest-enzyme";
|
|
3
|
+
import {render, screen} from "@testing-library/react";
|
|
5
4
|
|
|
5
|
+
import {
|
|
6
|
+
Breadcrumbs,
|
|
7
|
+
BreadcrumbsItem,
|
|
8
|
+
} from "@khanacademy/wonder-blocks-breadcrumbs";
|
|
6
9
|
import OnePaneDialog from "../one-pane-dialog.js";
|
|
7
10
|
|
|
8
11
|
describe("OnePaneDialog", () => {
|
|
9
12
|
test("testId should be set in the Dialog element", () => {
|
|
10
13
|
// Arrange
|
|
11
|
-
|
|
14
|
+
render(
|
|
12
15
|
<OnePaneDialog
|
|
13
16
|
title="Dialog with multi-step footer"
|
|
14
17
|
content="dummy content"
|
|
@@ -17,17 +20,21 @@ describe("OnePaneDialog", () => {
|
|
|
17
20
|
);
|
|
18
21
|
|
|
19
22
|
// Act
|
|
20
|
-
const dialog =
|
|
23
|
+
const dialog = screen.getByRole("dialog");
|
|
21
24
|
|
|
22
25
|
// Assert
|
|
23
|
-
expect(dialog
|
|
26
|
+
expect(dialog).toHaveAttribute(
|
|
27
|
+
"data-test-id",
|
|
28
|
+
"one-pane-dialog-example",
|
|
29
|
+
);
|
|
24
30
|
});
|
|
25
31
|
|
|
26
32
|
test("role can be overriden to alertdialog", () => {
|
|
27
33
|
// Arrange
|
|
28
|
-
|
|
34
|
+
render(
|
|
29
35
|
<OnePaneDialog
|
|
30
36
|
title="Dialog with multi-step footer"
|
|
37
|
+
subtitle="Dialog subtitle"
|
|
31
38
|
content="dummy content"
|
|
32
39
|
testId="one-pane-dialog-example"
|
|
33
40
|
role="alertdialog"
|
|
@@ -35,9 +42,30 @@ describe("OnePaneDialog", () => {
|
|
|
35
42
|
);
|
|
36
43
|
|
|
37
44
|
// Act
|
|
38
|
-
const dialog =
|
|
45
|
+
const dialog = screen.getByRole("alertdialog");
|
|
46
|
+
|
|
47
|
+
// Assert
|
|
48
|
+
expect(dialog).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("should include breadcrumbs", () => {
|
|
52
|
+
// Arrange
|
|
53
|
+
render(
|
|
54
|
+
<OnePaneDialog
|
|
55
|
+
title="Dialog with multi-step footer"
|
|
56
|
+
breadcrumbs={
|
|
57
|
+
<Breadcrumbs>
|
|
58
|
+
<BreadcrumbsItem>test</BreadcrumbsItem>
|
|
59
|
+
</Breadcrumbs>
|
|
60
|
+
}
|
|
61
|
+
content="dummy content"
|
|
62
|
+
testId="one-pane-dialog-example"
|
|
63
|
+
/>,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Act
|
|
39
67
|
|
|
40
68
|
// Assert
|
|
41
|
-
expect(
|
|
69
|
+
expect(screen.getByLabelText("Breadcrumbs")).toBeInTheDocument();
|
|
42
70
|
});
|
|
43
71
|
});
|