@khanacademy/wonder-blocks-modal 4.0.39 → 4.1.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/CHANGELOG.md +6 -0
- package/dist/components/modal-dialog.d.ts +3 -19
- package/dist/es/index.js +113 -96
- package/dist/index.js +128 -111
- package/dist/themes/default.d.ts +11 -0
- package/dist/themes/themed-modal-dialog.d.ts +26 -0
- package/package.json +2 -1
- package/src/components/__tests__/modal-dialog.test.tsx +78 -0
- package/src/components/modal-dialog.tsx +103 -103
- package/src/themes/default.ts +14 -0
- package/src/themes/themed-modal-dialog.tsx +43 -0
- package/tsconfig-build.json +1 -0
- package/tsconfig-build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -32,7 +32,7 @@ type Props = {
|
|
|
32
32
|
*/
|
|
33
33
|
testId?: string;
|
|
34
34
|
/**
|
|
35
|
-
* The ID of the
|
|
35
|
+
* The ID of the title labelling this dialog, if applicable.
|
|
36
36
|
*/
|
|
37
37
|
"aria-labelledby"?: string;
|
|
38
38
|
/**
|
|
@@ -40,21 +40,5 @@ type Props = {
|
|
|
40
40
|
*/
|
|
41
41
|
"aria-describedby"?: string;
|
|
42
42
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* `ModalDialog` is a component that contains these elements:
|
|
48
|
-
* - The visual dialog element itself (`<div role="dialog"/>`)
|
|
49
|
-
* - The custom contents below and/or above the Dialog itself (e.g. decorative graphics).
|
|
50
|
-
*
|
|
51
|
-
* **Accessibility notes:**
|
|
52
|
-
* - By default (e.g. using `OnePaneDialog`), `aria-labelledby` is populated automatically using the dialog title `id`.
|
|
53
|
-
* - If there is a custom Dialog implementation (e.g. `TwoPaneDialog`), the dialog element doesn’t have to have
|
|
54
|
-
* the `aria-labelledby` attribute however this is recommended. It should match the `id` of the dialog title.
|
|
55
|
-
*/
|
|
56
|
-
export default class ModalDialog extends React.Component<Props> {
|
|
57
|
-
static defaultProps: DefaultProps;
|
|
58
|
-
render(): React.ReactNode;
|
|
59
|
-
}
|
|
60
|
-
export {};
|
|
43
|
+
declare const ModalDialog: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLDivElement>>;
|
|
44
|
+
export default ModalDialog;
|
package/dist/es/index.js
CHANGED
|
@@ -1,98 +1,130 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { StyleSheet } from 'aphrodite';
|
|
3
|
-
import { MediaLayoutContext, MediaLayout, MEDIA_MODAL_SPEC } from '@khanacademy/wonder-blocks-layout';
|
|
4
2
|
import { View, IDProvider } from '@khanacademy/wonder-blocks-core';
|
|
5
|
-
import
|
|
3
|
+
import { tokens, createThemeContext, ThemeSwitcherContext, useScopedTheme, useStyles } from '@khanacademy/wonder-blocks-theming';
|
|
4
|
+
import { StyleSheet } from 'aphrodite';
|
|
6
5
|
import Color from '@khanacademy/wonder-blocks-color';
|
|
6
|
+
import Spacing from '@khanacademy/wonder-blocks-spacing';
|
|
7
|
+
import { MediaLayout } from '@khanacademy/wonder-blocks-layout';
|
|
7
8
|
import { HeadingMedium, LabelSmall } from '@khanacademy/wonder-blocks-typography';
|
|
8
9
|
import * as ReactDOM from 'react-dom';
|
|
9
10
|
import { withActionScheduler } from '@khanacademy/wonder-blocks-timing';
|
|
10
11
|
import xIcon from '@phosphor-icons/core/regular/x.svg';
|
|
11
12
|
import IconButton from '@khanacademy/wonder-blocks-icon-button';
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
ssrSize: "large",
|
|
27
|
-
mediaSpec: MEDIA_MODAL_SPEC
|
|
28
|
-
};
|
|
29
|
-
return React.createElement(MediaLayoutContext.Provider, {
|
|
30
|
-
value: contextValue
|
|
31
|
-
}, React.createElement(MediaLayout, {
|
|
32
|
-
styleSheets: styleSheets$3
|
|
33
|
-
}, ({
|
|
34
|
-
styles
|
|
35
|
-
}) => React.createElement(View, {
|
|
36
|
-
style: [styles.wrapper, style]
|
|
37
|
-
}, below && React.createElement(View, {
|
|
38
|
-
style: styles.below
|
|
39
|
-
}, below), React.createElement(View, {
|
|
40
|
-
role: role,
|
|
41
|
-
"aria-modal": "true",
|
|
42
|
-
"aria-labelledby": ariaLabelledBy,
|
|
43
|
-
"aria-describedby": ariaDescribedBy,
|
|
44
|
-
style: styles.dialog,
|
|
45
|
-
testId: testId
|
|
46
|
-
}, children), above && React.createElement(View, {
|
|
47
|
-
style: styles.above
|
|
48
|
-
}, above))));
|
|
49
|
-
}
|
|
14
|
+
function _extends() {
|
|
15
|
+
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
16
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
17
|
+
var source = arguments[i];
|
|
18
|
+
for (var key in source) {
|
|
19
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
20
|
+
target[key] = source[key];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return target;
|
|
25
|
+
};
|
|
26
|
+
return _extends.apply(this, arguments);
|
|
50
27
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
display: "flex",
|
|
58
|
-
flexDirection: "row",
|
|
59
|
-
alignItems: "stretch",
|
|
60
|
-
width: "100%",
|
|
61
|
-
height: "100%",
|
|
62
|
-
position: "relative"
|
|
63
|
-
},
|
|
28
|
+
|
|
29
|
+
const theme = {
|
|
30
|
+
border: {
|
|
31
|
+
radius: tokens.border.radius.medium_4
|
|
32
|
+
},
|
|
33
|
+
spacing: {
|
|
64
34
|
dialog: {
|
|
65
|
-
|
|
66
|
-
height: "100%",
|
|
67
|
-
borderRadius: 4,
|
|
68
|
-
overflow: "hidden"
|
|
69
|
-
},
|
|
70
|
-
above: {
|
|
71
|
-
pointerEvents: "none",
|
|
72
|
-
position: "absolute",
|
|
73
|
-
top: 0,
|
|
74
|
-
left: 0,
|
|
75
|
-
bottom: 0,
|
|
76
|
-
right: 0,
|
|
77
|
-
zIndex: 1
|
|
78
|
-
},
|
|
79
|
-
below: {
|
|
80
|
-
pointerEvents: "none",
|
|
81
|
-
position: "absolute",
|
|
82
|
-
top: 0,
|
|
83
|
-
left: 0,
|
|
84
|
-
bottom: 0,
|
|
85
|
-
right: 0,
|
|
86
|
-
zIndex: -1
|
|
35
|
+
small: tokens.spacing.medium_16
|
|
87
36
|
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const themes = {
|
|
41
|
+
default: theme
|
|
42
|
+
};
|
|
43
|
+
const ModalDialogThemeContext = createThemeContext(theme);
|
|
44
|
+
function ThemeModalDialog(props) {
|
|
45
|
+
const currentTheme = React.useContext(ThemeSwitcherContext);
|
|
46
|
+
const theme$1 = themes[currentTheme] || theme;
|
|
47
|
+
return React.createElement(ModalDialogThemeContext.Provider, {
|
|
48
|
+
value: theme$1
|
|
49
|
+
}, props.children);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ModalDialogCore = React.forwardRef(function ModalDialogCore(props, ref) {
|
|
53
|
+
const {
|
|
54
|
+
above,
|
|
55
|
+
below,
|
|
56
|
+
role = "dialog",
|
|
57
|
+
style,
|
|
58
|
+
children,
|
|
59
|
+
testId,
|
|
60
|
+
"aria-labelledby": ariaLabelledBy,
|
|
61
|
+
"aria-describedby": ariaDescribedBy
|
|
62
|
+
} = props;
|
|
63
|
+
const {
|
|
64
|
+
theme
|
|
65
|
+
} = useScopedTheme(ModalDialogThemeContext);
|
|
66
|
+
const styles = useStyles(themedStylesFn, theme);
|
|
67
|
+
return React.createElement(View, {
|
|
68
|
+
style: [styles.wrapper, style]
|
|
69
|
+
}, below && React.createElement(View, {
|
|
70
|
+
style: styles.below
|
|
71
|
+
}, below), React.createElement(View, {
|
|
72
|
+
role: role,
|
|
73
|
+
"aria-modal": "true",
|
|
74
|
+
"aria-labelledby": ariaLabelledBy,
|
|
75
|
+
"aria-describedby": ariaDescribedBy,
|
|
76
|
+
ref: ref,
|
|
77
|
+
style: styles.dialog,
|
|
78
|
+
testId: testId
|
|
79
|
+
}, children), above && React.createElement(View, {
|
|
80
|
+
style: styles.above
|
|
81
|
+
}, above));
|
|
82
|
+
});
|
|
83
|
+
const ModalDialog = React.forwardRef(function ModalDialog(props, ref) {
|
|
84
|
+
return React.createElement(ThemeModalDialog, null, React.createElement(ModalDialogCore, _extends({}, props, {
|
|
85
|
+
ref: ref
|
|
86
|
+
})));
|
|
87
|
+
});
|
|
88
|
+
const small = "@media (max-width: 767px)";
|
|
89
|
+
const themedStylesFn = theme => ({
|
|
90
|
+
wrapper: {
|
|
91
|
+
display: "flex",
|
|
92
|
+
flexDirection: "row",
|
|
93
|
+
alignItems: "stretch",
|
|
94
|
+
width: "100%",
|
|
95
|
+
height: "100%",
|
|
96
|
+
position: "relative",
|
|
97
|
+
[small]: {
|
|
98
|
+
padding: theme.spacing.dialog.small,
|
|
92
99
|
flexDirection: "column"
|
|
93
100
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
101
|
+
},
|
|
102
|
+
dialog: {
|
|
103
|
+
width: "100%",
|
|
104
|
+
height: "100%",
|
|
105
|
+
borderRadius: theme.border.radius,
|
|
106
|
+
overflow: "hidden"
|
|
107
|
+
},
|
|
108
|
+
above: {
|
|
109
|
+
pointerEvents: "none",
|
|
110
|
+
position: "absolute",
|
|
111
|
+
top: 0,
|
|
112
|
+
left: 0,
|
|
113
|
+
bottom: 0,
|
|
114
|
+
right: 0,
|
|
115
|
+
zIndex: 1
|
|
116
|
+
},
|
|
117
|
+
below: {
|
|
118
|
+
pointerEvents: "none",
|
|
119
|
+
position: "absolute",
|
|
120
|
+
top: 0,
|
|
121
|
+
left: 0,
|
|
122
|
+
bottom: 0,
|
|
123
|
+
right: 0,
|
|
124
|
+
zIndex: -1
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
ModalDialog.displayName = "ModalDialog";
|
|
96
128
|
|
|
97
129
|
class ModalFooter extends React.Component {
|
|
98
130
|
static isClassOf(instance) {
|
|
@@ -260,21 +292,6 @@ class FocusTrap extends React.Component {
|
|
|
260
292
|
}
|
|
261
293
|
}
|
|
262
294
|
|
|
263
|
-
function _extends() {
|
|
264
|
-
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
265
|
-
for (var i = 1; i < arguments.length; i++) {
|
|
266
|
-
var source = arguments[i];
|
|
267
|
-
for (var key in source) {
|
|
268
|
-
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
269
|
-
target[key] = source[key];
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return target;
|
|
274
|
-
};
|
|
275
|
-
return _extends.apply(this, arguments);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
295
|
const ModalLauncherPortalAttributeName = "data-modal-launcher-portal";
|
|
279
296
|
|
|
280
297
|
const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var React = require('react');
|
|
6
|
-
var aphrodite = require('aphrodite');
|
|
7
|
-
var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
|
|
8
6
|
var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
|
|
9
|
-
var
|
|
7
|
+
var wonderBlocksTheming = require('@khanacademy/wonder-blocks-theming');
|
|
8
|
+
var aphrodite = require('aphrodite');
|
|
10
9
|
var Color = require('@khanacademy/wonder-blocks-color');
|
|
10
|
+
var Spacing = require('@khanacademy/wonder-blocks-spacing');
|
|
11
|
+
var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
|
|
11
12
|
var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
|
|
12
13
|
var ReactDOM = require('react-dom');
|
|
13
14
|
var wonderBlocksTiming = require('@khanacademy/wonder-blocks-timing');
|
|
@@ -17,113 +18,144 @@ var IconButton = require('@khanacademy/wonder-blocks-icon-button');
|
|
|
17
18
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
18
19
|
|
|
19
20
|
function _interopNamespace(e) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
30
|
-
}
|
|
21
|
+
if (e && e.__esModule) return e;
|
|
22
|
+
var n = Object.create(null);
|
|
23
|
+
if (e) {
|
|
24
|
+
Object.keys(e).forEach(function (k) {
|
|
25
|
+
if (k !== 'default') {
|
|
26
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
27
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
get: function () { return e[k]; }
|
|
31
30
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
n["default"] = e;
|
|
35
|
+
return Object.freeze(n);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
38
|
-
var Spacing__default = /*#__PURE__*/_interopDefaultLegacy(Spacing);
|
|
39
39
|
var Color__default = /*#__PURE__*/_interopDefaultLegacy(Color);
|
|
40
|
+
var Spacing__default = /*#__PURE__*/_interopDefaultLegacy(Spacing);
|
|
40
41
|
var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
|
|
41
42
|
var xIcon__default = /*#__PURE__*/_interopDefaultLegacy(xIcon);
|
|
42
43
|
var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
ssrSize: "large",
|
|
58
|
-
mediaSpec: wonderBlocksLayout.MEDIA_MODAL_SPEC
|
|
59
|
-
};
|
|
60
|
-
return React__namespace.createElement(wonderBlocksLayout.MediaLayoutContext.Provider, {
|
|
61
|
-
value: contextValue
|
|
62
|
-
}, React__namespace.createElement(wonderBlocksLayout.MediaLayout, {
|
|
63
|
-
styleSheets: styleSheets$3
|
|
64
|
-
}, ({
|
|
65
|
-
styles
|
|
66
|
-
}) => React__namespace.createElement(wonderBlocksCore.View, {
|
|
67
|
-
style: [styles.wrapper, style]
|
|
68
|
-
}, below && React__namespace.createElement(wonderBlocksCore.View, {
|
|
69
|
-
style: styles.below
|
|
70
|
-
}, below), React__namespace.createElement(wonderBlocksCore.View, {
|
|
71
|
-
role: role,
|
|
72
|
-
"aria-modal": "true",
|
|
73
|
-
"aria-labelledby": ariaLabelledBy,
|
|
74
|
-
"aria-describedby": ariaDescribedBy,
|
|
75
|
-
style: styles.dialog,
|
|
76
|
-
testId: testId
|
|
77
|
-
}, children), above && React__namespace.createElement(wonderBlocksCore.View, {
|
|
78
|
-
style: styles.above
|
|
79
|
-
}, above))));
|
|
80
|
-
}
|
|
45
|
+
function _extends() {
|
|
46
|
+
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
47
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
48
|
+
var source = arguments[i];
|
|
49
|
+
for (var key in source) {
|
|
50
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
51
|
+
target[key] = source[key];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return target;
|
|
56
|
+
};
|
|
57
|
+
return _extends.apply(this, arguments);
|
|
81
58
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
display: "flex",
|
|
89
|
-
flexDirection: "row",
|
|
90
|
-
alignItems: "stretch",
|
|
91
|
-
width: "100%",
|
|
92
|
-
height: "100%",
|
|
93
|
-
position: "relative"
|
|
94
|
-
},
|
|
59
|
+
|
|
60
|
+
const theme = {
|
|
61
|
+
border: {
|
|
62
|
+
radius: wonderBlocksTheming.tokens.border.radius.medium_4
|
|
63
|
+
},
|
|
64
|
+
spacing: {
|
|
95
65
|
dialog: {
|
|
96
|
-
|
|
97
|
-
height: "100%",
|
|
98
|
-
borderRadius: 4,
|
|
99
|
-
overflow: "hidden"
|
|
100
|
-
},
|
|
101
|
-
above: {
|
|
102
|
-
pointerEvents: "none",
|
|
103
|
-
position: "absolute",
|
|
104
|
-
top: 0,
|
|
105
|
-
left: 0,
|
|
106
|
-
bottom: 0,
|
|
107
|
-
right: 0,
|
|
108
|
-
zIndex: 1
|
|
109
|
-
},
|
|
110
|
-
below: {
|
|
111
|
-
pointerEvents: "none",
|
|
112
|
-
position: "absolute",
|
|
113
|
-
top: 0,
|
|
114
|
-
left: 0,
|
|
115
|
-
bottom: 0,
|
|
116
|
-
right: 0,
|
|
117
|
-
zIndex: -1
|
|
66
|
+
small: wonderBlocksTheming.tokens.spacing.medium_16
|
|
118
67
|
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const themes = {
|
|
72
|
+
default: theme
|
|
73
|
+
};
|
|
74
|
+
const ModalDialogThemeContext = wonderBlocksTheming.createThemeContext(theme);
|
|
75
|
+
function ThemeModalDialog(props) {
|
|
76
|
+
const currentTheme = React__namespace.useContext(wonderBlocksTheming.ThemeSwitcherContext);
|
|
77
|
+
const theme$1 = themes[currentTheme] || theme;
|
|
78
|
+
return React__namespace.createElement(ModalDialogThemeContext.Provider, {
|
|
79
|
+
value: theme$1
|
|
80
|
+
}, props.children);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const ModalDialogCore = React__namespace.forwardRef(function ModalDialogCore(props, ref) {
|
|
84
|
+
const {
|
|
85
|
+
above,
|
|
86
|
+
below,
|
|
87
|
+
role = "dialog",
|
|
88
|
+
style,
|
|
89
|
+
children,
|
|
90
|
+
testId,
|
|
91
|
+
"aria-labelledby": ariaLabelledBy,
|
|
92
|
+
"aria-describedby": ariaDescribedBy
|
|
93
|
+
} = props;
|
|
94
|
+
const {
|
|
95
|
+
theme
|
|
96
|
+
} = wonderBlocksTheming.useScopedTheme(ModalDialogThemeContext);
|
|
97
|
+
const styles = wonderBlocksTheming.useStyles(themedStylesFn, theme);
|
|
98
|
+
return React__namespace.createElement(wonderBlocksCore.View, {
|
|
99
|
+
style: [styles.wrapper, style]
|
|
100
|
+
}, below && React__namespace.createElement(wonderBlocksCore.View, {
|
|
101
|
+
style: styles.below
|
|
102
|
+
}, below), React__namespace.createElement(wonderBlocksCore.View, {
|
|
103
|
+
role: role,
|
|
104
|
+
"aria-modal": "true",
|
|
105
|
+
"aria-labelledby": ariaLabelledBy,
|
|
106
|
+
"aria-describedby": ariaDescribedBy,
|
|
107
|
+
ref: ref,
|
|
108
|
+
style: styles.dialog,
|
|
109
|
+
testId: testId
|
|
110
|
+
}, children), above && React__namespace.createElement(wonderBlocksCore.View, {
|
|
111
|
+
style: styles.above
|
|
112
|
+
}, above));
|
|
113
|
+
});
|
|
114
|
+
const ModalDialog = React__namespace.forwardRef(function ModalDialog(props, ref) {
|
|
115
|
+
return React__namespace.createElement(ThemeModalDialog, null, React__namespace.createElement(ModalDialogCore, _extends({}, props, {
|
|
116
|
+
ref: ref
|
|
117
|
+
})));
|
|
118
|
+
});
|
|
119
|
+
const small = "@media (max-width: 767px)";
|
|
120
|
+
const themedStylesFn = theme => ({
|
|
121
|
+
wrapper: {
|
|
122
|
+
display: "flex",
|
|
123
|
+
flexDirection: "row",
|
|
124
|
+
alignItems: "stretch",
|
|
125
|
+
width: "100%",
|
|
126
|
+
height: "100%",
|
|
127
|
+
position: "relative",
|
|
128
|
+
[small]: {
|
|
129
|
+
padding: theme.spacing.dialog.small,
|
|
123
130
|
flexDirection: "column"
|
|
124
131
|
}
|
|
125
|
-
}
|
|
126
|
-
|
|
132
|
+
},
|
|
133
|
+
dialog: {
|
|
134
|
+
width: "100%",
|
|
135
|
+
height: "100%",
|
|
136
|
+
borderRadius: theme.border.radius,
|
|
137
|
+
overflow: "hidden"
|
|
138
|
+
},
|
|
139
|
+
above: {
|
|
140
|
+
pointerEvents: "none",
|
|
141
|
+
position: "absolute",
|
|
142
|
+
top: 0,
|
|
143
|
+
left: 0,
|
|
144
|
+
bottom: 0,
|
|
145
|
+
right: 0,
|
|
146
|
+
zIndex: 1
|
|
147
|
+
},
|
|
148
|
+
below: {
|
|
149
|
+
pointerEvents: "none",
|
|
150
|
+
position: "absolute",
|
|
151
|
+
top: 0,
|
|
152
|
+
left: 0,
|
|
153
|
+
bottom: 0,
|
|
154
|
+
right: 0,
|
|
155
|
+
zIndex: -1
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
ModalDialog.displayName = "ModalDialog";
|
|
127
159
|
|
|
128
160
|
class ModalFooter extends React__namespace.Component {
|
|
129
161
|
static isClassOf(instance) {
|
|
@@ -291,21 +323,6 @@ class FocusTrap extends React__namespace.Component {
|
|
|
291
323
|
}
|
|
292
324
|
}
|
|
293
325
|
|
|
294
|
-
function _extends() {
|
|
295
|
-
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
296
|
-
for (var i = 1; i < arguments.length; i++) {
|
|
297
|
-
var source = arguments[i];
|
|
298
|
-
for (var key in source) {
|
|
299
|
-
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
300
|
-
target[key] = source[key];
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return target;
|
|
305
|
-
};
|
|
306
|
-
return _extends.apply(this, arguments);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
326
|
const ModalLauncherPortalAttributeName = "data-modal-launcher-portal";
|
|
310
327
|
|
|
311
328
|
const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import defaultTheme from "./default";
|
|
3
|
+
type Props = {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
};
|
|
6
|
+
export type ModalDialogThemeContract = typeof defaultTheme;
|
|
7
|
+
/**
|
|
8
|
+
* The context that provides the theme to the ModalDialog component.
|
|
9
|
+
* This is generally consumed via the `useScopedTheme` hook.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ModalDialogThemeContext: React.Context<{
|
|
12
|
+
border: {
|
|
13
|
+
radius: number;
|
|
14
|
+
};
|
|
15
|
+
spacing: {
|
|
16
|
+
dialog: {
|
|
17
|
+
small: 16;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* ThemeModalDialog is a component that provides a theme to the <ModalDialog/>
|
|
23
|
+
* component.
|
|
24
|
+
*/
|
|
25
|
+
export default function ThemeModalDialog(props: Props): JSX.Element;
|
|
26
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-modal",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"design": "v2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"@khanacademy/wonder-blocks-icon-button": "^5.1.7",
|
|
23
23
|
"@khanacademy/wonder-blocks-layout": "^2.0.25",
|
|
24
24
|
"@khanacademy/wonder-blocks-spacing": "^4.0.1",
|
|
25
|
+
"@khanacademy/wonder-blocks-theming": "^1.2.1",
|
|
25
26
|
"@khanacademy/wonder-blocks-timing": "^4.0.2",
|
|
26
27
|
"@khanacademy/wonder-blocks-typography": "^2.1.10"
|
|
27
28
|
},
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {render, screen} from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import ModalDialog from "../modal-dialog";
|
|
5
|
+
|
|
6
|
+
describe("ModalDialog", () => {
|
|
7
|
+
it("should render its contents", () => {
|
|
8
|
+
// Arrange
|
|
9
|
+
|
|
10
|
+
// Act
|
|
11
|
+
render(
|
|
12
|
+
<ModalDialog>
|
|
13
|
+
<h1>A modal</h1>
|
|
14
|
+
<p>The contents</p>
|
|
15
|
+
</ModalDialog>,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// Assert
|
|
19
|
+
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should announce the dialog if aria-labelledby is set", () => {
|
|
23
|
+
// Arrange
|
|
24
|
+
|
|
25
|
+
// Act
|
|
26
|
+
render(
|
|
27
|
+
<ModalDialog aria-labelledby="dialog-title">
|
|
28
|
+
<h1 id="dialog-title">A modal</h1>
|
|
29
|
+
<p>The contents</p>
|
|
30
|
+
</ModalDialog>,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Assert
|
|
34
|
+
expect(
|
|
35
|
+
screen.getByRole("dialog", {name: "A modal"}),
|
|
36
|
+
).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should announce the dialog's contents if aria-describedby is set", () => {
|
|
40
|
+
// Arrange
|
|
41
|
+
|
|
42
|
+
// Act
|
|
43
|
+
render(
|
|
44
|
+
<ModalDialog aria-describedby="dialog-body">
|
|
45
|
+
<>
|
|
46
|
+
<h1>A modal</h1>
|
|
47
|
+
<p id="dialog-body">The contents</p>
|
|
48
|
+
</>
|
|
49
|
+
</ModalDialog>,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Assert
|
|
53
|
+
expect(
|
|
54
|
+
screen.getByRole("dialog", {description: "The contents"}),
|
|
55
|
+
).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("adds testId to the dialog element", () => {
|
|
59
|
+
// Arrange
|
|
60
|
+
|
|
61
|
+
// Act
|
|
62
|
+
render(<ModalDialog testId="test-id">A modal</ModalDialog>);
|
|
63
|
+
|
|
64
|
+
// Assert
|
|
65
|
+
expect(screen.getByTestId("test-id")).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("forwards the ref to the dialog element", () => {
|
|
69
|
+
// Arrange
|
|
70
|
+
const ref: React.RefObject<HTMLDivElement> = React.createRef();
|
|
71
|
+
|
|
72
|
+
// Act
|
|
73
|
+
render(<ModalDialog ref={ref}>A modal</ModalDialog>);
|
|
74
|
+
|
|
75
|
+
// Assert
|
|
76
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
77
|
+
});
|
|
78
|
+
});
|