@khanacademy/wonder-blocks-modal 4.1.0 → 4.2.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 +7 -0
- package/dist/components/modal-content.d.ts +8 -9
- package/dist/components/modal-footer.d.ts +5 -5
- package/dist/components/modal-header.d.ts +6 -7
- package/dist/components/modal-panel.d.ts +13 -14
- package/dist/es/index.js +189 -173
- package/dist/index.js +188 -172
- package/dist/themes/default.d.ts +22 -0
- package/dist/themes/khanmigo.d.ts +36 -0
- package/dist/themes/themed-modal-dialog.d.ts +22 -0
- package/package.json +1 -1
- package/src/components/modal-content.tsx +53 -54
- package/src/components/modal-footer.tsx +8 -10
- package/src/components/modal-header.tsx +95 -102
- package/src/components/modal-panel.tsx +66 -73
- package/src/themes/default.ts +22 -0
- package/src/themes/khanmigo.ts +15 -0
- package/src/themes/themed-modal-dialog.tsx +2 -1
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {StyleSheet} from "aphrodite";
|
|
3
2
|
import {View} from "@khanacademy/wonder-blocks-core";
|
|
4
|
-
import {MediaLayout} from "@khanacademy/wonder-blocks-layout";
|
|
5
3
|
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
6
4
|
|
|
7
5
|
import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import {
|
|
7
|
+
ThemedStylesFn,
|
|
8
|
+
useScopedTheme,
|
|
9
|
+
useStyles,
|
|
10
|
+
} from "@khanacademy/wonder-blocks-theming";
|
|
11
|
+
import {
|
|
12
|
+
ModalDialogThemeContext,
|
|
13
|
+
ModalDialogThemeContract,
|
|
14
|
+
} from "../themes/themed-modal-dialog";
|
|
8
15
|
|
|
9
16
|
type Props = {
|
|
10
17
|
/** Should the content scroll on overflow, or just expand. */
|
|
@@ -15,68 +22,60 @@ type Props = {
|
|
|
15
22
|
style?: StyleType;
|
|
16
23
|
};
|
|
17
24
|
|
|
18
|
-
type DefaultProps = {
|
|
19
|
-
scrollOverflow: Props["scrollOverflow"];
|
|
20
|
-
};
|
|
21
|
-
|
|
22
25
|
/**
|
|
23
26
|
* The Modal content included after the header
|
|
24
27
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
static defaultProps: DefaultProps = {
|
|
30
|
-
scrollOverflow: true,
|
|
31
|
-
};
|
|
28
|
+
function ModalContent(props: Props) {
|
|
29
|
+
const {scrollOverflow, style, children} = props;
|
|
30
|
+
const {theme} = useScopedTheme(ModalDialogThemeContext);
|
|
31
|
+
const styles = useStyles(themedStylesFn, theme);
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
return (
|
|
34
|
+
<View style={[styles.wrapper, scrollOverflow && styles.scrollOverflow]}>
|
|
35
|
+
<View style={[styles.content, style]}>{children}</View>
|
|
36
|
+
</View>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
const {scrollOverflow, style, children} = this.props;
|
|
40
|
+
ModalContent.__IS_MODAL_CONTENT__ = true;
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<View
|
|
42
|
-
style={[
|
|
43
|
-
styles.wrapper,
|
|
44
|
-
scrollOverflow && styles.scrollOverflow,
|
|
45
|
-
]}
|
|
46
|
-
>
|
|
47
|
-
<View style={[styles.content, style]}>{children}</View>
|
|
48
|
-
</View>
|
|
49
|
-
)}
|
|
50
|
-
</MediaLayout>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
42
|
+
ModalContent.isComponentOf = (instance: any): boolean => {
|
|
43
|
+
return instance && instance.type && instance.type.__IS_MODAL_CONTENT__;
|
|
44
|
+
};
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Media query for small screens.
|
|
48
|
+
* TODO(WB-1655): Change this to use the theme instead (inside themedStylesFn).
|
|
49
|
+
* e.g. `[theme.breakpoints.small]: {...}`
|
|
50
|
+
*/
|
|
51
|
+
const small = "@media (max-width: 767px)";
|
|
59
52
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
},
|
|
53
|
+
const themedStylesFn: ThemedStylesFn<ModalDialogThemeContract> = (theme) => ({
|
|
54
|
+
wrapper: {
|
|
55
|
+
flex: 1,
|
|
64
56
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
// This helps to ensure that the paddingBottom is preserved when
|
|
58
|
+
// the contents start to overflow, this goes away on display: flex
|
|
59
|
+
display: "block",
|
|
60
|
+
},
|
|
68
61
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
padding: Spacing.xLarge_32,
|
|
73
|
-
boxSizing: "border-box",
|
|
74
|
-
},
|
|
75
|
-
}),
|
|
62
|
+
scrollOverflow: {
|
|
63
|
+
overflow: "auto",
|
|
64
|
+
},
|
|
76
65
|
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
content: {
|
|
67
|
+
flex: 1,
|
|
68
|
+
minHeight: "100%",
|
|
69
|
+
padding: Spacing.xLarge_32,
|
|
70
|
+
boxSizing: "border-box",
|
|
71
|
+
[small]: {
|
|
79
72
|
padding: `${Spacing.xLarge_32}px ${Spacing.medium_16}px`,
|
|
80
73
|
},
|
|
81
|
-
}
|
|
82
|
-
}
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
ModalContent.defaultProps = {
|
|
78
|
+
scrollOverflow: true,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default ModalContent;
|
|
@@ -25,18 +25,16 @@ type Props = {
|
|
|
25
25
|
* </ModalFooter>
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
|
-
export default
|
|
29
|
-
|
|
30
|
-
return instance && instance.type && instance.type.__IS_MODAL_FOOTER__;
|
|
31
|
-
}
|
|
32
|
-
static __IS_MODAL_FOOTER__ = true;
|
|
33
|
-
|
|
34
|
-
render(): React.ReactNode {
|
|
35
|
-
const {children} = this.props;
|
|
36
|
-
return <View style={styles.footer}>{children}</View>;
|
|
37
|
-
}
|
|
28
|
+
export default function ModalFooter({children}: Props) {
|
|
29
|
+
return <View style={styles.footer}>{children}</View>;
|
|
38
30
|
}
|
|
39
31
|
|
|
32
|
+
ModalFooter.__IS_MODAL_FOOTER__ = true;
|
|
33
|
+
|
|
34
|
+
ModalFooter.isComponentOf = (instance: any): boolean => {
|
|
35
|
+
return instance && instance.type && instance.type.__IS_MODAL_FOOTER__;
|
|
36
|
+
};
|
|
37
|
+
|
|
40
38
|
const styles = StyleSheet.create({
|
|
41
39
|
footer: {
|
|
42
40
|
flex: "0 0 auto",
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {StyleSheet} from "aphrodite";
|
|
3
2
|
import {Breadcrumbs} from "@khanacademy/wonder-blocks-breadcrumbs";
|
|
4
|
-
import Color from "@khanacademy/wonder-blocks-color";
|
|
5
3
|
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
-
import {MediaLayout} from "@khanacademy/wonder-blocks-layout";
|
|
7
|
-
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
8
4
|
import {HeadingMedium, LabelSmall} from "@khanacademy/wonder-blocks-typography";
|
|
5
|
+
import {
|
|
6
|
+
ThemedStylesFn,
|
|
7
|
+
useScopedTheme,
|
|
8
|
+
useStyles,
|
|
9
|
+
} from "@khanacademy/wonder-blocks-theming";
|
|
10
|
+
import {
|
|
11
|
+
ModalDialogThemeContext,
|
|
12
|
+
ModalDialogThemeContract,
|
|
13
|
+
} from "../themes/themed-modal-dialog";
|
|
9
14
|
|
|
10
15
|
type Common = {
|
|
11
16
|
/**
|
|
@@ -51,10 +56,6 @@ type WithBreadcrumbs = Common & {
|
|
|
51
56
|
|
|
52
57
|
type Props = Common | WithSubtitle | WithBreadcrumbs;
|
|
53
58
|
|
|
54
|
-
type DefaultProps = {
|
|
55
|
-
light: Props["light"];
|
|
56
|
-
};
|
|
57
|
-
|
|
58
59
|
/**
|
|
59
60
|
* This is a helper component that is never rendered by itself. It is always
|
|
60
61
|
* pinned to the top of the dialog, is responsive using the same behavior as its
|
|
@@ -98,104 +99,96 @@ type DefaultProps = {
|
|
|
98
99
|
* />
|
|
99
100
|
* ```
|
|
100
101
|
*/
|
|
101
|
-
export default
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
titleId,
|
|
116
|
-
} = this.props;
|
|
117
|
-
|
|
118
|
-
if (subtitle && breadcrumbs) {
|
|
119
|
-
throw new Error(
|
|
120
|
-
"'subtitle' and 'breadcrumbs' can't be used together",
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
<MediaLayout styleSheets={styleSheets}>
|
|
126
|
-
{({styles}) => (
|
|
127
|
-
<View
|
|
128
|
-
style={[styles.header, !light && styles.dark]}
|
|
129
|
-
testId={testId}
|
|
130
|
-
>
|
|
131
|
-
{breadcrumbs && (
|
|
132
|
-
<View style={styles.breadcrumbs}>
|
|
133
|
-
{breadcrumbs}
|
|
134
|
-
</View>
|
|
135
|
-
)}
|
|
136
|
-
<HeadingMedium
|
|
137
|
-
style={styles.title}
|
|
138
|
-
id={titleId}
|
|
139
|
-
testId={testId && `${testId}-title`}
|
|
140
|
-
>
|
|
141
|
-
{title}
|
|
142
|
-
</HeadingMedium>
|
|
143
|
-
{subtitle && (
|
|
144
|
-
<LabelSmall
|
|
145
|
-
style={light && styles.subtitle}
|
|
146
|
-
testId={testId && `${testId}-subtitle`}
|
|
147
|
-
>
|
|
148
|
-
{subtitle}
|
|
149
|
-
</LabelSmall>
|
|
150
|
-
)}
|
|
151
|
-
</View>
|
|
152
|
-
)}
|
|
153
|
-
</MediaLayout>
|
|
154
|
-
);
|
|
102
|
+
export default function ModalHeader(props: Props) {
|
|
103
|
+
const {
|
|
104
|
+
// @ts-expect-error [FEI-5019] - TS2339 - Property 'breadcrumbs' does not exist on type 'Readonly<Props> & Readonly<{ children?: ReactNode; }>'.
|
|
105
|
+
breadcrumbs = undefined,
|
|
106
|
+
light,
|
|
107
|
+
// @ts-expect-error [FEI-5019] - TS2339 - Property 'subtitle' does not exist on type 'Readonly<Props> & Readonly<{ children?: ReactNode; }>'.
|
|
108
|
+
subtitle = undefined,
|
|
109
|
+
testId,
|
|
110
|
+
title,
|
|
111
|
+
titleId,
|
|
112
|
+
} = props;
|
|
113
|
+
|
|
114
|
+
if (subtitle && breadcrumbs) {
|
|
115
|
+
throw new Error("'subtitle' and 'breadcrumbs' can't be used together");
|
|
155
116
|
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const styleSheets = {
|
|
159
|
-
all: StyleSheet.create({
|
|
160
|
-
header: {
|
|
161
|
-
boxShadow: `0px 1px 0px ${Color.offBlack16}`,
|
|
162
|
-
display: "flex",
|
|
163
|
-
flexDirection: "column",
|
|
164
|
-
minHeight: 66,
|
|
165
|
-
padding: `${Spacing.large_24}px ${Spacing.xLarge_32}px`,
|
|
166
|
-
position: "relative",
|
|
167
|
-
width: "100%",
|
|
168
|
-
},
|
|
169
|
-
|
|
170
|
-
dark: {
|
|
171
|
-
background: Color.darkBlue,
|
|
172
|
-
color: Color.white,
|
|
173
|
-
},
|
|
174
117
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
118
|
+
const {theme} = useScopedTheme(ModalDialogThemeContext);
|
|
119
|
+
const styles = useStyles(themedStylesFn, theme);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<View style={[styles.header, !light && styles.dark]} testId={testId}>
|
|
123
|
+
{breadcrumbs && (
|
|
124
|
+
<View style={styles.breadcrumbs}>{breadcrumbs}</View>
|
|
125
|
+
)}
|
|
126
|
+
<HeadingMedium
|
|
127
|
+
style={styles.title}
|
|
128
|
+
id={titleId}
|
|
129
|
+
testId={testId && `${testId}-title`}
|
|
130
|
+
>
|
|
131
|
+
{title}
|
|
132
|
+
</HeadingMedium>
|
|
133
|
+
{subtitle && (
|
|
134
|
+
<LabelSmall
|
|
135
|
+
style={light && styles.subtitle}
|
|
136
|
+
testId={testId && `${testId}-subtitle`}
|
|
137
|
+
>
|
|
138
|
+
{subtitle}
|
|
139
|
+
</LabelSmall>
|
|
140
|
+
)}
|
|
141
|
+
</View>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
179
144
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Media query for small screens.
|
|
147
|
+
* TODO(WB-1655): Change this to use the theme instead (inside themedStylesFn).
|
|
148
|
+
* e.g. `[theme.breakpoints.small]: {...}`
|
|
149
|
+
*/
|
|
150
|
+
const small = "@media (max-width: 767px)";
|
|
151
|
+
|
|
152
|
+
const themedStylesFn: ThemedStylesFn<ModalDialogThemeContract> = (theme) => ({
|
|
153
|
+
header: {
|
|
154
|
+
boxShadow: `0px 1px 0px ${theme.color.shadow.default}`,
|
|
155
|
+
display: "flex",
|
|
156
|
+
flexDirection: "column",
|
|
157
|
+
minHeight: 66,
|
|
158
|
+
padding: `${theme.spacing.header.medium}px ${theme.spacing.header.large}px`,
|
|
159
|
+
position: "relative",
|
|
160
|
+
width: "100%",
|
|
161
|
+
|
|
162
|
+
[small]: {
|
|
163
|
+
paddingLeft: theme.spacing.header.small,
|
|
164
|
+
paddingRight: theme.spacing.header.small,
|
|
183
165
|
},
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
dark: {
|
|
169
|
+
background: theme.color.bg.inverse,
|
|
170
|
+
color: theme.color.text.inverse,
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
breadcrumbs: {
|
|
174
|
+
color: theme.color.text.secondary,
|
|
175
|
+
marginBottom: theme.spacing.header.xsmall,
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
title: {
|
|
179
|
+
// Prevent title from overlapping the close button
|
|
180
|
+
paddingRight: theme.spacing.header.small,
|
|
181
|
+
[small]: {
|
|
182
|
+
paddingRight: theme.spacing.header.large,
|
|
188
183
|
},
|
|
189
|
-
}
|
|
184
|
+
},
|
|
190
185
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
186
|
+
subtitle: {
|
|
187
|
+
color: theme.color.text.secondary,
|
|
188
|
+
marginTop: theme.spacing.header.xsmall,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
196
191
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}),
|
|
201
|
-
} as const;
|
|
192
|
+
ModalHeader.defaultProps = {
|
|
193
|
+
light: true,
|
|
194
|
+
};
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
3
|
-
import Color from "@khanacademy/wonder-blocks-color";
|
|
4
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
5
|
-
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
2
|
+
import {PropsFor, View} from "@khanacademy/wonder-blocks-core";
|
|
6
3
|
import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
7
4
|
|
|
5
|
+
import {
|
|
6
|
+
ThemedStylesFn,
|
|
7
|
+
useScopedTheme,
|
|
8
|
+
useStyles,
|
|
9
|
+
} from "@khanacademy/wonder-blocks-theming";
|
|
8
10
|
import ModalContent from "./modal-content";
|
|
9
11
|
import ModalHeader from "./modal-header";
|
|
10
12
|
import ModalFooter from "./modal-footer";
|
|
11
13
|
import CloseButton from "./close-button";
|
|
14
|
+
import {
|
|
15
|
+
ModalDialogThemeContext,
|
|
16
|
+
ModalDialogThemeContract,
|
|
17
|
+
} from "../themes/themed-modal-dialog";
|
|
12
18
|
|
|
13
19
|
type Props = {
|
|
14
20
|
/**
|
|
@@ -16,20 +22,16 @@ type Props = {
|
|
|
16
22
|
* are positioned around it.
|
|
17
23
|
*/
|
|
18
24
|
content:
|
|
19
|
-
| React.ReactElement<
|
|
25
|
+
| React.ReactElement<PropsFor<typeof ModalContent>>
|
|
20
26
|
| React.ReactNode;
|
|
21
27
|
/**
|
|
22
28
|
* The modal header to show at the top of the panel.
|
|
23
29
|
*/
|
|
24
|
-
header?:
|
|
25
|
-
| React.ReactElement<React.ComponentProps<typeof ModalHeader>>
|
|
26
|
-
| React.ReactNode;
|
|
30
|
+
header?: React.ReactElement<PropsFor<typeof ModalHeader>> | React.ReactNode;
|
|
27
31
|
/**
|
|
28
32
|
* A footer to show beneath the contents.
|
|
29
33
|
*/
|
|
30
|
-
footer?:
|
|
31
|
-
| React.ReactElement<React.ComponentProps<typeof ModalFooter>>
|
|
32
|
-
| React.ReactNode;
|
|
34
|
+
footer?: React.ReactElement<PropsFor<typeof ModalFooter>> | React.ReactNode;
|
|
33
35
|
/**
|
|
34
36
|
* When true, the close button is shown; otherwise, the close button is not shown.
|
|
35
37
|
*/
|
|
@@ -65,14 +67,8 @@ type Props = {
|
|
|
65
67
|
testId?: string;
|
|
66
68
|
};
|
|
67
69
|
|
|
68
|
-
type DefaultProps = {
|
|
69
|
-
closeButtonVisible: Props["closeButtonVisible"];
|
|
70
|
-
scrollOverflow: Props["scrollOverflow"];
|
|
71
|
-
light: Props["light"];
|
|
72
|
-
};
|
|
73
|
-
|
|
74
70
|
/**
|
|
75
|
-
* ModalPanel is
|
|
71
|
+
* ModalPanel is the content container.
|
|
76
72
|
*
|
|
77
73
|
* **Implementation notes:**
|
|
78
74
|
*
|
|
@@ -91,20 +87,23 @@ type DefaultProps = {
|
|
|
91
87
|
* </ModalDialog>
|
|
92
88
|
* ```
|
|
93
89
|
*/
|
|
94
|
-
export default
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
90
|
+
export default function ModalPanel({
|
|
91
|
+
closeButtonVisible = true,
|
|
92
|
+
scrollOverflow = true,
|
|
93
|
+
light = true,
|
|
94
|
+
content,
|
|
95
|
+
footer,
|
|
96
|
+
header,
|
|
97
|
+
onClose,
|
|
98
|
+
style,
|
|
99
|
+
testId,
|
|
100
|
+
}: Props) {
|
|
101
|
+
const {theme} = useScopedTheme(ModalDialogThemeContext);
|
|
102
|
+
const styles = useStyles(themedStylesFn, theme);
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
>)
|
|
104
|
+
const renderMainContent = React.useCallback((): React.ReactNode => {
|
|
105
|
+
const mainContent = ModalContent.isComponentOf(content) ? (
|
|
106
|
+
(content as React.ReactElement<PropsFor<typeof ModalContent>>)
|
|
108
107
|
) : (
|
|
109
108
|
<ModalContent>{content}</ModalContent>
|
|
110
109
|
);
|
|
@@ -122,47 +121,41 @@ export default class ModalPanel extends React.Component<Props> {
|
|
|
122
121
|
// know about things being positioned around it.
|
|
123
122
|
style: [!!footer && styles.hasFooter, mainContent.props.style],
|
|
124
123
|
});
|
|
125
|
-
}
|
|
124
|
+
}, [content, footer, scrollOverflow, styles.hasFooter]);
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
const {
|
|
129
|
-
closeButtonVisible,
|
|
130
|
-
footer,
|
|
131
|
-
header,
|
|
132
|
-
light,
|
|
133
|
-
onClose,
|
|
134
|
-
style,
|
|
135
|
-
testId,
|
|
136
|
-
} = this.props;
|
|
126
|
+
const mainContent = renderMainContent();
|
|
137
127
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
</View>
|
|
161
|
-
);
|
|
162
|
-
}
|
|
128
|
+
return (
|
|
129
|
+
<View
|
|
130
|
+
style={[styles.wrapper, !light && styles.dark, style]}
|
|
131
|
+
testId={testId && `${testId}-panel`}
|
|
132
|
+
>
|
|
133
|
+
{closeButtonVisible && (
|
|
134
|
+
<CloseButton
|
|
135
|
+
light={!light}
|
|
136
|
+
onClick={onClose}
|
|
137
|
+
style={styles.closeButton}
|
|
138
|
+
testId={testId && `${testId}-close`}
|
|
139
|
+
/>
|
|
140
|
+
)}
|
|
141
|
+
{header}
|
|
142
|
+
{mainContent}
|
|
143
|
+
{!footer || ModalFooter.isComponentOf(footer) ? (
|
|
144
|
+
footer
|
|
145
|
+
) : (
|
|
146
|
+
<ModalFooter>{footer}</ModalFooter>
|
|
147
|
+
)}
|
|
148
|
+
</View>
|
|
149
|
+
);
|
|
163
150
|
}
|
|
164
151
|
|
|
165
|
-
|
|
152
|
+
ModalPanel.defaultProps = {
|
|
153
|
+
closeButtonVisible: true,
|
|
154
|
+
scrollOverflow: true,
|
|
155
|
+
light: true,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const themedStylesFn: ThemedStylesFn<ModalDialogThemeContract> = (theme) => ({
|
|
166
159
|
wrapper: {
|
|
167
160
|
flex: "1 1 auto",
|
|
168
161
|
position: "relative",
|
|
@@ -177,19 +170,19 @@ const styles = StyleSheet.create({
|
|
|
177
170
|
|
|
178
171
|
closeButton: {
|
|
179
172
|
position: "absolute",
|
|
180
|
-
right:
|
|
181
|
-
top:
|
|
173
|
+
right: theme.spacing.panel.closeButton,
|
|
174
|
+
top: theme.spacing.panel.closeButton,
|
|
182
175
|
// This is to allow the button to be tab-ordered before the modal
|
|
183
176
|
// content but still be above the header and content.
|
|
184
177
|
zIndex: 1,
|
|
185
178
|
},
|
|
186
179
|
|
|
187
180
|
dark: {
|
|
188
|
-
background:
|
|
189
|
-
color:
|
|
181
|
+
background: theme.color.bg.inverse,
|
|
182
|
+
color: theme.color.text.inverse,
|
|
190
183
|
},
|
|
191
184
|
|
|
192
185
|
hasFooter: {
|
|
193
|
-
paddingBottom:
|
|
186
|
+
paddingBottom: theme.spacing.panel.footer,
|
|
194
187
|
},
|
|
195
188
|
});
|
package/src/themes/default.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import {tokens} from "@khanacademy/wonder-blocks-theming";
|
|
2
2
|
|
|
3
3
|
const theme = {
|
|
4
|
+
color: {
|
|
5
|
+
bg: {
|
|
6
|
+
inverse: tokens.color.darkBlue,
|
|
7
|
+
},
|
|
8
|
+
text: {
|
|
9
|
+
inverse: tokens.color.white,
|
|
10
|
+
secondary: tokens.color.offBlack64,
|
|
11
|
+
},
|
|
12
|
+
shadow: {
|
|
13
|
+
default: tokens.color.offBlack16,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
4
16
|
border: {
|
|
5
17
|
radius: tokens.border.radius.medium_4,
|
|
6
18
|
},
|
|
@@ -8,6 +20,16 @@ const theme = {
|
|
|
8
20
|
dialog: {
|
|
9
21
|
small: tokens.spacing.medium_16,
|
|
10
22
|
},
|
|
23
|
+
panel: {
|
|
24
|
+
closeButton: tokens.spacing.medium_16,
|
|
25
|
+
footer: tokens.spacing.xLarge_32,
|
|
26
|
+
},
|
|
27
|
+
header: {
|
|
28
|
+
xsmall: tokens.spacing.xSmall_8,
|
|
29
|
+
small: tokens.spacing.medium_16,
|
|
30
|
+
medium: tokens.spacing.large_24,
|
|
31
|
+
large: tokens.spacing.xLarge_32,
|
|
32
|
+
},
|
|
11
33
|
},
|
|
12
34
|
};
|
|
13
35
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {mergeTheme, tokens} from "@khanacademy/wonder-blocks-theming";
|
|
2
|
+
import defaultTheme from "./default";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The overrides for the Khanmigo theme.
|
|
6
|
+
*/
|
|
7
|
+
const theme = mergeTheme(defaultTheme, {
|
|
8
|
+
color: {
|
|
9
|
+
bg: {
|
|
10
|
+
inverse: tokens.color.eggplant,
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export default theme;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "@khanacademy/wonder-blocks-theming";
|
|
7
7
|
|
|
8
8
|
import defaultTheme from "./default";
|
|
9
|
+
import khanmigoTheme from "./khanmigo";
|
|
9
10
|
|
|
10
11
|
type Props = {
|
|
11
12
|
children: React.ReactNode;
|
|
@@ -18,7 +19,7 @@ export type ModalDialogThemeContract = typeof defaultTheme;
|
|
|
18
19
|
*/
|
|
19
20
|
const themes: Themes<ModalDialogThemeContract> = {
|
|
20
21
|
default: defaultTheme,
|
|
21
|
-
|
|
22
|
+
khanmigo: khanmigoTheme,
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
/**
|