@khanacademy/wonder-blocks-modal 3.0.7 → 3.0.8
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 +32 -0
- package/dist/components/close-button.d.ts +31 -0
- package/dist/components/close-button.js.flow +43 -0
- package/dist/components/focus-trap.d.ts +57 -0
- package/dist/components/focus-trap.js.flow +69 -0
- package/dist/components/modal-backdrop.d.ts +50 -0
- package/dist/components/modal-backdrop.js.flow +63 -0
- package/dist/components/modal-content.d.ts +23 -0
- package/dist/components/modal-content.js.flow +39 -0
- package/dist/components/modal-context.d.ts +6 -0
- package/dist/components/modal-context.js.flow +14 -0
- package/dist/components/modal-dialog.d.ts +60 -0
- package/dist/components/modal-dialog.js.flow +75 -0
- package/dist/components/modal-footer.d.ts +27 -0
- package/dist/components/modal-footer.js.flow +34 -0
- package/dist/components/modal-header.d.ts +93 -0
- package/dist/components/modal-header.js.flow +110 -0
- package/dist/components/modal-launcher.d.ts +17 -0
- package/dist/components/modal-launcher.js.flow +34 -0
- package/dist/components/modal-panel.d.ts +84 -0
- package/dist/components/modal-panel.js.flow +102 -0
- package/dist/components/one-pane-dialog.d.ts +124 -0
- package/dist/components/one-pane-dialog.js.flow +153 -0
- package/dist/components/scroll-disabler.d.ts +24 -0
- package/dist/components/scroll-disabler.js.flow +21 -0
- package/dist/es/index.js +222 -210
- package/dist/index.d.ts +8 -0
- package/dist/index.js +237 -226
- package/dist/index.js.flow +23 -2
- package/dist/util/constants.d.ts +5 -0
- package/dist/util/constants.js.flow +12 -0
- package/dist/util/find-focusable-nodes.d.ts +1 -0
- package/dist/util/find-focusable-nodes.js.flow +10 -0
- package/dist/util/maybe-get-portal-mounted-modal-host-element.d.ts +9 -0
- package/dist/util/maybe-get-portal-mounted-modal-host-element.js.flow +18 -0
- package/dist/util/types.d.ts +12 -0
- package/dist/util/types.js.flow +20 -0
- package/package.json +13 -13
- package/src/components/__tests__/{close-button.test.js → close-button.test.tsx} +0 -1
- package/src/components/__tests__/{focus-trap.test.js → focus-trap.test.tsx} +0 -1
- package/src/components/__tests__/{modal-backdrop.test.js → modal-backdrop.test.tsx} +0 -1
- package/src/components/__tests__/{modal-header.test.js → modal-header.test.tsx} +3 -2
- package/src/components/__tests__/{modal-launcher.test.js → modal-launcher.test.tsx} +11 -15
- package/src/components/__tests__/{modal-panel.test.js → modal-panel.test.tsx} +0 -1
- package/src/components/__tests__/{one-pane-dialog.test.js → one-pane-dialog.test.tsx} +0 -1
- package/src/components/{close-button.js → close-button.tsx} +7 -11
- package/src/components/{focus-trap.js → focus-trap.tsx} +12 -12
- package/src/components/{modal-backdrop.js → modal-backdrop.tsx} +20 -18
- package/src/components/{modal-content.js → modal-content.tsx} +11 -12
- package/src/components/modal-context.ts +13 -0
- package/src/components/{modal-dialog.js → modal-dialog.tsx} +15 -23
- package/src/components/{modal-footer.js → modal-footer.tsx} +5 -6
- package/src/components/{modal-header.js → modal-header.tsx} +20 -26
- package/src/components/{modal-launcher.js → modal-launcher.tsx} +29 -50
- package/src/components/{modal-panel.js → modal-panel.tsx} +27 -28
- package/src/components/{one-pane-dialog.js → one-pane-dialog.tsx} +37 -45
- package/src/components/{scroll-disabler.js → scroll-disabler.ts} +3 -4
- package/src/{index.js → index.ts} +0 -1
- package/src/util/{constants.js → constants.ts} +0 -2
- package/src/util/{find-focusable-nodes.js → find-focusable-nodes.ts} +0 -2
- package/src/util/{maybe-get-portal-mounted-modal-host-element.test.js → maybe-get-portal-mounted-modal-host-element.test.tsx} +4 -5
- package/src/util/{maybe-get-portal-mounted-modal-host-element.js → maybe-get-portal-mounted-modal-host-element.ts} +5 -4
- package/src/util/{types.js → types.ts} +3 -2
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/src/components/__docs__/modal-dialog.stories.js +0 -308
- package/src/components/__docs__/modal-footer.stories.js +0 -337
- package/src/components/__docs__/modal-header.argtypes.js +0 -76
- package/src/components/__docs__/modal-header.stories.js +0 -294
- package/src/components/__docs__/modal-launcher.argtypes.js +0 -78
- package/src/components/__docs__/modal-launcher.stories.js +0 -513
- package/src/components/__docs__/modal-panel.stories.js +0 -414
- package/src/components/__docs__/one-pane-dialog.argtypes.js +0 -108
- package/src/components/__docs__/one-pane-dialog.stories.js +0 -582
- package/src/components/modal-context.js +0 -14
package/dist/index.js.flow
CHANGED
|
@@ -1,2 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Flowtype definitions for index
|
|
3
|
+
* Generated by Flowgen from a Typescript Definition
|
|
4
|
+
* Flowgen v1.21.0
|
|
5
|
+
* @flow
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import ModalDialog from "./components/modal-dialog";
|
|
9
|
+
import ModalFooter from "./components/modal-footer";
|
|
10
|
+
import ModalHeader from "./components/modal-header";
|
|
11
|
+
import ModalLauncher from "./components/modal-launcher";
|
|
12
|
+
import ModalPanel from "./components/modal-panel";
|
|
13
|
+
import OnePaneDialog from "./components/one-pane-dialog";
|
|
14
|
+
import maybeGetPortalMountedModalHostElement from "./util/maybe-get-portal-mounted-modal-host-element";
|
|
15
|
+
declare export {
|
|
16
|
+
ModalHeader,
|
|
17
|
+
ModalFooter,
|
|
18
|
+
ModalDialog,
|
|
19
|
+
ModalPanel,
|
|
20
|
+
ModalLauncher,
|
|
21
|
+
OnePaneDialog,
|
|
22
|
+
maybeGetPortalMountedModalHostElement,
|
|
23
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flowtype definitions for constants
|
|
3
|
+
* Generated by Flowgen from a Typescript Definition
|
|
4
|
+
* Flowgen v1.21.0
|
|
5
|
+
* @flow
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The attribute used to identify a modal launcher portal.
|
|
10
|
+
*/
|
|
11
|
+
declare var ModalLauncherPortalAttributeName: "data-modal-launcher-portal";
|
|
12
|
+
declare export { ModalLauncherPortalAttributeName };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function findFocusableNodes(root: HTMLElement | Document): Array<HTMLElement>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* From a given element, finds the next modal host that has been mounted in
|
|
3
|
+
* a modal portal.
|
|
4
|
+
* @param {?(Element | Text)} element The element whose ancestors are to be
|
|
5
|
+
* walked.
|
|
6
|
+
* @returns {?Element} The next portal-mounted modal host element.
|
|
7
|
+
* TODO(kevinb): look into getting rid of this
|
|
8
|
+
*/
|
|
9
|
+
export default function maybeGetPortalMountedModalHostElement(element?: Element | Text | null): Element | null | undefined;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flowtype definitions for maybe-get-portal-mounted-modal-host-element
|
|
3
|
+
* Generated by Flowgen from a Typescript Definition
|
|
4
|
+
* Flowgen v1.21.0
|
|
5
|
+
* @flow
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* From a given element, finds the next modal host that has been mounted in
|
|
10
|
+
* a modal portal.
|
|
11
|
+
* @param {?(Element | Text)} element The element whose ancestors are to be
|
|
12
|
+
* walked.
|
|
13
|
+
* @returns {?Element} The next portal-mounted modal host element.
|
|
14
|
+
* TODO(kevinb): look into getting rid of this
|
|
15
|
+
*/
|
|
16
|
+
declare export default function maybeGetPortalMountedModalHostElement(
|
|
17
|
+
element?: Element | Text | null
|
|
18
|
+
): Element | null | void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A `ModalElement` is a React element that should either itself be a modal
|
|
4
|
+
* (OnePaneDialog), or wrap a modal.
|
|
5
|
+
*
|
|
6
|
+
* If it's a wrapper component, then its props must be passed along to the child
|
|
7
|
+
* modal, because we clone this element and add new props in order to capture
|
|
8
|
+
* `onClose` events.
|
|
9
|
+
*
|
|
10
|
+
* NOTE(kevinb): we include `| null` here because that's what React.FC<> returns.
|
|
11
|
+
*/
|
|
12
|
+
export type ModalElement = React.ReactElement | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flowtype definitions for types
|
|
3
|
+
* Generated by Flowgen from a Typescript Definition
|
|
4
|
+
* Flowgen v1.21.0
|
|
5
|
+
* @flow
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A `ModalElement` is a React element that should either itself be a modal
|
|
12
|
+
* (OnePaneDialog), or wrap a modal.
|
|
13
|
+
*
|
|
14
|
+
* If it's a wrapper component, then its props must be passed along to the child
|
|
15
|
+
* modal, because we clone this element and add new props in order to capture
|
|
16
|
+
* `onClose` events.
|
|
17
|
+
*
|
|
18
|
+
* NOTE(kevinb): we include `| null` here because that's what React.FC<> returns.
|
|
19
|
+
*/
|
|
20
|
+
export type ModalElement = React.Element<> | null;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-modal",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.8",
|
|
4
4
|
"design": "v2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"description": "",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
|
-
"
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
11
|
"module": "dist/es/index.js",
|
|
12
12
|
"scripts": {
|
|
13
13
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@babel/runtime": "^7.18.6",
|
|
19
|
-
"@khanacademy/wonder-blocks-breadcrumbs": "^1.0.
|
|
20
|
-
"@khanacademy/wonder-blocks-color": "^1.2.
|
|
21
|
-
"@khanacademy/wonder-blocks-core": "^4.
|
|
22
|
-
"@khanacademy/wonder-blocks-icon": "^1.2.
|
|
23
|
-
"@khanacademy/wonder-blocks-icon-button": "^3.4.
|
|
24
|
-
"@khanacademy/wonder-blocks-layout": "^1.4.
|
|
25
|
-
"@khanacademy/wonder-blocks-spacing": "^3.0.
|
|
26
|
-
"@khanacademy/wonder-blocks-timing": "^2.1.
|
|
27
|
-
"@khanacademy/wonder-blocks-typography": "^1.1.
|
|
19
|
+
"@khanacademy/wonder-blocks-breadcrumbs": "^1.0.39",
|
|
20
|
+
"@khanacademy/wonder-blocks-color": "^1.2.2",
|
|
21
|
+
"@khanacademy/wonder-blocks-core": "^4.8.0",
|
|
22
|
+
"@khanacademy/wonder-blocks-icon": "^1.2.38",
|
|
23
|
+
"@khanacademy/wonder-blocks-icon-button": "^3.4.22",
|
|
24
|
+
"@khanacademy/wonder-blocks-layout": "^1.4.17",
|
|
25
|
+
"@khanacademy/wonder-blocks-spacing": "^3.0.6",
|
|
26
|
+
"@khanacademy/wonder-blocks-timing": "^2.1.2",
|
|
27
|
+
"@khanacademy/wonder-blocks-typography": "^1.1.39"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"aphrodite": "^1.2.5",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"react-dom": "16.14.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@khanacademy/wonder-blocks-breadcrumbs": "^1.0.
|
|
36
|
-
"wb-dev-build-settings": "^0.7.
|
|
35
|
+
"@khanacademy/wonder-blocks-breadcrumbs": "^1.0.39",
|
|
36
|
+
"wb-dev-build-settings": "^0.7.2"
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import {render, screen} from "@testing-library/react";
|
|
4
3
|
|
|
@@ -9,7 +8,9 @@ import {
|
|
|
9
8
|
|
|
10
9
|
import ModalHeader from "../modal-header";
|
|
11
10
|
|
|
12
|
-
const exampleBreadcrumbs: React.
|
|
11
|
+
const exampleBreadcrumbs: React.ReactElement<
|
|
12
|
+
React.ComponentProps<typeof Breadcrumbs>
|
|
13
|
+
> = (
|
|
13
14
|
<Breadcrumbs>
|
|
14
15
|
<BreadcrumbsItem>breadcrumb item</BreadcrumbsItem>
|
|
15
16
|
</Breadcrumbs>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import {render, screen, waitFor} from "@testing-library/react";
|
|
4
3
|
import userEvent from "@testing-library/user-event";
|
|
@@ -23,7 +22,7 @@ describe("ModalLauncher", () => {
|
|
|
23
22
|
// Arrange
|
|
24
23
|
render(
|
|
25
24
|
<ModalLauncher modal={exampleModal} testId="modal-launcher-portal">
|
|
26
|
-
{({openModal}) => <button onClick={openModal} />}
|
|
25
|
+
{({openModal}: any) => <button onClick={openModal} />}
|
|
27
26
|
</ModalLauncher>,
|
|
28
27
|
);
|
|
29
28
|
|
|
@@ -38,7 +37,7 @@ describe("ModalLauncher", () => {
|
|
|
38
37
|
|
|
39
38
|
test("Modal can be manually opened and closed", () => {
|
|
40
39
|
// Arrange
|
|
41
|
-
const UnderTest = ({opened}: {
|
|
40
|
+
const UnderTest = ({opened}: {opened: boolean}) => (
|
|
42
41
|
<ModalLauncher
|
|
43
42
|
modal={exampleModal}
|
|
44
43
|
opened={opened}
|
|
@@ -62,7 +61,7 @@ describe("ModalLauncher", () => {
|
|
|
62
61
|
|
|
63
62
|
test("Modal can close itself after launching", async () => {
|
|
64
63
|
// Arrange
|
|
65
|
-
const modalFn = ({closeModal}: {
|
|
64
|
+
const modalFn = ({closeModal}: {closeModal: () => void}) => (
|
|
66
65
|
<OnePaneDialog
|
|
67
66
|
title="Modal launcher test"
|
|
68
67
|
content={
|
|
@@ -83,7 +82,7 @@ describe("ModalLauncher", () => {
|
|
|
83
82
|
onClose={onCloseMock}
|
|
84
83
|
testId="modal-launcher-portal"
|
|
85
84
|
>
|
|
86
|
-
{({openModal}) => <button onClick={openModal} />}
|
|
85
|
+
{({openModal}: any) => <button onClick={openModal} />}
|
|
87
86
|
</ModalLauncher>,
|
|
88
87
|
);
|
|
89
88
|
|
|
@@ -103,7 +102,7 @@ describe("ModalLauncher", () => {
|
|
|
103
102
|
// Arrange
|
|
104
103
|
render(
|
|
105
104
|
<ModalLauncher modal={exampleModal}>
|
|
106
|
-
{({openModal}) => <button onClick={openModal} />}
|
|
105
|
+
{({openModal}: any) => <button onClick={openModal} />}
|
|
107
106
|
</ModalLauncher>,
|
|
108
107
|
);
|
|
109
108
|
|
|
@@ -126,7 +125,7 @@ describe("ModalLauncher", () => {
|
|
|
126
125
|
// Arrange
|
|
127
126
|
render(
|
|
128
127
|
<ModalLauncher modal={exampleModal}>
|
|
129
|
-
{({openModal}) => <button onClick={openModal} />}
|
|
128
|
+
{({openModal}: any) => <button onClick={openModal} />}
|
|
130
129
|
</ModalLauncher>,
|
|
131
130
|
);
|
|
132
131
|
|
|
@@ -146,7 +145,7 @@ describe("ModalLauncher", () => {
|
|
|
146
145
|
// Arrange
|
|
147
146
|
render(
|
|
148
147
|
<ModalLauncher modal={exampleModal}>
|
|
149
|
-
{({openModal}) => <button onClick={openModal} />}
|
|
148
|
+
{({openModal}: any) => <button onClick={openModal} />}
|
|
150
149
|
</ModalLauncher>,
|
|
151
150
|
);
|
|
152
151
|
|
|
@@ -169,13 +168,12 @@ describe("ModalLauncher", () => {
|
|
|
169
168
|
|
|
170
169
|
// Act
|
|
171
170
|
render(
|
|
172
|
-
// $FlowIgnore
|
|
173
171
|
<ModalLauncher
|
|
174
172
|
modal={exampleModal}
|
|
175
173
|
opened={false}
|
|
176
174
|
onClose={() => {}}
|
|
177
175
|
>
|
|
178
|
-
{({openModal}) => <button onClick={openModal} />}
|
|
176
|
+
{({openModal}: any) => <button onClick={openModal} />}
|
|
179
177
|
</ModalLauncher>,
|
|
180
178
|
);
|
|
181
179
|
|
|
@@ -191,7 +189,6 @@ describe("ModalLauncher", () => {
|
|
|
191
189
|
jest.spyOn(console, "warn");
|
|
192
190
|
|
|
193
191
|
// Act
|
|
194
|
-
// $FlowIgnore
|
|
195
192
|
render(<ModalLauncher modal={exampleModal} opened={false} />);
|
|
196
193
|
|
|
197
194
|
// Assert
|
|
@@ -206,7 +203,6 @@ describe("ModalLauncher", () => {
|
|
|
206
203
|
jest.spyOn(console, "warn");
|
|
207
204
|
|
|
208
205
|
// Act
|
|
209
|
-
// $FlowIgnore
|
|
210
206
|
render(<ModalLauncher modal={exampleModal} />);
|
|
211
207
|
|
|
212
208
|
// Assert
|
|
@@ -253,7 +249,7 @@ describe("ModalLauncher", () => {
|
|
|
253
249
|
/>
|
|
254
250
|
}
|
|
255
251
|
>
|
|
256
|
-
{({openModal}) => (
|
|
252
|
+
{({openModal}: any) => (
|
|
257
253
|
<button onClick={openModal}>Open modal</button>
|
|
258
254
|
)}
|
|
259
255
|
</ModalLauncher>,
|
|
@@ -303,7 +299,7 @@ describe("ModalLauncher", () => {
|
|
|
303
299
|
<ModalLauncher
|
|
304
300
|
onClose={() => handleClose()}
|
|
305
301
|
opened={opened}
|
|
306
|
-
modal={({closeModal}) => (
|
|
302
|
+
modal={({closeModal}: any) => (
|
|
307
303
|
<OnePaneDialog
|
|
308
304
|
title="Regular modal"
|
|
309
305
|
content={<View>Hello World</View>}
|
|
@@ -371,7 +367,7 @@ describe("ModalLauncher", () => {
|
|
|
371
367
|
onClose={() => handleClose()}
|
|
372
368
|
opened={opened}
|
|
373
369
|
closedFocusId="button-to-focus-on"
|
|
374
|
-
modal={({closeModal}) => (
|
|
370
|
+
modal={({closeModal}: any) => (
|
|
375
371
|
<OnePaneDialog
|
|
376
372
|
title="Triggered from action menu"
|
|
377
373
|
content={<View>Hello World</View>}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import {icons} from "@khanacademy/wonder-blocks-icon";
|
|
4
3
|
import IconButton from "@khanacademy/wonder-blocks-icon-button";
|
|
@@ -6,21 +5,18 @@ import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
|
6
5
|
|
|
7
6
|
import ModalContext from "./modal-context";
|
|
8
7
|
|
|
9
|
-
type Props = {
|
|
8
|
+
type Props = {
|
|
10
9
|
/**
|
|
11
10
|
* Whether the button is on a dark/colored background.
|
|
12
11
|
*
|
|
13
12
|
* Sets primary button background color to white, and secondary and
|
|
14
13
|
* tertiary button title to color.
|
|
15
14
|
*/
|
|
16
|
-
light?: boolean
|
|
17
|
-
|
|
15
|
+
light?: boolean;
|
|
18
16
|
/** Optional click handler */
|
|
19
|
-
onClick?: () =>
|
|
20
|
-
|
|
17
|
+
onClick?: () => unknown;
|
|
21
18
|
/** Optional custom styles. */
|
|
22
|
-
style?: StyleType
|
|
23
|
-
|
|
19
|
+
style?: StyleType;
|
|
24
20
|
/**
|
|
25
21
|
* Test ID used for e2e testing.
|
|
26
22
|
*
|
|
@@ -32,11 +28,11 @@ type Props = {|
|
|
|
32
28
|
* For testId="some-random-id"
|
|
33
29
|
* The result will be: `some-random-id-modal-panel`
|
|
34
30
|
*/
|
|
35
|
-
testId?: string
|
|
36
|
-
|
|
31
|
+
testId?: string;
|
|
32
|
+
};
|
|
37
33
|
|
|
38
34
|
export default class CloseButton extends React.Component<Props> {
|
|
39
|
-
render(): React.
|
|
35
|
+
render(): React.ReactElement {
|
|
40
36
|
const {light, onClick, style, testId} = this.props;
|
|
41
37
|
|
|
42
38
|
return (
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import * as ReactDOM from "react-dom";
|
|
4
3
|
|
|
@@ -30,24 +29,23 @@ const FOCUSABLE_ELEMENTS =
|
|
|
30
29
|
* instead if we were to generalize!
|
|
31
30
|
*/
|
|
32
31
|
|
|
33
|
-
type Props = {
|
|
34
|
-
children: React.
|
|
35
|
-
|
|
32
|
+
type Props = {
|
|
33
|
+
children: React.ReactNode;
|
|
36
34
|
/**
|
|
37
35
|
* Style applied to the View containing children.
|
|
38
36
|
* TODO(kevinb): only allow z-index to be specified. We'll be able to remove
|
|
39
37
|
* this prop once we remove all uses of z-indexes from webapp.
|
|
40
38
|
*/
|
|
41
|
-
style?: StyleType
|
|
42
|
-
|
|
39
|
+
style?: StyleType;
|
|
40
|
+
};
|
|
43
41
|
|
|
44
42
|
export default class FocusTrap extends React.Component<Props> {
|
|
45
43
|
/**
|
|
46
44
|
* Tabbing is restricted to descendents of this element.
|
|
47
45
|
*/
|
|
48
|
-
modalRoot:
|
|
46
|
+
modalRoot: Node | null | undefined;
|
|
49
47
|
|
|
50
|
-
getModalRoot: (node
|
|
48
|
+
getModalRoot: (node?: any) => void = (node) => {
|
|
51
49
|
if (!node) {
|
|
52
50
|
// The component is being umounted
|
|
53
51
|
return;
|
|
@@ -65,11 +63,11 @@ export default class FocusTrap extends React.Component<Props> {
|
|
|
65
63
|
/**
|
|
66
64
|
* Try to focus the given node. Return true if successful.
|
|
67
65
|
*/
|
|
68
|
-
tryToFocus(node: Node):
|
|
66
|
+
tryToFocus(node: Node): boolean | null | undefined {
|
|
69
67
|
if (node instanceof HTMLElement) {
|
|
70
68
|
try {
|
|
71
69
|
node.focus();
|
|
72
|
-
} catch (e) {
|
|
70
|
+
} catch (e: any) {
|
|
73
71
|
// ignore error
|
|
74
72
|
}
|
|
75
73
|
|
|
@@ -84,7 +82,7 @@ export default class FocusTrap extends React.Component<Props> {
|
|
|
84
82
|
* First element within the modal, false = Last element within the modal.
|
|
85
83
|
*/
|
|
86
84
|
focusElementIn(isLast: boolean) {
|
|
87
|
-
const modalRootAsHtmlEl =
|
|
85
|
+
const modalRootAsHtmlEl = this.modalRoot as HTMLElement;
|
|
88
86
|
// Get the list of available focusable elements within the modal.
|
|
89
87
|
const focusableNodes = Array.from(
|
|
90
88
|
modalRootAsHtmlEl.querySelectorAll(FOCUSABLE_ELEMENTS),
|
|
@@ -112,7 +110,7 @@ export default class FocusTrap extends React.Component<Props> {
|
|
|
112
110
|
this.focusElementIn(true);
|
|
113
111
|
};
|
|
114
112
|
|
|
115
|
-
render(): React.
|
|
113
|
+
render(): React.ReactElement {
|
|
116
114
|
const {style} = this.props;
|
|
117
115
|
|
|
118
116
|
return (
|
|
@@ -130,6 +128,7 @@ export default class FocusTrap extends React.Component<Props> {
|
|
|
130
128
|
* they're always in view, this prevents page scrolling when
|
|
131
129
|
* tabbing. */}
|
|
132
130
|
<div
|
|
131
|
+
// @ts-expect-error [FEI-5019] - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
|
|
133
132
|
tabIndex="0"
|
|
134
133
|
className="modal-focus-trap-first"
|
|
135
134
|
onFocus={this.handleFocusMoveToLast}
|
|
@@ -139,6 +138,7 @@ export default class FocusTrap extends React.Component<Props> {
|
|
|
139
138
|
{this.props.children}
|
|
140
139
|
</View>
|
|
141
140
|
<div
|
|
141
|
+
// @ts-expect-error [FEI-5019] - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
|
|
142
142
|
tabIndex="0"
|
|
143
143
|
className="modal-focus-trap-last"
|
|
144
144
|
onFocus={this.handleFocusMoveToFirst}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import * as ReactDOM from "react-dom";
|
|
4
3
|
import {StyleSheet} from "aphrodite";
|
|
@@ -11,20 +10,19 @@ import {findFocusableNodes} from "../util/find-focusable-nodes";
|
|
|
11
10
|
|
|
12
11
|
import type {ModalElement} from "../util/types";
|
|
13
12
|
|
|
14
|
-
type Props = {
|
|
15
|
-
children: ModalElement
|
|
16
|
-
onCloseModal: () =>
|
|
13
|
+
type Props = {
|
|
14
|
+
children: ModalElement;
|
|
15
|
+
onCloseModal: () => unknown;
|
|
17
16
|
/**
|
|
18
17
|
* The selector for the element that will be focused when the dialog shows.
|
|
19
18
|
* When not set, the first tabbable element within the dialog will be used.
|
|
20
19
|
*/
|
|
21
|
-
initialFocusId?: string
|
|
22
|
-
|
|
20
|
+
initialFocusId?: string;
|
|
23
21
|
/**
|
|
24
22
|
* Test ID used for e2e testing.
|
|
25
23
|
*/
|
|
26
|
-
testId?: string
|
|
27
|
-
|
|
24
|
+
testId?: string;
|
|
25
|
+
};
|
|
28
26
|
|
|
29
27
|
/**
|
|
30
28
|
* A private component used by ModalLauncher. This is the fixed-position
|
|
@@ -38,7 +36,7 @@ type Props = {|
|
|
|
38
36
|
*/
|
|
39
37
|
export default class ModalBackdrop extends React.Component<Props> {
|
|
40
38
|
componentDidMount() {
|
|
41
|
-
const node: HTMLElement =
|
|
39
|
+
const node: HTMLElement = ReactDOM.findDOMNode(this) as any;
|
|
42
40
|
if (!node) {
|
|
43
41
|
return;
|
|
44
42
|
}
|
|
@@ -57,7 +55,7 @@ export default class ModalBackdrop extends React.Component<Props> {
|
|
|
57
55
|
}, 0);
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
_mousePressedOutside
|
|
58
|
+
_mousePressedOutside = false;
|
|
61
59
|
|
|
62
60
|
/**
|
|
63
61
|
* Returns an element specified by the user
|
|
@@ -69,9 +67,9 @@ export default class ModalBackdrop extends React.Component<Props> {
|
|
|
69
67
|
return null;
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
return
|
|
70
|
+
return ReactDOM.findDOMNode(
|
|
73
71
|
node.querySelector(`#${initialFocusId}`),
|
|
74
|
-
)
|
|
72
|
+
) as any;
|
|
75
73
|
}
|
|
76
74
|
|
|
77
75
|
/**
|
|
@@ -95,9 +93,9 @@ export default class ModalBackdrop extends React.Component<Props> {
|
|
|
95
93
|
_getDialogElement(node: HTMLElement): HTMLElement {
|
|
96
94
|
// If no focusable elements are found,
|
|
97
95
|
// the dialog content element itself will receive focus.
|
|
98
|
-
const dialogElement: HTMLElement =
|
|
96
|
+
const dialogElement: HTMLElement = ReactDOM.findDOMNode(
|
|
99
97
|
node.querySelector('[role="dialog"]'),
|
|
100
|
-
)
|
|
98
|
+
) as any;
|
|
101
99
|
// add tabIndex to make the Dialog focusable
|
|
102
100
|
dialogElement.tabIndex = -1;
|
|
103
101
|
|
|
@@ -109,12 +107,16 @@ export default class ModalBackdrop extends React.Component<Props> {
|
|
|
109
107
|
* _directly_ from the positioner, not bubbled up from its children), close
|
|
110
108
|
* the modal.
|
|
111
109
|
*/
|
|
112
|
-
handleMouseDown: (e: SyntheticEvent
|
|
110
|
+
handleMouseDown: (e: React.SyntheticEvent) => void = (
|
|
111
|
+
e: React.SyntheticEvent,
|
|
112
|
+
) => {
|
|
113
113
|
// Confirm that it is the backdrop that is being clicked, not the child
|
|
114
114
|
this._mousePressedOutside = e.target === e.currentTarget;
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
-
handleMouseUp: (e: SyntheticEvent
|
|
117
|
+
handleMouseUp: (e: React.SyntheticEvent) => void = (
|
|
118
|
+
e: React.SyntheticEvent,
|
|
119
|
+
) => {
|
|
118
120
|
// Confirm that it is the backdrop that is being clicked, not the child
|
|
119
121
|
// and that the mouse was pressed in the backdrop first.
|
|
120
122
|
if (e.target === e.currentTarget && this._mousePressedOutside) {
|
|
@@ -123,11 +125,11 @@ export default class ModalBackdrop extends React.Component<Props> {
|
|
|
123
125
|
this._mousePressedOutside = false;
|
|
124
126
|
};
|
|
125
127
|
|
|
126
|
-
render(): React.
|
|
128
|
+
render(): React.ReactElement {
|
|
127
129
|
const {children, testId} = this.props;
|
|
128
130
|
const backdropProps = {
|
|
129
131
|
[ModalLauncherPortalAttributeName]: true,
|
|
130
|
-
};
|
|
132
|
+
} as const;
|
|
131
133
|
|
|
132
134
|
return (
|
|
133
135
|
<View
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import {StyleSheet} from "aphrodite";
|
|
4
3
|
import {View} from "@khanacademy/wonder-blocks-core";
|
|
@@ -7,18 +6,18 @@ import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
|
7
6
|
|
|
8
7
|
import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
9
8
|
|
|
10
|
-
type Props = {
|
|
9
|
+
type Props = {
|
|
11
10
|
/** Should the content scroll on overflow, or just expand. */
|
|
12
|
-
scrollOverflow: boolean
|
|
11
|
+
scrollOverflow: boolean;
|
|
13
12
|
/** The contents of the ModalContent */
|
|
14
|
-
children: React.
|
|
13
|
+
children: React.ReactNode;
|
|
15
14
|
/** Optional styling to apply to the contents. */
|
|
16
|
-
style?: StyleType
|
|
17
|
-
|
|
15
|
+
style?: StyleType;
|
|
16
|
+
};
|
|
18
17
|
|
|
19
|
-
type DefaultProps = {
|
|
20
|
-
scrollOverflow:
|
|
21
|
-
|
|
18
|
+
type DefaultProps = {
|
|
19
|
+
scrollOverflow: Props["scrollOverflow"];
|
|
20
|
+
};
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* The Modal content included after the header
|
|
@@ -31,9 +30,9 @@ export default class ModalContent extends React.Component<Props> {
|
|
|
31
30
|
scrollOverflow: true,
|
|
32
31
|
};
|
|
33
32
|
|
|
34
|
-
static __IS_MODAL_CONTENT__
|
|
33
|
+
static __IS_MODAL_CONTENT__ = true;
|
|
35
34
|
|
|
36
|
-
render(): React.
|
|
35
|
+
render(): React.ReactElement {
|
|
37
36
|
const {scrollOverflow, style, children} = this.props;
|
|
38
37
|
|
|
39
38
|
return (
|
|
@@ -80,4 +79,4 @@ const styleSheets = {
|
|
|
80
79
|
padding: `${Spacing.xLarge_32}px ${Spacing.medium_16}px`,
|
|
81
80
|
},
|
|
82
81
|
}),
|
|
83
|
-
};
|
|
82
|
+
} as const;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
type ContextType = {
|
|
4
|
+
closeModal?: () => unknown;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const defaultContext: ContextType = {
|
|
8
|
+
closeModal: undefined,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default React.createContext<ContextType>(
|
|
12
|
+
defaultContext,
|
|
13
|
+
) as React.Context<ContextType>;
|