@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,414 @@
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
+ ModalDialog,
11
+ ModalPanel,
12
+ ModalHeader,
13
+ ModalFooter,
14
+ } from "@khanacademy/wonder-blocks-modal";
15
+ import Spacing from "@khanacademy/wonder-blocks-spacing";
16
+ import {Body, Title} from "@khanacademy/wonder-blocks-typography";
17
+
18
+ import type {StoryComponentType} from "@storybook/react";
19
+
20
+ import ComponentInfo from "../../../../../.storybook/components/component-info.js";
21
+ import {name, version} from "../../../package.json";
22
+
23
+ const customViewports = {
24
+ phone: {
25
+ name: "phone",
26
+ styles: {
27
+ width: "320px",
28
+ height: "568px",
29
+ },
30
+ },
31
+ tablet: {
32
+ name: "tablet",
33
+ styles: {
34
+ width: "640px",
35
+ height: "960px",
36
+ },
37
+ },
38
+ desktop: {
39
+ name: "desktop",
40
+ styles: {
41
+ width: "1024px",
42
+ height: "768px",
43
+ },
44
+ },
45
+ };
46
+
47
+ const longBody = (
48
+ <>
49
+ <Body>
50
+ {`Let's make this body content long in order
51
+ to test scroll overflow.`}
52
+ </Body>
53
+ <br />
54
+ <Body>
55
+ {`Lorem ipsum dolor sit amet, consectetur
56
+ adipiscing elit, sed do eiusmod tempor incididunt
57
+ ut labore et dolore magna aliqua. Ut enim ad minim
58
+ veniam, quis nostrud exercitation ullamco laboris
59
+ nisi ut aliquip ex ea commodo consequat. Duis aute
60
+ irure dolor in reprehenderit in voluptate velit
61
+ esse cillum dolore eu fugiat nulla pariatur.
62
+ Excepteur sint occaecat cupidatat non proident,
63
+ sunt in culpa qui officia deserunt mollit anim id
64
+ est.`}
65
+ </Body>
66
+ <br />
67
+ <Body>
68
+ {`Lorem ipsum dolor sit amet, consectetur
69
+ adipiscing elit, sed do eiusmod tempor incididunt
70
+ ut labore et dolore magna aliqua. Ut enim ad minim
71
+ veniam, quis nostrud exercitation ullamco laboris
72
+ nisi ut aliquip ex ea commodo consequat. Duis aute
73
+ irure dolor in reprehenderit in voluptate velit
74
+ esse cillum dolore eu fugiat nulla pariatur.
75
+ Excepteur sint occaecat cupidatat non proident,
76
+ sunt in culpa qui officia deserunt mollit anim id
77
+ est.`}
78
+ </Body>
79
+ <br />
80
+ <Body>
81
+ {`Lorem ipsum dolor sit amet, consectetur
82
+ adipiscing elit, sed do eiusmod tempor incididunt
83
+ ut labore et dolore magna aliqua. Ut enim ad minim
84
+ veniam, quis nostrud exercitation ullamco laboris
85
+ nisi ut aliquip ex ea commodo consequat. Duis aute
86
+ irure dolor in reprehenderit in voluptate velit
87
+ esse cillum dolore eu fugiat nulla pariatur.
88
+ Excepteur sint occaecat cupidatat non proident,
89
+ sunt in culpa qui officia deserunt mollit anim id
90
+ est.`}
91
+ </Body>
92
+ </>
93
+ );
94
+
95
+ export default {
96
+ title: "Modal/Building Blocks/ModalPanel",
97
+ component: ModalPanel,
98
+ decorators: [
99
+ (Story: StoryComponentType): React.Element<typeof View> => (
100
+ <View style={styles.previewSizer}>
101
+ <View style={styles.modalPositioner}>
102
+ <Story />
103
+ </View>
104
+ </View>
105
+ ),
106
+ ],
107
+ parameters: {
108
+ componentSubtitle: ((
109
+ <ComponentInfo name={name} version={version} />
110
+ ): any),
111
+ docs: {
112
+ description: {
113
+ component: null,
114
+ },
115
+ source: {
116
+ // See https://github.com/storybookjs/storybook/issues/12596
117
+ excludeDecorators: true,
118
+ },
119
+ },
120
+ viewport: {
121
+ viewports: customViewports,
122
+ defaultViewport: "desktop",
123
+ },
124
+ chromatic: {
125
+ viewports: [320, 640, 1024],
126
+ },
127
+ },
128
+ // Make the following props null in the control panel
129
+ // because setting an object for a React node is
130
+ // not practical to do manually, and adding a React Element
131
+ // as one of the default args just results in an incoherent
132
+ // json object in the control panel.
133
+ argTypes: {
134
+ content: {
135
+ control: {type: null},
136
+ },
137
+ header: {
138
+ control: {type: null},
139
+ },
140
+ footer: {
141
+ control: {type: null},
142
+ },
143
+ },
144
+ };
145
+
146
+ export const Default: StoryComponentType = (args) => (
147
+ <ModalDialog aria-labelledby="modal-title-0" style={styles.dialog}>
148
+ <ModalPanel
149
+ content={
150
+ <>
151
+ <Title id="modal-title-0">Modal Title</Title>
152
+ <Strut size={Spacing.large_24} />
153
+ {longBody}
154
+ </>
155
+ }
156
+ {...args}
157
+ />
158
+ </ModalDialog>
159
+ );
160
+
161
+ export const Simple: StoryComponentType = () => (
162
+ <ModalDialog aria-labelledby="modal-title-1" style={styles.dialog}>
163
+ <ModalPanel
164
+ content={
165
+ <>
166
+ <Title id="modal-title-1">Modal Title</Title>
167
+ <Strut size={Spacing.large_24} />
168
+ {longBody}
169
+ </>
170
+ }
171
+ />
172
+ </ModalDialog>
173
+ );
174
+
175
+ Simple.parameters = {
176
+ docs: {
177
+ storyDescription: `This is a basic \`<ModalPanel>\`. It just has a
178
+ \`content\` prop that contains a title and a body.`,
179
+ },
180
+ };
181
+
182
+ export const Dark: StoryComponentType = () => (
183
+ <ModalDialog aria-labelledby="modal-title-a" style={styles.dialog}>
184
+ <ModalPanel
185
+ content={
186
+ <>
187
+ <Title id="modal-title-a">Modal Title</Title>
188
+ <Strut size={Spacing.large_24} />
189
+ {longBody}
190
+ </>
191
+ }
192
+ light={false}
193
+ />
194
+ </ModalDialog>
195
+ );
196
+
197
+ Dark.parameters = {
198
+ docs: {
199
+ storyDescription:
200
+ "This is what a modal panel looks like when its `light` prop is set to false.",
201
+ },
202
+ };
203
+
204
+ export const WithHeader: StoryComponentType = () => (
205
+ <ModalDialog aria-labelledby="modal-title-2" style={styles.dialog}>
206
+ <ModalPanel
207
+ header={<ModalHeader titleId="modal-title-2" title="Modal Title" />}
208
+ content={longBody}
209
+ />
210
+ </ModalDialog>
211
+ );
212
+
213
+ WithHeader.parameters = {
214
+ docs: {
215
+ storyDescription: `This is a \`<ModalPanel>\` with a \`header\`
216
+ prop. Note that the header that renders here as part of the
217
+ \`header\` prop is sticky, so it remains even if you scroll
218
+ down in the modal.`,
219
+ },
220
+ };
221
+
222
+ export const WithFooter: StoryComponentType = () => (
223
+ <ModalDialog aria-labelledby="modal-title-3" style={styles.dialog}>
224
+ <ModalPanel
225
+ content={
226
+ <>
227
+ <Title id="modal-title-3">Modal Title</Title>
228
+ <Strut size={Spacing.large_24} />
229
+ {longBody}
230
+ </>
231
+ }
232
+ footer={
233
+ <ModalFooter>
234
+ <Button onClick={() => {}}>Continue</Button>
235
+ </ModalFooter>
236
+ }
237
+ />
238
+ </ModalDialog>
239
+ );
240
+
241
+ WithFooter.parameters = {
242
+ docs: {
243
+ storyDescription: `A modal panel can have a footer with the \`footer\`
244
+ prop. In this example, the footer just contains a button. Note
245
+ that the footer is sticky.`,
246
+ },
247
+ };
248
+
249
+ export const DarkWithHeaderAndFooter: StoryComponentType = () => (
250
+ <ModalDialog aria-labelledby="modal-title-3" style={styles.dialog}>
251
+ <ModalPanel
252
+ header={<ModalHeader titleId="modal-title-2" title="Modal Title" />}
253
+ content={longBody}
254
+ footer={
255
+ <ModalFooter>
256
+ <Button onClick={() => {}} light={true}>
257
+ Continue
258
+ </Button>
259
+ </ModalFooter>
260
+ }
261
+ light={false}
262
+ />
263
+ </ModalDialog>
264
+ );
265
+
266
+ DarkWithHeaderAndFooter.parameters = {
267
+ docs: {
268
+ storyDescription: `Here is a dark \`<ModalPanel>\` with a header
269
+ and a footer. The \`<Button>\` in the footer must have the
270
+ \`light\` prop set to true in order to be visible on the dark
271
+ background.`,
272
+ },
273
+ };
274
+
275
+ export const TwoPanels: StoryComponentType = () => {
276
+ const mobile = "@media (max-width: 1023px)";
277
+ const desktop = "@media (min-width: 1024px)";
278
+
279
+ const twoPaneDialogStyle = {
280
+ [desktop]: {
281
+ width: "86.72%",
282
+ maxWidth: 888,
283
+ height: "60.42%",
284
+ minHeight: 308,
285
+ },
286
+ [mobile]: {
287
+ width: "100%",
288
+ height: "100%",
289
+ overflow: "hidden",
290
+ },
291
+ };
292
+
293
+ const panelGroupStyle = {
294
+ flex: 1,
295
+
296
+ [desktop]: {
297
+ flexDirection: "row",
298
+ },
299
+ [mobile]: {
300
+ flexDirection: "column",
301
+ },
302
+ };
303
+
304
+ return (
305
+ <ModalDialog style={twoPaneDialogStyle}>
306
+ <View style={panelGroupStyle}>
307
+ <ModalPanel
308
+ content={
309
+ <View>
310
+ <Title>Sidebar</Title>
311
+ <Strut size={Spacing.large_24} />
312
+ <Body>
313
+ Lorem ipsum dolor sit amet, consectetur
314
+ adipiscing elit, sed do eiusmod tempor
315
+ incididunt ut labore et dolore magna aliqua. Ut
316
+ enim ad minim veniam, quis nostrud exercitation
317
+ ullamco laboris.
318
+ </Body>
319
+ </View>
320
+ }
321
+ light={false}
322
+ closeButtonVisible={false}
323
+ />
324
+ <ModalPanel
325
+ content={
326
+ <View>
327
+ <Title>Contents</Title>
328
+ <Strut size={Spacing.large_24} />
329
+ <Body>
330
+ Lorem ipsum dolor sit amet, consectetur
331
+ adipiscing elit, sed do eiusmod tempor
332
+ incididunt ut labore et dolore magna aliqua.
333
+ </Body>
334
+ <Strut size={Spacing.large_24} />
335
+ <Button>Primary action</Button>
336
+ </View>
337
+ }
338
+ closeButtonVisible={false}
339
+ />
340
+ </View>
341
+ </ModalDialog>
342
+ );
343
+ };
344
+
345
+ TwoPanels.parameters = {
346
+ docs: {
347
+ storyDescription: `Here is an example of how you can have a modal
348
+ with two panels. Observe that it is responsive, so it uses a
349
+ row layout with a larger window size and a column layout on
350
+ a smaller window size. The "X" close button has been disabled
351
+ for both panels since the top right spot would change depending
352
+ on which layout is being used.`,
353
+ },
354
+ };
355
+
356
+ export const WithStyle: StoryComponentType = () => {
357
+ const modalStyles = {
358
+ color: Color.blue,
359
+ border: `2px solid ${Color.darkBlue}`,
360
+ borderRadius: 20,
361
+ };
362
+
363
+ return (
364
+ <ModalDialog aria-labelledby="modal-title-1" style={styles.dialog}>
365
+ <ModalPanel
366
+ header={
367
+ <ModalHeader titleId="modal-title-1" title="Modal Title" />
368
+ }
369
+ content={longBody}
370
+ style={modalStyles}
371
+ />
372
+ </ModalDialog>
373
+ );
374
+ };
375
+
376
+ WithStyle.parameters = {
377
+ docs: {
378
+ storyDescription: `A \`<ModalPanel>\` can have custom styles.
379
+ In this example, the styles for the modal panel include blue
380
+ text color, a 2px solid dark blue border, and a border
381
+ radius of 20px.`,
382
+ },
383
+ };
384
+
385
+ const styles = StyleSheet.create({
386
+ dialog: {
387
+ maxWidth: 600,
388
+ maxHeight: 500,
389
+ },
390
+ modalPositioner: {
391
+ // Checkerboard background
392
+ backgroundImage:
393
+ "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%)",
394
+ backgroundSize: "20px 20px",
395
+ backgroundPosition: "0 0, 0 10px, 10px -10px, -10px 0px",
396
+
397
+ flexDirection: "row",
398
+ alignItems: "center",
399
+ justifyContent: "center",
400
+
401
+ position: "absolute",
402
+ left: 0,
403
+ right: 0,
404
+ top: 0,
405
+ bottom: 0,
406
+ },
407
+ previewSizer: {
408
+ height: 600,
409
+ },
410
+ example: {
411
+ alignItems: "center",
412
+ justifyContent: "center",
413
+ },
414
+ });
@@ -0,0 +1,102 @@
1
+ // @flow
2
+
3
+ export default {
4
+ content: {
5
+ control: {type: null},
6
+ description: `The content of the modal, appearing between the
7
+ titlebar and footer.`,
8
+ table: {type: {summary: "React.Node"}},
9
+ type: {required: true},
10
+ },
11
+ title: {
12
+ control: {type: "text"},
13
+ description: "The title of the modal, appearing in the titlebar.",
14
+ table: {type: {summary: "string"}},
15
+ type: {required: true},
16
+ },
17
+ footer: {
18
+ control: {type: null},
19
+ description: `The content of the modal's footer.
20
+ A great place for buttons! Content is right-aligned by default.
21
+ To control alignment yourself, provide a container element
22
+ with 100% width.`,
23
+ table: {type: {summary: "React.Node"}},
24
+ },
25
+ breadcrumbs: {
26
+ control: {type: null},
27
+ description: `Adds a breadcrumb-trail, appearing in the ModalHeader,
28
+ above the title.`,
29
+ table: {type: {summary: "React.Element<Breadcrumbs>"}},
30
+ },
31
+ subtitle: {
32
+ control: {type: "text"},
33
+ description: `The subtitle of the modal, appearing in the titlebar,
34
+ below the title.`,
35
+ table: {type: {summary: "string"}},
36
+ },
37
+ onClose: {
38
+ description: `Called when the button is clicked.
39
+ If you're using \`ModalLauncher\`, you probably shouldn't use this
40
+ prop! Instead, to listen for when the modal closes, add an
41
+ \`onClose\` handler to the \`ModalLauncher\`. Doing so will
42
+ result in a console.warn().`,
43
+ table: {type: {summary: "() => mixed"}},
44
+ },
45
+ closeButtonVisible: {
46
+ control: {type: "boolean"},
47
+ defaultValue: "true",
48
+ description: `When true, the close button is shown; otherwise,
49
+ the close button is not shown.`,
50
+ table: {
51
+ defaultValue: {summary: "true"},
52
+ type: {summary: "boolean"},
53
+ },
54
+ },
55
+ above: {
56
+ control: {type: null},
57
+ description: `When set, provides a component that can render
58
+ content above the top of the modal; when not set, no additional
59
+ content is shown above the modal. This prop is passed down to
60
+ the ModalDialog.`,
61
+ table: {type: {summary: "React.Node"}},
62
+ },
63
+ below: {
64
+ control: {type: null},
65
+ description: `When set, provides a component that will render
66
+ content below the bottom of the modal; when not set, no additional
67
+ content is shown below the modal. This prop is passed down to
68
+ the ModalDialog. NOTE: Devs can customize this content by
69
+ rendering the component assigned to this prop with custom styles,
70
+ such as by wrapping it in a View.`,
71
+ table: {type: {summary: "React.Node"}},
72
+ },
73
+ role: {
74
+ control: {type: "select"},
75
+ defaultValue: "dialog",
76
+ description: `When set, overrides the default role value. Default
77
+ role is "dialog" Roles other than dialog and alertdialog aren't
78
+ appropriate for this component`,
79
+ options: ["dialog", "alertdialog"],
80
+ table: {
81
+ defaultValue: {summary: "dialog"},
82
+ type: {summary: `"dialog" | "alertdialog"`},
83
+ },
84
+ },
85
+ style: {
86
+ control: {type: "object"},
87
+ description: "Optional custom styles.",
88
+ table: {type: {summary: "StyleType"}},
89
+ },
90
+ testId: {
91
+ control: {type: "text"},
92
+ description: `Test ID used for e2e testing.
93
+ This ID will be passed down to the Dialog.`,
94
+ table: {type: {summary: "string"}},
95
+ },
96
+ titleId: {
97
+ control: {type: "text"},
98
+ description: `An optional id parameter for the title. If one is
99
+ not provided, a unique id will be generated.`,
100
+ table: {type: {summary: "string"}},
101
+ },
102
+ };