@khanacademy/wonder-blocks-modal 2.3.4 → 2.3.6

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.
@@ -0,0 +1,308 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {StyleSheet} from "aphrodite";
4
+
5
+ import Button from "@khanacademy/wonder-blocks-button";
6
+ import Color from "@khanacademy/wonder-blocks-color";
7
+ import {View} from "@khanacademy/wonder-blocks-core";
8
+ import {Strut} from "@khanacademy/wonder-blocks-layout";
9
+ import {
10
+ ModalLauncher,
11
+ ModalDialog,
12
+ ModalPanel,
13
+ } from "@khanacademy/wonder-blocks-modal";
14
+ import Spacing from "@khanacademy/wonder-blocks-spacing";
15
+ import {Body, Title} from "@khanacademy/wonder-blocks-typography";
16
+
17
+ import type {StoryComponentType} from "@storybook/react";
18
+
19
+ import ComponentInfo from "../../../../../.storybook/components/component-info.js";
20
+ import {name, version} from "../../../package.json";
21
+
22
+ const customViewports = {
23
+ phone: {
24
+ name: "phone",
25
+ styles: {
26
+ width: "320px",
27
+ height: "568px",
28
+ },
29
+ },
30
+ tablet: {
31
+ name: "tablet",
32
+ styles: {
33
+ width: "640px",
34
+ height: "960px",
35
+ },
36
+ },
37
+ desktop: {
38
+ name: "desktop",
39
+ styles: {
40
+ width: "1024px",
41
+ height: "768px",
42
+ },
43
+ },
44
+ };
45
+
46
+ export default {
47
+ title: "Modal/Building Blocks/ModalDialog",
48
+ component: ModalDialog,
49
+ decorators: [
50
+ (Story: any): React.Element<typeof View> => (
51
+ <View style={styles.example}>
52
+ <Story />
53
+ </View>
54
+ ),
55
+ ],
56
+ parameters: {
57
+ componentSubtitle: ((
58
+ <ComponentInfo name={name} version={version} />
59
+ ): any),
60
+ docs: {
61
+ description: {
62
+ component: null,
63
+ },
64
+ source: {
65
+ // See https://github.com/storybookjs/storybook/issues/12596
66
+ excludeDecorators: true,
67
+ },
68
+ },
69
+ viewport: {
70
+ viewports: customViewports,
71
+ defaultViewport: "desktop",
72
+ },
73
+ chromatic: {
74
+ viewports: [320, 640, 1024],
75
+ },
76
+ },
77
+ // Make the following props null in the control panel
78
+ // because setting an object for a React node is
79
+ // not practical to do manually.
80
+ argTypes: {
81
+ children: {
82
+ control: {type: null},
83
+ },
84
+ above: {
85
+ control: {type: null},
86
+ },
87
+ below: {
88
+ control: {type: null},
89
+ },
90
+ },
91
+ };
92
+
93
+ export const Default: StoryComponentType = (args) => (
94
+ <View style={styles.previewSizer}>
95
+ <View style={styles.modalPositioner}>
96
+ <ModalDialog aria-labelledby="modal-title-0" {...args}>
97
+ <ModalPanel
98
+ content={
99
+ <>
100
+ <Title id="modal-title-0">Modal Title</Title>
101
+ <Strut size={Spacing.large_24} />
102
+ <Body>Here is some text in the modal.</Body>
103
+ </>
104
+ }
105
+ />
106
+ </ModalDialog>
107
+ </View>
108
+ </View>
109
+ );
110
+
111
+ Default.args = {
112
+ style: {
113
+ maxWidth: 500,
114
+ maxHeight: 500,
115
+ },
116
+ };
117
+
118
+ export const Simple: StoryComponentType = () => (
119
+ <View style={styles.previewSizer}>
120
+ <View style={styles.modalPositioner}>
121
+ <ModalDialog
122
+ aria-labelledby="modal-title-1"
123
+ style={styles.squareDialog}
124
+ >
125
+ <ModalPanel
126
+ content={
127
+ <>
128
+ <Title id="modal-title-1">Modal Title</Title>
129
+ <Strut size={Spacing.large_24} />
130
+ <Body>Here is some text in the modal.</Body>
131
+ </>
132
+ }
133
+ />
134
+ </ModalDialog>
135
+ </View>
136
+ </View>
137
+ );
138
+
139
+ Simple.parameters = {
140
+ docs: {
141
+ storyDescription: `This is a basic \`<ModalDialog>\` that wraps a
142
+ \`<ModalPanel>\` element. The \`<ModalDialog>\` is just a a wrapper
143
+ for the visual components of the overall modal. It sets
144
+ the modal's role to \`"dialog"\`. If it did not have another
145
+ element as a child here (a \`<ModalPanel>\` in this case),
146
+ nothing would be visible. If the \`<ModalDialog>\` were not given
147
+ a \`maxHeight\` or \`maxWidth\` style, it would take up the
148
+ entire viewport. To demonstrate the difference between
149
+ the \`<ModalDialog>\` and the \`<ModalPanel>\` elements, the panel
150
+ has been given a smaller height and width than \`<ModalDialog>\`,
151
+ and \`<ModalDialog>\` has been given a dark blue background.`,
152
+ },
153
+ };
154
+
155
+ export const WithAboveAndBelow: StoryComponentType = () => {
156
+ const aboveStyle = {
157
+ background: "url(./modal-above.png)",
158
+ width: 874,
159
+ height: 551,
160
+ position: "absolute",
161
+ top: 40,
162
+ left: -140,
163
+ };
164
+
165
+ const belowStyle = {
166
+ background: "url(./modal-below.png)",
167
+ width: 868,
168
+ height: 521,
169
+ position: "absolute",
170
+ top: -100,
171
+ left: -300,
172
+ };
173
+
174
+ return (
175
+ <View style={styles.previewSizer}>
176
+ <View style={styles.modalPositioner}>
177
+ <ModalDialog
178
+ aria-labelledby="modal-title-2"
179
+ style={styles.squareDialog}
180
+ above={<View style={aboveStyle} />}
181
+ below={<View style={belowStyle} />}
182
+ >
183
+ <ModalPanel
184
+ content={
185
+ <>
186
+ <Title id="modal-title-2">Modal Title</Title>
187
+ <Strut size={Spacing.large_24} />
188
+ <Body>Here is some text in the modal.</Body>
189
+ </>
190
+ }
191
+ />
192
+ </ModalDialog>
193
+ </View>
194
+ </View>
195
+ );
196
+ };
197
+
198
+ WithAboveAndBelow.parameters = {
199
+ docs: {
200
+ storyDescription: `The \`above\` and \`below\` props work the same
201
+ for \`<ModalDialog>\` as they do for \`<OnePaneDialog>\`.
202
+ The element passed into the \`above\` prop is rendered in front
203
+ of the modal. The element passed into the \`below\` prop is
204
+ rendered behind the modal. In this example, a \`<View>\` element
205
+ with a background image of a person and an orange blob is passed
206
+ into the \`below\` prop. A \`<View>\` element with a background
207
+ image of an arc and a blue semicircle is passed into the \`above\`
208
+ prop. This results in the person's head and the orange blob
209
+ peeking out from behind the modal, and the arc and semicircle
210
+ going over the front of the modal.`,
211
+ },
212
+ };
213
+
214
+ export const WithLauncher: StoryComponentType = () => {
215
+ type MyModalProps = {|
216
+ closeModal: () => void,
217
+ |};
218
+
219
+ const MyModal = ({
220
+ closeModal,
221
+ }: MyModalProps): React.Element<typeof ModalDialog> => (
222
+ <ModalDialog
223
+ aria-labelledby="modal-title-3"
224
+ style={styles.squareDialog}
225
+ >
226
+ <ModalPanel
227
+ content={
228
+ <>
229
+ <Title id="modal-title-3">Modal Title</Title>
230
+ <Strut size={Spacing.large_24} />
231
+ <Body>Here is some text in the modal.</Body>
232
+ </>
233
+ }
234
+ />
235
+ </ModalDialog>
236
+ );
237
+
238
+ return (
239
+ <ModalLauncher modal={MyModal}>
240
+ {({openModal}) => (
241
+ <Button onClick={openModal}>Click me to open the modal</Button>
242
+ )}
243
+ </ModalLauncher>
244
+ );
245
+ };
246
+
247
+ WithLauncher.parameters = {
248
+ chromatic: {
249
+ // Don't take screenshots of this story since it would only show a
250
+ // button and not the actual modal.
251
+ disableSnapshot: true,
252
+ },
253
+ docs: {
254
+ storyDescription: `A modal can be launched using a launcher. Here,
255
+ the launcher is a \`<Button>\` element whose \`onClick\` function
256
+ opens the modal. The modal passed into the \`modal\` prop of
257
+ the \`<ModalLauncher>\` element is a \`<ModalDialog>\` element.
258
+ To turn an element into a launcher, wrap the element in a
259
+ \`<ModalLauncher>\` element.`,
260
+ },
261
+ };
262
+
263
+ const styles = StyleSheet.create({
264
+ example: {
265
+ alignItems: "center",
266
+ justifyContent: "center",
267
+ },
268
+ modalPositioner: {
269
+ // Checkerboard background
270
+ backgroundImage:
271
+ "linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%)",
272
+ backgroundSize: "20px 20px",
273
+ backgroundPosition: "0 0, 0 10px, 10px -10px, -10px 0px",
274
+
275
+ flexDirection: "row",
276
+ alignItems: "center",
277
+ justifyContent: "center",
278
+
279
+ position: "absolute",
280
+ left: 0,
281
+ right: 0,
282
+ top: 0,
283
+ bottom: 0,
284
+ },
285
+ previewSizer: {
286
+ minHeight: 600,
287
+ width: "100%",
288
+ },
289
+ row: {
290
+ flexDirection: "row",
291
+ justifyContent: "flex-end",
292
+ },
293
+ footer: {
294
+ alignItems: "center",
295
+ flexDirection: "row",
296
+ justifyContent: "space-between",
297
+ width: "100%",
298
+ },
299
+ squareDialog: {
300
+ maxHeight: 500,
301
+ maxWidth: 500,
302
+ backgroundColor: Color.darkBlue,
303
+ },
304
+ smallSquarePanel: {
305
+ maxHeight: 400,
306
+ maxWidth: 400,
307
+ },
308
+ });
@@ -0,0 +1,337 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {StyleSheet} from "aphrodite";
4
+
5
+ import Button from "@khanacademy/wonder-blocks-button";
6
+ import {View} from "@khanacademy/wonder-blocks-core";
7
+ import {Strut} from "@khanacademy/wonder-blocks-layout";
8
+ import {
9
+ ModalDialog,
10
+ ModalPanel,
11
+ ModalFooter,
12
+ } from "@khanacademy/wonder-blocks-modal";
13
+ import Spacing from "@khanacademy/wonder-blocks-spacing";
14
+ import {Body, LabelLarge, Title} from "@khanacademy/wonder-blocks-typography";
15
+
16
+ import type {StoryComponentType} from "@storybook/react";
17
+
18
+ import ComponentInfo from "../../../../../.storybook/components/component-info.js";
19
+ import {name, version} from "../../../package.json";
20
+
21
+ const customViewports = {
22
+ phone: {
23
+ name: "phone",
24
+ styles: {
25
+ width: "320px",
26
+ height: "568px",
27
+ },
28
+ },
29
+ tablet: {
30
+ name: "tablet",
31
+ styles: {
32
+ width: "640px",
33
+ height: "960px",
34
+ },
35
+ },
36
+ desktop: {
37
+ name: "desktop",
38
+ styles: {
39
+ width: "1024px",
40
+ height: "768px",
41
+ },
42
+ },
43
+ };
44
+
45
+ const longBody = (
46
+ <>
47
+ <Body>
48
+ {`Let's make this body content long in order
49
+ to test scroll overflow.`}
50
+ </Body>
51
+ <br />
52
+ <Body>
53
+ {`Lorem ipsum dolor sit amet, consectetur
54
+ adipiscing elit, sed do eiusmod tempor incididunt
55
+ ut labore et dolore magna aliqua. Ut enim ad minim
56
+ veniam, quis nostrud exercitation ullamco laboris
57
+ nisi ut aliquip ex ea commodo consequat. Duis aute
58
+ irure dolor in reprehenderit in voluptate velit
59
+ esse cillum dolore eu fugiat nulla pariatur.
60
+ Excepteur sint occaecat cupidatat non proident,
61
+ sunt in culpa qui officia deserunt mollit anim id
62
+ est.`}
63
+ </Body>
64
+ <br />
65
+ <Body>
66
+ {`Lorem ipsum dolor sit amet, consectetur
67
+ adipiscing elit, sed do eiusmod tempor incididunt
68
+ ut labore et dolore magna aliqua. Ut enim ad minim
69
+ veniam, quis nostrud exercitation ullamco laboris
70
+ nisi ut aliquip ex ea commodo consequat. Duis aute
71
+ irure dolor in reprehenderit in voluptate velit
72
+ esse cillum dolore eu fugiat nulla pariatur.
73
+ Excepteur sint occaecat cupidatat non proident,
74
+ sunt in culpa qui officia deserunt mollit anim id
75
+ est.`}
76
+ </Body>
77
+ <br />
78
+ <Body>
79
+ {`Lorem ipsum dolor sit amet, consectetur
80
+ adipiscing elit, sed do eiusmod tempor incididunt
81
+ ut labore et dolore magna aliqua. Ut enim ad minim
82
+ veniam, quis nostrud exercitation ullamco laboris
83
+ nisi ut aliquip ex ea commodo consequat. Duis aute
84
+ irure dolor in reprehenderit in voluptate velit
85
+ esse cillum dolore eu fugiat nulla pariatur.
86
+ Excepteur sint occaecat cupidatat non proident,
87
+ sunt in culpa qui officia deserunt mollit anim id
88
+ est.`}
89
+ </Body>
90
+ </>
91
+ );
92
+
93
+ export default {
94
+ title: "Modal/Building Blocks/ModalFooter",
95
+ component: ModalFooter,
96
+ decorators: [
97
+ (Story: StoryComponentType): React.Element<typeof View> => (
98
+ <View style={styles.previewSizer}>
99
+ <View style={styles.modalPositioner}>
100
+ <Story />
101
+ </View>
102
+ </View>
103
+ ),
104
+ ],
105
+ parameters: {
106
+ componentSubtitle: ((
107
+ <ComponentInfo name={name} version={version} />
108
+ ): any),
109
+ docs: {
110
+ description: {
111
+ component: null,
112
+ },
113
+ source: {
114
+ // See https://github.com/storybookjs/storybook/issues/12596
115
+ excludeDecorators: true,
116
+ },
117
+ },
118
+ viewport: {
119
+ viewports: customViewports,
120
+ defaultViewport: "desktop",
121
+ },
122
+ chromatic: {
123
+ viewports: [320, 640, 1024],
124
+ },
125
+ },
126
+ argTypes: {
127
+ children: {
128
+ control: {type: null},
129
+ },
130
+ },
131
+ };
132
+
133
+ export const Default: StoryComponentType = (args) => (
134
+ <ModalDialog aria-labelledby={"modal-id-0"} style={styles.dialog}>
135
+ <ModalPanel
136
+ content={
137
+ <>
138
+ <Title id="modal-id-0">Modal Title</Title>
139
+ <Strut size={Spacing.large_24} />
140
+ {longBody}
141
+ </>
142
+ }
143
+ footer={<ModalFooter {...args} />}
144
+ />
145
+ </ModalDialog>
146
+ );
147
+
148
+ export const Simple: StoryComponentType = () => (
149
+ <ModalDialog aria-labelledby={"modal-id-1"} style={styles.dialog}>
150
+ <ModalPanel
151
+ content={
152
+ <>
153
+ <Title id="modal-id-1">Modal Title</Title>
154
+ <Strut size={Spacing.large_24} />
155
+ {longBody}
156
+ </>
157
+ }
158
+ footer={
159
+ <ModalFooter>
160
+ <View />
161
+ </ModalFooter>
162
+ }
163
+ />
164
+ </ModalDialog>
165
+ );
166
+
167
+ Simple.parameters = {
168
+ docs: {
169
+ storyDescription: `This is a basic footer. It contains an empty
170
+ \`<View>\`, so it is completely blank.`,
171
+ },
172
+ };
173
+
174
+ export const WithButton: StoryComponentType = () => (
175
+ <ModalDialog aria-labelledby={"modal-id-2"} style={styles.dialog}>
176
+ <ModalPanel
177
+ content={
178
+ <>
179
+ <Title id="modal-id-2">Modal Title</Title>
180
+ <Strut size={Spacing.large_24} />
181
+ {longBody}
182
+ </>
183
+ }
184
+ footer={
185
+ <ModalFooter>
186
+ <Button onClick={() => {}}>Submit</Button>
187
+ </ModalFooter>
188
+ }
189
+ />
190
+ </ModalDialog>
191
+ );
192
+
193
+ WithButton.parameters = {
194
+ docs: {
195
+ storyDescription: `This is a \`<ModalFooter>\` with a \`<Button>\`
196
+ as a child. No additional styling is needed, as the footer
197
+ already has the style \`{justifyContent: "flex-end"}\`.`,
198
+ },
199
+ };
200
+
201
+ export const WithThreeActions: StoryComponentType = () => {
202
+ const mobile = "@media (max-width: 1023px)";
203
+ const desktop = "@media (min-width: 1024px)";
204
+
205
+ const buttonStyle = {
206
+ [desktop]: {
207
+ marginRight: Spacing.medium_16,
208
+ },
209
+ [mobile]: {
210
+ marginBottom: Spacing.medium_16,
211
+ },
212
+ };
213
+
214
+ const containerStyle = {
215
+ [desktop]: {
216
+ flexDirection: "row",
217
+ justifyContent: "flex-end",
218
+ },
219
+ [mobile]: {
220
+ flexDirection: "column-reverse",
221
+ width: "100%",
222
+ },
223
+ };
224
+
225
+ return (
226
+ <ModalDialog aria-labelledby={"modal-id-3"} style={styles.dialog}>
227
+ <ModalPanel
228
+ content={
229
+ <>
230
+ <Title id="modal-id-3">Modal Title</Title>
231
+ <Strut size={Spacing.large_24} />
232
+ {longBody}
233
+ </>
234
+ }
235
+ footer={
236
+ <ModalFooter>
237
+ <View style={containerStyle}>
238
+ <Button style={buttonStyle} kind="tertiary">
239
+ Tertiary action
240
+ </Button>
241
+ <Button style={buttonStyle} kind="tertiary">
242
+ Secondary action
243
+ </Button>
244
+ <Button style={buttonStyle}>Primary action</Button>
245
+ </View>
246
+ </ModalFooter>
247
+ }
248
+ />
249
+ </ModalDialog>
250
+ );
251
+ };
252
+
253
+ WithThreeActions.parameters = {
254
+ docs: {
255
+ storyDescription: `This is an example of a footer with multiple
256
+ actions. It's fully responsive, so the buttons are in a
257
+ column layout when the window is small.`,
258
+ },
259
+ };
260
+
261
+ export const WithMultipleActions: StoryComponentType = () => {
262
+ const footerStyle = {
263
+ alignItems: "center",
264
+ flexDirection: "row",
265
+ justifyContent: "space-between",
266
+ width: "100%",
267
+ };
268
+
269
+ const rowStyle = {
270
+ flexDirection: "row",
271
+ justifyContent: "flex-end",
272
+ };
273
+
274
+ return (
275
+ <ModalDialog aria-labelledby={"modal-id-4"} style={styles.dialog}>
276
+ <ModalPanel
277
+ content={
278
+ <>
279
+ <Title id="modal-id-4">Modal Title</Title>
280
+ <Strut size={Spacing.large_24} />
281
+ {longBody}
282
+ </>
283
+ }
284
+ footer={
285
+ <ModalFooter>
286
+ <View style={footerStyle}>
287
+ <LabelLarge>Step 1 of 4</LabelLarge>
288
+ <View style={rowStyle}>
289
+ <Button kind="tertiary">Previous</Button>
290
+ <Strut size={16} />
291
+ <Button kind="primary">Next</Button>
292
+ </View>
293
+ </View>
294
+ </ModalFooter>
295
+ }
296
+ />
297
+ </ModalDialog>
298
+ );
299
+ };
300
+
301
+ WithMultipleActions.parameters = {
302
+ docs: {
303
+ storyDescription: `This is an example of a footer that indicates
304
+ multiple steps in a flow.`,
305
+ },
306
+ };
307
+
308
+ const styles = StyleSheet.create({
309
+ dialog: {
310
+ maxWidth: 600,
311
+ maxHeight: 500,
312
+ },
313
+ modalPositioner: {
314
+ // Checkerboard background
315
+ backgroundImage:
316
+ "linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%)",
317
+ backgroundSize: "20px 20px",
318
+ backgroundPosition: "0 0, 0 10px, 10px -10px, -10px 0px",
319
+
320
+ flexDirection: "row",
321
+ alignItems: "center",
322
+ justifyContent: "center",
323
+
324
+ position: "absolute",
325
+ left: 0,
326
+ right: 0,
327
+ top: 0,
328
+ bottom: 0,
329
+ },
330
+ previewSizer: {
331
+ height: 600,
332
+ },
333
+ example: {
334
+ alignItems: "center",
335
+ justifyContent: "center",
336
+ },
337
+ });