@khanacademy/wonder-blocks-button 2.9.13 → 2.10.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/dist/es/index.js +17 -15
- package/dist/index.js +188 -249
- package/package.json +14 -14
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +240 -11
- package/src/__tests__/generated-snapshot.test.js +74 -12
- package/src/components/__docs__/accessibility.stories.mdx +92 -0
- package/src/components/__docs__/best-practices.stories.mdx +107 -0
- package/src/components/__docs__/button.argtypes.js +231 -0
- package/src/components/__docs__/navigation-callbacks.stories.mdx +68 -0
- package/src/components/__tests__/button.test.js +11 -0
- package/src/components/button-core.js +10 -9
- package/src/components/button.js +20 -16
- package/src/components/button.md +134 -23
- package/src/components/button.stories.js +413 -104
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
<Meta
|
|
8
|
+
title="Navigation/Button/Best practices"
|
|
9
|
+
component={Button}
|
|
10
|
+
parameters={{
|
|
11
|
+
previewTabs: {
|
|
12
|
+
canvas: {hidden: true},
|
|
13
|
+
},
|
|
14
|
+
viewMode: "docs",
|
|
15
|
+
chromatic: {
|
|
16
|
+
// Disables chromatic testing for these stories.
|
|
17
|
+
disableSnapshot: true,
|
|
18
|
+
},
|
|
19
|
+
}}
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
## Best Practices
|
|
23
|
+
|
|
24
|
+
### Layout
|
|
25
|
+
|
|
26
|
+
In vertical layouts, buttons will stretch horizontally to fill the available
|
|
27
|
+
space. This is probably not what you want unless you're on a very narrow
|
|
28
|
+
screen.
|
|
29
|
+
|
|
30
|
+
<Canvas>
|
|
31
|
+
<Story name="Full-bleed button">
|
|
32
|
+
<View>
|
|
33
|
+
<Button>Label</Button>
|
|
34
|
+
</View>
|
|
35
|
+
</Story>
|
|
36
|
+
</Canvas>
|
|
37
|
+
|
|
38
|
+
This can be corrected by applying appropriate flex styles to the container.
|
|
39
|
+
|
|
40
|
+
<Canvas>
|
|
41
|
+
<Story name="Buttons in rows">
|
|
42
|
+
<View>
|
|
43
|
+
<View style={styles.row}>
|
|
44
|
+
<Button>Button in a row</Button>
|
|
45
|
+
</View>
|
|
46
|
+
<View style={styles.gap} />
|
|
47
|
+
<View style={styles.column}>
|
|
48
|
+
<Button>Button in a column</Button>
|
|
49
|
+
</View>
|
|
50
|
+
</View>
|
|
51
|
+
</Story>
|
|
52
|
+
</Canvas>
|
|
53
|
+
|
|
54
|
+
### Usign minWidth for internationalization
|
|
55
|
+
|
|
56
|
+
Layouts often specify a specific width of button. When implementing such designs use `minWidth` instead of `width`. `minWidth` allows the button to resize to fit the content whereas `width` does not. This is important for international sites since sometimes strings for UI elements can be much longer in other languages. Both of the buttons below have a "natural" width of `144px`. The one on the right is wider but it accommodates the full string instead of wrapping it.
|
|
57
|
+
|
|
58
|
+
<Canvas>
|
|
59
|
+
<Story name="Using minWidth">
|
|
60
|
+
<View style={styles.row}>
|
|
61
|
+
<Button style={styles.buttonMinWidth} kind="secondary">
|
|
62
|
+
label
|
|
63
|
+
</Button>
|
|
64
|
+
<Button style={styles.buttonMinWidth}>
|
|
65
|
+
label in a different language
|
|
66
|
+
</Button>
|
|
67
|
+
</View>
|
|
68
|
+
</Story>
|
|
69
|
+
</Canvas>
|
|
70
|
+
|
|
71
|
+
### Truncating text
|
|
72
|
+
|
|
73
|
+
If the parent container of the button doesn't have enough room to accommodate
|
|
74
|
+
the width of the button, the text will truncate. This should ideally never
|
|
75
|
+
happen, but it's sometimes a necessary fallback.
|
|
76
|
+
|
|
77
|
+
<Canvas>
|
|
78
|
+
<Story name="Truncating text">
|
|
79
|
+
<View style={{flexDirection: "row", width: 300}}>
|
|
80
|
+
<Button style={styles.buttonMinWidth} kind="secondary">
|
|
81
|
+
label
|
|
82
|
+
</Button>
|
|
83
|
+
<Button style={styles.buttonMinWidth}>
|
|
84
|
+
label too long for the parent container
|
|
85
|
+
</Button>
|
|
86
|
+
</View>
|
|
87
|
+
</Story>
|
|
88
|
+
</Canvas>
|
|
89
|
+
|
|
90
|
+
export const styles = StyleSheet.create({
|
|
91
|
+
column: {
|
|
92
|
+
alignItems: "flex-start",
|
|
93
|
+
},
|
|
94
|
+
row: {
|
|
95
|
+
flexDirection: "row",
|
|
96
|
+
},
|
|
97
|
+
gap: {
|
|
98
|
+
height: 16,
|
|
99
|
+
},
|
|
100
|
+
button: {
|
|
101
|
+
marginRight: 10,
|
|
102
|
+
},
|
|
103
|
+
buttonMinWidth: {
|
|
104
|
+
marginRight: 10,
|
|
105
|
+
minWidth: 144,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {icons} from "@khanacademy/wonder-blocks-icon";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
children: {
|
|
6
|
+
description: "Text to appear on the button.",
|
|
7
|
+
type: {required: true},
|
|
8
|
+
},
|
|
9
|
+
icon: {
|
|
10
|
+
description: "An icon, displayed to the left of the title.",
|
|
11
|
+
type: {required: false},
|
|
12
|
+
control: {type: "select"},
|
|
13
|
+
options: (Object.keys(icons): Array<string>),
|
|
14
|
+
mapping: icons,
|
|
15
|
+
table: {
|
|
16
|
+
category: "Layout",
|
|
17
|
+
type: {summary: "IconAsset"},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
spinner: {
|
|
21
|
+
description: "If true, replaces the contents with a spinner.",
|
|
22
|
+
control: {type: "boolean"},
|
|
23
|
+
table: {
|
|
24
|
+
category: "Layout",
|
|
25
|
+
type: {
|
|
26
|
+
summary: "boolean",
|
|
27
|
+
detail: "Setting this prop to `true` will disable the button.",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
color: {
|
|
32
|
+
description: "The color of the button, either blue or red.",
|
|
33
|
+
options: ["default", "destructive"],
|
|
34
|
+
control: {type: "radio"},
|
|
35
|
+
table: {
|
|
36
|
+
category: "Theming",
|
|
37
|
+
type: {
|
|
38
|
+
summary: `"default" | "destructive"`,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
kind: {
|
|
43
|
+
description:
|
|
44
|
+
"The kind of the button, either primary, secondary, or tertiary.",
|
|
45
|
+
options: ["primary", "secondary", "tertiary"],
|
|
46
|
+
control: {type: "select"},
|
|
47
|
+
table: {
|
|
48
|
+
type: {summary: "primary | secondary | tertiary"},
|
|
49
|
+
defaultValue: {
|
|
50
|
+
detail: `
|
|
51
|
+
- Primary buttons have background colors.\n- Secondary buttons have a border and no background color.\n- Tertiary buttons have no background or border.
|
|
52
|
+
`,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
light: {
|
|
57
|
+
description: "Whether the button is on a dark/colored background.",
|
|
58
|
+
control: {type: "boolean"},
|
|
59
|
+
table: {
|
|
60
|
+
category: "Theming",
|
|
61
|
+
type: {
|
|
62
|
+
summary: "boolean",
|
|
63
|
+
detail: "Sets primary button background color to white, and secondary and tertiary button title to color.",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
size: {
|
|
68
|
+
description: "The size of the button.",
|
|
69
|
+
options: ["small", "medium", "xlarge"],
|
|
70
|
+
control: {type: "select"},
|
|
71
|
+
table: {
|
|
72
|
+
category: "Layout",
|
|
73
|
+
defaultValue: {
|
|
74
|
+
detail: `"medium" = height: 40; "small" = height: 32; "xlarge" = height: 60;`,
|
|
75
|
+
},
|
|
76
|
+
type: {
|
|
77
|
+
summary: `"medium" | "small" | "xlarge"`,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
disabled: {
|
|
82
|
+
description: "Whether the button is disabled.",
|
|
83
|
+
table: {
|
|
84
|
+
type: {
|
|
85
|
+
summary: "boolean",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
id: {
|
|
90
|
+
description: "An optional id attribute.",
|
|
91
|
+
control: {type: "text"},
|
|
92
|
+
table: {
|
|
93
|
+
type: {
|
|
94
|
+
summary: "string",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
testId: {
|
|
99
|
+
description: "Test ID used for e2e testing.",
|
|
100
|
+
control: {type: "text"},
|
|
101
|
+
table: {
|
|
102
|
+
type: {
|
|
103
|
+
summary: "string",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
tabIndex: {
|
|
109
|
+
description: "Set the tabindex attribute on the rendered element.",
|
|
110
|
+
control: {type: "number", min: -1},
|
|
111
|
+
table: {
|
|
112
|
+
type: {
|
|
113
|
+
summary: "number",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
style: {
|
|
118
|
+
description: "Optional custom styles.",
|
|
119
|
+
table: {
|
|
120
|
+
category: "Layout",
|
|
121
|
+
type: {
|
|
122
|
+
summary: "StyleType",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
className: {
|
|
127
|
+
description: "Adds CSS classes to the Button.",
|
|
128
|
+
control: {type: "text"},
|
|
129
|
+
table: {
|
|
130
|
+
category: "Layout",
|
|
131
|
+
type: {
|
|
132
|
+
summary: "string",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
/**
|
|
137
|
+
* Events
|
|
138
|
+
*/
|
|
139
|
+
onClick: {
|
|
140
|
+
action: "clicked",
|
|
141
|
+
description: `Function to call when button is clicked.
|
|
142
|
+
This callback should be used for things like marking BigBingo conversions. It should NOT be used to redirect to a different URL or to prevent navigation via e.preventDefault(). The event passed to this handler will have its preventDefault() and stopPropagation() methods stubbed out.
|
|
143
|
+
`,
|
|
144
|
+
table: {
|
|
145
|
+
category: "Events",
|
|
146
|
+
type: {
|
|
147
|
+
summary: "(e: SyntheticEvent<>) => mixed",
|
|
148
|
+
detail: `onClick is optional if href is present, but must be defined if
|
|
149
|
+
* href is not`,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
/**
|
|
154
|
+
* Navigation
|
|
155
|
+
*/
|
|
156
|
+
skipClientNav: {
|
|
157
|
+
description: `Whether to avoid using client-side navigation. If the URL passed to href is local to the client-side, e.g. /math/algebra/eval-exprs, then it tries to use react-router-dom's Link component which handles the client-side navigation. You can set "skipClientNav" to true avoid using client-side nav entirely.`,
|
|
158
|
+
control: {type: "boolean"},
|
|
159
|
+
table: {
|
|
160
|
+
category: "Navigation",
|
|
161
|
+
type: {
|
|
162
|
+
summary: "Note",
|
|
163
|
+
detail: "All URLs containing a protocol are considered external, e.g. https://khanacademy.org/math/algebra/eval-exprs will trigger a full page reload.",
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
rel: {
|
|
168
|
+
description: `Specifies the type of relationship between the current document and the linked document. Should only be used when "href" is specified. This defaults to "noopener noreferrer" when target="_blank", but can be overridden by setting this prop to something else.`,
|
|
169
|
+
control: {type: "text"},
|
|
170
|
+
table: {
|
|
171
|
+
category: "Navigation",
|
|
172
|
+
type: {
|
|
173
|
+
summary: "string",
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
target: {
|
|
178
|
+
description: `A target destination window for a link to open in. Should only be used
|
|
179
|
+
* when "href" is specified.`,
|
|
180
|
+
control: {type: "text"},
|
|
181
|
+
table: {
|
|
182
|
+
category: "Navigation",
|
|
183
|
+
type: {
|
|
184
|
+
summary: "string",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
href: {
|
|
189
|
+
description: "URL to navigate to.",
|
|
190
|
+
control: {type: "text"},
|
|
191
|
+
table: {
|
|
192
|
+
category: "Navigation",
|
|
193
|
+
type: {
|
|
194
|
+
summary: "string",
|
|
195
|
+
detail: "URL is required when we use `safeWithNav`",
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
beforeNav: {
|
|
200
|
+
description: `Run async code before navigating. If the promise returned rejects then navigation will not occur. If both safeWithNav and beforeNav are provided, beforeNav will be run first and safeWithNav will only be run if beforeNav does not reject.`,
|
|
201
|
+
table: {
|
|
202
|
+
category: "Navigation",
|
|
203
|
+
type: {
|
|
204
|
+
summary: "() => Promise<mixed>",
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
safeWithNav: {
|
|
209
|
+
description: `Run async code in the background while client-side navigating. If the browser does a full page load navigation, the callback promise must be settled before the navigation will occur. Errors are ignored so that navigation is guaranteed to succeed.`,
|
|
210
|
+
table: {
|
|
211
|
+
category: "Navigation",
|
|
212
|
+
type: {
|
|
213
|
+
summary: "() => Promise<mixed>",
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
/**
|
|
218
|
+
* Accessibility
|
|
219
|
+
*/
|
|
220
|
+
ariaLabel: {
|
|
221
|
+
name: "aria-label",
|
|
222
|
+
description: "A label for the button.",
|
|
223
|
+
table: {
|
|
224
|
+
category: "Accessibility",
|
|
225
|
+
type: {
|
|
226
|
+
summary: "string",
|
|
227
|
+
detail: `aria-label should be used when spinner={true} to let people using screen readers that the action taken by clicking the button will take some time to complete.`,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
<Meta
|
|
8
|
+
title="Navigation/Button/Navigation Callbacks"
|
|
9
|
+
component={Button}
|
|
10
|
+
parameters={{
|
|
11
|
+
previewTabs: {
|
|
12
|
+
canvas: {hidden: true},
|
|
13
|
+
},
|
|
14
|
+
viewMode: "docs",
|
|
15
|
+
chromatic: {
|
|
16
|
+
// Disables chromatic testing for these stories.
|
|
17
|
+
disableSnapshot: true,
|
|
18
|
+
},
|
|
19
|
+
}}
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
## Running Callbacks on Navigation
|
|
23
|
+
|
|
24
|
+
Sometimes you may need to run some code and also navigate when the user
|
|
25
|
+
clicks the button. For example, you might want to send a request to the
|
|
26
|
+
server and also send the user to a different page. You can do this by
|
|
27
|
+
passing in a URL to the `href` prop and also passing in a callback
|
|
28
|
+
function to either the `onClick`, `beforeNav`, or `safeWithNav` prop.
|
|
29
|
+
Which prop you choose depends on your use case.
|
|
30
|
+
|
|
31
|
+
- `onClick` is guaranteed to run to completion before navigation starts,
|
|
32
|
+
but it is not async aware, so it should only be used if all of the code
|
|
33
|
+
in your callback function executes synchronously.
|
|
34
|
+
|
|
35
|
+
- `beforeNav` is guaranteed to run async operations before navigation
|
|
36
|
+
starts. You must return a promise from the callback function passed in
|
|
37
|
+
to this prop, and the navigation will happen after the promise
|
|
38
|
+
resolves. If the promise rejects, the navigation will not occur.
|
|
39
|
+
This prop should be used if it's important that the async code
|
|
40
|
+
completely finishes before the next URL starts loading.
|
|
41
|
+
|
|
42
|
+
- `safeWithNav` runs async code concurrently with navigation when safe,
|
|
43
|
+
but delays navigation until the async code is finished when
|
|
44
|
+
concurrent execution is not safe. You must return a promise from the
|
|
45
|
+
callback function passed in to this prop, and Wonder Blocks will run
|
|
46
|
+
the async code in parallel with client-side navigation or while opening
|
|
47
|
+
a new tab, but will wait until the async code finishes to start a
|
|
48
|
+
server-side navigation. If the promise rejects the navigation will
|
|
49
|
+
happen anyway. This prop should be used when it's okay to load
|
|
50
|
+
the next URL while the async callback code is running.
|
|
51
|
+
|
|
52
|
+
This table gives an overview of the options:
|
|
53
|
+
|
|
54
|
+
| Prop | Async safe? | Completes before navigation? |
|
|
55
|
+
|-------------|-------------|------------------------------|
|
|
56
|
+
| onClick | no | yes |
|
|
57
|
+
| beforeNav | yes | yes |
|
|
58
|
+
| safeWithNav | yes | no |
|
|
59
|
+
|
|
60
|
+
It is possible to use more than one of these props on the same element.
|
|
61
|
+
If multiple props are used, they will run in this order: first `onClick`,
|
|
62
|
+
then `beforeNav`, then `safeWithNav`. If both `beforeNav` and `safeWithNav`
|
|
63
|
+
are used, the `safeWithNav` callback will not be called until the
|
|
64
|
+
`beforeNav` promise resolves successfully. If the `beforeNav` promise
|
|
65
|
+
rejects, `safeWithNav` will not be run.
|
|
66
|
+
|
|
67
|
+
If the `onClick` handler calls `preventDefault()`, then `beforeNav`
|
|
68
|
+
and `safeWithNav` will still run, but navigation will not occur.
|
|
@@ -18,6 +18,17 @@ const keyCodes = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
describe("Button", () => {
|
|
21
|
+
const {location} = window;
|
|
22
|
+
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
delete window.location;
|
|
25
|
+
window.location = {assign: jest.fn()};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterAll(() => {
|
|
29
|
+
window.location = location;
|
|
30
|
+
});
|
|
31
|
+
|
|
21
32
|
test("client-side navigation", () => {
|
|
22
33
|
// Arrange
|
|
23
34
|
const wrapper = mount(
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import {StyleSheet} from "aphrodite";
|
|
4
4
|
import {Link} from "react-router-dom";
|
|
5
|
-
import
|
|
5
|
+
import {__RouterContext} from "react-router";
|
|
6
6
|
|
|
7
7
|
import {LabelLarge, LabelSmall} from "@khanacademy/wonder-blocks-typography";
|
|
8
8
|
import Color, {
|
|
@@ -30,18 +30,12 @@ type Props = {|
|
|
|
30
30
|
type?: "submit",
|
|
31
31
|
|};
|
|
32
32
|
|
|
33
|
-
type ContextTypes = {|
|
|
34
|
-
router: $FlowFixMe,
|
|
35
|
-
|};
|
|
36
|
-
|
|
37
33
|
const StyledAnchor = addStyle<"a">("a");
|
|
38
34
|
const StyledButton = addStyle<"button">("button");
|
|
39
35
|
const StyledLink = addStyle<typeof Link>(Link);
|
|
40
36
|
|
|
41
37
|
export default class ButtonCore extends React.Component<Props> {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
render(): React.Node {
|
|
38
|
+
renderInner(router: any): React.Node {
|
|
45
39
|
const {
|
|
46
40
|
children,
|
|
47
41
|
skipClientNav,
|
|
@@ -63,7 +57,6 @@ export default class ButtonCore extends React.Component<Props> {
|
|
|
63
57
|
waiting: _,
|
|
64
58
|
...restProps
|
|
65
59
|
} = this.props;
|
|
66
|
-
const {router} = this.context;
|
|
67
60
|
|
|
68
61
|
const buttonColor =
|
|
69
62
|
color === "destructive"
|
|
@@ -176,6 +169,14 @@ export default class ButtonCore extends React.Component<Props> {
|
|
|
176
169
|
);
|
|
177
170
|
}
|
|
178
171
|
}
|
|
172
|
+
|
|
173
|
+
render(): React.Node {
|
|
174
|
+
return (
|
|
175
|
+
<__RouterContext.Consumer>
|
|
176
|
+
{(router) => this.renderInner(router)}
|
|
177
|
+
</__RouterContext.Consumer>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
const sharedStyles = StyleSheet.create({
|
package/src/components/button.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import
|
|
3
|
+
import {__RouterContext} from "react-router";
|
|
4
4
|
|
|
5
5
|
import {getClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
|
|
6
6
|
import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
|
|
@@ -139,11 +139,10 @@ export type SharedProps = {|
|
|
|
139
139
|
/**
|
|
140
140
|
* Function to call when button is clicked.
|
|
141
141
|
*
|
|
142
|
-
* This callback should be used for
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* stubbed out.
|
|
142
|
+
* This callback should be used for running synchronous code, like
|
|
143
|
+
* dispatching a Redux action. For asynchronous code see the
|
|
144
|
+
* beforeNav and safeWithNav props. It should NOT be used to redirect
|
|
145
|
+
* to a different URL.
|
|
147
146
|
*
|
|
148
147
|
* Note: onClick is optional if href is present, but must be defined if
|
|
149
148
|
* href is not
|
|
@@ -222,10 +221,6 @@ type Props =
|
|
|
222
221
|
safeWithNav: () => Promise<mixed>,
|
|
223
222
|
|};
|
|
224
223
|
|
|
225
|
-
type ContextTypes = {|
|
|
226
|
-
router: $FlowFixMe,
|
|
227
|
-
|};
|
|
228
|
-
|
|
229
224
|
type DefaultProps = {|
|
|
230
225
|
color: $PropertyType<Props, "color">,
|
|
231
226
|
kind: $PropertyType<Props, "kind">,
|
|
@@ -243,18 +238,19 @@ type DefaultProps = {|
|
|
|
243
238
|
* `ButtonCore` is a stateless component which displays the different states
|
|
244
239
|
* the `Button` can take.
|
|
245
240
|
*
|
|
246
|
-
*
|
|
241
|
+
* ### Usage
|
|
242
|
+
*
|
|
247
243
|
* ```jsx
|
|
244
|
+
* import Button from "@khanacademy/wonder-blocks-button";
|
|
245
|
+
*
|
|
248
246
|
* <Button
|
|
249
247
|
* onClick={(e) => console.log("Hello, world!")}
|
|
250
248
|
* >
|
|
251
|
-
*
|
|
249
|
+
* Hello, world!
|
|
252
250
|
* </Button>
|
|
253
251
|
* ```
|
|
254
252
|
*/
|
|
255
253
|
export default class Button extends React.Component<Props> {
|
|
256
|
-
static contextTypes: ContextTypes = {router: PropTypes.any};
|
|
257
|
-
|
|
258
254
|
static defaultProps: DefaultProps = {
|
|
259
255
|
color: "default",
|
|
260
256
|
kind: "primary",
|
|
@@ -264,7 +260,7 @@ export default class Button extends React.Component<Props> {
|
|
|
264
260
|
spinner: false,
|
|
265
261
|
};
|
|
266
262
|
|
|
267
|
-
|
|
263
|
+
renderClickableBehavior(router: any): React.Node {
|
|
268
264
|
const {
|
|
269
265
|
href = undefined,
|
|
270
266
|
type = undefined,
|
|
@@ -284,7 +280,7 @@ export default class Button extends React.Component<Props> {
|
|
|
284
280
|
const ClickableBehavior = getClickableBehavior(
|
|
285
281
|
href,
|
|
286
282
|
skipClientNav,
|
|
287
|
-
|
|
283
|
+
router,
|
|
288
284
|
);
|
|
289
285
|
|
|
290
286
|
const renderProp = (
|
|
@@ -344,4 +340,12 @@ export default class Button extends React.Component<Props> {
|
|
|
344
340
|
);
|
|
345
341
|
}
|
|
346
342
|
}
|
|
343
|
+
|
|
344
|
+
render(): React.Node {
|
|
345
|
+
return (
|
|
346
|
+
<__RouterContext.Consumer>
|
|
347
|
+
{(router) => this.renderClickableBehavior(router)}
|
|
348
|
+
</__RouterContext.Consumer>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
347
351
|
}
|