@khanacademy/wonder-blocks-button 6.0.1 → 6.2.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 +17 -0
- package/dist/components/button-core.d.ts +2 -0
- package/dist/components/button.d.ts +4 -0
- package/dist/es/index.js +81 -47
- package/dist/index.js +80 -46
- 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 +2 -2
- package/src/__tests__/__snapshots__/custom-snapshot.test.tsx.snap +384 -446
- 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 +94 -66
- package/src/components/button.tsx +4 -0
- package/src/themes/default.ts +27 -1
- package/src/themes/khanmigo.ts +14 -0
- 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 {
|
|
@@ -47,6 +47,7 @@ const ButtonCore: React.ForwardRefExoticComponent<
|
|
|
47
47
|
hovered,
|
|
48
48
|
href = undefined,
|
|
49
49
|
kind = "primary",
|
|
50
|
+
labelStyle,
|
|
50
51
|
light = false,
|
|
51
52
|
pressed,
|
|
52
53
|
size = "medium",
|
|
@@ -110,6 +111,7 @@ const ButtonCore: React.ForwardRefExoticComponent<
|
|
|
110
111
|
style={[
|
|
111
112
|
sharedStyles.text,
|
|
112
113
|
size === "large" && sharedStyles.largeText,
|
|
114
|
+
labelStyle,
|
|
113
115
|
spinner && sharedStyles.hiddenText,
|
|
114
116
|
kind === "tertiary" && sharedStyles.textWithFocus,
|
|
115
117
|
// apply press/hover effects on the label
|
|
@@ -138,12 +140,24 @@ const ButtonCore: React.ForwardRefExoticComponent<
|
|
|
138
140
|
const contents = (
|
|
139
141
|
<React.Fragment>
|
|
140
142
|
{startIcon && (
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
<View
|
|
144
|
+
// The start icon doesn't have the circle around it
|
|
145
|
+
// in the Khanmigo theme, but we wrap it with
|
|
146
|
+
// iconWrapper anyway to give it the same spacing
|
|
147
|
+
// as the end icon so the button is symmetrical.
|
|
148
|
+
style={sharedStyles.iconWrapper}
|
|
149
|
+
>
|
|
150
|
+
<ButtonIcon
|
|
151
|
+
size={iconSize}
|
|
152
|
+
icon={startIcon}
|
|
153
|
+
style={[
|
|
154
|
+
sharedStyles.startIcon,
|
|
155
|
+
kind === "tertiary" &&
|
|
156
|
+
sharedStyles.tertiaryStartIcon,
|
|
157
|
+
]}
|
|
158
|
+
testId={testId ? `${testId}-start-icon` : undefined}
|
|
159
|
+
/>
|
|
160
|
+
</View>
|
|
147
161
|
)}
|
|
148
162
|
{label}
|
|
149
163
|
{spinner && (
|
|
@@ -155,12 +169,27 @@ const ButtonCore: React.ForwardRefExoticComponent<
|
|
|
155
169
|
/>
|
|
156
170
|
)}
|
|
157
171
|
{endIcon && (
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
172
|
+
<View
|
|
173
|
+
testId={
|
|
174
|
+
testId ? `${testId}-end-icon-wrapper` : undefined
|
|
175
|
+
}
|
|
176
|
+
style={[
|
|
177
|
+
styles.endIcon,
|
|
178
|
+
sharedStyles.iconWrapper,
|
|
179
|
+
sharedStyles.endIconWrapper,
|
|
180
|
+
kind === "tertiary" &&
|
|
181
|
+
sharedStyles.endIconWrapperTertiary,
|
|
182
|
+
(focused || hovered) &&
|
|
183
|
+
kind !== "primary" &&
|
|
184
|
+
sharedStyles.iconWrapperSecondaryHovered,
|
|
185
|
+
]}
|
|
186
|
+
>
|
|
187
|
+
<ButtonIcon
|
|
188
|
+
size={iconSize}
|
|
189
|
+
icon={endIcon}
|
|
190
|
+
testId={testId ? `${testId}-end-icon` : undefined}
|
|
191
|
+
/>
|
|
192
|
+
</View>
|
|
164
193
|
)}
|
|
165
194
|
</React.Fragment>
|
|
166
195
|
);
|
|
@@ -232,10 +261,6 @@ const themedSharedStyles: ThemedStylesFn<ButtonThemeContract> = (theme) => ({
|
|
|
232
261
|
WebkitTapHighlightColor: "rgba(0,0,0,0)",
|
|
233
262
|
},
|
|
234
263
|
},
|
|
235
|
-
withIcon: {
|
|
236
|
-
// The left padding for the button with icon should have 4px less padding
|
|
237
|
-
paddingLeft: theme.padding.medium,
|
|
238
|
-
},
|
|
239
264
|
disabled: {
|
|
240
265
|
cursor: "auto",
|
|
241
266
|
},
|
|
@@ -258,7 +283,7 @@ const themedSharedStyles: ThemedStylesFn<ButtonThemeContract> = (theme) => ({
|
|
|
258
283
|
},
|
|
259
284
|
largeText: {
|
|
260
285
|
fontSize: theme.font.size.large,
|
|
261
|
-
lineHeight: theme.font.lineHeight.large
|
|
286
|
+
lineHeight: `${theme.font.lineHeight.large}px`,
|
|
262
287
|
},
|
|
263
288
|
textWithFocus: {
|
|
264
289
|
position: "relative", // allows the tertiary button border to use the label width
|
|
@@ -270,16 +295,42 @@ const themedSharedStyles: ThemedStylesFn<ButtonThemeContract> = (theme) => ({
|
|
|
270
295
|
position: "absolute",
|
|
271
296
|
},
|
|
272
297
|
startIcon: {
|
|
273
|
-
|
|
298
|
+
marginRight: theme.padding.small,
|
|
299
|
+
marginLeft: theme.margin.icon.offset,
|
|
300
|
+
},
|
|
301
|
+
tertiaryStartIcon: {
|
|
302
|
+
// Undo the negative padding from startIcon since tertiary
|
|
303
|
+
// buttons don't have extra padding.
|
|
304
|
+
marginLeft: 0,
|
|
274
305
|
},
|
|
275
306
|
endIcon: {
|
|
276
|
-
|
|
307
|
+
marginLeft: theme.padding.small,
|
|
308
|
+
},
|
|
309
|
+
iconWrapper: {
|
|
310
|
+
borderRadius: theme.border.radius.icon,
|
|
311
|
+
padding: theme.padding.xsmall,
|
|
312
|
+
// View has a default minWidth of 0, which causes the label text
|
|
313
|
+
// to encroach on the icon when it needs to truncate. We can fix
|
|
314
|
+
// this by setting the minWidth to auto.
|
|
315
|
+
minWidth: "auto",
|
|
316
|
+
},
|
|
317
|
+
iconWrapperSecondaryHovered: {
|
|
318
|
+
backgroundColor: theme.color.bg.icon.secondaryHover,
|
|
319
|
+
color: theme.color.text.icon.secondaryHover,
|
|
320
|
+
},
|
|
321
|
+
endIconWrapper: {
|
|
322
|
+
marginLeft: theme.padding.small,
|
|
323
|
+
marginRight: theme.margin.icon.offset,
|
|
324
|
+
},
|
|
325
|
+
endIconWrapperTertiary: {
|
|
326
|
+
marginRight: 0,
|
|
277
327
|
},
|
|
278
328
|
});
|
|
279
329
|
|
|
280
330
|
const styles: Record<string, any> = {};
|
|
281
331
|
|
|
282
|
-
|
|
332
|
+
// export for testing only
|
|
333
|
+
export const _generateStyles = (
|
|
283
334
|
buttonColor = "default",
|
|
284
335
|
kind: "primary" | "secondary" | "tertiary",
|
|
285
336
|
light: boolean,
|
|
@@ -356,7 +407,6 @@ const _generateStyles = (
|
|
|
356
407
|
},
|
|
357
408
|
};
|
|
358
409
|
} else if (kind === "secondary") {
|
|
359
|
-
const horizontalPadding = padding - (theme.border.width.focused - 1);
|
|
360
410
|
const secondaryBorderColor =
|
|
361
411
|
buttonColor === "destructive"
|
|
362
412
|
? theme.color.border.secondary.critical
|
|
@@ -372,11 +422,12 @@ const _generateStyles = (
|
|
|
372
422
|
? theme.color.bg.secondary.inverse
|
|
373
423
|
: theme.color.bg.secondary.default,
|
|
374
424
|
color: light ? theme.color.text.inverse : color,
|
|
375
|
-
|
|
425
|
+
outlineColor: light
|
|
376
426
|
? theme.color.border.secondary.inverse
|
|
377
427
|
: secondaryBorderColor,
|
|
378
|
-
|
|
379
|
-
|
|
428
|
+
outlineStyle: "solid",
|
|
429
|
+
outlineWidth: theme.border.width.secondary,
|
|
430
|
+
outlineOffset: 1,
|
|
380
431
|
paddingLeft: padding,
|
|
381
432
|
paddingRight: padding,
|
|
382
433
|
},
|
|
@@ -384,37 +435,29 @@ const _generateStyles = (
|
|
|
384
435
|
background: light
|
|
385
436
|
? theme.color.bg.secondary.inverse
|
|
386
437
|
: theme.color.bg.secondary.focus,
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
438
|
+
outlineColor: light
|
|
439
|
+
? theme.color.border.primary.inverse
|
|
440
|
+
: color,
|
|
441
|
+
outlineWidth: theme.border.width.focused,
|
|
391
442
|
},
|
|
392
443
|
|
|
393
444
|
active: {
|
|
394
445
|
background: light ? activeColor : secondaryActiveColor,
|
|
395
446
|
color: light ? fadedColor : activeColor,
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
// We need to reduce padding to offset the difference
|
|
399
|
-
// caused by the border becoming thicker on focus.
|
|
400
|
-
paddingLeft: horizontalPadding,
|
|
401
|
-
paddingRight: horizontalPadding,
|
|
447
|
+
outlineColor: light ? fadedColor : activeColor,
|
|
448
|
+
outlineWidth: theme.border.width.focused,
|
|
402
449
|
},
|
|
403
450
|
disabled: {
|
|
404
451
|
color: light
|
|
405
452
|
? theme.color.text.secondary.inverse
|
|
406
453
|
: theme.color.text.disabled,
|
|
407
|
-
|
|
454
|
+
outlineColor: light ? fadedColor : theme.color.border.disabled,
|
|
408
455
|
cursor: "default",
|
|
409
456
|
":focus": {
|
|
410
|
-
|
|
457
|
+
outlineColor: light
|
|
411
458
|
? theme.color.border.secondary.inverse
|
|
412
459
|
: theme.color.border.disabled,
|
|
413
|
-
|
|
414
|
-
// We need to reduce padding to offset the difference
|
|
415
|
-
// caused by the border becoming thicker on focus.
|
|
416
|
-
paddingLeft: padding - 1,
|
|
417
|
-
paddingRight: padding - 1,
|
|
460
|
+
outlineWidth: theme.border.width.disabled,
|
|
418
461
|
},
|
|
419
462
|
},
|
|
420
463
|
};
|
|
@@ -439,25 +482,12 @@ const _generateStyles = (
|
|
|
439
482
|
},
|
|
440
483
|
},
|
|
441
484
|
focus: {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
// Keeps the button at the same size when applying the
|
|
449
|
-
// borderWidth property, so we can apply the correct value
|
|
450
|
-
// per theme for each side (left and right).
|
|
451
|
-
width: `calc(100% + ${theme.border.width.focused * 2}px)`,
|
|
452
|
-
// Same as above, but for the height (top and bottom).
|
|
453
|
-
height: `calc(100% - ${theme.border.width.focused * 2}px)`,
|
|
454
|
-
borderStyle: "solid",
|
|
455
|
-
borderColor: light
|
|
456
|
-
? theme.color.border.tertiary.inverse
|
|
457
|
-
: color,
|
|
458
|
-
borderWidth: theme.border.width.focused,
|
|
459
|
-
borderRadius: theme.border.radius.default,
|
|
460
|
-
},
|
|
485
|
+
outlineStyle: "solid",
|
|
486
|
+
outlineColor: light
|
|
487
|
+
? theme.color.border.tertiary.inverse
|
|
488
|
+
: color,
|
|
489
|
+
outlineWidth: theme.border.width.focused,
|
|
490
|
+
borderRadius: theme.border.radius.default,
|
|
461
491
|
},
|
|
462
492
|
active: {
|
|
463
493
|
color: light ? fadedColor : activeColor,
|
|
@@ -471,11 +501,9 @@ const _generateStyles = (
|
|
|
471
501
|
cursor: "default",
|
|
472
502
|
},
|
|
473
503
|
disabledFocus: {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
: theme.color.border.disabled,
|
|
478
|
-
},
|
|
504
|
+
outlineColor: light
|
|
505
|
+
? theme.color.border.tertiary.inverse
|
|
506
|
+
: theme.color.border.disabled,
|
|
479
507
|
},
|
|
480
508
|
};
|
|
481
509
|
} else {
|