@khanacademy/wonder-blocks-button 3.0.13 → 3.0.15
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 +46 -0
- package/dist/components/button-core.d.ts +9 -0
- package/dist/components/button-core.js.flow +18 -0
- package/dist/components/button.d.ts +178 -0
- package/dist/components/button.js.flow +203 -0
- package/dist/es/index.js +108 -75
- package/dist/index.d.ts +4 -0
- package/dist/index.js +127 -96
- package/dist/index.js.flow +11 -2
- package/package.json +10 -10
- package/src/__tests__/{custom-snapshot.test.js → custom-snapshot.test.tsx} +13 -14
- package/src/components/__tests__/{button.flowtest.js → button.flowtest.tsx} +1 -6
- package/src/components/__tests__/{button.test.js → button.test.tsx} +17 -19
- package/src/components/{button-core.js → button-core.tsx} +24 -25
- package/src/components/button.tsx +298 -0
- package/src/index.ts +5 -0
- package/tsconfig.json +17 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/src/components/__docs__/accessibility.stories.mdx +0 -92
- package/src/components/__docs__/best-practices.stories.mdx +0 -107
- package/src/components/__docs__/button.argtypes.js +0 -231
- package/src/components/__docs__/button.stories.js +0 -508
- package/src/components/__docs__/navigation-callbacks.stories.mdx +0 -187
- package/src/components/button.js +0 -347
- package/src/index.js +0 -6
- /package/src/__tests__/__snapshots__/{custom-snapshot.test.js.snap → custom-snapshot.test.tsx.snap} +0 -0
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import {StyleSheet} from "aphrodite";
|
|
4
|
-
import {withDesign} from "storybook-addon-designs";
|
|
5
|
-
import {action} from "@storybook/addon-actions";
|
|
6
|
-
|
|
7
|
-
import {MemoryRouter, Route, Switch} from "react-router-dom";
|
|
8
|
-
|
|
9
|
-
import Color from "@khanacademy/wonder-blocks-color";
|
|
10
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
11
|
-
import {icons} from "@khanacademy/wonder-blocks-icon";
|
|
12
|
-
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
13
|
-
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
14
|
-
import {LabelMedium} from "@khanacademy/wonder-blocks-typography";
|
|
15
|
-
import type {StoryComponentType} from "@storybook/react";
|
|
16
|
-
import type {StyleDeclaration} from "aphrodite";
|
|
17
|
-
|
|
18
|
-
import Button from "../button.js";
|
|
19
|
-
|
|
20
|
-
import ComponentInfo from "../../../../../.storybook/components/component-info.js";
|
|
21
|
-
import ButtonArgTypes from "./button.argtypes.js";
|
|
22
|
-
import {name, version} from "../../../package.json";
|
|
23
|
-
|
|
24
|
-
export default {
|
|
25
|
-
title: "Button",
|
|
26
|
-
component: Button,
|
|
27
|
-
parameters: {
|
|
28
|
-
componentSubtitle: ((
|
|
29
|
-
<ComponentInfo name={name} version={version} />
|
|
30
|
-
): any),
|
|
31
|
-
},
|
|
32
|
-
decorators: [withDesign],
|
|
33
|
-
argTypes: ButtonArgTypes,
|
|
34
|
-
excludeStories: ["styles"],
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const Template = (args) => <Button {...args} />;
|
|
38
|
-
|
|
39
|
-
export const Default: StoryComponentType = Template.bind({});
|
|
40
|
-
|
|
41
|
-
Default.args = {
|
|
42
|
-
children: "Hello, world!",
|
|
43
|
-
kind: "primary",
|
|
44
|
-
color: "default",
|
|
45
|
-
size: "medium",
|
|
46
|
-
light: false,
|
|
47
|
-
disabled: false,
|
|
48
|
-
style: {maxWidth: 200},
|
|
49
|
-
onClick: () => {
|
|
50
|
-
// eslint-disable-next-line no-alert
|
|
51
|
-
alert("Click!");
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
Default.parameters = {
|
|
56
|
-
design: {
|
|
57
|
-
type: "figma",
|
|
58
|
-
url: "https://www.figma.com/file/VbVu3h2BpBhH80niq101MHHE/Wonder-Blocks-(Web)?node-id=401%3A307",
|
|
59
|
-
},
|
|
60
|
-
chromatic: {
|
|
61
|
-
// We already have screenshots of other stories that cover more of the button states
|
|
62
|
-
disableSnapshot: true,
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export const styles: StyleDeclaration = StyleSheet.create({
|
|
67
|
-
row: {
|
|
68
|
-
flexDirection: "row",
|
|
69
|
-
alignItems: "center",
|
|
70
|
-
marginBottom: Spacing.xSmall_8,
|
|
71
|
-
},
|
|
72
|
-
button: {
|
|
73
|
-
marginRight: Spacing.xSmall_8,
|
|
74
|
-
},
|
|
75
|
-
fillSpace: {
|
|
76
|
-
minWidth: 140,
|
|
77
|
-
},
|
|
78
|
-
example: {
|
|
79
|
-
background: Color.offWhite,
|
|
80
|
-
padding: Spacing.medium_16,
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
export const Variants: StoryComponentType = () => (
|
|
85
|
-
<View>
|
|
86
|
-
<View style={{flexDirection: "row"}}>
|
|
87
|
-
<Button onClick={() => {}}>Hello, world!</Button>
|
|
88
|
-
<Strut size={16} />
|
|
89
|
-
<Button onClick={() => {}} kind="secondary">
|
|
90
|
-
Hello, world!
|
|
91
|
-
</Button>
|
|
92
|
-
<Strut size={16} />
|
|
93
|
-
<Button onClick={() => {}} kind="tertiary">
|
|
94
|
-
Hello, world!
|
|
95
|
-
</Button>
|
|
96
|
-
</View>
|
|
97
|
-
<Strut size={16} />
|
|
98
|
-
<View style={{flexDirection: "row"}}>
|
|
99
|
-
<Button onClick={() => {}} disabled={true}>
|
|
100
|
-
Hello, world!
|
|
101
|
-
</Button>
|
|
102
|
-
<Strut size={16} />
|
|
103
|
-
<Button onClick={() => {}} disabled={true} kind="secondary">
|
|
104
|
-
Hello, world!
|
|
105
|
-
</Button>
|
|
106
|
-
<Strut size={16} />
|
|
107
|
-
<Button onClick={() => {}} disabled={true} kind="tertiary">
|
|
108
|
-
Hello, world!
|
|
109
|
-
</Button>
|
|
110
|
-
</View>
|
|
111
|
-
<Strut size={16} />
|
|
112
|
-
<View style={{flexDirection: "row"}}>
|
|
113
|
-
<Button onClick={() => {}} color="destructive">
|
|
114
|
-
Hello, world!
|
|
115
|
-
</Button>
|
|
116
|
-
<Strut size={16} />
|
|
117
|
-
<Button onClick={() => {}} kind="secondary" color="destructive">
|
|
118
|
-
Hello, world!
|
|
119
|
-
</Button>
|
|
120
|
-
<Strut size={16} />
|
|
121
|
-
<Button onClick={() => {}} kind="tertiary" color="destructive">
|
|
122
|
-
Hello, world!
|
|
123
|
-
</Button>
|
|
124
|
-
</View>
|
|
125
|
-
</View>
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
Variants.parameters = {
|
|
129
|
-
docs: {
|
|
130
|
-
storyDescription:
|
|
131
|
-
"There are three kinds of buttons: `primary` (default), `secondary`, and `tertiary`.",
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
export const WithColor: StoryComponentType = () => (
|
|
136
|
-
<View style={styles.row}>
|
|
137
|
-
<Button style={styles.button} onClick={() => {}} color="destructive">
|
|
138
|
-
Primary
|
|
139
|
-
</Button>
|
|
140
|
-
<Button
|
|
141
|
-
style={styles.button}
|
|
142
|
-
onClick={() => {}}
|
|
143
|
-
kind="secondary"
|
|
144
|
-
color="destructive"
|
|
145
|
-
>
|
|
146
|
-
Secondary
|
|
147
|
-
</Button>
|
|
148
|
-
<Button
|
|
149
|
-
style={styles.button}
|
|
150
|
-
onClick={() => {}}
|
|
151
|
-
kind="tertiary"
|
|
152
|
-
color="destructive"
|
|
153
|
-
>
|
|
154
|
-
Tertiary
|
|
155
|
-
</Button>
|
|
156
|
-
</View>
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
WithColor.storyName = "Color";
|
|
160
|
-
|
|
161
|
-
WithColor.parameters = {
|
|
162
|
-
docs: {
|
|
163
|
-
storyDescription:
|
|
164
|
-
"Buttons have a `color` that is either `default` (the default, as shown above) or `destructive` (as can seen below):",
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
export const Dark: StoryComponentType = () => (
|
|
169
|
-
<View style={{backgroundColor: Color.darkBlue}}>
|
|
170
|
-
<View style={{flexDirection: "row"}}>
|
|
171
|
-
<Button onClick={() => {}} light={true}>
|
|
172
|
-
Hello, world!
|
|
173
|
-
</Button>
|
|
174
|
-
<Strut size={16} />
|
|
175
|
-
<Button onClick={() => {}} light={true} kind="secondary">
|
|
176
|
-
Hello, world!
|
|
177
|
-
</Button>
|
|
178
|
-
<Strut size={16} />
|
|
179
|
-
<Button onClick={() => {}} light={true} kind="tertiary">
|
|
180
|
-
Hello, world!
|
|
181
|
-
</Button>
|
|
182
|
-
</View>
|
|
183
|
-
<Strut size={16} />
|
|
184
|
-
<View style={{flexDirection: "row"}}>
|
|
185
|
-
<Button onClick={() => {}} light={true} disabled={true}>
|
|
186
|
-
Hello, world!
|
|
187
|
-
</Button>
|
|
188
|
-
<Strut size={16} />
|
|
189
|
-
<Button
|
|
190
|
-
onClick={() => {}}
|
|
191
|
-
light={true}
|
|
192
|
-
disabled={true}
|
|
193
|
-
kind="secondary"
|
|
194
|
-
>
|
|
195
|
-
Hello, world!
|
|
196
|
-
</Button>
|
|
197
|
-
<Strut size={16} />
|
|
198
|
-
<Button
|
|
199
|
-
onClick={() => {}}
|
|
200
|
-
light={true}
|
|
201
|
-
disabled={true}
|
|
202
|
-
kind="tertiary"
|
|
203
|
-
>
|
|
204
|
-
Hello, world!
|
|
205
|
-
</Button>
|
|
206
|
-
</View>
|
|
207
|
-
<Strut size={16} />
|
|
208
|
-
<View style={{flexDirection: "row"}}>
|
|
209
|
-
<Button onClick={() => {}} light={true} color="destructive">
|
|
210
|
-
Hello, world!
|
|
211
|
-
</Button>
|
|
212
|
-
<Strut size={16} />
|
|
213
|
-
<Button
|
|
214
|
-
onClick={() => {}}
|
|
215
|
-
light={true}
|
|
216
|
-
kind="secondary"
|
|
217
|
-
color="destructive"
|
|
218
|
-
>
|
|
219
|
-
Hello, world!
|
|
220
|
-
</Button>
|
|
221
|
-
<Strut size={16} />
|
|
222
|
-
<Button
|
|
223
|
-
onClick={() => {}}
|
|
224
|
-
light={true}
|
|
225
|
-
kind="tertiary"
|
|
226
|
-
color="destructive"
|
|
227
|
-
>
|
|
228
|
-
Hello, world!
|
|
229
|
-
</Button>
|
|
230
|
-
</View>
|
|
231
|
-
</View>
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
Dark.parameters = {
|
|
235
|
-
backgrounds: {
|
|
236
|
-
default: "darkBlue",
|
|
237
|
-
},
|
|
238
|
-
docs: {
|
|
239
|
-
storyDescription:
|
|
240
|
-
"Buttons on a `darkBlue` background should set `light` to `true`.",
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
const kinds = ["primary", "secondary", "tertiary"];
|
|
245
|
-
|
|
246
|
-
export const Icon: StoryComponentType = () => (
|
|
247
|
-
<View>
|
|
248
|
-
<View style={styles.row}>
|
|
249
|
-
{kinds.map((kind, idx) => (
|
|
250
|
-
<Button
|
|
251
|
-
kind={kind}
|
|
252
|
-
icon={icons.contentExercise}
|
|
253
|
-
style={styles.button}
|
|
254
|
-
key={idx}
|
|
255
|
-
>
|
|
256
|
-
{kind}
|
|
257
|
-
</Button>
|
|
258
|
-
))}
|
|
259
|
-
</View>
|
|
260
|
-
<View style={styles.row}>
|
|
261
|
-
{kinds.map((kind, idx) => (
|
|
262
|
-
<Button
|
|
263
|
-
kind={kind}
|
|
264
|
-
icon={icons.contentExercise}
|
|
265
|
-
style={styles.button}
|
|
266
|
-
key={idx}
|
|
267
|
-
size="small"
|
|
268
|
-
>
|
|
269
|
-
{`${kind} small`}
|
|
270
|
-
</Button>
|
|
271
|
-
))}
|
|
272
|
-
</View>
|
|
273
|
-
</View>
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
Icon.parameters = {
|
|
277
|
-
docs: {
|
|
278
|
-
storyDescription: "Buttons can have an icon on it's left side.",
|
|
279
|
-
},
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
export const Size: StoryComponentType = () => (
|
|
283
|
-
<View>
|
|
284
|
-
<View style={styles.row}>
|
|
285
|
-
<LabelMedium style={styles.fillSpace}>small</LabelMedium>
|
|
286
|
-
<View style={[styles.row, styles.example]}>
|
|
287
|
-
<Button style={styles.button} onClick={() => {}} size="small">
|
|
288
|
-
Label
|
|
289
|
-
</Button>
|
|
290
|
-
<Button
|
|
291
|
-
style={styles.button}
|
|
292
|
-
onClick={() => {}}
|
|
293
|
-
kind="secondary"
|
|
294
|
-
size="small"
|
|
295
|
-
>
|
|
296
|
-
Label
|
|
297
|
-
</Button>
|
|
298
|
-
<Button
|
|
299
|
-
style={styles.button}
|
|
300
|
-
onClick={() => {}}
|
|
301
|
-
kind="tertiary"
|
|
302
|
-
size="small"
|
|
303
|
-
>
|
|
304
|
-
Label
|
|
305
|
-
</Button>
|
|
306
|
-
</View>
|
|
307
|
-
</View>
|
|
308
|
-
<View style={styles.row}>
|
|
309
|
-
<LabelMedium style={styles.fillSpace}>medium (default)</LabelMedium>
|
|
310
|
-
|
|
311
|
-
<View style={[styles.row, styles.example]}>
|
|
312
|
-
<Button style={styles.button} onClick={() => {}} size="medium">
|
|
313
|
-
Label
|
|
314
|
-
</Button>
|
|
315
|
-
<Button
|
|
316
|
-
style={styles.button}
|
|
317
|
-
onClick={() => {}}
|
|
318
|
-
kind="secondary"
|
|
319
|
-
size="medium"
|
|
320
|
-
>
|
|
321
|
-
Label
|
|
322
|
-
</Button>
|
|
323
|
-
<Button
|
|
324
|
-
style={styles.button}
|
|
325
|
-
onClick={() => {}}
|
|
326
|
-
kind="tertiary"
|
|
327
|
-
size="medium"
|
|
328
|
-
>
|
|
329
|
-
Label
|
|
330
|
-
</Button>
|
|
331
|
-
</View>
|
|
332
|
-
</View>
|
|
333
|
-
<View style={styles.row}>
|
|
334
|
-
<LabelMedium style={styles.fillSpace}>large</LabelMedium>
|
|
335
|
-
<View style={[styles.row, styles.example]}>
|
|
336
|
-
<Button style={styles.button} onClick={() => {}} size="large">
|
|
337
|
-
Label
|
|
338
|
-
</Button>
|
|
339
|
-
<Button
|
|
340
|
-
style={styles.button}
|
|
341
|
-
onClick={() => {}}
|
|
342
|
-
kind="secondary"
|
|
343
|
-
size="large"
|
|
344
|
-
>
|
|
345
|
-
Label
|
|
346
|
-
</Button>
|
|
347
|
-
<Button
|
|
348
|
-
style={styles.button}
|
|
349
|
-
onClick={() => {}}
|
|
350
|
-
kind="tertiary"
|
|
351
|
-
size="large"
|
|
352
|
-
>
|
|
353
|
-
Label
|
|
354
|
-
</Button>
|
|
355
|
-
</View>
|
|
356
|
-
</View>
|
|
357
|
-
</View>
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
Size.parameters = {
|
|
361
|
-
docs: {
|
|
362
|
-
storyDescription:
|
|
363
|
-
"Buttons have a size that's either `medium` (default), `small`, or `large`.",
|
|
364
|
-
},
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
export const Spinner: StoryComponentType = () => (
|
|
368
|
-
<View style={{flexDirection: "row"}}>
|
|
369
|
-
<Button
|
|
370
|
-
onClick={() => {}}
|
|
371
|
-
spinner={true}
|
|
372
|
-
size="large"
|
|
373
|
-
aria-label={"waiting"}
|
|
374
|
-
>
|
|
375
|
-
Hello, world
|
|
376
|
-
</Button>
|
|
377
|
-
<Strut size={16} />
|
|
378
|
-
<Button onClick={() => {}} spinner={true} aria-label={"waiting"}>
|
|
379
|
-
Hello, world
|
|
380
|
-
</Button>
|
|
381
|
-
<Strut size={16} />
|
|
382
|
-
<Button
|
|
383
|
-
onClick={() => {}}
|
|
384
|
-
spinner={true}
|
|
385
|
-
size="small"
|
|
386
|
-
aria-label={"waiting"}
|
|
387
|
-
>
|
|
388
|
-
Hello, world
|
|
389
|
-
</Button>
|
|
390
|
-
</View>
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
Spinner.parameters = {
|
|
394
|
-
docs: {
|
|
395
|
-
storyDescription:
|
|
396
|
-
"Buttons can show a spinner. This is useful when indicating to a user that their input has been recognized but that the operation will take some time. While the spinner property is set to true the button is disabled.",
|
|
397
|
-
},
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
export const TruncatingLabels: StoryComponentType = () => (
|
|
401
|
-
<Button onClick={() => {}} style={{maxWidth: 200}}>
|
|
402
|
-
label too long for the parent container
|
|
403
|
-
</Button>
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
TruncatingLabels.parameters = {
|
|
407
|
-
docs: {
|
|
408
|
-
storyDescription:
|
|
409
|
-
"If the label is too long for the button width, the text will be truncated.",
|
|
410
|
-
},
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
TruncatingLabels.storyName = "Truncating labels";
|
|
414
|
-
|
|
415
|
-
export const SubmittingForms: StoryComponentType = () => (
|
|
416
|
-
<form
|
|
417
|
-
onSubmit={(e) => {
|
|
418
|
-
e.preventDefault();
|
|
419
|
-
window.alert("form submitted"); // eslint-disable-line no-alert
|
|
420
|
-
}}
|
|
421
|
-
>
|
|
422
|
-
<View>
|
|
423
|
-
Foo: <input id="foo" value="bar" />
|
|
424
|
-
<Button type="submit">Submit</Button>
|
|
425
|
-
</View>
|
|
426
|
-
</form>
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
SubmittingForms.parameters = {
|
|
430
|
-
docs: {
|
|
431
|
-
storyDescription:
|
|
432
|
-
'If the button is inside a form, you can use the `type="submit"` variant, so the form will be submitted on click.',
|
|
433
|
-
},
|
|
434
|
-
options: {
|
|
435
|
-
showAddonPanel: true,
|
|
436
|
-
},
|
|
437
|
-
chromatic: {
|
|
438
|
-
// We already have screenshots of other stories that cover more of the
|
|
439
|
-
// button states.
|
|
440
|
-
disableSnapshot: true,
|
|
441
|
-
},
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
SubmittingForms.storyName = "Submitting forms";
|
|
445
|
-
|
|
446
|
-
export const PreventNavigation: StoryComponentType = () => (
|
|
447
|
-
<MemoryRouter>
|
|
448
|
-
<View style={styles.row}>
|
|
449
|
-
<Button
|
|
450
|
-
href="/foo"
|
|
451
|
-
style={styles.button}
|
|
452
|
-
onClick={(e) => {
|
|
453
|
-
action("clicked")(e);
|
|
454
|
-
e.preventDefault();
|
|
455
|
-
}}
|
|
456
|
-
>
|
|
457
|
-
This button prevents navigation.
|
|
458
|
-
</Button>
|
|
459
|
-
<Switch>
|
|
460
|
-
<Route path="/foo">
|
|
461
|
-
<View id="foo">Hello, world!</View>
|
|
462
|
-
</Route>
|
|
463
|
-
</Switch>
|
|
464
|
-
</View>
|
|
465
|
-
</MemoryRouter>
|
|
466
|
-
);
|
|
467
|
-
|
|
468
|
-
PreventNavigation.storyName = "Preventing navigation";
|
|
469
|
-
|
|
470
|
-
PreventNavigation.parameters = {
|
|
471
|
-
docs: {
|
|
472
|
-
storyDescription:
|
|
473
|
-
"Sometimes you may need to perform an async action either before or during navigation. This can be accomplished with `beforeNav` and `safeWithNav` respectively.",
|
|
474
|
-
},
|
|
475
|
-
chromatic: {
|
|
476
|
-
disableSnapshot: true,
|
|
477
|
-
},
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
export const WithRouter: StoryComponentType = () => (
|
|
481
|
-
<MemoryRouter>
|
|
482
|
-
<View style={styles.row}>
|
|
483
|
-
<Button href="/foo" style={styles.button}>
|
|
484
|
-
Uses Client-side Nav
|
|
485
|
-
</Button>
|
|
486
|
-
<Button href="/foo" style={styles.button} skipClientNav>
|
|
487
|
-
Avoids Client-side Nav
|
|
488
|
-
</Button>
|
|
489
|
-
<Switch>
|
|
490
|
-
<Route path="/foo">
|
|
491
|
-
<View id="foo">Hello, world!</View>
|
|
492
|
-
</Route>
|
|
493
|
-
</Switch>
|
|
494
|
-
</View>
|
|
495
|
-
</MemoryRouter>
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
WithRouter.storyName = "Navigation with React Router";
|
|
499
|
-
|
|
500
|
-
WithRouter.parameters = {
|
|
501
|
-
docs: {
|
|
502
|
-
storyDescription:
|
|
503
|
-
"Buttons do client-side navigation by default, if React Router exists:",
|
|
504
|
-
},
|
|
505
|
-
chromatic: {
|
|
506
|
-
disableSnapshot: true,
|
|
507
|
-
},
|
|
508
|
-
};
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import {Meta, Story, Canvas} from "@storybook/addon-docs";
|
|
2
|
-
import {StyleSheet} from "aphrodite";
|
|
3
|
-
|
|
4
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
5
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
-
|
|
7
|
-
import {styles} from "./button.stories.js";
|
|
8
|
-
|
|
9
|
-
<Meta
|
|
10
|
-
title="Button / Navigation Callbacks"
|
|
11
|
-
component={Button}
|
|
12
|
-
parameters={{
|
|
13
|
-
previewTabs: {
|
|
14
|
-
canvas: {hidden: true},
|
|
15
|
-
},
|
|
16
|
-
viewMode: "docs",
|
|
17
|
-
chromatic: {
|
|
18
|
-
// Disables chromatic testing for these stories.
|
|
19
|
-
disableSnapshot: true,
|
|
20
|
-
},
|
|
21
|
-
}}
|
|
22
|
-
/>
|
|
23
|
-
|
|
24
|
-
# Running Callbacks on Navigation
|
|
25
|
-
|
|
26
|
-
Sometimes you may need to run some code and also navigate when the user
|
|
27
|
-
clicks the button. For example, you might want to send a request to the
|
|
28
|
-
server and also send the user to a different page. You can do this by
|
|
29
|
-
passing in a URL to the `href` prop and also passing in a callback
|
|
30
|
-
function to either the `onClick`, `beforeNav`, or `safeWithNav` prop.
|
|
31
|
-
Which prop you choose depends on your use case.
|
|
32
|
-
|
|
33
|
-
- `onClick` is guaranteed to run to completion before navigation starts,
|
|
34
|
-
but it is not async aware, so it should only be used if all of the code
|
|
35
|
-
in your callback function executes synchronously.
|
|
36
|
-
|
|
37
|
-
- `beforeNav` is guaranteed to run async operations before navigation
|
|
38
|
-
starts. You must return a promise from the callback function passed in
|
|
39
|
-
to this prop, and the navigation will happen after the promise
|
|
40
|
-
resolves. If the promise rejects, the navigation will not occur.
|
|
41
|
-
This prop should be used if it's important that the async code
|
|
42
|
-
completely finishes before the next URL starts loading.
|
|
43
|
-
|
|
44
|
-
- `safeWithNav` runs async code concurrently with navigation when safe,
|
|
45
|
-
but delays navigation until the async code is finished when
|
|
46
|
-
concurrent execution is not safe. You must return a promise from the
|
|
47
|
-
callback function passed in to this prop, and Wonder Blocks will run
|
|
48
|
-
the async code in parallel with client-side navigation or while opening
|
|
49
|
-
a new tab, but will wait until the async code finishes to start a
|
|
50
|
-
server-side navigation. If the promise rejects the navigation will
|
|
51
|
-
happen anyway. This prop should be used when it's okay to load
|
|
52
|
-
the next URL while the async callback code is running.
|
|
53
|
-
|
|
54
|
-
This table gives an overview of the options:
|
|
55
|
-
|
|
56
|
-
| Prop | Async safe? | Completes before navigation? |
|
|
57
|
-
|-------------|-------------|------------------------------|
|
|
58
|
-
| onClick | no | yes |
|
|
59
|
-
| beforeNav | yes | yes |
|
|
60
|
-
| safeWithNav | yes | no |
|
|
61
|
-
|
|
62
|
-
It is possible to use more than one of these props on the same element.
|
|
63
|
-
If multiple props are used, they will run in this order: first `onClick`,
|
|
64
|
-
then `beforeNav`, then `safeWithNav`. If both `beforeNav` and `safeWithNav`
|
|
65
|
-
are used, the `safeWithNav` callback will not be called until the
|
|
66
|
-
`beforeNav` promise resolves successfully. If the `beforeNav` promise
|
|
67
|
-
rejects, `safeWithNav` will not be run.
|
|
68
|
-
|
|
69
|
-
If the `onClick` handler calls `preventDefault()`, then `beforeNav`
|
|
70
|
-
and `safeWithNav` will still run, but navigation will not occur.
|
|
71
|
-
|
|
72
|
-
export const BeforeNavCallbacks = () => (
|
|
73
|
-
<MemoryRouter>
|
|
74
|
-
<View style={styles.row}>
|
|
75
|
-
<Button
|
|
76
|
-
href="/foo"
|
|
77
|
-
style={styles.button}
|
|
78
|
-
beforeNav={() =>
|
|
79
|
-
new Promise((resolve, reject) => {
|
|
80
|
-
setTimeout(resolve, 1000);
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
>
|
|
84
|
-
beforeNav, client-side nav
|
|
85
|
-
</Button>
|
|
86
|
-
<Button
|
|
87
|
-
href="/foo"
|
|
88
|
-
style={styles.button}
|
|
89
|
-
skipClientNav={true}
|
|
90
|
-
beforeNav={() =>
|
|
91
|
-
new Promise((resolve, reject) => {
|
|
92
|
-
setTimeout(resolve, 1000);
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
>
|
|
96
|
-
beforeNav, server-side nav
|
|
97
|
-
</Button>
|
|
98
|
-
<Button
|
|
99
|
-
href="https://google.com"
|
|
100
|
-
style={styles.button}
|
|
101
|
-
skipClientNav={true}
|
|
102
|
-
beforeNav={() =>
|
|
103
|
-
new Promise((resolve, reject) => {
|
|
104
|
-
setTimeout(resolve, 1000);
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
>
|
|
108
|
-
beforeNav, open URL in new tab
|
|
109
|
-
</Button>
|
|
110
|
-
<Switch>
|
|
111
|
-
<Route path="/foo">
|
|
112
|
-
<View id="foo">Hello, world!</View>
|
|
113
|
-
</Route>
|
|
114
|
-
</Switch>
|
|
115
|
-
</View>
|
|
116
|
-
</MemoryRouter>
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
export const SafeWithNavCallbacks = () => (
|
|
120
|
-
<MemoryRouter>
|
|
121
|
-
<View style={styles.row}>
|
|
122
|
-
<Button
|
|
123
|
-
href="/foo"
|
|
124
|
-
style={styles.button}
|
|
125
|
-
safeWithNav={() =>
|
|
126
|
-
new Promise((resolve, reject) => {
|
|
127
|
-
setTimeout(resolve, 1000);
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
>
|
|
131
|
-
safeWithNav, client-side nav
|
|
132
|
-
</Button>
|
|
133
|
-
<Button
|
|
134
|
-
href="/foo"
|
|
135
|
-
style={styles.button}
|
|
136
|
-
skipClientNav={true}
|
|
137
|
-
safeWithNav={() =>
|
|
138
|
-
new Promise((resolve, reject) => {
|
|
139
|
-
setTimeout(resolve, 1000);
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
>
|
|
143
|
-
safeWithNav, server-side nav
|
|
144
|
-
</Button>
|
|
145
|
-
<Button
|
|
146
|
-
href="https://google.com"
|
|
147
|
-
style={styles.button}
|
|
148
|
-
skipClientNav={true}
|
|
149
|
-
safeWithNav={() =>
|
|
150
|
-
new Promise((resolve, reject) => {
|
|
151
|
-
setTimeout(resolve, 1000);
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
>
|
|
155
|
-
safeWithNav, open URL in new tab
|
|
156
|
-
</Button>
|
|
157
|
-
<Switch>
|
|
158
|
-
<Route path="/foo">
|
|
159
|
-
<View id="foo">Hello, world!</View>
|
|
160
|
-
</Route>
|
|
161
|
-
</Switch>
|
|
162
|
-
</View>
|
|
163
|
-
</MemoryRouter>
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
## Stories
|
|
167
|
-
|
|
168
|
-
### beforeNav Callbacks
|
|
169
|
-
|
|
170
|
-
These buttons always wait until the async callback code completes before
|
|
171
|
-
starting navigation.
|
|
172
|
-
|
|
173
|
-
<Canvas>
|
|
174
|
-
<Story name="beforeNav Callbacks">
|
|
175
|
-
{BeforeNavCallbacks.bind({})}
|
|
176
|
-
</Story>
|
|
177
|
-
</Canvas>
|
|
178
|
-
|
|
179
|
-
### safeWithNav Callbacks
|
|
180
|
-
|
|
181
|
-
If the `onClick` callback calls `preventDefault()`, then navigation will not occur.
|
|
182
|
-
|
|
183
|
-
<Canvas>
|
|
184
|
-
<Story name="safeWithNav Callbacks">
|
|
185
|
-
{SafeWithNavCallbacks.bind({})}
|
|
186
|
-
</Story>
|
|
187
|
-
</Canvas>
|