@khanacademy/wonder-blocks-button 6.0.0 → 6.1.0
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 +12 -0
- package/dist/components/button-core.d.ts +2 -0
- package/dist/es/index.js +62 -15
- package/dist/index.js +61 -14
- package/dist/themes/default.d.ts +23 -1
- package/dist/themes/khanmigo.d.ts +14 -1
- package/dist/themes/themed-button.d.ts +14 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/custom-snapshot.test.tsx.snap +48 -48
- package/src/components/__tests__/button-with-icon.test.tsx +274 -0
- package/src/components/__tests__/button.test.tsx +6 -88
- package/src/components/button-core.tsx +70 -21
- package/src/themes/default.ts +27 -1
- package/src/themes/khanmigo.ts +14 -0
- package/src/themes/themed-button.tsx +5 -4
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Wonder Blocks Button with icons.
|
|
3
|
+
* The rest of the button tests can be found in button.test.tsx.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import {render, screen} from "@testing-library/react";
|
|
8
|
+
import userEvent from "@testing-library/user-event";
|
|
9
|
+
import plus from "@phosphor-icons/core/regular/plus.svg";
|
|
10
|
+
|
|
11
|
+
import {ThemeSwitcherContext, tokens} from "@khanacademy/wonder-blocks-theming";
|
|
12
|
+
|
|
13
|
+
import Button from "../button";
|
|
14
|
+
|
|
15
|
+
describe("button with icon", () => {
|
|
16
|
+
test("start icon should be hidden from Screen Readers", () => {
|
|
17
|
+
// Arrange
|
|
18
|
+
render(
|
|
19
|
+
<Button testId={"button-focus-test"} startIcon={plus}>
|
|
20
|
+
Label
|
|
21
|
+
</Button>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Act
|
|
25
|
+
const icon = screen.getByTestId("button-focus-test-start-icon");
|
|
26
|
+
|
|
27
|
+
// Assert
|
|
28
|
+
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("end icon should be hidden from Screen Readers", () => {
|
|
32
|
+
// Arrange
|
|
33
|
+
render(
|
|
34
|
+
<Button testId={"button-focus-test"} endIcon={plus}>
|
|
35
|
+
Label
|
|
36
|
+
</Button>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Act
|
|
40
|
+
const icon = screen.getByTestId("button-focus-test-end-icon");
|
|
41
|
+
|
|
42
|
+
// Assert
|
|
43
|
+
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Primary button
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
test("icon is displayed when button contains startIcon", () => {
|
|
51
|
+
// Arrange
|
|
52
|
+
render(
|
|
53
|
+
<Button testId={"button-focus-test"} startIcon={plus}>
|
|
54
|
+
Label
|
|
55
|
+
</Button>,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Act
|
|
59
|
+
const icon = screen.getByTestId("button-focus-test-start-icon");
|
|
60
|
+
|
|
61
|
+
// Assert
|
|
62
|
+
expect(icon).toBeInTheDocument();
|
|
63
|
+
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("icon is displayed when button contains endIcon", () => {
|
|
67
|
+
// Arrange
|
|
68
|
+
render(
|
|
69
|
+
<Button testId={"button-focus-test"} endIcon={plus}>
|
|
70
|
+
Label
|
|
71
|
+
</Button>,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Act
|
|
75
|
+
const icon = screen.getByTestId("button-focus-test-end-icon");
|
|
76
|
+
|
|
77
|
+
// Assert
|
|
78
|
+
expect(icon).toBeInTheDocument();
|
|
79
|
+
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("both icons are displayed when button contains startIcon and endIcon", () => {
|
|
83
|
+
// Arrange
|
|
84
|
+
render(
|
|
85
|
+
<Button
|
|
86
|
+
testId={"button-focus-test"}
|
|
87
|
+
startIcon={plus}
|
|
88
|
+
endIcon={plus}
|
|
89
|
+
>
|
|
90
|
+
Label
|
|
91
|
+
</Button>,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Act
|
|
95
|
+
const startIcon = screen.getByTestId("button-focus-test-start-icon");
|
|
96
|
+
const endIcon = screen.getByTestId("button-focus-test-end-icon");
|
|
97
|
+
|
|
98
|
+
// Assert
|
|
99
|
+
expect(startIcon).toBeInTheDocument();
|
|
100
|
+
expect(endIcon).toBeInTheDocument();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Secondary button
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
test("icon is displayed when secondary button contains startIcon", () => {
|
|
108
|
+
// Arrange
|
|
109
|
+
render(
|
|
110
|
+
<Button
|
|
111
|
+
kind="secondary"
|
|
112
|
+
testId={"button-icon-test"}
|
|
113
|
+
startIcon={plus}
|
|
114
|
+
>
|
|
115
|
+
Label
|
|
116
|
+
</Button>,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Act
|
|
120
|
+
const icon = screen.getByTestId("button-icon-test-start-icon");
|
|
121
|
+
|
|
122
|
+
// Assert
|
|
123
|
+
expect(icon).toBeInTheDocument();
|
|
124
|
+
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("icon is displayed when secondary button contains endIcon", () => {
|
|
128
|
+
// Arrange
|
|
129
|
+
render(
|
|
130
|
+
<Button kind="secondary" testId={"button-icon-test"} endIcon={plus}>
|
|
131
|
+
Label
|
|
132
|
+
</Button>,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Act
|
|
136
|
+
const icon = screen.getByTestId("button-icon-test-end-icon");
|
|
137
|
+
|
|
138
|
+
// Assert
|
|
139
|
+
expect(icon).toBeInTheDocument();
|
|
140
|
+
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("default theme secondary button icon has no hover style", () => {
|
|
144
|
+
// Arrange
|
|
145
|
+
render(
|
|
146
|
+
<Button kind="secondary" testId={"button-icon-test"} endIcon={plus}>
|
|
147
|
+
Label
|
|
148
|
+
</Button>,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Act
|
|
152
|
+
const button = screen.getByTestId("button-icon-test");
|
|
153
|
+
const iconWrapper = screen.getByTestId(
|
|
154
|
+
"button-icon-test-end-icon-wrapper",
|
|
155
|
+
);
|
|
156
|
+
userEvent.hover(button);
|
|
157
|
+
|
|
158
|
+
// Assert
|
|
159
|
+
expect(iconWrapper).toHaveStyle(`backgroundColor: transparent`);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("Khanmigo secondary button icon has hover style", () => {
|
|
163
|
+
// Arrange
|
|
164
|
+
render(
|
|
165
|
+
<ThemeSwitcherContext.Provider value="khanmigo">
|
|
166
|
+
<Button
|
|
167
|
+
kind="secondary"
|
|
168
|
+
testId={"button-icon-test"}
|
|
169
|
+
endIcon={plus}
|
|
170
|
+
>
|
|
171
|
+
Label
|
|
172
|
+
</Button>
|
|
173
|
+
</ThemeSwitcherContext.Provider>,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Act
|
|
177
|
+
const button = screen.getByTestId("button-icon-test");
|
|
178
|
+
const iconWrapper = screen.getByTestId(
|
|
179
|
+
"button-icon-test-end-icon-wrapper",
|
|
180
|
+
);
|
|
181
|
+
userEvent.hover(button);
|
|
182
|
+
|
|
183
|
+
// Assert
|
|
184
|
+
expect(iconWrapper).toHaveStyle(
|
|
185
|
+
`backgroundColor: ${tokens.color.fadedBlue16}`,
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Tertiary button
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
test("icon is displayed when tertiary button contains startIcon", () => {
|
|
194
|
+
// Arrange
|
|
195
|
+
render(
|
|
196
|
+
<Button
|
|
197
|
+
kind="tertiary"
|
|
198
|
+
testId={"button-focus-test"}
|
|
199
|
+
startIcon={plus}
|
|
200
|
+
>
|
|
201
|
+
Label
|
|
202
|
+
</Button>,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Act
|
|
206
|
+
const icon = screen.getByTestId("button-focus-test-start-icon");
|
|
207
|
+
|
|
208
|
+
// Assert
|
|
209
|
+
expect(icon).toBeInTheDocument();
|
|
210
|
+
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("icon is displayed when tertiary button contains endIcon", () => {
|
|
214
|
+
// Arrange
|
|
215
|
+
render(
|
|
216
|
+
<Button kind="tertiary" testId={"button-focus-test"} endIcon={plus}>
|
|
217
|
+
Label
|
|
218
|
+
</Button>,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// Act
|
|
222
|
+
const icon = screen.getByTestId("button-focus-test-end-icon");
|
|
223
|
+
|
|
224
|
+
// Assert
|
|
225
|
+
expect(icon).toBeInTheDocument();
|
|
226
|
+
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("default theme tertiary button icon has no hover style", () => {
|
|
230
|
+
// Arrange
|
|
231
|
+
render(
|
|
232
|
+
<Button kind="tertiary" testId={"button-icon-test"} endIcon={plus}>
|
|
233
|
+
Label
|
|
234
|
+
</Button>,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// Act
|
|
238
|
+
const button = screen.getByTestId("button-icon-test");
|
|
239
|
+
const iconWrapper = screen.getByTestId(
|
|
240
|
+
"button-icon-test-end-icon-wrapper",
|
|
241
|
+
);
|
|
242
|
+
userEvent.hover(button);
|
|
243
|
+
|
|
244
|
+
// Assert
|
|
245
|
+
expect(iconWrapper).toHaveStyle(`backgroundColor: transparent`);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("Khanmigo tertiary button icon has hover style", () => {
|
|
249
|
+
// Arrange
|
|
250
|
+
render(
|
|
251
|
+
<ThemeSwitcherContext.Provider value="khanmigo">
|
|
252
|
+
<Button
|
|
253
|
+
kind="tertiary"
|
|
254
|
+
testId={"button-icon-test"}
|
|
255
|
+
endIcon={plus}
|
|
256
|
+
>
|
|
257
|
+
Label
|
|
258
|
+
</Button>
|
|
259
|
+
</ThemeSwitcherContext.Provider>,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Act
|
|
263
|
+
const button = screen.getByTestId("button-icon-test");
|
|
264
|
+
const iconWrapper = screen.getByTestId(
|
|
265
|
+
"button-icon-test-end-icon-wrapper",
|
|
266
|
+
);
|
|
267
|
+
userEvent.hover(button);
|
|
268
|
+
|
|
269
|
+
// Assert
|
|
270
|
+
expect(iconWrapper).toHaveStyle(
|
|
271
|
+
`backgroundColor: ${tokens.color.fadedBlue16}`,
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test for Wonder Blocks Button component.
|
|
3
|
+
*
|
|
4
|
+
* The test for buttons with icons are in a separate file
|
|
5
|
+
* (button-with-icon.test.tsx) since this one is already too long.
|
|
6
|
+
*/
|
|
1
7
|
import * as React from "react";
|
|
2
8
|
import {MemoryRouter, Route, Switch} from "react-router-dom";
|
|
3
9
|
import {render, screen, waitFor} from "@testing-library/react";
|
|
4
10
|
import userEvent from "@testing-library/user-event";
|
|
5
|
-
import plus from "@phosphor-icons/core/regular/plus.svg";
|
|
6
11
|
|
|
7
12
|
import Button from "../button";
|
|
8
13
|
|
|
@@ -828,91 +833,4 @@ describe("Button", () => {
|
|
|
828
833
|
}).not.toThrow();
|
|
829
834
|
});
|
|
830
835
|
});
|
|
831
|
-
|
|
832
|
-
describe("button with icon", () => {
|
|
833
|
-
test("icon is displayed when button contains startIcon", () => {
|
|
834
|
-
// Arrange
|
|
835
|
-
render(
|
|
836
|
-
<Button testId={"button-focus-test"} startIcon={plus}>
|
|
837
|
-
Label
|
|
838
|
-
</Button>,
|
|
839
|
-
);
|
|
840
|
-
|
|
841
|
-
// Act
|
|
842
|
-
const icon = screen.getByTestId("button-focus-test-start-icon");
|
|
843
|
-
|
|
844
|
-
// Assert
|
|
845
|
-
expect(icon).toBeInTheDocument();
|
|
846
|
-
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
847
|
-
});
|
|
848
|
-
|
|
849
|
-
test("icon is displayed when button contains endIcon", () => {
|
|
850
|
-
// Arrange
|
|
851
|
-
render(
|
|
852
|
-
<Button testId={"button-focus-test"} endIcon={plus}>
|
|
853
|
-
Label
|
|
854
|
-
</Button>,
|
|
855
|
-
);
|
|
856
|
-
|
|
857
|
-
// Act
|
|
858
|
-
const icon = screen.getByTestId("button-focus-test-end-icon");
|
|
859
|
-
|
|
860
|
-
// Assert
|
|
861
|
-
expect(icon).toBeInTheDocument();
|
|
862
|
-
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
test("both icons are displayed when button contains startIcon and endIcon", () => {
|
|
866
|
-
// Arrange
|
|
867
|
-
render(
|
|
868
|
-
<Button
|
|
869
|
-
testId={"button-focus-test"}
|
|
870
|
-
startIcon={plus}
|
|
871
|
-
endIcon={plus}
|
|
872
|
-
>
|
|
873
|
-
Label
|
|
874
|
-
</Button>,
|
|
875
|
-
);
|
|
876
|
-
|
|
877
|
-
// Act
|
|
878
|
-
const startIcon = screen.getByTestId(
|
|
879
|
-
"button-focus-test-start-icon",
|
|
880
|
-
);
|
|
881
|
-
const endIcon = screen.getByTestId("button-focus-test-end-icon");
|
|
882
|
-
|
|
883
|
-
// Assert
|
|
884
|
-
expect(startIcon).toBeInTheDocument();
|
|
885
|
-
expect(endIcon).toBeInTheDocument();
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
test("start icon should be hidden from Screen Readers", () => {
|
|
889
|
-
// Arrange
|
|
890
|
-
render(
|
|
891
|
-
<Button testId={"button-focus-test"} startIcon={plus}>
|
|
892
|
-
Label
|
|
893
|
-
</Button>,
|
|
894
|
-
);
|
|
895
|
-
|
|
896
|
-
// Act
|
|
897
|
-
const icon = screen.getByTestId("button-focus-test-start-icon");
|
|
898
|
-
|
|
899
|
-
// Assert
|
|
900
|
-
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
test("end icon should be hidden from Screen Readers", () => {
|
|
904
|
-
// Arrange
|
|
905
|
-
render(
|
|
906
|
-
<Button testId={"button-focus-test"} endIcon={plus}>
|
|
907
|
-
Label
|
|
908
|
-
</Button>,
|
|
909
|
-
);
|
|
910
|
-
|
|
911
|
-
// Act
|
|
912
|
-
const icon = screen.getByTestId("button-focus-test-end-icon");
|
|
913
|
-
|
|
914
|
-
// Assert
|
|
915
|
-
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
916
|
-
});
|
|
917
|
-
});
|
|
918
836
|
});
|
|
@@ -4,7 +4,7 @@ import {Link} from "react-router-dom";
|
|
|
4
4
|
import {__RouterContext} from "react-router";
|
|
5
5
|
|
|
6
6
|
import {LabelLarge, LabelSmall} from "@khanacademy/wonder-blocks-typography";
|
|
7
|
-
import {addStyle} from "@khanacademy/wonder-blocks-core";
|
|
7
|
+
import {addStyle, View} from "@khanacademy/wonder-blocks-core";
|
|
8
8
|
import {CircularSpinner} from "@khanacademy/wonder-blocks-progress-spinner";
|
|
9
9
|
import {isClientSideUrl} from "@khanacademy/wonder-blocks-clickable";
|
|
10
10
|
import {
|
|
@@ -138,12 +138,24 @@ const ButtonCore: React.ForwardRefExoticComponent<
|
|
|
138
138
|
const contents = (
|
|
139
139
|
<React.Fragment>
|
|
140
140
|
{startIcon && (
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
<View
|
|
142
|
+
// The start icon doesn't have the circle around it
|
|
143
|
+
// in the Khanmigo theme, but we wrap it with
|
|
144
|
+
// iconWrapper anyway to give it the same spacing
|
|
145
|
+
// as the end icon so the button is symmetrical.
|
|
146
|
+
style={sharedStyles.iconWrapper}
|
|
147
|
+
>
|
|
148
|
+
<ButtonIcon
|
|
149
|
+
size={iconSize}
|
|
150
|
+
icon={startIcon}
|
|
151
|
+
style={[
|
|
152
|
+
sharedStyles.startIcon,
|
|
153
|
+
kind === "tertiary" &&
|
|
154
|
+
sharedStyles.tertiaryStartIcon,
|
|
155
|
+
]}
|
|
156
|
+
testId={testId ? `${testId}-start-icon` : undefined}
|
|
157
|
+
/>
|
|
158
|
+
</View>
|
|
147
159
|
)}
|
|
148
160
|
{label}
|
|
149
161
|
{spinner && (
|
|
@@ -155,12 +167,27 @@ const ButtonCore: React.ForwardRefExoticComponent<
|
|
|
155
167
|
/>
|
|
156
168
|
)}
|
|
157
169
|
{endIcon && (
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
<View
|
|
171
|
+
testId={
|
|
172
|
+
testId ? `${testId}-end-icon-wrapper` : undefined
|
|
173
|
+
}
|
|
174
|
+
style={[
|
|
175
|
+
styles.endIcon,
|
|
176
|
+
sharedStyles.iconWrapper,
|
|
177
|
+
sharedStyles.endIconWrapper,
|
|
178
|
+
kind === "tertiary" &&
|
|
179
|
+
sharedStyles.endIconWrapperTertiary,
|
|
180
|
+
(focused || hovered) &&
|
|
181
|
+
kind !== "primary" &&
|
|
182
|
+
sharedStyles.iconWrapperSecondaryHovered,
|
|
183
|
+
]}
|
|
184
|
+
>
|
|
185
|
+
<ButtonIcon
|
|
186
|
+
size={iconSize}
|
|
187
|
+
icon={endIcon}
|
|
188
|
+
testId={testId ? `${testId}-end-icon` : undefined}
|
|
189
|
+
/>
|
|
190
|
+
</View>
|
|
164
191
|
)}
|
|
165
192
|
</React.Fragment>
|
|
166
193
|
);
|
|
@@ -232,10 +259,6 @@ const themedSharedStyles: ThemedStylesFn<ButtonThemeContract> = (theme) => ({
|
|
|
232
259
|
WebkitTapHighlightColor: "rgba(0,0,0,0)",
|
|
233
260
|
},
|
|
234
261
|
},
|
|
235
|
-
withIcon: {
|
|
236
|
-
// The left padding for the button with icon should have 4px less padding
|
|
237
|
-
paddingLeft: theme.padding.medium,
|
|
238
|
-
},
|
|
239
262
|
disabled: {
|
|
240
263
|
cursor: "auto",
|
|
241
264
|
},
|
|
@@ -258,7 +281,7 @@ const themedSharedStyles: ThemedStylesFn<ButtonThemeContract> = (theme) => ({
|
|
|
258
281
|
},
|
|
259
282
|
largeText: {
|
|
260
283
|
fontSize: theme.font.size.large,
|
|
261
|
-
lineHeight: theme.font.lineHeight.large
|
|
284
|
+
lineHeight: `${theme.font.lineHeight.large}px`,
|
|
262
285
|
},
|
|
263
286
|
textWithFocus: {
|
|
264
287
|
position: "relative", // allows the tertiary button border to use the label width
|
|
@@ -270,16 +293,42 @@ const themedSharedStyles: ThemedStylesFn<ButtonThemeContract> = (theme) => ({
|
|
|
270
293
|
position: "absolute",
|
|
271
294
|
},
|
|
272
295
|
startIcon: {
|
|
273
|
-
|
|
296
|
+
marginRight: theme.padding.small,
|
|
297
|
+
marginLeft: theme.margin.icon.offset,
|
|
298
|
+
},
|
|
299
|
+
tertiaryStartIcon: {
|
|
300
|
+
// Undo the negative padding from startIcon since tertiary
|
|
301
|
+
// buttons don't have extra padding.
|
|
302
|
+
marginLeft: 0,
|
|
274
303
|
},
|
|
275
304
|
endIcon: {
|
|
276
|
-
|
|
305
|
+
marginLeft: theme.padding.small,
|
|
306
|
+
},
|
|
307
|
+
iconWrapper: {
|
|
308
|
+
borderRadius: theme.border.radius.icon,
|
|
309
|
+
padding: theme.padding.xsmall,
|
|
310
|
+
// View has a default minWidth of 0, which causes the label text
|
|
311
|
+
// to encroach on the icon when it needs to truncate. We can fix
|
|
312
|
+
// this by setting the minWidth to auto.
|
|
313
|
+
minWidth: "auto",
|
|
314
|
+
},
|
|
315
|
+
iconWrapperSecondaryHovered: {
|
|
316
|
+
backgroundColor: theme.color.bg.icon.secondaryHover,
|
|
317
|
+
color: theme.color.text.icon.secondaryHover,
|
|
318
|
+
},
|
|
319
|
+
endIconWrapper: {
|
|
320
|
+
marginLeft: theme.padding.small,
|
|
321
|
+
marginRight: theme.margin.icon.offset,
|
|
322
|
+
},
|
|
323
|
+
endIconWrapperTertiary: {
|
|
324
|
+
marginRight: 0,
|
|
277
325
|
},
|
|
278
326
|
});
|
|
279
327
|
|
|
280
328
|
const styles: Record<string, any> = {};
|
|
281
329
|
|
|
282
|
-
|
|
330
|
+
// export for testing only
|
|
331
|
+
export const _generateStyles = (
|
|
283
332
|
buttonColor = "default",
|
|
284
333
|
kind: "primary" | "secondary" | "tertiary",
|
|
285
334
|
light: boolean,
|
package/src/themes/default.ts
CHANGED
|
@@ -42,6 +42,13 @@ const theme = {
|
|
|
42
42
|
tertiary: {
|
|
43
43
|
hover: tokens.color.white,
|
|
44
44
|
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Icons
|
|
48
|
+
*/
|
|
49
|
+
icon: {
|
|
50
|
+
secondaryHover: "transparent",
|
|
51
|
+
},
|
|
45
52
|
},
|
|
46
53
|
text: {
|
|
47
54
|
/**
|
|
@@ -51,6 +58,7 @@ const theme = {
|
|
|
51
58
|
disabled: tokens.color.offBlack32,
|
|
52
59
|
// kind="primary", light=false | kind="secondary, tertiary", light=true
|
|
53
60
|
inverse: tokens.color.white,
|
|
61
|
+
|
|
54
62
|
/**
|
|
55
63
|
* Kind
|
|
56
64
|
*/
|
|
@@ -60,6 +68,13 @@ const theme = {
|
|
|
60
68
|
secondary: {
|
|
61
69
|
inverse: tokens.color.white50,
|
|
62
70
|
},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Icons
|
|
74
|
+
*/
|
|
75
|
+
icon: {
|
|
76
|
+
secondaryHover: tokens.color.blue,
|
|
77
|
+
},
|
|
63
78
|
},
|
|
64
79
|
border: {
|
|
65
80
|
/**
|
|
@@ -101,6 +116,11 @@ const theme = {
|
|
|
101
116
|
small: tokens.border.radius.medium_4,
|
|
102
117
|
// large button
|
|
103
118
|
large: tokens.border.radius.large_6,
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Icons
|
|
122
|
+
*/
|
|
123
|
+
icon: tokens.border.radius.full,
|
|
104
124
|
},
|
|
105
125
|
},
|
|
106
126
|
size: {
|
|
@@ -112,8 +132,14 @@ const theme = {
|
|
|
112
132
|
large: 56,
|
|
113
133
|
},
|
|
114
134
|
},
|
|
135
|
+
margin: {
|
|
136
|
+
icon: {
|
|
137
|
+
offset: -tokens.spacing.xxxxSmall_2,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
115
140
|
padding: {
|
|
116
|
-
|
|
141
|
+
xsmall: tokens.spacing.xxxxSmall_2,
|
|
142
|
+
small: tokens.spacing.xxSmall_6,
|
|
117
143
|
medium: tokens.spacing.small_12,
|
|
118
144
|
large: tokens.spacing.medium_16,
|
|
119
145
|
xLarge: tokens.spacing.xLarge_32,
|
package/src/themes/khanmigo.ts
CHANGED
|
@@ -15,6 +15,9 @@ const theme = mergeTheme(defaultTheme, {
|
|
|
15
15
|
},
|
|
16
16
|
focus: tokens.color.offWhite,
|
|
17
17
|
},
|
|
18
|
+
icon: {
|
|
19
|
+
secondaryHover: tokens.color.fadedBlue16,
|
|
20
|
+
},
|
|
18
21
|
},
|
|
19
22
|
border: {
|
|
20
23
|
secondary: {
|
|
@@ -22,6 +25,11 @@ const theme = mergeTheme(defaultTheme, {
|
|
|
22
25
|
critical: tokens.color.fadedRed,
|
|
23
26
|
},
|
|
24
27
|
},
|
|
28
|
+
text: {
|
|
29
|
+
icon: {
|
|
30
|
+
secondaryHover: tokens.color.blue,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
25
33
|
},
|
|
26
34
|
border: {
|
|
27
35
|
radius: {
|
|
@@ -33,6 +41,12 @@ const theme = mergeTheme(defaultTheme, {
|
|
|
33
41
|
focused: tokens.border.width.hairline,
|
|
34
42
|
},
|
|
35
43
|
},
|
|
44
|
+
margin: {
|
|
45
|
+
icon: {
|
|
46
|
+
// Bring the icons closer to the edges of the button.
|
|
47
|
+
offset: -tokens.spacing.xSmall_8,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
36
50
|
font: {
|
|
37
51
|
weight: {
|
|
38
52
|
default: tokens.font.weight.regular,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import {
|
|
3
3
|
createThemeContext,
|
|
4
|
+
Themes,
|
|
4
5
|
ThemeSwitcherContext,
|
|
5
6
|
} from "@khanacademy/wonder-blocks-theming";
|
|
6
7
|
|
|
@@ -11,16 +12,16 @@ type Props = {
|
|
|
11
12
|
children: React.ReactNode;
|
|
12
13
|
};
|
|
13
14
|
|
|
15
|
+
export type ButtonThemeContract = typeof defaultTheme;
|
|
16
|
+
|
|
14
17
|
/**
|
|
15
18
|
* The themes available to the Button component.
|
|
16
19
|
*/
|
|
17
|
-
const themes = {
|
|
20
|
+
const themes: Themes<ButtonThemeContract> = {
|
|
18
21
|
default: defaultTheme,
|
|
19
22
|
khanmigo: khanmigoTheme,
|
|
20
23
|
};
|
|
21
24
|
|
|
22
|
-
export type ButtonThemeContract = typeof defaultTheme;
|
|
23
|
-
|
|
24
25
|
/**
|
|
25
26
|
* The context that provides the theme to the Button component.
|
|
26
27
|
* This is generally consumed via the `useScopedTheme` hook.
|
|
@@ -33,7 +34,7 @@ export const ButtonThemeContext = createThemeContext(defaultTheme);
|
|
|
33
34
|
export default function ThemedButton(props: Props) {
|
|
34
35
|
const currentTheme = React.useContext(ThemeSwitcherContext);
|
|
35
36
|
|
|
36
|
-
const theme = themes[currentTheme
|
|
37
|
+
const theme = themes[currentTheme] || defaultTheme;
|
|
37
38
|
return (
|
|
38
39
|
<ButtonThemeContext.Provider value={theme}>
|
|
39
40
|
{props.children}
|