@khanacademy/wonder-blocks-button 2.11.7 → 3.0.2
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 +21 -0
- package/dist/es/index.js +12 -9
- package/dist/index.js +25 -20
- package/package.json +5 -5
- package/src/__tests__/__snapshots__/custom-snapshot.test.js.snap +4660 -794
- package/src/__tests__/custom-snapshot.test.js +1 -1
- package/src/components/__docs__/accessibility.stories.mdx +1 -1
- package/src/components/__docs__/best-practices.stories.mdx +1 -1
- package/src/components/__docs__/button.argtypes.js +3 -3
- package/src/components/{button.stories.js → __docs__/button.stories.js} +195 -275
- package/src/components/__docs__/navigation-callbacks.stories.mdx +121 -2
- package/src/components/__tests__/button.flowtest.js +1 -1
- package/src/components/__tests__/button.test.js +138 -191
- package/src/components/button-core.js +14 -8
- package/src/components/button.js +2 -2
- package/src/components/button.md +4 -920
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +0 -5009
- package/src/__tests__/generated-snapshot.test.js +0 -789
package/src/components/button.md
CHANGED
|
@@ -1,921 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
Documentation for `@khanacademy/wonder-blocks-button` is now in Storybook.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
7
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
8
|
-
import {StyleSheet} from "aphrodite";
|
|
9
|
-
|
|
10
|
-
const styles = StyleSheet.create({
|
|
11
|
-
row: {
|
|
12
|
-
flexDirection: "row",
|
|
13
|
-
},
|
|
14
|
-
button: {
|
|
15
|
-
marginRight: 10,
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
<View style={styles.row}>
|
|
20
|
-
<Button
|
|
21
|
-
style={styles.button}
|
|
22
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
23
|
-
>
|
|
24
|
-
Primary
|
|
25
|
-
</Button>
|
|
26
|
-
<Button
|
|
27
|
-
style={styles.button}
|
|
28
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
29
|
-
kind="secondary"
|
|
30
|
-
>
|
|
31
|
-
Secondary
|
|
32
|
-
</Button>
|
|
33
|
-
<Button
|
|
34
|
-
style={styles.button}
|
|
35
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
36
|
-
kind="tertiary"
|
|
37
|
-
>
|
|
38
|
-
Tertiary
|
|
39
|
-
</Button>
|
|
40
|
-
</View>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Example: color
|
|
44
|
-
|
|
45
|
-
Buttons have a `color` that is either `"default"` (the default, as shown above) or `"destructive"` (as can seen below):
|
|
46
|
-
|
|
47
|
-
```jsx
|
|
48
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
49
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
50
|
-
import {StyleSheet} from "aphrodite";
|
|
51
|
-
|
|
52
|
-
const styles = StyleSheet.create({
|
|
53
|
-
row: {
|
|
54
|
-
flexDirection: "row",
|
|
55
|
-
},
|
|
56
|
-
button: {
|
|
57
|
-
marginRight: 10,
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
<View style={styles.row}>
|
|
62
|
-
<Button
|
|
63
|
-
style={styles.button}
|
|
64
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
65
|
-
color="destructive"
|
|
66
|
-
>
|
|
67
|
-
Primary
|
|
68
|
-
</Button>
|
|
69
|
-
<Button
|
|
70
|
-
style={styles.button}
|
|
71
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
72
|
-
kind="secondary"
|
|
73
|
-
color="destructive"
|
|
74
|
-
>
|
|
75
|
-
Secondary
|
|
76
|
-
</Button>
|
|
77
|
-
<Button
|
|
78
|
-
style={styles.button}
|
|
79
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
80
|
-
kind="tertiary"
|
|
81
|
-
color="destructive"
|
|
82
|
-
>
|
|
83
|
-
Tertiary
|
|
84
|
-
</Button>
|
|
85
|
-
</View>
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Example: disabled
|
|
89
|
-
|
|
90
|
-
Buttons can be `disabled`:
|
|
91
|
-
|
|
92
|
-
⚠️ Buttons do not need an `aria-disabled` attribute, if it also has a `disabled` attribute.
|
|
93
|
-
Users operating the web page through a screen reader may not be able to fully evaluate the implied
|
|
94
|
-
behaviors of the button element itself.
|
|
95
|
-
|
|
96
|
-
Links:
|
|
97
|
-
- [Implicit ARIA semantics](https://www.w3.org/TR/wai-aria-1.1/#implicit_semantics)
|
|
98
|
-
- [Document conformance requirements](https://www.w3.org/TR/html-aria/#document-conformance-requirements-for-use-of-aria-attributes-in-html)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
```jsx
|
|
102
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
103
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
104
|
-
import {StyleSheet} from "aphrodite";
|
|
105
|
-
|
|
106
|
-
const styles = StyleSheet.create({
|
|
107
|
-
row: {
|
|
108
|
-
flexDirection: "row",
|
|
109
|
-
},
|
|
110
|
-
button: {
|
|
111
|
-
marginRight: 10,
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
<View style={styles.row}>
|
|
116
|
-
<Button
|
|
117
|
-
style={styles.button}
|
|
118
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
119
|
-
disabled={true}
|
|
120
|
-
>
|
|
121
|
-
Primary
|
|
122
|
-
</Button>
|
|
123
|
-
<Button
|
|
124
|
-
style={styles.button}
|
|
125
|
-
href={"/foo"}
|
|
126
|
-
kind="secondary"
|
|
127
|
-
disabled={true}
|
|
128
|
-
>
|
|
129
|
-
Secondary
|
|
130
|
-
</Button>
|
|
131
|
-
<Button
|
|
132
|
-
style={styles.button}
|
|
133
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
134
|
-
kind="tertiary"
|
|
135
|
-
disabled={true}
|
|
136
|
-
>
|
|
137
|
-
Tertiary
|
|
138
|
-
</Button>
|
|
139
|
-
</View>
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Example: dark
|
|
143
|
-
|
|
144
|
-
Buttons on a `darkBlue` background should set `light` to `true`.
|
|
145
|
-
```jsx
|
|
146
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
147
|
-
import Color from "@khanacademy/wonder-blocks-color";
|
|
148
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
149
|
-
import {StyleSheet} from "aphrodite";
|
|
150
|
-
|
|
151
|
-
const styles = StyleSheet.create({
|
|
152
|
-
row: {
|
|
153
|
-
flexDirection: "row",
|
|
154
|
-
backgroundColor: Color.darkBlue,
|
|
155
|
-
padding: 10,
|
|
156
|
-
},
|
|
157
|
-
button: {
|
|
158
|
-
marginRight: 10,
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
<View style={styles.row}>
|
|
163
|
-
<Button
|
|
164
|
-
light={true}
|
|
165
|
-
style={styles.button}
|
|
166
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
167
|
-
>
|
|
168
|
-
Primary
|
|
169
|
-
</Button>
|
|
170
|
-
<Button
|
|
171
|
-
light={true}
|
|
172
|
-
style={styles.button}
|
|
173
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
174
|
-
kind="secondary"
|
|
175
|
-
>
|
|
176
|
-
Secondary
|
|
177
|
-
</Button>
|
|
178
|
-
<Button
|
|
179
|
-
light={true}
|
|
180
|
-
style={styles.button}
|
|
181
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
182
|
-
kind="tertiary"
|
|
183
|
-
>
|
|
184
|
-
Tertiary
|
|
185
|
-
</Button>
|
|
186
|
-
<Button
|
|
187
|
-
light={true}
|
|
188
|
-
style={styles.button}
|
|
189
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
190
|
-
disabled={true}
|
|
191
|
-
>
|
|
192
|
-
Primary
|
|
193
|
-
</Button>
|
|
194
|
-
<Button
|
|
195
|
-
light={true}
|
|
196
|
-
style={styles.button}
|
|
197
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
198
|
-
kind="secondary"
|
|
199
|
-
disabled={true}
|
|
200
|
-
>
|
|
201
|
-
Secondary
|
|
202
|
-
</Button>
|
|
203
|
-
<Button
|
|
204
|
-
light={true}
|
|
205
|
-
style={styles.button}
|
|
206
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
207
|
-
kind="tertiary"
|
|
208
|
-
disabled={true}
|
|
209
|
-
>
|
|
210
|
-
Tertiary
|
|
211
|
-
</Button>
|
|
212
|
-
</View>
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### Example: size
|
|
216
|
-
|
|
217
|
-
Buttons have a `size` that's either `"medium"` (default), `"small"`, or `"xlarge"`.
|
|
218
|
-
```js
|
|
219
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
220
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
221
|
-
import {StyleSheet} from "aphrodite";
|
|
222
|
-
|
|
223
|
-
const styles = StyleSheet.create({
|
|
224
|
-
row: {
|
|
225
|
-
flexDirection: "row",
|
|
226
|
-
marginBottom: 8,
|
|
227
|
-
},
|
|
228
|
-
button: {
|
|
229
|
-
marginRight: 10,
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
<View>
|
|
234
|
-
<View style={styles.row}>
|
|
235
|
-
<Button
|
|
236
|
-
style={styles.button}
|
|
237
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
238
|
-
size="small"
|
|
239
|
-
>
|
|
240
|
-
Label
|
|
241
|
-
</Button>
|
|
242
|
-
<Button
|
|
243
|
-
style={styles.button}
|
|
244
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
245
|
-
kind="secondary"
|
|
246
|
-
size="small"
|
|
247
|
-
>
|
|
248
|
-
Label
|
|
249
|
-
</Button>
|
|
250
|
-
<Button
|
|
251
|
-
style={styles.button}
|
|
252
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
253
|
-
kind="tertiary"
|
|
254
|
-
size="small"
|
|
255
|
-
>
|
|
256
|
-
Label
|
|
257
|
-
</Button>
|
|
258
|
-
</View>
|
|
259
|
-
<View style={styles.row}>
|
|
260
|
-
<Button
|
|
261
|
-
style={styles.button}
|
|
262
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
263
|
-
size="medium"
|
|
264
|
-
>
|
|
265
|
-
Label
|
|
266
|
-
</Button>
|
|
267
|
-
<Button
|
|
268
|
-
style={styles.button}
|
|
269
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
270
|
-
kind="secondary"
|
|
271
|
-
size="medium"
|
|
272
|
-
>
|
|
273
|
-
Label
|
|
274
|
-
</Button>
|
|
275
|
-
<Button
|
|
276
|
-
style={styles.button}
|
|
277
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
278
|
-
kind="tertiary"
|
|
279
|
-
size="medium"
|
|
280
|
-
>
|
|
281
|
-
Label
|
|
282
|
-
</Button>
|
|
283
|
-
</View>
|
|
284
|
-
<View style={styles.row}>
|
|
285
|
-
<Button
|
|
286
|
-
style={styles.button}
|
|
287
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
288
|
-
size="xlarge"
|
|
289
|
-
>
|
|
290
|
-
Label
|
|
291
|
-
</Button>
|
|
292
|
-
<Button
|
|
293
|
-
style={styles.button}
|
|
294
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
295
|
-
kind="secondary"
|
|
296
|
-
size="xlarge"
|
|
297
|
-
>
|
|
298
|
-
Label
|
|
299
|
-
</Button>
|
|
300
|
-
<Button
|
|
301
|
-
style={styles.button}
|
|
302
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
303
|
-
kind="tertiary"
|
|
304
|
-
size="xlarge"
|
|
305
|
-
>
|
|
306
|
-
Label
|
|
307
|
-
</Button>
|
|
308
|
-
</View>
|
|
309
|
-
</View>
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
### Example: spinner
|
|
313
|
-
|
|
314
|
-
Buttons can show a `spinner`. This is useful when indicating to a user that
|
|
315
|
-
their input has been recognized but that the operation will take some time.
|
|
316
|
-
While the `spinner` property is set to `true` the button is disabled.
|
|
317
|
-
|
|
318
|
-
```jsx
|
|
319
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
320
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
321
|
-
import {StyleSheet} from "aphrodite";
|
|
322
|
-
|
|
323
|
-
const styles = StyleSheet.create({
|
|
324
|
-
row: {
|
|
325
|
-
flexDirection: "row",
|
|
326
|
-
alignItems: "center",
|
|
327
|
-
},
|
|
328
|
-
button: {
|
|
329
|
-
marginRight: 10,
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
<View style={styles.row}>
|
|
334
|
-
<Button spinner={true} aria-label="loading" size="xlarge" style={styles.button}>
|
|
335
|
-
Click me!
|
|
336
|
-
</Button>
|
|
337
|
-
<Button spinner={true} aria-label="loading" style={styles.button} href="/foo">
|
|
338
|
-
Click me!
|
|
339
|
-
</Button>
|
|
340
|
-
<Button spinner={true} aria-label="loading" size="small" style={styles.button}>
|
|
341
|
-
Click me!
|
|
342
|
-
</Button>
|
|
343
|
-
</View>
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
### Example: Navigation
|
|
347
|
-
|
|
348
|
-
```jsx
|
|
349
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
350
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
351
|
-
import {StyleSheet} from "aphrodite";
|
|
352
|
-
|
|
353
|
-
const styles = StyleSheet.create({
|
|
354
|
-
row: {
|
|
355
|
-
flexDirection: "row",
|
|
356
|
-
},
|
|
357
|
-
button: {
|
|
358
|
-
marginRight: 10,
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
<View style={styles.row}>
|
|
363
|
-
<Button
|
|
364
|
-
href="#button-1"
|
|
365
|
-
style={styles.button}
|
|
366
|
-
>
|
|
367
|
-
href
|
|
368
|
-
</Button>
|
|
369
|
-
<Button
|
|
370
|
-
kind="secondary"
|
|
371
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
372
|
-
style={styles.button}
|
|
373
|
-
>
|
|
374
|
-
onClick
|
|
375
|
-
</Button>
|
|
376
|
-
<Button
|
|
377
|
-
kind="tertiary"
|
|
378
|
-
href="#button-1"
|
|
379
|
-
onClick={(e) => window.alert("Hello, world!")}
|
|
380
|
-
style={styles.button}
|
|
381
|
-
>
|
|
382
|
-
both
|
|
383
|
-
</Button>
|
|
384
|
-
</View>
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
### Example: Navigation with React Router
|
|
388
|
-
|
|
389
|
-
Buttons do client-side navigation by default, if React Router exists:
|
|
390
|
-
```jsx
|
|
391
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
392
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
393
|
-
import {StyleSheet} from "aphrodite";
|
|
394
|
-
import {MemoryRouter, Route, Switch} from "react-router-dom";
|
|
395
|
-
|
|
396
|
-
const styles = StyleSheet.create({
|
|
397
|
-
row: {
|
|
398
|
-
flexDirection: "row",
|
|
399
|
-
alignItems: "center",
|
|
400
|
-
},
|
|
401
|
-
button: {
|
|
402
|
-
marginRight: 10,
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
// NOTE: In actual code you would use BrowserRouter instead
|
|
407
|
-
<MemoryRouter>
|
|
408
|
-
<View style={styles.row}>
|
|
409
|
-
<Button href="/foo" style={styles.button}>
|
|
410
|
-
Uses Client-side Nav
|
|
411
|
-
</Button>
|
|
412
|
-
<Button href="/foo" style={styles.button} skipClientNav>
|
|
413
|
-
Avoids Client-side Nav
|
|
414
|
-
</Button>
|
|
415
|
-
<Switch>
|
|
416
|
-
<Route path="/foo">
|
|
417
|
-
<View id="foo">Hello, world!</View>
|
|
418
|
-
</Route>
|
|
419
|
-
</Switch>
|
|
420
|
-
</View>
|
|
421
|
-
</MemoryRouter>
|
|
422
|
-
```
|
|
423
|
-
### Running callbacks on navigation
|
|
424
|
-
|
|
425
|
-
Sometimes you may need to run some code and also navigate when the user
|
|
426
|
-
clicks the button. For example, you might want to send a request to the
|
|
427
|
-
server and also send the user to a different page. You can do this by
|
|
428
|
-
passing in a URL to the `href` prop and also passing in a callback
|
|
429
|
-
function to either the `onClick`, `beforeNav`, or `safeWithNav` prop.
|
|
430
|
-
Which prop you choose depends on your use case.
|
|
431
|
-
|
|
432
|
-
- `onClick` is guaranteed to run to completion before navigation starts,
|
|
433
|
-
but it is not async aware, so it should only be used if all of the code
|
|
434
|
-
in your callback function executes synchronously.
|
|
435
|
-
|
|
436
|
-
- `beforeNav` is guaranteed to run async operations before navigation
|
|
437
|
-
starts. You must return a promise from the callback function passed in
|
|
438
|
-
to this prop, and the navigation will happen after the promise
|
|
439
|
-
resolves. If the promise rejects, the navigation will not occur.
|
|
440
|
-
This prop should be used if it's important that the async code
|
|
441
|
-
completely finishes before the next URL starts loading.
|
|
442
|
-
|
|
443
|
-
- `safeWithNav` runs async code concurrently with navigation when safe,
|
|
444
|
-
but delays navigation until the async code is finished when
|
|
445
|
-
concurrent execution is not safe. You must return a promise from the
|
|
446
|
-
callback function passed in to this prop, and Wonder Blocks will run
|
|
447
|
-
the async code in parallel with client-side navigation or while opening
|
|
448
|
-
a new tab, but will wait until the async code finishes to start a
|
|
449
|
-
server-side navigation. If the promise rejects the navigation will
|
|
450
|
-
happen anyway. This prop should be used when it's okay to load
|
|
451
|
-
the next URL while the async callback code is running.
|
|
452
|
-
|
|
453
|
-
This table gives an overview of the options:
|
|
454
|
-
|
|
455
|
-
| Prop | Async safe? | Completes before navigation? |
|
|
456
|
-
|-------------|-------------|------------------------------|
|
|
457
|
-
| onClick | no | yes |
|
|
458
|
-
| beforeNav | yes | yes |
|
|
459
|
-
| safeWithNav | yes | no |
|
|
460
|
-
|
|
461
|
-
It is possible to use more than one of these props on the same element.
|
|
462
|
-
If multiple props are used, they will run in this order: first `onClick`,
|
|
463
|
-
then `beforeNav`, then `safeWithNav`. If both `beforeNav` and `safeWithNav`
|
|
464
|
-
are used, the `safeWithNav` callback will not be called until the
|
|
465
|
-
`beforeNav` promise resolves successfully. If the `beforeNav` promise
|
|
466
|
-
rejects, `safeWithNav` will not be run.
|
|
467
|
-
|
|
468
|
-
If the `onClick` handler calls `preventDefault()`, then `beforeNav`
|
|
469
|
-
and `safeWithNav` will still run, but navigation will not occur.
|
|
470
|
-
|
|
471
|
-
### Example: beforeNav callbacks
|
|
472
|
-
|
|
473
|
-
These buttons always wait until the async callback code completes before
|
|
474
|
-
starting navigation.
|
|
475
|
-
|
|
476
|
-
```jsx
|
|
477
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
478
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
479
|
-
import {StyleSheet} from "aphrodite";
|
|
480
|
-
import {MemoryRouter, Route, Switch} from "react-router-dom";
|
|
481
|
-
|
|
482
|
-
const styles = StyleSheet.create({
|
|
483
|
-
row: {
|
|
484
|
-
flexDirection: "row",
|
|
485
|
-
alignItems: "center",
|
|
486
|
-
},
|
|
487
|
-
button: {
|
|
488
|
-
marginRight: 10,
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
// NOTE: In actual code you would use BrowserRouter instead
|
|
493
|
-
<MemoryRouter>
|
|
494
|
-
<View style={styles.row}>
|
|
495
|
-
<Button
|
|
496
|
-
href="/foo"
|
|
497
|
-
style={styles.button}
|
|
498
|
-
beforeNav={() => new Promise((resolve, reject) => {
|
|
499
|
-
setTimeout(resolve, 1000);
|
|
500
|
-
})}
|
|
501
|
-
>
|
|
502
|
-
beforeNav, client-side nav
|
|
503
|
-
</Button>
|
|
504
|
-
<Button
|
|
505
|
-
href="/foo"
|
|
506
|
-
style={styles.button}
|
|
507
|
-
skipClientNav={true}
|
|
508
|
-
beforeNav={() => new Promise((resolve, reject) => {
|
|
509
|
-
setTimeout(resolve, 1000);
|
|
510
|
-
})}
|
|
511
|
-
>
|
|
512
|
-
beforeNav, server-side nav
|
|
513
|
-
</Button>
|
|
514
|
-
<Button
|
|
515
|
-
href="https://google.com"
|
|
516
|
-
target="_blank"
|
|
517
|
-
style={styles.button}
|
|
518
|
-
skipClientNav={true}
|
|
519
|
-
beforeNav={() => new Promise((resolve, reject) => {
|
|
520
|
-
setTimeout(resolve, 1000);
|
|
521
|
-
})}
|
|
522
|
-
>
|
|
523
|
-
beforeNav, open URL in new tab
|
|
524
|
-
</Button>
|
|
525
|
-
<Switch>
|
|
526
|
-
<Route path="/foo">
|
|
527
|
-
<View id="foo">Hello, world!</View>
|
|
528
|
-
</Route>
|
|
529
|
-
</Switch>
|
|
530
|
-
</View>
|
|
531
|
-
</MemoryRouter>
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
### Example: safeWithNav callbacks
|
|
535
|
-
|
|
536
|
-
These buttons navigate immediately when doing client-side navigation
|
|
537
|
-
or when opening a new tab, but wait until the async callback code
|
|
538
|
-
completes before starting server-side navigation.
|
|
539
|
-
|
|
540
|
-
```jsx
|
|
541
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
542
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
543
|
-
import {StyleSheet} from "aphrodite";
|
|
544
|
-
import {MemoryRouter, Route, Switch} from "react-router-dom";
|
|
545
|
-
|
|
546
|
-
const styles = StyleSheet.create({
|
|
547
|
-
row: {
|
|
548
|
-
flexDirection: "row",
|
|
549
|
-
alignItems: "center",
|
|
550
|
-
},
|
|
551
|
-
button: {
|
|
552
|
-
marginRight: 10,
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
// NOTE: In actual code you would use BrowserRouter instead
|
|
557
|
-
<MemoryRouter>
|
|
558
|
-
<View style={styles.row}>
|
|
559
|
-
<Button
|
|
560
|
-
href="/foo"
|
|
561
|
-
style={styles.button}
|
|
562
|
-
safeWithNav={() => new Promise((resolve, reject) => {
|
|
563
|
-
setTimeout(resolve, 1000);
|
|
564
|
-
})}
|
|
565
|
-
>
|
|
566
|
-
safeWithNav, client-side nav
|
|
567
|
-
</Button>
|
|
568
|
-
<Button
|
|
569
|
-
href="/foo"
|
|
570
|
-
style={styles.button}
|
|
571
|
-
skipClientNav={true}
|
|
572
|
-
safeWithNav={() => new Promise((resolve, reject) => {
|
|
573
|
-
setTimeout(resolve, 1000);
|
|
574
|
-
})}
|
|
575
|
-
>
|
|
576
|
-
safeWithNav, server-side nav
|
|
577
|
-
</Button>
|
|
578
|
-
<Button
|
|
579
|
-
href="https://google.com"
|
|
580
|
-
target="_blank"
|
|
581
|
-
style={styles.button}
|
|
582
|
-
skipClientNav={true}
|
|
583
|
-
safeWithNav={() => new Promise((resolve, reject) => {
|
|
584
|
-
setTimeout(resolve, 1000);
|
|
585
|
-
})}
|
|
586
|
-
>
|
|
587
|
-
safeWithNav, open URL in new tab
|
|
588
|
-
</Button>
|
|
589
|
-
<Switch>
|
|
590
|
-
<Route path="/foo">
|
|
591
|
-
<View id="foo">Hello, world!</View>
|
|
592
|
-
</Route>
|
|
593
|
-
</Switch>
|
|
594
|
-
</View>
|
|
595
|
-
</MemoryRouter>
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
### Example: Prevent navigation by calling e.preventDefault()
|
|
599
|
-
|
|
600
|
-
If the `onClick` callback calls `preventDefault()`, then navigation
|
|
601
|
-
will not occur.
|
|
602
|
-
|
|
603
|
-
```jsx
|
|
604
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
605
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
606
|
-
import {StyleSheet} from "aphrodite";
|
|
607
|
-
import {MemoryRouter, Route, Switch} from "react-router-dom";
|
|
608
|
-
|
|
609
|
-
const styles = StyleSheet.create({
|
|
610
|
-
row: {
|
|
611
|
-
flexDirection: "row",
|
|
612
|
-
alignItems: "center",
|
|
613
|
-
},
|
|
614
|
-
button: {
|
|
615
|
-
marginRight: 10,
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
// NOTE: In actual code you would use BrowserRouter instead
|
|
620
|
-
<MemoryRouter>
|
|
621
|
-
<View style={styles.row}>
|
|
622
|
-
<Button
|
|
623
|
-
href="/foo"
|
|
624
|
-
style={styles.button}
|
|
625
|
-
onClick={e => e.preventDefault()}
|
|
626
|
-
>
|
|
627
|
-
This button prevents navigation.
|
|
628
|
-
</Button>
|
|
629
|
-
<Switch>
|
|
630
|
-
<Route path="/foo">
|
|
631
|
-
<View id="foo">Hello, world!</View>
|
|
632
|
-
</Route>
|
|
633
|
-
</Switch>
|
|
634
|
-
</View>
|
|
635
|
-
</MemoryRouter>
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### Example: style
|
|
639
|
-
|
|
640
|
-
Buttons can have a `style` props which supports width, position, margin,
|
|
641
|
-
and flex styles.
|
|
642
|
-
|
|
643
|
-
Buttons can have an icon on it's left side.
|
|
644
|
-
|
|
645
|
-
```jsx
|
|
646
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
647
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
648
|
-
import {StyleSheet} from "aphrodite";
|
|
649
|
-
import {icons} from "@khanacademy/wonder-blocks-icon";
|
|
650
|
-
|
|
651
|
-
const styles = StyleSheet.create({
|
|
652
|
-
row: {
|
|
653
|
-
flexDirection: "row",
|
|
654
|
-
marginBottom: 10,
|
|
655
|
-
},
|
|
656
|
-
button: {
|
|
657
|
-
marginRight: 10,
|
|
658
|
-
},
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
const kinds = ["primary", "secondary", "tertiary"];
|
|
662
|
-
|
|
663
|
-
<View>
|
|
664
|
-
<View style={styles.row}>
|
|
665
|
-
{
|
|
666
|
-
kinds.map((kind, idx) => (
|
|
667
|
-
<Button
|
|
668
|
-
kind={kind}
|
|
669
|
-
icon={icons.contentExercise}
|
|
670
|
-
style={styles.button}
|
|
671
|
-
key={idx}
|
|
672
|
-
>
|
|
673
|
-
{kind}
|
|
674
|
-
</Button>
|
|
675
|
-
))
|
|
676
|
-
}
|
|
677
|
-
</View>
|
|
678
|
-
<View style={styles.row}>
|
|
679
|
-
{
|
|
680
|
-
kinds.map((kind, idx) => (
|
|
681
|
-
<Button
|
|
682
|
-
kind={kind}
|
|
683
|
-
icon={icons.contentExercise}
|
|
684
|
-
style={styles.button}
|
|
685
|
-
key={idx}
|
|
686
|
-
size="small"
|
|
687
|
-
>
|
|
688
|
-
{kind} small
|
|
689
|
-
</Button>
|
|
690
|
-
))
|
|
691
|
-
}
|
|
692
|
-
</View>
|
|
693
|
-
</View>
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
### Example: "submit" buttons in forms
|
|
697
|
-
|
|
698
|
-
```jsx
|
|
699
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
700
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
701
|
-
|
|
702
|
-
<View>
|
|
703
|
-
<form onSubmit={() => alert("the form was submitted")}>
|
|
704
|
-
<Button type="submit">Submit</Button>
|
|
705
|
-
</form>
|
|
706
|
-
</View>
|
|
707
|
-
```
|
|
708
|
-
|
|
709
|
-
### Best Practices
|
|
710
|
-
|
|
711
|
-
In vertical layouts, buttons will stretch horizontally to fill the available
|
|
712
|
-
space. This is probably not what you want unless you're on a very narrow
|
|
713
|
-
screen.
|
|
714
|
-
|
|
715
|
-
```jsx
|
|
716
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
717
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
718
|
-
|
|
719
|
-
<View>
|
|
720
|
-
<Button>
|
|
721
|
-
Label
|
|
722
|
-
</Button>
|
|
723
|
-
</View>
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
This can be corrected by applying appropriate flex styles to the container.
|
|
727
|
-
|
|
728
|
-
```jsx
|
|
729
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
730
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
731
|
-
import {StyleSheet} from "aphrodite";
|
|
732
|
-
|
|
733
|
-
const styles = StyleSheet.create({
|
|
734
|
-
column: {
|
|
735
|
-
alignItems: "flex-start",
|
|
736
|
-
},
|
|
737
|
-
row: {
|
|
738
|
-
flexDirection: "row",
|
|
739
|
-
},
|
|
740
|
-
gap: {
|
|
741
|
-
height: 16,
|
|
742
|
-
},
|
|
743
|
-
button: {
|
|
744
|
-
marginRight: 10,
|
|
745
|
-
},
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
<View>
|
|
749
|
-
<View style={styles.row}>
|
|
750
|
-
<Button>
|
|
751
|
-
Button in a row
|
|
752
|
-
</Button>
|
|
753
|
-
</View>
|
|
754
|
-
<View style={styles.gap} />
|
|
755
|
-
<View style={styles.column}>
|
|
756
|
-
<Button>
|
|
757
|
-
Button in a column
|
|
758
|
-
</Button>
|
|
759
|
-
</View>
|
|
760
|
-
</View>
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
Layouts often specify a specific width of button. When implementing such
|
|
764
|
-
designs use `minWidth` instead of `width`. `minWidth` allows the button
|
|
765
|
-
to resize to fit the content whereas `width` does not. This is important
|
|
766
|
-
for international sites since sometimes strings for UI elements can be much
|
|
767
|
-
longer in other languages. Both of the buttons below have a "natural" width
|
|
768
|
-
of 144px. The one on the right is wider but it accommodates the full string
|
|
769
|
-
instead of wrapping it.
|
|
770
|
-
```jsx
|
|
771
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
772
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
773
|
-
import {StyleSheet} from "aphrodite";
|
|
774
|
-
|
|
775
|
-
const styles = StyleSheet.create({
|
|
776
|
-
row: {
|
|
777
|
-
flexDirection: "row",
|
|
778
|
-
},
|
|
779
|
-
gap: {
|
|
780
|
-
height: 16,
|
|
781
|
-
},
|
|
782
|
-
button: {
|
|
783
|
-
marginRight: 10,
|
|
784
|
-
minWidth: 144,
|
|
785
|
-
},
|
|
786
|
-
});
|
|
787
|
-
|
|
788
|
-
<View style={styles.row}>
|
|
789
|
-
<Button
|
|
790
|
-
style={styles.button}
|
|
791
|
-
kind="secondary"
|
|
792
|
-
>
|
|
793
|
-
label
|
|
794
|
-
</Button>
|
|
795
|
-
<Button
|
|
796
|
-
style={styles.button}
|
|
797
|
-
>
|
|
798
|
-
label in a different language
|
|
799
|
-
</Button>
|
|
800
|
-
</View>
|
|
801
|
-
```
|
|
802
|
-
|
|
803
|
-
If the parent container of the button doesn't have enough room to accommodate
|
|
804
|
-
the width of the button, the text will truncate. This should ideally never
|
|
805
|
-
happen, but it's sometimes a necessary fallback.
|
|
806
|
-
```jsx
|
|
807
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
808
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
809
|
-
import {StyleSheet} from "aphrodite";
|
|
810
|
-
|
|
811
|
-
const styles = StyleSheet.create({
|
|
812
|
-
row: {
|
|
813
|
-
flexDirection: "row",
|
|
814
|
-
width: 300,
|
|
815
|
-
},
|
|
816
|
-
gap: {
|
|
817
|
-
height: 16,
|
|
818
|
-
},
|
|
819
|
-
button: {
|
|
820
|
-
marginRight: 10,
|
|
821
|
-
minWidth: 144,
|
|
822
|
-
},
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
<View style={styles.row}>
|
|
826
|
-
<Button
|
|
827
|
-
style={styles.button}
|
|
828
|
-
kind="secondary"
|
|
829
|
-
>
|
|
830
|
-
label
|
|
831
|
-
</Button>
|
|
832
|
-
<Button
|
|
833
|
-
style={styles.button}
|
|
834
|
-
>
|
|
835
|
-
label too long for the parent container
|
|
836
|
-
</Button>
|
|
837
|
-
</View>
|
|
838
|
-
```
|
|
839
|
-
|
|
840
|
-
Only one button in a layout should be `primary`.
|
|
841
|
-
```jsx
|
|
842
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
843
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
844
|
-
import {StyleSheet} from "aphrodite";
|
|
845
|
-
|
|
846
|
-
const styles = StyleSheet.create({
|
|
847
|
-
row: {
|
|
848
|
-
flexDirection: "row",
|
|
849
|
-
},
|
|
850
|
-
button: {
|
|
851
|
-
marginRight: 10,
|
|
852
|
-
},
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
<View>
|
|
856
|
-
<View style={styles.row}>
|
|
857
|
-
<Button
|
|
858
|
-
style={styles.button}
|
|
859
|
-
kind="tertiary"
|
|
860
|
-
>
|
|
861
|
-
Tertiary
|
|
862
|
-
</Button>
|
|
863
|
-
<Button
|
|
864
|
-
style={styles.badButton}
|
|
865
|
-
>
|
|
866
|
-
Primary
|
|
867
|
-
</Button>
|
|
868
|
-
</View>
|
|
869
|
-
</View>
|
|
870
|
-
```
|
|
871
|
-
|
|
872
|
-
When an action is going to take a while, show a spinner during that time.
|
|
873
|
-
|
|
874
|
-
```jsx
|
|
875
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
876
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
877
|
-
import {StyleSheet} from "aphrodite";
|
|
878
|
-
|
|
879
|
-
const styles = StyleSheet.create({
|
|
880
|
-
row: {
|
|
881
|
-
flexDirection: "row",
|
|
882
|
-
},
|
|
883
|
-
button: {
|
|
884
|
-
marginRight: 10,
|
|
885
|
-
},
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
class Example extends React.Component {
|
|
889
|
-
constructor(props) {
|
|
890
|
-
super(props);
|
|
891
|
-
this.state = {
|
|
892
|
-
waiting: false,
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
componentWillUnmount() {
|
|
897
|
-
this.timeout && this.timeout.clear();
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
handleClick() {
|
|
901
|
-
this.setState({waiting: true});
|
|
902
|
-
this.timeout = setTimeout(() => {
|
|
903
|
-
this.setState({waiting: false});
|
|
904
|
-
}, 2000);
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
render() {
|
|
908
|
-
return <View style={styles.row}>
|
|
909
|
-
<Button
|
|
910
|
-
spinner={this.state.waiting}
|
|
911
|
-
aria-label={this.state.waiting ? "waiting" : ""}
|
|
912
|
-
onClick={() => this.handleClick()}
|
|
913
|
-
>
|
|
914
|
-
Click me!
|
|
915
|
-
</Button>
|
|
916
|
-
</View>
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
<Example />
|
|
921
|
-
```
|
|
3
|
+
Either run `yarn start:storybook` locally, or visit the docs for the `main`
|
|
4
|
+
branch on [GitHub
|
|
5
|
+
Pages](https://khan.github.io/wonder-blocks/?path=/docs/button--default).
|