@khanacademy/wonder-blocks-icon-button 5.0.0 → 5.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 +22 -0
- package/dist/components/icon-button-core.d.ts +1 -2
- package/dist/es/index.js +276 -74
- package/dist/index.js +276 -77
- package/dist/themes/default.d.ts +93 -0
- package/dist/themes/khanmigo.d.ts +81 -0
- package/dist/themes/themed-icon-button.d.ts +93 -0
- package/package.json +5 -5
- package/src/__tests__/__snapshots__/custom-snapshot.test.tsx.snap +3402 -606
- package/src/components/__tests__/icon-button.test.tsx +53 -0
- package/src/components/icon-button-core.tsx +205 -65
- package/src/components/icon-button.tsx +14 -41
- package/src/themes/default.ts +111 -0
- package/src/themes/khanmigo.ts +76 -0
- package/src/themes/themed-icon-button.tsx +44 -0
- package/tsconfig-build.json +1 -1
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -162,4 +162,57 @@ describe("IconButton", () => {
|
|
|
162
162
|
// Assert
|
|
163
163
|
expect(screen.queryByText("Hello, world!")).not.toBeInTheDocument();
|
|
164
164
|
});
|
|
165
|
+
|
|
166
|
+
test("disallow press/click when disabled is set", () => {
|
|
167
|
+
// Arrange
|
|
168
|
+
const onClickMock = jest.fn();
|
|
169
|
+
render(
|
|
170
|
+
<IconButton
|
|
171
|
+
icon={magnifyingGlassIcon}
|
|
172
|
+
aria-label="search"
|
|
173
|
+
testId="icon-button"
|
|
174
|
+
onClick={onClickMock}
|
|
175
|
+
disabled={true}
|
|
176
|
+
/>,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Act
|
|
180
|
+
userEvent.click(screen.getByRole("button"));
|
|
181
|
+
|
|
182
|
+
// Assert
|
|
183
|
+
expect(onClickMock).not.toBeCalled();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("sets the 'target' prop on the underlying element", () => {
|
|
187
|
+
// Arrange
|
|
188
|
+
render(
|
|
189
|
+
<IconButton
|
|
190
|
+
icon={magnifyingGlassIcon}
|
|
191
|
+
href="https://www.khanacademy.org"
|
|
192
|
+
target="_blank"
|
|
193
|
+
/>,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// Act
|
|
197
|
+
const link = screen.getByRole("link");
|
|
198
|
+
userEvent.click(link);
|
|
199
|
+
|
|
200
|
+
// Assert
|
|
201
|
+
expect(link).toHaveAttribute("target", "_blank");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("renders an <a> if the href is '#'", () => {
|
|
205
|
+
// Arrange
|
|
206
|
+
render(
|
|
207
|
+
<MemoryRouter>
|
|
208
|
+
<IconButton icon={magnifyingGlassIcon} href="#" />,
|
|
209
|
+
</MemoryRouter>,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// Act
|
|
213
|
+
const link = screen.getByRole("link");
|
|
214
|
+
|
|
215
|
+
// Assert
|
|
216
|
+
expect(link.tagName).toBe("A");
|
|
217
|
+
});
|
|
165
218
|
});
|
|
@@ -3,24 +3,20 @@ import {StyleSheet} from "aphrodite";
|
|
|
3
3
|
import {Link} from "react-router-dom";
|
|
4
4
|
import {__RouterContext} from "react-router";
|
|
5
5
|
|
|
6
|
-
import Color, {
|
|
7
|
-
SemanticColor,
|
|
8
|
-
mix,
|
|
9
|
-
fade,
|
|
10
|
-
} from "@khanacademy/wonder-blocks-color";
|
|
11
6
|
import {addStyle} from "@khanacademy/wonder-blocks-core";
|
|
12
7
|
import {isClientSideUrl} from "@khanacademy/wonder-blocks-clickable";
|
|
13
8
|
import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
|
|
9
|
+
import {useScopedTheme} from "@khanacademy/wonder-blocks-theming";
|
|
14
10
|
|
|
15
|
-
import type {
|
|
16
|
-
ChildrenProps,
|
|
17
|
-
ClickableState,
|
|
18
|
-
} from "@khanacademy/wonder-blocks-clickable";
|
|
19
11
|
import type {IconButtonSize, SharedProps} from "./icon-button";
|
|
20
12
|
import {
|
|
21
13
|
iconSizeForButtonSize,
|
|
22
14
|
targetPixelsForSize,
|
|
23
15
|
} from "../util/icon-button-util";
|
|
16
|
+
import {
|
|
17
|
+
IconButtonThemeContext,
|
|
18
|
+
IconButtonThemeContract,
|
|
19
|
+
} from "../themes/themed-icon-button";
|
|
24
20
|
|
|
25
21
|
/**
|
|
26
22
|
* Returns the phosphor icon component based on the size. This is necessary
|
|
@@ -55,17 +51,15 @@ function IconChooser({
|
|
|
55
51
|
}
|
|
56
52
|
}
|
|
57
53
|
|
|
58
|
-
type Props = SharedProps &
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
href?: string;
|
|
68
|
-
};
|
|
54
|
+
type Props = SharedProps & {
|
|
55
|
+
/**
|
|
56
|
+
* URL to navigate to.
|
|
57
|
+
*
|
|
58
|
+
* Used to determine whether to render an `<a>` or `<button>` tag. Also
|
|
59
|
+
* passed in as the `<a>` tag's `href` if present.
|
|
60
|
+
*/
|
|
61
|
+
href?: string;
|
|
62
|
+
};
|
|
69
63
|
|
|
70
64
|
const StyledAnchor = addStyle("a");
|
|
71
65
|
const StyledButton = addStyle("button");
|
|
@@ -81,37 +75,32 @@ const IconButtonCore: React.ForwardRefExoticComponent<
|
|
|
81
75
|
const {
|
|
82
76
|
color,
|
|
83
77
|
disabled,
|
|
84
|
-
focused,
|
|
85
|
-
hovered,
|
|
86
78
|
href,
|
|
87
79
|
icon,
|
|
88
80
|
kind = "primary",
|
|
89
81
|
light = false,
|
|
90
|
-
pressed,
|
|
91
82
|
size = "medium",
|
|
92
83
|
skipClientNav,
|
|
93
84
|
style,
|
|
94
85
|
testId,
|
|
95
|
-
waiting: _,
|
|
96
86
|
...restProps
|
|
97
87
|
} = props;
|
|
88
|
+
const {theme, themeName} = useScopedTheme(IconButtonThemeContext);
|
|
98
89
|
|
|
99
90
|
const renderInner = (router: any): React.ReactNode => {
|
|
100
|
-
const
|
|
101
|
-
color
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
91
|
+
const buttonStyles = _generateStyles(
|
|
92
|
+
color,
|
|
93
|
+
kind,
|
|
94
|
+
light,
|
|
95
|
+
size,
|
|
96
|
+
theme,
|
|
97
|
+
themeName,
|
|
98
|
+
);
|
|
106
99
|
|
|
107
100
|
const defaultStyle = [
|
|
108
101
|
sharedStyles.shared,
|
|
109
102
|
buttonStyles.default,
|
|
110
103
|
disabled && buttonStyles.disabled,
|
|
111
|
-
!disabled &&
|
|
112
|
-
(pressed
|
|
113
|
-
? buttonStyles.active
|
|
114
|
-
: (hovered || focused) && buttonStyles.focus),
|
|
115
104
|
];
|
|
116
105
|
|
|
117
106
|
const child = <IconChooser size={size} icon={icon} />;
|
|
@@ -145,7 +134,8 @@ const IconButtonCore: React.ForwardRefExoticComponent<
|
|
|
145
134
|
<StyledButton
|
|
146
135
|
type="button"
|
|
147
136
|
{...commonProps}
|
|
148
|
-
|
|
137
|
+
onClick={disabled ? undefined : restProps.onClick}
|
|
138
|
+
aria-disabled={disabled}
|
|
149
139
|
ref={ref as React.Ref<HTMLButtonElement>}
|
|
150
140
|
>
|
|
151
141
|
{child}
|
|
@@ -180,22 +170,91 @@ const sharedStyles = StyleSheet.create({
|
|
|
180
170
|
// This removes the 300ms click delay on mobile browsers by indicating that
|
|
181
171
|
// "double-tap to zoom" shouldn't be used on this element.
|
|
182
172
|
touchAction: "manipulation",
|
|
183
|
-
":focus": {
|
|
184
|
-
// Mobile: Removes a blue highlight style shown when the user clicks a button
|
|
185
|
-
WebkitTapHighlightColor: "rgba(0,0,0,0)",
|
|
186
|
-
},
|
|
187
173
|
},
|
|
188
174
|
});
|
|
189
175
|
|
|
190
176
|
const styles: Record<string, any> = {};
|
|
191
177
|
|
|
192
|
-
|
|
178
|
+
function getStylesByKind(
|
|
179
|
+
kind: "primary" | "secondary" | "tertiary",
|
|
180
|
+
theme: IconButtonThemeContract,
|
|
193
181
|
color: string,
|
|
182
|
+
light: boolean,
|
|
183
|
+
buttonColor: string,
|
|
184
|
+
) {
|
|
185
|
+
switch (kind) {
|
|
186
|
+
case "primary":
|
|
187
|
+
const primaryHoveredColor =
|
|
188
|
+
buttonColor === "destructive"
|
|
189
|
+
? theme.color.stroke.primary.critical.hovered
|
|
190
|
+
: theme.color.stroke.primary.action.hovered;
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
":hover": {
|
|
194
|
+
backgroundColor: theme.color.bg.hovered,
|
|
195
|
+
color: light
|
|
196
|
+
? theme.color.stroke.primary.inverse.hovered
|
|
197
|
+
: primaryHoveredColor,
|
|
198
|
+
outlineColor: light ? theme.color.stroke.inverse : color,
|
|
199
|
+
outlineOffset: 1,
|
|
200
|
+
outlineStyle: "solid",
|
|
201
|
+
outlineWidth: light
|
|
202
|
+
? theme.border.width.hoveredInverse
|
|
203
|
+
: theme.border.width.hovered,
|
|
204
|
+
},
|
|
205
|
+
":active": {
|
|
206
|
+
backgroundColor: theme.color.bg.active,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
case "secondary":
|
|
210
|
+
case "tertiary":
|
|
211
|
+
return {
|
|
212
|
+
":hover": {
|
|
213
|
+
backgroundColor:
|
|
214
|
+
buttonColor === "destructive"
|
|
215
|
+
? theme.color.bg.filled.critical.hovered
|
|
216
|
+
: theme.color.bg.filled.action.hovered,
|
|
217
|
+
color:
|
|
218
|
+
buttonColor === "destructive"
|
|
219
|
+
? theme.color.stroke.filled.critical.hovered
|
|
220
|
+
: theme.color.stroke.filled.action.hovered,
|
|
221
|
+
outlineWidth: theme.border.width.active,
|
|
222
|
+
},
|
|
223
|
+
":active": {
|
|
224
|
+
backgroundColor:
|
|
225
|
+
buttonColor === "destructive"
|
|
226
|
+
? theme.color.bg.filled.critical.active
|
|
227
|
+
: theme.color.bg.filled.action.active,
|
|
228
|
+
color:
|
|
229
|
+
buttonColor === "destructive"
|
|
230
|
+
? theme.color.stroke.filled.critical.active
|
|
231
|
+
: theme.color.stroke.filled.action.active,
|
|
232
|
+
outlineWidth: theme.border.width.active,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
default:
|
|
236
|
+
return {
|
|
237
|
+
":focus-visible": {},
|
|
238
|
+
":hover": {},
|
|
239
|
+
":active": {},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const _generateStyles = (
|
|
245
|
+
buttonColor = "default",
|
|
194
246
|
kind: "primary" | "secondary" | "tertiary",
|
|
195
247
|
light: boolean,
|
|
196
248
|
size: IconButtonSize,
|
|
249
|
+
theme: IconButtonThemeContract,
|
|
250
|
+
themeName: string,
|
|
197
251
|
) => {
|
|
198
|
-
const
|
|
252
|
+
const color: string =
|
|
253
|
+
buttonColor === "destructive"
|
|
254
|
+
? theme.color.stroke.critical.default
|
|
255
|
+
: theme.color.stroke.action.default;
|
|
256
|
+
|
|
257
|
+
const buttonType = `${color}-${kind}-${light}-${size}-${themeName}`;
|
|
199
258
|
if (styles[buttonType]) {
|
|
200
259
|
return styles[buttonType];
|
|
201
260
|
}
|
|
@@ -204,48 +263,129 @@ const _generateStyles = (
|
|
|
204
263
|
throw new Error("Light is only supported for primary IconButtons");
|
|
205
264
|
}
|
|
206
265
|
|
|
207
|
-
const {white, offBlack32, offBlack64, offBlack} = Color;
|
|
208
266
|
const defaultColor = ((): string => {
|
|
209
267
|
switch (kind) {
|
|
210
268
|
case "primary":
|
|
211
|
-
return light
|
|
269
|
+
return light
|
|
270
|
+
? theme.color.stroke.primary.inverse.default
|
|
271
|
+
: color;
|
|
212
272
|
case "secondary":
|
|
213
|
-
return
|
|
273
|
+
return theme.color.stroke.secondary.default;
|
|
214
274
|
case "tertiary":
|
|
215
|
-
return
|
|
275
|
+
return theme.color.stroke.tertiary.default;
|
|
216
276
|
default:
|
|
217
277
|
throw new Error("IconButton kind not recognized");
|
|
218
278
|
}
|
|
219
279
|
})();
|
|
220
280
|
const pixelsForSize = targetPixelsForSize(size);
|
|
221
281
|
|
|
282
|
+
// Override styles for each kind of button. This is useful for merging
|
|
283
|
+
// pseudo-classes properly.
|
|
284
|
+
const kindOverrides = getStylesByKind(
|
|
285
|
+
kind,
|
|
286
|
+
theme,
|
|
287
|
+
color,
|
|
288
|
+
light,
|
|
289
|
+
buttonColor,
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const activeInverseColor =
|
|
293
|
+
buttonColor === "destructive"
|
|
294
|
+
? theme.color.stroke.critical.inverse
|
|
295
|
+
: theme.color.stroke.action.inverse;
|
|
296
|
+
const activeColor =
|
|
297
|
+
buttonColor === "destructive"
|
|
298
|
+
? theme.color.stroke.critical.active
|
|
299
|
+
: theme.color.stroke.action.active;
|
|
300
|
+
|
|
301
|
+
// Shared by hover and focus states.
|
|
302
|
+
const defaultStrokeColor = light ? theme.color.stroke.inverse : color;
|
|
303
|
+
|
|
304
|
+
const disabledStrokeColor = light
|
|
305
|
+
? theme.color.stroke.disabled.inverse
|
|
306
|
+
: theme.color.stroke.disabled.default;
|
|
307
|
+
|
|
308
|
+
const disabledStatesStyles = {
|
|
309
|
+
backgroundColor: theme.color.bg.disabled,
|
|
310
|
+
color: disabledStrokeColor,
|
|
311
|
+
outlineColor: disabledStrokeColor,
|
|
312
|
+
};
|
|
313
|
+
|
|
222
314
|
const newStyles = {
|
|
223
315
|
default: {
|
|
224
316
|
height: pixelsForSize,
|
|
225
317
|
width: pixelsForSize,
|
|
226
318
|
color: defaultColor,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
319
|
+
borderRadius: theme.border.radius.default,
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* States
|
|
323
|
+
*
|
|
324
|
+
* Defined in the following order: hover, focus, active.
|
|
325
|
+
*/
|
|
326
|
+
":hover": {
|
|
327
|
+
boxShadow: "none",
|
|
328
|
+
color: defaultStrokeColor,
|
|
329
|
+
borderRadius: theme.border.radius.default,
|
|
330
|
+
outlineWidth: theme.border.width.default,
|
|
331
|
+
...kindOverrides[":hover"],
|
|
332
|
+
},
|
|
333
|
+
// Provide basic, default focus styles on older browsers (e.g.
|
|
334
|
+
// Safari 14)
|
|
335
|
+
":focus": {
|
|
336
|
+
boxShadow: `0 0 0 ${theme.border.width.default}px ${defaultStrokeColor}`,
|
|
337
|
+
borderRadius: theme.border.radius.default,
|
|
338
|
+
},
|
|
339
|
+
// Remove default focus styles for mouse users ONLY if
|
|
340
|
+
// :focus-visible is supported on this platform.
|
|
341
|
+
":focus:not(:focus-visible)": {
|
|
342
|
+
boxShadow: "none",
|
|
343
|
+
},
|
|
344
|
+
// Provide focus styles for keyboard users on modern browsers.
|
|
345
|
+
":focus-visible": {
|
|
346
|
+
// Reset default focus styles
|
|
347
|
+
boxShadow: "none",
|
|
348
|
+
// Apply modern focus styles
|
|
349
|
+
outlineWidth: theme.border.width.default,
|
|
350
|
+
outlineColor: defaultStrokeColor,
|
|
351
|
+
outlineOffset: 1,
|
|
352
|
+
outlineStyle: "solid",
|
|
353
|
+
borderRadius: theme.border.radius.default,
|
|
354
|
+
...kindOverrides[":focus-visible"],
|
|
355
|
+
},
|
|
356
|
+
":active": {
|
|
357
|
+
color: light ? activeInverseColor : activeColor,
|
|
358
|
+
outlineWidth: theme.border.width.default,
|
|
359
|
+
outlineColor: light ? activeInverseColor : activeColor,
|
|
360
|
+
outlineOffset: 1,
|
|
361
|
+
outlineStyle: "solid",
|
|
362
|
+
borderRadius: theme.border.radius.default,
|
|
363
|
+
...kindOverrides[":active"],
|
|
364
|
+
},
|
|
245
365
|
},
|
|
246
366
|
disabled: {
|
|
247
|
-
color:
|
|
248
|
-
cursor: "
|
|
367
|
+
color: disabledStrokeColor,
|
|
368
|
+
cursor: "not-allowed",
|
|
369
|
+
// NOTE: Even that browsers recommend to specify pseudo-classes in
|
|
370
|
+
// this order: link, visited, focus, hover, active, we need to
|
|
371
|
+
// specify focus after hover to override hover styles. By doing this
|
|
372
|
+
// we are able to remove the hover outline when the button is
|
|
373
|
+
// disabled.
|
|
374
|
+
// For order reference: https://css-tricks.com/snippets/css/link-pseudo-classes-in-order/
|
|
375
|
+
":hover": {...disabledStatesStyles, outline: "none"},
|
|
376
|
+
":active": {...disabledStatesStyles, outline: "none"},
|
|
377
|
+
// Provide basic, default focus styles on older browsers (e.g.
|
|
378
|
+
// Safari 14)
|
|
379
|
+
":focus": {
|
|
380
|
+
boxShadow: `0 0 0 ${theme.border.width.default}px ${disabledStrokeColor}`,
|
|
381
|
+
borderRadius: theme.border.radius.default,
|
|
382
|
+
},
|
|
383
|
+
// Remove default focus styles for mouse users ONLY if
|
|
384
|
+
// :focus-visible is supported on this platform.
|
|
385
|
+
":focus:not(:focus-visible)": {
|
|
386
|
+
boxShadow: "none",
|
|
387
|
+
},
|
|
388
|
+
":focus-visible": disabledStatesStyles,
|
|
249
389
|
},
|
|
250
390
|
} as const;
|
|
251
391
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {__RouterContext} from "react-router";
|
|
3
2
|
|
|
4
|
-
import {getClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
|
|
5
3
|
import type {PhosphorIconAsset} from "@khanacademy/wonder-blocks-icon";
|
|
6
4
|
import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
|
|
7
5
|
import {Link} from "react-router-dom";
|
|
8
6
|
import IconButtonCore from "./icon-button-core";
|
|
7
|
+
import ThemedIconButton from "../themes/themed-icon-button";
|
|
9
8
|
|
|
10
9
|
export type IconButtonSize = "xsmall" | "small" | "medium";
|
|
11
10
|
|
|
@@ -172,54 +171,28 @@ export const IconButton: React.ForwardRefExoticComponent<
|
|
|
172
171
|
href,
|
|
173
172
|
kind = "primary",
|
|
174
173
|
light = false,
|
|
175
|
-
onClick,
|
|
176
174
|
size = "medium",
|
|
177
175
|
skipClientNav,
|
|
178
176
|
tabIndex,
|
|
179
177
|
target,
|
|
180
178
|
...sharedProps
|
|
181
179
|
} = props;
|
|
182
|
-
const renderClickableBehavior = (router: any): React.ReactNode => {
|
|
183
|
-
const ClickableBehavior = getClickableBehavior(
|
|
184
|
-
href,
|
|
185
|
-
skipClientNav,
|
|
186
|
-
router,
|
|
187
|
-
);
|
|
188
180
|
|
|
189
|
-
|
|
190
|
-
|
|
181
|
+
return (
|
|
182
|
+
<ThemedIconButton>
|
|
183
|
+
<IconButtonCore
|
|
184
|
+
{...sharedProps}
|
|
185
|
+
color={color}
|
|
191
186
|
disabled={disabled}
|
|
192
187
|
href={href}
|
|
193
|
-
|
|
194
|
-
|
|
188
|
+
kind={kind}
|
|
189
|
+
light={light}
|
|
190
|
+
ref={ref}
|
|
191
|
+
skipClientNav={skipClientNav}
|
|
192
|
+
size={size}
|
|
195
193
|
target={target}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<IconButtonCore
|
|
200
|
-
{...sharedProps}
|
|
201
|
-
{...state}
|
|
202
|
-
{...childrenProps}
|
|
203
|
-
color={color}
|
|
204
|
-
disabled={disabled}
|
|
205
|
-
href={href}
|
|
206
|
-
kind={kind}
|
|
207
|
-
light={light}
|
|
208
|
-
ref={ref}
|
|
209
|
-
skipClientNav={skipClientNav}
|
|
210
|
-
size={size}
|
|
211
|
-
target={target}
|
|
212
|
-
tabIndex={tabIndex}
|
|
213
|
-
/>
|
|
214
|
-
);
|
|
215
|
-
}}
|
|
216
|
-
</ClickableBehavior>
|
|
217
|
-
);
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
return (
|
|
221
|
-
<__RouterContext.Consumer>
|
|
222
|
-
{(router) => renderClickableBehavior(router)}
|
|
223
|
-
</__RouterContext.Consumer>
|
|
194
|
+
tabIndex={tabIndex}
|
|
195
|
+
/>
|
|
196
|
+
</ThemedIconButton>
|
|
224
197
|
);
|
|
225
198
|
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {tokens} from "@khanacademy/wonder-blocks-theming";
|
|
2
|
+
|
|
3
|
+
const theme = {
|
|
4
|
+
color: {
|
|
5
|
+
bg: {
|
|
6
|
+
/**
|
|
7
|
+
* Default
|
|
8
|
+
*/
|
|
9
|
+
hovered: "transparent",
|
|
10
|
+
active: "transparent",
|
|
11
|
+
disabled: "transparent",
|
|
12
|
+
/**
|
|
13
|
+
* Kind
|
|
14
|
+
*/
|
|
15
|
+
// Filled icon buttons (secondary, tertiary)
|
|
16
|
+
// NOTE: Transparent in the default theme, but we want to use the
|
|
17
|
+
// filled colors for Khanmigo.
|
|
18
|
+
filled: {
|
|
19
|
+
action: {
|
|
20
|
+
hovered: "transparent",
|
|
21
|
+
active: "transparent",
|
|
22
|
+
},
|
|
23
|
+
critical: {
|
|
24
|
+
hovered: "transparent",
|
|
25
|
+
active: "transparent",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
// Shared colors for icon and borders
|
|
30
|
+
stroke: {
|
|
31
|
+
/**
|
|
32
|
+
* Default
|
|
33
|
+
*/
|
|
34
|
+
disabled: {
|
|
35
|
+
default: tokens.color.offBlack32,
|
|
36
|
+
inverse: tokens.color.white50,
|
|
37
|
+
},
|
|
38
|
+
// focus, hover
|
|
39
|
+
inverse: tokens.color.white,
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Color
|
|
43
|
+
*/
|
|
44
|
+
// color="default"
|
|
45
|
+
action: {
|
|
46
|
+
default: tokens.color.blue,
|
|
47
|
+
active: tokens.color.activeBlue,
|
|
48
|
+
inverse: tokens.color.fadedBlue,
|
|
49
|
+
},
|
|
50
|
+
// color="destructive"
|
|
51
|
+
critical: {
|
|
52
|
+
default: tokens.color.red,
|
|
53
|
+
active: tokens.color.activeRed,
|
|
54
|
+
inverse: tokens.color.fadedRed,
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Kind
|
|
59
|
+
*/
|
|
60
|
+
primary: {
|
|
61
|
+
// primary + action
|
|
62
|
+
action: {
|
|
63
|
+
hovered: tokens.color.blue,
|
|
64
|
+
active: tokens.color.activeBlue,
|
|
65
|
+
},
|
|
66
|
+
// primary + critical
|
|
67
|
+
critical: {
|
|
68
|
+
hovered: tokens.color.red,
|
|
69
|
+
active: tokens.color.activeRed,
|
|
70
|
+
},
|
|
71
|
+
// on dark background
|
|
72
|
+
inverse: {
|
|
73
|
+
default: tokens.color.white,
|
|
74
|
+
hovered: tokens.color.white,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
secondary: {
|
|
78
|
+
default: tokens.color.offBlack,
|
|
79
|
+
},
|
|
80
|
+
tertiary: {
|
|
81
|
+
default: tokens.color.offBlack64,
|
|
82
|
+
},
|
|
83
|
+
// Filled icon buttons (secondary, tertiary)
|
|
84
|
+
filled: {
|
|
85
|
+
// filled + action
|
|
86
|
+
action: {
|
|
87
|
+
hovered: tokens.color.blue,
|
|
88
|
+
active: tokens.color.activeBlue,
|
|
89
|
+
},
|
|
90
|
+
// filled + critical
|
|
91
|
+
critical: {
|
|
92
|
+
hovered: tokens.color.red,
|
|
93
|
+
active: tokens.color.activeRed,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
border: {
|
|
99
|
+
width: {
|
|
100
|
+
default: tokens.border.width.thin,
|
|
101
|
+
active: tokens.border.width.none,
|
|
102
|
+
hovered: tokens.border.width.thin,
|
|
103
|
+
hoveredInverse: tokens.border.width.thin,
|
|
104
|
+
},
|
|
105
|
+
radius: {
|
|
106
|
+
default: tokens.border.radius.medium_4,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default theme;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {mergeTheme, tokens} from "@khanacademy/wonder-blocks-theming";
|
|
2
|
+
import defaultTheme from "./default";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The overrides for the Khanmigo theme.
|
|
6
|
+
*/
|
|
7
|
+
const theme = mergeTheme(defaultTheme, {
|
|
8
|
+
color: {
|
|
9
|
+
bg: {
|
|
10
|
+
hovered: tokens.color.white,
|
|
11
|
+
active: tokens.color.white64,
|
|
12
|
+
// Filled icon buttons (secondary, tertiary)
|
|
13
|
+
filled: {
|
|
14
|
+
action: {
|
|
15
|
+
hovered: tokens.color.blue,
|
|
16
|
+
active: tokens.color.activeBlue,
|
|
17
|
+
},
|
|
18
|
+
critical: {
|
|
19
|
+
hovered: tokens.color.red,
|
|
20
|
+
active: tokens.color.activeRed,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
stroke: {
|
|
25
|
+
/**
|
|
26
|
+
* Color
|
|
27
|
+
*/
|
|
28
|
+
action: {
|
|
29
|
+
inverse: tokens.color.eggplant,
|
|
30
|
+
},
|
|
31
|
+
critical: {
|
|
32
|
+
inverse: tokens.color.eggplant,
|
|
33
|
+
},
|
|
34
|
+
/**
|
|
35
|
+
* Kind
|
|
36
|
+
*/
|
|
37
|
+
primary: {
|
|
38
|
+
// primary + action
|
|
39
|
+
action: {
|
|
40
|
+
hovered: tokens.color.eggplant,
|
|
41
|
+
active: tokens.color.eggplant,
|
|
42
|
+
},
|
|
43
|
+
// primary + critical
|
|
44
|
+
critical: {
|
|
45
|
+
hovered: tokens.color.eggplant,
|
|
46
|
+
active: tokens.color.eggplant,
|
|
47
|
+
},
|
|
48
|
+
// on dark background
|
|
49
|
+
inverse: {
|
|
50
|
+
hovered: tokens.color.eggplant,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
// Filled icon buttons (secondary, tertiary)
|
|
54
|
+
filled: {
|
|
55
|
+
// filled + action
|
|
56
|
+
action: {
|
|
57
|
+
hovered: tokens.color.white,
|
|
58
|
+
active: tokens.color.white,
|
|
59
|
+
},
|
|
60
|
+
// filled + critical
|
|
61
|
+
critical: {
|
|
62
|
+
hovered: tokens.color.white,
|
|
63
|
+
active: tokens.color.white,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
border: {
|
|
69
|
+
width: {
|
|
70
|
+
hovered: tokens.border.width.none,
|
|
71
|
+
hoveredInverse: tokens.border.width.none,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export default theme;
|