@khanacademy/wonder-blocks-modal 2.3.0 → 2.3.1
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 +12 -0
- package/dist/es/index.js +60 -351
- package/package.json +9 -9
- package/src/components/one-pane-dialog.stories.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-modal
|
|
2
2
|
|
|
3
|
+
## 2.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @khanacademy/wonder-blocks-breadcrumbs@1.0.30
|
|
8
|
+
- @khanacademy/wonder-blocks-core@4.3.1
|
|
9
|
+
- @khanacademy/wonder-blocks-icon@1.2.27
|
|
10
|
+
- @khanacademy/wonder-blocks-icon-button@3.4.6
|
|
11
|
+
- @khanacademy/wonder-blocks-layout@1.4.9
|
|
12
|
+
- @khanacademy/wonder-blocks-toolbar@2.1.31
|
|
13
|
+
- @khanacademy/wonder-blocks-typography@1.1.31
|
|
14
|
+
|
|
3
15
|
## 2.3.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/es/index.js
CHANGED
|
@@ -11,16 +11,6 @@ import _extends from '@babel/runtime/helpers/extends';
|
|
|
11
11
|
import { icons } from '@khanacademy/wonder-blocks-icon';
|
|
12
12
|
import IconButton from '@khanacademy/wonder-blocks-icon-button';
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* `ModalDialog` is a component that contains these elements:
|
|
16
|
-
* - The visual dialog element itself (`<div role="dialog"/>`)
|
|
17
|
-
* - The custom contents below and/or above the Dialog itself (e.g. decorative graphics).
|
|
18
|
-
*
|
|
19
|
-
* **Accessibility notes:**
|
|
20
|
-
* - By default (e.g. using `OnePaneDialog`), `aria-labelledby` is populated automatically using the dialog title `id`.
|
|
21
|
-
* - If there is a custom Dialog implementation (e.g. `TwoPaneDialog`), the dialog element doesn’t have to have
|
|
22
|
-
* the `aria-labelledby` attribute however this is recommended. It should match the `id` of the dialog title.
|
|
23
|
-
*/
|
|
24
14
|
class ModalDialog extends React.Component {
|
|
25
15
|
render() {
|
|
26
16
|
const {
|
|
@@ -36,23 +26,23 @@ class ModalDialog extends React.Component {
|
|
|
36
26
|
ssrSize: "large",
|
|
37
27
|
mediaSpec: MEDIA_MODAL_SPEC
|
|
38
28
|
};
|
|
39
|
-
return
|
|
29
|
+
return React.createElement(MediaLayoutContext.Provider, {
|
|
40
30
|
value: contextValue
|
|
41
|
-
},
|
|
31
|
+
}, React.createElement(MediaLayout, {
|
|
42
32
|
styleSheets: styleSheets$3
|
|
43
33
|
}, ({
|
|
44
34
|
styles
|
|
45
|
-
}) =>
|
|
35
|
+
}) => React.createElement(View, {
|
|
46
36
|
style: [styles.wrapper, style]
|
|
47
|
-
}, below &&
|
|
37
|
+
}, below && React.createElement(View, {
|
|
48
38
|
style: styles.below
|
|
49
|
-
}, below),
|
|
39
|
+
}, below), React.createElement(View, {
|
|
50
40
|
role: role,
|
|
51
41
|
"aria-modal": "true",
|
|
52
42
|
"aria-labelledby": ariaLabelledBy,
|
|
53
43
|
style: styles.dialog,
|
|
54
44
|
testId: testId
|
|
55
|
-
}, children), above &&
|
|
45
|
+
}, children), above && React.createElement(View, {
|
|
56
46
|
style: styles.above
|
|
57
47
|
}, above))));
|
|
58
48
|
}
|
|
@@ -71,10 +61,6 @@ const styleSheets$3 = {
|
|
|
71
61
|
height: "100%",
|
|
72
62
|
position: "relative"
|
|
73
63
|
},
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Ensures the dialog container uses the container size
|
|
77
|
-
*/
|
|
78
64
|
dialog: {
|
|
79
65
|
width: "100%",
|
|
80
66
|
height: "100%",
|
|
@@ -108,15 +94,6 @@ const styleSheets$3 = {
|
|
|
108
94
|
})
|
|
109
95
|
};
|
|
110
96
|
|
|
111
|
-
/**
|
|
112
|
-
* Modal footer included after the content.
|
|
113
|
-
*
|
|
114
|
-
* **Implementation notes**:
|
|
115
|
-
*
|
|
116
|
-
* If you are creating a custom Dialog, make sure to follow these guidelines:
|
|
117
|
-
* - Make sure to include it as part of [ModalPanel](/#modalpanel) by using the `footer` prop.
|
|
118
|
-
* - The footer is completely flexible. Meaning the developer needs to add its own custom layout to match design specs.
|
|
119
|
-
*/
|
|
120
97
|
class ModalFooter extends React.Component {
|
|
121
98
|
static isClassOf(instance) {
|
|
122
99
|
return instance && instance.type && instance.type.__IS_MODAL_FOOTER__;
|
|
@@ -126,7 +103,7 @@ class ModalFooter extends React.Component {
|
|
|
126
103
|
const {
|
|
127
104
|
children
|
|
128
105
|
} = this.props;
|
|
129
|
-
return
|
|
106
|
+
return React.createElement(View, {
|
|
130
107
|
style: styles$3.footer
|
|
131
108
|
}, children);
|
|
132
109
|
}
|
|
@@ -150,49 +127,6 @@ const styles$3 = StyleSheet.create({
|
|
|
150
127
|
}
|
|
151
128
|
});
|
|
152
129
|
|
|
153
|
-
/**
|
|
154
|
-
* This is a helper component that is never rendered by itself. It is always
|
|
155
|
-
* pinned to the top of the dialog, is responsive using the same behavior as its
|
|
156
|
-
* parent dialog, and has the following properties:
|
|
157
|
-
* - title
|
|
158
|
-
* - breadcrumb OR subtitle, but not both.
|
|
159
|
-
*
|
|
160
|
-
* **Accessibility notes:**
|
|
161
|
-
*
|
|
162
|
-
* - By default (e.g. using [OnePaneDialog](/#onepanedialog)), `titleId` is
|
|
163
|
-
* populated automatically by the parent container.
|
|
164
|
-
* - If there is a custom Dialog implementation (e.g. `TwoPaneDialog`), the
|
|
165
|
-
* ModalHeader doesn’t have to have the `titleId` prop however this is
|
|
166
|
-
* recommended. It should match the `aria-labelledby` prop of the
|
|
167
|
-
* [ModalDialog](/#modaldialog) component. If you want to see an example of
|
|
168
|
-
* how to generate this ID, check [IDProvider](/#idprovider).
|
|
169
|
-
*
|
|
170
|
-
* **Implementation notes:**
|
|
171
|
-
*
|
|
172
|
-
* If you are creating a custom Dialog, make sure to follow these guidelines:
|
|
173
|
-
* - Make sure to include it as part of [ModalPanel](/#modalpanel) by using the
|
|
174
|
-
* `header` prop.
|
|
175
|
-
* - Add a title (required).
|
|
176
|
-
* - Optionally add a subtitle or breadcrumbs.
|
|
177
|
-
* - We encourage you to add `titleId` (see Accessibility notes).
|
|
178
|
-
* - If the `ModalPanel` has a dark background, make sure to set `light` to
|
|
179
|
-
* `false`.
|
|
180
|
-
* - If you need to create e2e tests, make sure to pass a `testId` prop and
|
|
181
|
-
* add a sufix to scope the testId to this component: e.g.
|
|
182
|
-
* `some-random-id-ModalHeader`. This scope will also be passed to the title
|
|
183
|
-
* and subtitle elements: e.g. `some-random-id-ModalHeader-title`.
|
|
184
|
-
*
|
|
185
|
-
* Example:
|
|
186
|
-
*
|
|
187
|
-
* ```js
|
|
188
|
-
* <ModalHeader
|
|
189
|
-
* title="Sidebar using ModalHeader"
|
|
190
|
-
* subtitle="subtitle"
|
|
191
|
-
* titleId="uniqueTitleId"
|
|
192
|
-
* light={false}
|
|
193
|
-
* />
|
|
194
|
-
* ```
|
|
195
|
-
*/
|
|
196
130
|
class ModalHeader extends React.Component {
|
|
197
131
|
render() {
|
|
198
132
|
const {
|
|
@@ -208,20 +142,20 @@ class ModalHeader extends React.Component {
|
|
|
208
142
|
throw new Error("'subtitle' and 'breadcrumbs' can't be used together");
|
|
209
143
|
}
|
|
210
144
|
|
|
211
|
-
return
|
|
145
|
+
return React.createElement(MediaLayout, {
|
|
212
146
|
styleSheets: styleSheets$2
|
|
213
147
|
}, ({
|
|
214
148
|
styles
|
|
215
|
-
}) =>
|
|
149
|
+
}) => React.createElement(View, {
|
|
216
150
|
style: [styles.header, !light && styles.dark],
|
|
217
151
|
testId: testId
|
|
218
|
-
}, breadcrumbs &&
|
|
152
|
+
}, breadcrumbs && React.createElement(View, {
|
|
219
153
|
style: styles.breadcrumbs
|
|
220
|
-
}, breadcrumbs),
|
|
154
|
+
}, breadcrumbs), React.createElement(HeadingMedium, {
|
|
221
155
|
style: styles.title,
|
|
222
156
|
id: titleId,
|
|
223
157
|
testId: testId && `${testId}-title`
|
|
224
|
-
}, title), subtitle &&
|
|
158
|
+
}, title), subtitle && React.createElement(LabelSmall, {
|
|
225
159
|
style: light && styles.subtitle,
|
|
226
160
|
testId: testId && `${testId}-subtitle`
|
|
227
161
|
}, subtitle)));
|
|
@@ -251,7 +185,6 @@ const styleSheets$2 = {
|
|
|
251
185
|
marginBottom: Spacing.xSmall_8
|
|
252
186
|
},
|
|
253
187
|
title: {
|
|
254
|
-
// Prevent title from overlapping the close button
|
|
255
188
|
paddingRight: Spacing.medium_16
|
|
256
189
|
},
|
|
257
190
|
subtitle: {
|
|
@@ -271,22 +204,11 @@ const styleSheets$2 = {
|
|
|
271
204
|
};
|
|
272
205
|
|
|
273
206
|
class FocusTrap extends React.Component {
|
|
274
|
-
/** The most recent node _inside this component_ to receive focus. */
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Whether we're currently applying programmatic focus, and should therefore
|
|
278
|
-
* ignore focus change events.
|
|
279
|
-
*/
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Tabbing is restricted to descendents of this element.
|
|
283
|
-
*/
|
|
284
207
|
constructor(props) {
|
|
285
208
|
super(props);
|
|
286
209
|
|
|
287
210
|
this.getModalRoot = node => {
|
|
288
211
|
if (!node) {
|
|
289
|
-
// The component is being umounted
|
|
290
212
|
return;
|
|
291
213
|
}
|
|
292
214
|
|
|
@@ -300,8 +222,6 @@ class FocusTrap extends React.Component {
|
|
|
300
222
|
};
|
|
301
223
|
|
|
302
224
|
this.handleGlobalFocus = e => {
|
|
303
|
-
// If we're busy applying our own programmatic focus, we ignore focus
|
|
304
|
-
// changes, to avoid an infinite loop.
|
|
305
225
|
if (this.ignoreFocusChanges) {
|
|
306
226
|
return;
|
|
307
227
|
}
|
|
@@ -309,7 +229,6 @@ class FocusTrap extends React.Component {
|
|
|
309
229
|
const target = e.target;
|
|
310
230
|
|
|
311
231
|
if (!(target instanceof Node)) {
|
|
312
|
-
// Sometimes focus events trigger on the document itself. Ignore!
|
|
313
232
|
return;
|
|
314
233
|
}
|
|
315
234
|
|
|
@@ -320,25 +239,13 @@ class FocusTrap extends React.Component {
|
|
|
320
239
|
}
|
|
321
240
|
|
|
322
241
|
if (modalRoot.contains(target)) {
|
|
323
|
-
// If the newly focused node is inside the modal, we just keep track
|
|
324
|
-
// of that.
|
|
325
242
|
this.lastNodeFocusedInModal = target;
|
|
326
243
|
} else {
|
|
327
|
-
|
|
328
|
-
// the first focusable node of the modal. (This could be the user
|
|
329
|
-
// pressing Tab on the last node of the modal, or focus escaping in
|
|
330
|
-
// some other way.)
|
|
331
|
-
this.focusFirstElementIn(modalRoot); // But, if it turns out that the first focusable node of the modal
|
|
332
|
-
// was what we were previously focusing, then this is probably the
|
|
333
|
-
// user pressing Shift-Tab on the first node, wanting to go to the
|
|
334
|
-
// end. So, we instead try focusing the last focusable node of the
|
|
335
|
-
// modal.
|
|
244
|
+
this.focusFirstElementIn(modalRoot);
|
|
336
245
|
|
|
337
246
|
if (document.activeElement === this.lastNodeFocusedInModal) {
|
|
338
247
|
this.focusLastElementIn(modalRoot);
|
|
339
|
-
}
|
|
340
|
-
// node as the last node focused in the modal.
|
|
341
|
-
|
|
248
|
+
}
|
|
342
249
|
|
|
343
250
|
this.lastNodeFocusedInModal = document.activeElement;
|
|
344
251
|
}
|
|
@@ -356,27 +263,18 @@ class FocusTrap extends React.Component {
|
|
|
356
263
|
window.removeEventListener("focus", this.handleGlobalFocus, true);
|
|
357
264
|
}
|
|
358
265
|
|
|
359
|
-
/** Try to focus the given node. Return true iff successful. */
|
|
360
266
|
tryToFocus(node) {
|
|
361
267
|
if (node instanceof HTMLElement) {
|
|
362
268
|
this.ignoreFocusChanges = true;
|
|
363
269
|
|
|
364
270
|
try {
|
|
365
271
|
node.focus();
|
|
366
|
-
} catch (e) {
|
|
367
|
-
}
|
|
272
|
+
} catch (e) {}
|
|
368
273
|
|
|
369
274
|
this.ignoreFocusChanges = false;
|
|
370
275
|
return document.activeElement === node;
|
|
371
276
|
}
|
|
372
277
|
}
|
|
373
|
-
/**
|
|
374
|
-
* Focus the first focusable descendant of the given node.
|
|
375
|
-
*
|
|
376
|
-
* Return true if we succeed. Or, if the given node has no focusable
|
|
377
|
-
* descendants, return false.
|
|
378
|
-
*/
|
|
379
|
-
|
|
380
278
|
|
|
381
279
|
focusFirstElementIn(currentParent) {
|
|
382
280
|
const children = currentParent.childNodes;
|
|
@@ -391,13 +289,6 @@ class FocusTrap extends React.Component {
|
|
|
391
289
|
|
|
392
290
|
return false;
|
|
393
291
|
}
|
|
394
|
-
/**
|
|
395
|
-
* Focus the last focusable descendant of the given node.
|
|
396
|
-
*
|
|
397
|
-
* Return true if we succeed. Or, if the given node has no focusable
|
|
398
|
-
* descendants, return false.
|
|
399
|
-
*/
|
|
400
|
-
|
|
401
292
|
|
|
402
293
|
focusLastElementIn(currentParent) {
|
|
403
294
|
const children = currentParent.childNodes;
|
|
@@ -412,22 +303,20 @@ class FocusTrap extends React.Component {
|
|
|
412
303
|
|
|
413
304
|
return false;
|
|
414
305
|
}
|
|
415
|
-
/** This method is called when any node on the page is focused. */
|
|
416
|
-
|
|
417
306
|
|
|
418
307
|
render() {
|
|
419
308
|
const {
|
|
420
309
|
style
|
|
421
310
|
} = this.props;
|
|
422
|
-
return
|
|
311
|
+
return React.createElement(React.Fragment, null, React.createElement("div", {
|
|
423
312
|
tabIndex: "0",
|
|
424
313
|
style: {
|
|
425
314
|
position: "fixed"
|
|
426
315
|
}
|
|
427
|
-
}),
|
|
316
|
+
}), React.createElement(View, {
|
|
428
317
|
style: style,
|
|
429
318
|
ref: this.getModalRoot
|
|
430
|
-
}, this.props.children),
|
|
319
|
+
}, this.props.children), React.createElement("div", {
|
|
431
320
|
tabIndex: "0",
|
|
432
321
|
style: {
|
|
433
322
|
position: "fixed"
|
|
@@ -437,43 +326,23 @@ class FocusTrap extends React.Component {
|
|
|
437
326
|
|
|
438
327
|
}
|
|
439
328
|
|
|
440
|
-
/**
|
|
441
|
-
* The attribute used to identify a modal launcher portal.
|
|
442
|
-
*/
|
|
443
329
|
const ModalLauncherPortalAttributeName = "data-modal-launcher-portal";
|
|
444
330
|
|
|
445
|
-
/**
|
|
446
|
-
* List of elements that can be focused
|
|
447
|
-
* @see https://www.w3.org/TR/html5/editing.html#can-be-focused
|
|
448
|
-
*/
|
|
449
331
|
const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
|
|
450
332
|
function findFocusableNodes(root) {
|
|
451
333
|
return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS));
|
|
452
334
|
}
|
|
453
335
|
|
|
454
|
-
/**
|
|
455
|
-
* A private component used by ModalLauncher. This is the fixed-position
|
|
456
|
-
* container element that gets mounted outside the DOM. It overlays the modal
|
|
457
|
-
* content (provided as `children`) over the content, with a gray backdrop
|
|
458
|
-
* behind it.
|
|
459
|
-
*
|
|
460
|
-
* This component is also responsible for cloning the provided modal `children`,
|
|
461
|
-
* and adding an `onClose` prop that will call `onCloseModal`. If an
|
|
462
|
-
* `onClose` prop is already provided, the two are merged.
|
|
463
|
-
*/
|
|
464
336
|
class ModalBackdrop extends React.Component {
|
|
465
337
|
constructor(...args) {
|
|
466
338
|
super(...args);
|
|
467
339
|
this._mousePressedOutside = false;
|
|
468
340
|
|
|
469
341
|
this.handleMouseDown = e => {
|
|
470
|
-
// Confirm that it is the backdrop that is being clicked, not the child
|
|
471
342
|
this._mousePressedOutside = e.target === e.currentTarget;
|
|
472
343
|
};
|
|
473
344
|
|
|
474
345
|
this.handleMouseUp = e => {
|
|
475
|
-
// Confirm that it is the backdrop that is being clicked, not the child
|
|
476
|
-
// and that the mouse was pressed in the backdrop first.
|
|
477
346
|
if (e.target === e.currentTarget && this._mousePressedOutside) {
|
|
478
347
|
this.props.onCloseModal();
|
|
479
348
|
}
|
|
@@ -489,20 +358,13 @@ class ModalBackdrop extends React.Component {
|
|
|
489
358
|
return;
|
|
490
359
|
}
|
|
491
360
|
|
|
492
|
-
const firstFocusableElement =
|
|
493
|
-
this._getInitialFocusElement(node) || // 2. get first occurence from list of focusable elements
|
|
494
|
-
this._getFirstFocusableElement(node) || // 3. get the dialog itself
|
|
495
|
-
this._getDialogElement(node); // wait for styles to applied
|
|
496
|
-
|
|
361
|
+
const firstFocusableElement = this._getInitialFocusElement(node) || this._getFirstFocusableElement(node) || this._getDialogElement(node);
|
|
497
362
|
|
|
498
363
|
setTimeout(() => {
|
|
499
364
|
firstFocusableElement.focus();
|
|
500
365
|
}, 0);
|
|
501
366
|
}
|
|
502
367
|
|
|
503
|
-
/**
|
|
504
|
-
* Returns an element specified by the user
|
|
505
|
-
*/
|
|
506
368
|
_getInitialFocusElement(node) {
|
|
507
369
|
const {
|
|
508
370
|
initialFocusId
|
|
@@ -514,41 +376,22 @@ class ModalBackdrop extends React.Component {
|
|
|
514
376
|
|
|
515
377
|
return ReactDOM.findDOMNode(node.querySelector(`#${initialFocusId}`));
|
|
516
378
|
}
|
|
517
|
-
/**
|
|
518
|
-
* Returns the first focusable element found inside the Dialog
|
|
519
|
-
*/
|
|
520
|
-
|
|
521
379
|
|
|
522
380
|
_getFirstFocusableElement(node) {
|
|
523
|
-
// get a collection of elements that can be focused
|
|
524
381
|
const focusableElements = findFocusableNodes(node);
|
|
525
382
|
|
|
526
383
|
if (!focusableElements) {
|
|
527
384
|
return null;
|
|
528
|
-
}
|
|
529
|
-
|
|
385
|
+
}
|
|
530
386
|
|
|
531
387
|
return focusableElements[0];
|
|
532
388
|
}
|
|
533
|
-
/**
|
|
534
|
-
* Returns the dialog element
|
|
535
|
-
*/
|
|
536
|
-
|
|
537
389
|
|
|
538
390
|
_getDialogElement(node) {
|
|
539
|
-
|
|
540
|
-
// the dialog content element itself will receive focus.
|
|
541
|
-
const dialogElement = ReactDOM.findDOMNode(node.querySelector('[role="dialog"]')); // add tabIndex to make the Dialog focusable
|
|
542
|
-
|
|
391
|
+
const dialogElement = ReactDOM.findDOMNode(node.querySelector('[role="dialog"]'));
|
|
543
392
|
dialogElement.tabIndex = -1;
|
|
544
393
|
return dialogElement;
|
|
545
394
|
}
|
|
546
|
-
/**
|
|
547
|
-
* When the user clicks on the gray backdrop area (i.e., the click came
|
|
548
|
-
* _directly_ from the positioner, not bubbled up from its children), close
|
|
549
|
-
* the modal.
|
|
550
|
-
*/
|
|
551
|
-
|
|
552
395
|
|
|
553
396
|
render() {
|
|
554
397
|
const {
|
|
@@ -558,7 +401,7 @@ class ModalBackdrop extends React.Component {
|
|
|
558
401
|
const backdropProps = {
|
|
559
402
|
[ModalLauncherPortalAttributeName]: true
|
|
560
403
|
};
|
|
561
|
-
return
|
|
404
|
+
return React.createElement(View, _extends({
|
|
562
405
|
style: styles$2.modalPositioner,
|
|
563
406
|
onMouseDown: this.handleMouseDown,
|
|
564
407
|
onMouseUp: this.handleMouseUp,
|
|
@@ -576,30 +419,11 @@ const styles$2 = StyleSheet.create({
|
|
|
576
419
|
height: "100%",
|
|
577
420
|
alignItems: "center",
|
|
578
421
|
justifyContent: "center",
|
|
579
|
-
// If the modal ends up being too big for the viewport (e.g., the min
|
|
580
|
-
// height is triggered), add another scrollbar specifically for
|
|
581
|
-
// scrolling modal content.
|
|
582
|
-
//
|
|
583
|
-
// TODO(mdr): The specified behavior is that the modal should scroll
|
|
584
|
-
// with the rest of the page, rather than separately, if overflow
|
|
585
|
-
// turns out to be necessary. That sounds hard to do; punting for
|
|
586
|
-
// now!
|
|
587
422
|
overflow: "auto",
|
|
588
423
|
background: Color.offBlack64
|
|
589
424
|
}
|
|
590
425
|
});
|
|
591
426
|
|
|
592
|
-
/**
|
|
593
|
-
* A UI-less component that lets `ModalLauncher` disable page scroll.
|
|
594
|
-
*
|
|
595
|
-
* The positioning of the modal requires some global page state changed
|
|
596
|
-
* unfortunately, and this handles that in an encapsulated way.
|
|
597
|
-
*
|
|
598
|
-
* NOTE(mdr): This component was copied from webapp. Be wary of sync issues. It
|
|
599
|
-
* also doesn't have unit tests, and we haven't added any, since it's a
|
|
600
|
-
* relatively stable component that has now been stress-tested lots in prod.
|
|
601
|
-
*/
|
|
602
|
-
|
|
603
427
|
const needsHackyMobileSafariScrollDisabler = (() => {
|
|
604
428
|
if (typeof window === "undefined") {
|
|
605
429
|
return false;
|
|
@@ -616,13 +440,10 @@ class ScrollDisabler extends React.Component {
|
|
|
616
440
|
|
|
617
441
|
if (!body) {
|
|
618
442
|
throw new Error("couldn't find document.body");
|
|
619
|
-
}
|
|
620
|
-
// opened.
|
|
621
|
-
|
|
443
|
+
}
|
|
622
444
|
|
|
623
445
|
ScrollDisabler.oldOverflow = body.style.overflow;
|
|
624
|
-
ScrollDisabler.oldScrollY = window.scrollY;
|
|
625
|
-
// modified any of them.
|
|
446
|
+
ScrollDisabler.oldScrollY = window.scrollY;
|
|
626
447
|
|
|
627
448
|
if (needsHackyMobileSafariScrollDisabler) {
|
|
628
449
|
ScrollDisabler.oldPosition = body.style.position;
|
|
@@ -630,9 +451,7 @@ class ScrollDisabler extends React.Component {
|
|
|
630
451
|
ScrollDisabler.oldTop = body.style.top;
|
|
631
452
|
}
|
|
632
453
|
|
|
633
|
-
body.style.overflow = "hidden";
|
|
634
|
-
// fixed is also required. Setting style.top = -scollTop maintains
|
|
635
|
-
// the scroll position (without which we'd scroll to the top).
|
|
454
|
+
body.style.overflow = "hidden";
|
|
636
455
|
|
|
637
456
|
if (needsHackyMobileSafariScrollDisabler) {
|
|
638
457
|
body.style.position = "fixed";
|
|
@@ -652,8 +471,7 @@ class ScrollDisabler extends React.Component {
|
|
|
652
471
|
|
|
653
472
|
if (!body) {
|
|
654
473
|
throw new Error("couldn't find document.body");
|
|
655
|
-
}
|
|
656
|
-
|
|
474
|
+
}
|
|
657
475
|
|
|
658
476
|
body.style.overflow = ScrollDisabler.oldOverflow;
|
|
659
477
|
|
|
@@ -680,24 +498,8 @@ ScrollDisabler.numModalsOpened = 0;
|
|
|
680
498
|
const defaultContext = {
|
|
681
499
|
closeModal: undefined
|
|
682
500
|
};
|
|
683
|
-
var ModalContext =
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* This component enables you to launch a modal, covering the screen.
|
|
687
|
-
*
|
|
688
|
-
* Children have access to `openModal` function via the function-as-children
|
|
689
|
-
* pattern, so one common use case is for this component to wrap a button:
|
|
690
|
-
*
|
|
691
|
-
* ```js
|
|
692
|
-
* <ModalLauncher modal={<TwoColumnModal ... />}>
|
|
693
|
-
* {({openModal}) => <button onClick={openModal}>Learn more</button>}
|
|
694
|
-
* </ModalLauncher>
|
|
695
|
-
* ```
|
|
696
|
-
*
|
|
697
|
-
* The actual modal itself is constructed separately, using a layout component
|
|
698
|
-
* like OnePaneDialog and is provided via
|
|
699
|
-
* the `modal` prop.
|
|
700
|
-
*/
|
|
501
|
+
var ModalContext = React.createContext(defaultContext);
|
|
502
|
+
|
|
701
503
|
class ModalLauncher extends React.Component {
|
|
702
504
|
constructor(...args) {
|
|
703
505
|
super(...args);
|
|
@@ -706,7 +508,6 @@ class ModalLauncher extends React.Component {
|
|
|
706
508
|
};
|
|
707
509
|
|
|
708
510
|
this._saveLastElementFocused = () => {
|
|
709
|
-
// keep a reference of the element that triggers the modal
|
|
710
511
|
this.lastElementFocusedOutsideModal = document.activeElement;
|
|
711
512
|
};
|
|
712
513
|
|
|
@@ -723,14 +524,12 @@ class ModalLauncher extends React.Component {
|
|
|
723
524
|
closedFocusId,
|
|
724
525
|
schedule
|
|
725
526
|
} = this.props;
|
|
726
|
-
const lastElement = this.lastElementFocusedOutsideModal;
|
|
527
|
+
const lastElement = this.lastElementFocusedOutsideModal;
|
|
727
528
|
|
|
728
529
|
if (closedFocusId) {
|
|
729
530
|
const focusElement = ReactDOM.findDOMNode(document.getElementById(closedFocusId));
|
|
730
531
|
|
|
731
532
|
if (focusElement) {
|
|
732
|
-
// Wait for the modal to leave the DOM before trying
|
|
733
|
-
// to focus on the specified element.
|
|
734
533
|
schedule.animationFrame(() => {
|
|
735
534
|
focusElement.focus();
|
|
736
535
|
});
|
|
@@ -739,8 +538,6 @@ class ModalLauncher extends React.Component {
|
|
|
739
538
|
}
|
|
740
539
|
|
|
741
540
|
if (lastElement != null) {
|
|
742
|
-
// Wait for the modal to leave the DOM before trying to
|
|
743
|
-
// return focus to the element that triggered the modal.
|
|
744
541
|
schedule.animationFrame(() => {
|
|
745
542
|
lastElement.focus();
|
|
746
543
|
});
|
|
@@ -763,17 +560,14 @@ class ModalLauncher extends React.Component {
|
|
|
763
560
|
|
|
764
561
|
static getDerivedStateFromProps(props, state) {
|
|
765
562
|
if (typeof props.opened === "boolean" && props.children) {
|
|
766
|
-
// eslint-disable-next-line no-console
|
|
767
563
|
console.warn("'children' and 'opened' can't be used together");
|
|
768
564
|
}
|
|
769
565
|
|
|
770
566
|
if (typeof props.opened === "boolean" && !props.onClose) {
|
|
771
|
-
// eslint-disable-next-line no-console
|
|
772
567
|
console.warn("'onClose' should be used with 'opened'");
|
|
773
568
|
}
|
|
774
569
|
|
|
775
570
|
if (typeof props.opened !== "boolean" && !props.children) {
|
|
776
|
-
// eslint-disable-next-line no-console
|
|
777
571
|
console.warn("either 'children' or 'opened' must be set");
|
|
778
572
|
}
|
|
779
573
|
|
|
@@ -783,7 +577,6 @@ class ModalLauncher extends React.Component {
|
|
|
783
577
|
}
|
|
784
578
|
|
|
785
579
|
componentDidUpdate(prevProps) {
|
|
786
|
-
// ensures the element is stored only when the modal is opened
|
|
787
580
|
if (!prevProps.opened && this.props.opened) {
|
|
788
581
|
this._saveLastElementFocused();
|
|
789
582
|
}
|
|
@@ -811,35 +604,22 @@ class ModalLauncher extends React.Component {
|
|
|
811
604
|
return null;
|
|
812
605
|
}
|
|
813
606
|
|
|
814
|
-
return (
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
correct z-index so that it'll be above the global nav in webapp. */
|
|
828
|
-
React.createElement(FocusTrap, {
|
|
829
|
-
style: styles$1.container
|
|
830
|
-
}, /*#__PURE__*/React.createElement(ModalBackdrop, {
|
|
831
|
-
initialFocusId: this.props.initialFocusId,
|
|
832
|
-
testId: this.props.testId,
|
|
833
|
-
onCloseModal: this.props.backdropDismissEnabled ? this.handleCloseModal : () => {}
|
|
834
|
-
}, this._renderModal())), body), this.state.opened && /*#__PURE__*/React.createElement(ModalLauncherKeypressListener, {
|
|
835
|
-
onClose: this.handleCloseModal
|
|
836
|
-
}), this.state.opened && /*#__PURE__*/React.createElement(ScrollDisabler, null))
|
|
837
|
-
);
|
|
607
|
+
return React.createElement(ModalContext.Provider, {
|
|
608
|
+
value: {
|
|
609
|
+
closeModal: this.handleCloseModal
|
|
610
|
+
}
|
|
611
|
+
}, renderedChildren, this.state.opened && ReactDOM.createPortal(React.createElement(FocusTrap, {
|
|
612
|
+
style: styles$1.container
|
|
613
|
+
}, React.createElement(ModalBackdrop, {
|
|
614
|
+
initialFocusId: this.props.initialFocusId,
|
|
615
|
+
testId: this.props.testId,
|
|
616
|
+
onCloseModal: this.props.backdropDismissEnabled ? this.handleCloseModal : () => {}
|
|
617
|
+
}, this._renderModal())), body), this.state.opened && React.createElement(ModalLauncherKeypressListener, {
|
|
618
|
+
onClose: this.handleCloseModal
|
|
619
|
+
}), this.state.opened && React.createElement(ScrollDisabler, null));
|
|
838
620
|
}
|
|
839
621
|
|
|
840
622
|
}
|
|
841
|
-
/** A component that, when mounted, calls `onClose` when Escape is pressed. */
|
|
842
|
-
|
|
843
623
|
|
|
844
624
|
ModalLauncher.defaultProps = {
|
|
845
625
|
backdropDismissEnabled: true
|
|
@@ -850,16 +630,7 @@ class ModalLauncherKeypressListener extends React.Component {
|
|
|
850
630
|
super(...args);
|
|
851
631
|
|
|
852
632
|
this._handleKeyup = e => {
|
|
853
|
-
// We check the key as that's keyboard layout agnostic and also avoids
|
|
854
|
-
// the minefield of deprecated number type properties like keyCode and
|
|
855
|
-
// which, with the replacement code, which uses a string instead.
|
|
856
633
|
if (e.key === "Escape") {
|
|
857
|
-
// Stop the event going any further.
|
|
858
|
-
// For cancellation events, like the Escape key, we generally should
|
|
859
|
-
// air on the side of caution and only allow it to cancel one thing.
|
|
860
|
-
// So, it's polite for us to stop propagation of the event.
|
|
861
|
-
// Otherwise, we end up with UX where one Escape key press
|
|
862
|
-
// unexpectedly cancels multiple things.
|
|
863
634
|
e.preventDefault();
|
|
864
635
|
e.stopPropagation();
|
|
865
636
|
this.props.onClose();
|
|
@@ -883,19 +654,11 @@ class ModalLauncherKeypressListener extends React.Component {
|
|
|
883
654
|
|
|
884
655
|
const styles$1 = StyleSheet.create({
|
|
885
656
|
container: {
|
|
886
|
-
// This z-index is copied from the Khan Academy webapp.
|
|
887
|
-
//
|
|
888
|
-
// TODO(mdr): Should we keep this in a constants file somewhere? Or
|
|
889
|
-
// not hardcode it at all, and provide it to Wonder Blocks via
|
|
890
|
-
// configuration?
|
|
891
657
|
zIndex: 1080
|
|
892
658
|
}
|
|
893
659
|
});
|
|
894
660
|
var modalLauncher = withActionScheduler(ModalLauncher);
|
|
895
661
|
|
|
896
|
-
/**
|
|
897
|
-
* The Modal content included after the header
|
|
898
|
-
*/
|
|
899
662
|
class ModalContent extends React.Component {
|
|
900
663
|
static isClassOf(instance) {
|
|
901
664
|
return instance && instance.type && instance.type.__IS_MODAL_CONTENT__;
|
|
@@ -907,13 +670,13 @@ class ModalContent extends React.Component {
|
|
|
907
670
|
style,
|
|
908
671
|
children
|
|
909
672
|
} = this.props;
|
|
910
|
-
return
|
|
673
|
+
return React.createElement(MediaLayout, {
|
|
911
674
|
styleSheets: styleSheets$1
|
|
912
675
|
}, ({
|
|
913
676
|
styles
|
|
914
|
-
}) =>
|
|
677
|
+
}) => React.createElement(View, {
|
|
915
678
|
style: [styles.wrapper, scrollOverflow && styles.scrollOverflow]
|
|
916
|
-
},
|
|
679
|
+
}, React.createElement(View, {
|
|
917
680
|
style: [styles.content, style]
|
|
918
681
|
}, children)));
|
|
919
682
|
}
|
|
@@ -927,8 +690,6 @@ const styleSheets$1 = {
|
|
|
927
690
|
all: StyleSheet.create({
|
|
928
691
|
wrapper: {
|
|
929
692
|
flex: 1,
|
|
930
|
-
// This helps to ensure that the paddingBottom is preserved when
|
|
931
|
-
// the contents start to overflow, this goes away on display: flex
|
|
932
693
|
display: "block"
|
|
933
694
|
},
|
|
934
695
|
scrollOverflow: {
|
|
@@ -956,17 +717,15 @@ class CloseButton extends React.Component {
|
|
|
956
717
|
style,
|
|
957
718
|
testId
|
|
958
719
|
} = this.props;
|
|
959
|
-
return
|
|
720
|
+
return React.createElement(ModalContext.Consumer, null, ({
|
|
960
721
|
closeModal
|
|
961
722
|
}) => {
|
|
962
723
|
if (closeModal && onClick) {
|
|
963
724
|
throw new Error("You've specified 'onClose' on a modal when using ModalLauncher. Please specify 'onClose' on the ModalLauncher instead");
|
|
964
725
|
}
|
|
965
726
|
|
|
966
|
-
return
|
|
967
|
-
icon: icons.dismiss
|
|
968
|
-
// TODO(kevinb): provide a way to set this label
|
|
969
|
-
,
|
|
727
|
+
return React.createElement(IconButton, {
|
|
728
|
+
icon: icons.dismiss,
|
|
970
729
|
"aria-label": "Close modal",
|
|
971
730
|
onClick: onClick || closeModal,
|
|
972
731
|
kind: light ? "primary" : "tertiary",
|
|
@@ -979,26 +738,6 @@ class CloseButton extends React.Component {
|
|
|
979
738
|
|
|
980
739
|
}
|
|
981
740
|
|
|
982
|
-
/**
|
|
983
|
-
* ModalPanel is the content container.
|
|
984
|
-
*
|
|
985
|
-
* **Implementation notes:**
|
|
986
|
-
*
|
|
987
|
-
* If you are creating a custom Dialog, make sure to follow these guidelines:
|
|
988
|
-
* - Make sure to add this component inside the [ModalDialog](/#modaldialog).
|
|
989
|
-
* - If needed, you can also add a [ModalHeader](/#modalheader) using the
|
|
990
|
-
* `header` prop. Same goes for [ModalFooter](/#modalfooter).
|
|
991
|
-
* - If you need to create e2e tests, make sure to pass a `testId` prop. This
|
|
992
|
-
* will be passed down to this component using a sufix: e.g.
|
|
993
|
-
* `some-random-id-ModalPanel`. This scope will be propagated to the
|
|
994
|
-
* CloseButton element as well: e.g. `some-random-id-CloseButton`.
|
|
995
|
-
*
|
|
996
|
-
* ```js
|
|
997
|
-
* <ModalDialog>
|
|
998
|
-
* <ModalPanel content={"custom content goes here"} />
|
|
999
|
-
* </ModalDialog>
|
|
1000
|
-
* ```
|
|
1001
|
-
*/
|
|
1002
741
|
class ModalPanel extends React.Component {
|
|
1003
742
|
renderMainContent() {
|
|
1004
743
|
const {
|
|
@@ -1006,19 +745,14 @@ class ModalPanel extends React.Component {
|
|
|
1006
745
|
footer,
|
|
1007
746
|
scrollOverflow
|
|
1008
747
|
} = this.props;
|
|
1009
|
-
const mainContent = ModalContent.isClassOf(content) ? content :
|
|
748
|
+
const mainContent = ModalContent.isClassOf(content) ? content : React.createElement(ModalContent, null, content);
|
|
1010
749
|
|
|
1011
750
|
if (!mainContent) {
|
|
1012
751
|
return mainContent;
|
|
1013
752
|
}
|
|
1014
753
|
|
|
1015
|
-
return
|
|
1016
|
-
// Pass the scrollOverflow and header in to the main content
|
|
754
|
+
return React.cloneElement(mainContent, {
|
|
1017
755
|
scrollOverflow,
|
|
1018
|
-
// We override the styling of the main content to help position
|
|
1019
|
-
// it if there is a footer or close button being
|
|
1020
|
-
// shown. We have to do this here as the ModalContent doesn't
|
|
1021
|
-
// know about things being positioned around it.
|
|
1022
756
|
style: [!!footer && styles.hasFooter, mainContent.props.style]
|
|
1023
757
|
});
|
|
1024
758
|
}
|
|
@@ -1034,15 +768,15 @@ class ModalPanel extends React.Component {
|
|
|
1034
768
|
testId
|
|
1035
769
|
} = this.props;
|
|
1036
770
|
const mainContent = this.renderMainContent();
|
|
1037
|
-
return
|
|
771
|
+
return React.createElement(View, {
|
|
1038
772
|
style: [styles.wrapper, !light && styles.dark, style],
|
|
1039
773
|
testId: testId && `${testId}-panel`
|
|
1040
|
-
}, closeButtonVisible &&
|
|
774
|
+
}, closeButtonVisible && React.createElement(CloseButton, {
|
|
1041
775
|
light: !light,
|
|
1042
776
|
onClick: onClose,
|
|
1043
777
|
style: styles.closeButton,
|
|
1044
778
|
testId: testId && `${testId}-close`
|
|
1045
|
-
}), header, mainContent, !footer || ModalFooter.isClassOf(footer) ? footer :
|
|
779
|
+
}), header, mainContent, !footer || ModalFooter.isClassOf(footer) ? footer : React.createElement(ModalFooter, null, footer));
|
|
1046
780
|
}
|
|
1047
781
|
|
|
1048
782
|
}
|
|
@@ -1067,8 +801,6 @@ const styles = StyleSheet.create({
|
|
|
1067
801
|
position: "absolute",
|
|
1068
802
|
right: Spacing.medium_16,
|
|
1069
803
|
top: Spacing.medium_16,
|
|
1070
|
-
// This is to allow the button to be tab-ordered before the modal
|
|
1071
|
-
// content but still be above the header and content.
|
|
1072
804
|
zIndex: 1
|
|
1073
805
|
},
|
|
1074
806
|
dark: {
|
|
@@ -1080,12 +812,6 @@ const styles = StyleSheet.create({
|
|
|
1080
812
|
}
|
|
1081
813
|
});
|
|
1082
814
|
|
|
1083
|
-
/**
|
|
1084
|
-
* This is the standard layout for most straightforward modal experiences.
|
|
1085
|
-
*
|
|
1086
|
-
* The ModalHeader is required, but the ModalFooter is optional.
|
|
1087
|
-
* The content of the dialog itself is fully customizable, but the left/right/top/bottom padding is fixed.
|
|
1088
|
-
*/
|
|
1089
815
|
class OnePaneDialog extends React.Component {
|
|
1090
816
|
renderHeader(uniqueId) {
|
|
1091
817
|
const {
|
|
@@ -1096,21 +822,21 @@ class OnePaneDialog extends React.Component {
|
|
|
1096
822
|
} = this.props;
|
|
1097
823
|
|
|
1098
824
|
if (breadcrumbs) {
|
|
1099
|
-
return
|
|
825
|
+
return React.createElement(ModalHeader, {
|
|
1100
826
|
title: title,
|
|
1101
827
|
breadcrumbs: breadcrumbs,
|
|
1102
828
|
titleId: uniqueId,
|
|
1103
829
|
testId: testId && `${testId}-header`
|
|
1104
830
|
});
|
|
1105
831
|
} else if (subtitle) {
|
|
1106
|
-
return
|
|
832
|
+
return React.createElement(ModalHeader, {
|
|
1107
833
|
title: title,
|
|
1108
834
|
subtitle: subtitle,
|
|
1109
835
|
titleId: uniqueId,
|
|
1110
836
|
testId: testId && `${testId}-header`
|
|
1111
837
|
});
|
|
1112
838
|
} else {
|
|
1113
|
-
return
|
|
839
|
+
return React.createElement(ModalHeader, {
|
|
1114
840
|
title: title,
|
|
1115
841
|
titleId: uniqueId,
|
|
1116
842
|
testId: testId && `${testId}-header`
|
|
@@ -1131,21 +857,21 @@ class OnePaneDialog extends React.Component {
|
|
|
1131
857
|
titleId,
|
|
1132
858
|
role
|
|
1133
859
|
} = this.props;
|
|
1134
|
-
return
|
|
860
|
+
return React.createElement(MediaLayout, {
|
|
1135
861
|
styleSheets: styleSheets
|
|
1136
862
|
}, ({
|
|
1137
863
|
styles
|
|
1138
|
-
}) =>
|
|
864
|
+
}) => React.createElement(IDProvider, {
|
|
1139
865
|
id: titleId,
|
|
1140
866
|
scope: "modal"
|
|
1141
|
-
}, uniqueId =>
|
|
867
|
+
}, uniqueId => React.createElement(ModalDialog, {
|
|
1142
868
|
style: [styles.dialog, style],
|
|
1143
869
|
above: above,
|
|
1144
870
|
below: below,
|
|
1145
871
|
testId: testId,
|
|
1146
872
|
"aria-labelledby": uniqueId,
|
|
1147
873
|
role: role
|
|
1148
|
-
},
|
|
874
|
+
}, React.createElement(ModalPanel, {
|
|
1149
875
|
onClose: onClose,
|
|
1150
876
|
header: this.renderHeader(uniqueId),
|
|
1151
877
|
content: content,
|
|
@@ -1177,14 +903,6 @@ const styleSheets = {
|
|
|
1177
903
|
})
|
|
1178
904
|
};
|
|
1179
905
|
|
|
1180
|
-
/**
|
|
1181
|
-
* From a given element, finds its next ancestor that is a modal launcher portal
|
|
1182
|
-
* element.
|
|
1183
|
-
* @param {?(Element | Text)} element The element whose ancestors are to be
|
|
1184
|
-
* walked.
|
|
1185
|
-
* @returns {?Element} The nearest parent modal launcher portal.
|
|
1186
|
-
*/
|
|
1187
|
-
|
|
1188
906
|
function maybeGetNextAncestorModalLauncherPortal(element) {
|
|
1189
907
|
let candidateElement = element && element.parentElement;
|
|
1190
908
|
|
|
@@ -1194,15 +912,6 @@ function maybeGetNextAncestorModalLauncherPortal(element) {
|
|
|
1194
912
|
|
|
1195
913
|
return candidateElement;
|
|
1196
914
|
}
|
|
1197
|
-
/**
|
|
1198
|
-
* From a given element, finds the next modal host that has been mounted in
|
|
1199
|
-
* a modal portal.
|
|
1200
|
-
* @param {?(Element | Text)} element The element whose ancestors are to be
|
|
1201
|
-
* walked.
|
|
1202
|
-
* @returns {?Element} The next portal-mounted modal host element.
|
|
1203
|
-
* TODO(kevinb): look into getting rid of this
|
|
1204
|
-
*/
|
|
1205
|
-
|
|
1206
915
|
|
|
1207
916
|
function maybeGetPortalMountedModalHostElement(element) {
|
|
1208
917
|
return maybeGetNextAncestorModalLauncherPortal(element);
|
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.1",
|
|
4
4
|
"design": "v2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@babel/runtime": "^7.16.3",
|
|
19
|
-
"@khanacademy/wonder-blocks-breadcrumbs": "^1.0.
|
|
19
|
+
"@khanacademy/wonder-blocks-breadcrumbs": "^1.0.30",
|
|
20
20
|
"@khanacademy/wonder-blocks-color": "^1.1.20",
|
|
21
|
-
"@khanacademy/wonder-blocks-core": "^4.3.
|
|
22
|
-
"@khanacademy/wonder-blocks-icon": "^1.2.
|
|
23
|
-
"@khanacademy/wonder-blocks-icon-button": "^3.4.
|
|
24
|
-
"@khanacademy/wonder-blocks-layout": "^1.4.
|
|
21
|
+
"@khanacademy/wonder-blocks-core": "^4.3.1",
|
|
22
|
+
"@khanacademy/wonder-blocks-icon": "^1.2.27",
|
|
23
|
+
"@khanacademy/wonder-blocks-icon-button": "^3.4.6",
|
|
24
|
+
"@khanacademy/wonder-blocks-layout": "^1.4.9",
|
|
25
25
|
"@khanacademy/wonder-blocks-spacing": "^3.0.5",
|
|
26
|
-
"@khanacademy/wonder-blocks-toolbar": "^2.1.
|
|
27
|
-
"@khanacademy/wonder-blocks-typography": "^1.1.
|
|
26
|
+
"@khanacademy/wonder-blocks-toolbar": "^2.1.31",
|
|
27
|
+
"@khanacademy/wonder-blocks-typography": "^1.1.31"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"aphrodite": "^1.2.5",
|
|
@@ -32,6 +32,6 @@
|
|
|
32
32
|
"react-dom": "16.14.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"wb-dev-build-settings": "^0.
|
|
35
|
+
"wb-dev-build-settings": "^0.4.0"
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -81,7 +81,7 @@ export const Simple: StoryComponentType = () => {
|
|
|
81
81
|
|
|
82
82
|
const styles = StyleSheet.create({
|
|
83
83
|
above: {
|
|
84
|
-
background: "url(
|
|
84
|
+
background: "url(./modal-above.png)",
|
|
85
85
|
width: 874,
|
|
86
86
|
height: 551,
|
|
87
87
|
position: "absolute",
|
|
@@ -90,7 +90,7 @@ const styles = StyleSheet.create({
|
|
|
90
90
|
},
|
|
91
91
|
|
|
92
92
|
below: {
|
|
93
|
-
background: "url(
|
|
93
|
+
background: "url(./modal-below.png)",
|
|
94
94
|
width: 868,
|
|
95
95
|
height: 521,
|
|
96
96
|
position: "absolute",
|