@khanacademy/wonder-blocks-icon-button 4.1.9 → 4.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 +11 -0
- package/dist/components/icon-button.d.ts +7 -0
- package/dist/es/index.js +55 -39
- package/dist/index.js +55 -39
- package/dist/util/icon-button-util.d.ts +10 -0
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/custom-snapshot.test.tsx.snap +2523 -251
- package/src/__tests__/custom-snapshot.test.tsx +7 -1
- package/src/components/icon-button-core.tsx +35 -30
- package/src/components/icon-button.tsx +18 -8
- package/src/util/icon-button-util.test.ts +29 -0
- package/src/util/icon-button-util.ts +22 -0
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -4,6 +4,7 @@ import * as renderer from "react-test-renderer";
|
|
|
4
4
|
import {icons} from "@khanacademy/wonder-blocks-icon";
|
|
5
5
|
|
|
6
6
|
import IconButtonCore from "../components/icon-button-core";
|
|
7
|
+
import {IconButtonSize} from "../components/icon-button";
|
|
7
8
|
|
|
8
9
|
const defaultHandlers = {
|
|
9
10
|
onClick: () => void 0,
|
|
@@ -23,7 +24,11 @@ const defaultHandlers = {
|
|
|
23
24
|
describe("IconButtonCore", () => {
|
|
24
25
|
for (const kind of ["primary", "secondary", "tertiary"] as const) {
|
|
25
26
|
for (const color of ["default", "destructive"] as const) {
|
|
26
|
-
for (const size of [
|
|
27
|
+
for (const size of [
|
|
28
|
+
"xsmall",
|
|
29
|
+
"small",
|
|
30
|
+
"medium",
|
|
31
|
+
] as IconButtonSize[]) {
|
|
27
32
|
for (const light of kind === "primary"
|
|
28
33
|
? [true, false]
|
|
29
34
|
: [false]) {
|
|
@@ -52,6 +57,7 @@ describe("IconButtonCore", () => {
|
|
|
52
57
|
kind={kind}
|
|
53
58
|
color={color}
|
|
54
59
|
light={light}
|
|
60
|
+
size={size}
|
|
55
61
|
tabIndex={disabled ? -1 : 0}
|
|
56
62
|
{...stateProps}
|
|
57
63
|
{...defaultHandlers}
|
|
@@ -16,7 +16,11 @@ import type {
|
|
|
16
16
|
ChildrenProps,
|
|
17
17
|
ClickableState,
|
|
18
18
|
} from "@khanacademy/wonder-blocks-clickable";
|
|
19
|
-
import type {SharedProps} from "./icon-button";
|
|
19
|
+
import type {IconButtonSize, SharedProps} from "./icon-button";
|
|
20
|
+
import {
|
|
21
|
+
iconSizeForButtonSize,
|
|
22
|
+
targetPixelsForSize,
|
|
23
|
+
} from "../util/icon-button-util";
|
|
20
24
|
|
|
21
25
|
type Props = SharedProps &
|
|
22
26
|
ChildrenProps &
|
|
@@ -42,7 +46,6 @@ const IconButtonCore: React.ForwardRefExoticComponent<
|
|
|
42
46
|
Props
|
|
43
47
|
>(function IconButtonCore(props: Props, ref) {
|
|
44
48
|
const {
|
|
45
|
-
skipClientNav,
|
|
46
49
|
color,
|
|
47
50
|
disabled,
|
|
48
51
|
focused,
|
|
@@ -52,6 +55,8 @@ const IconButtonCore: React.ForwardRefExoticComponent<
|
|
|
52
55
|
kind = "primary",
|
|
53
56
|
light = false,
|
|
54
57
|
pressed,
|
|
58
|
+
size = "medium",
|
|
59
|
+
skipClientNav,
|
|
55
60
|
style,
|
|
56
61
|
testId,
|
|
57
62
|
waiting: _,
|
|
@@ -64,11 +69,10 @@ const IconButtonCore: React.ForwardRefExoticComponent<
|
|
|
64
69
|
? SemanticColor.controlDestructive
|
|
65
70
|
: SemanticColor.controlDefault;
|
|
66
71
|
|
|
67
|
-
const buttonStyles = _generateStyles(buttonColor, kind, light);
|
|
72
|
+
const buttonStyles = _generateStyles(buttonColor, kind, light, size);
|
|
68
73
|
|
|
69
74
|
const defaultStyle = [
|
|
70
75
|
sharedStyles.shared,
|
|
71
|
-
disabled && sharedStyles.disabled,
|
|
72
76
|
buttonStyles.default,
|
|
73
77
|
disabled && buttonStyles.disabled,
|
|
74
78
|
!disabled &&
|
|
@@ -77,7 +81,13 @@ const IconButtonCore: React.ForwardRefExoticComponent<
|
|
|
77
81
|
: (hovered || focused) && buttonStyles.focus),
|
|
78
82
|
];
|
|
79
83
|
|
|
80
|
-
const child =
|
|
84
|
+
const child = (
|
|
85
|
+
<Icon
|
|
86
|
+
size={iconSizeForButtonSize(size)}
|
|
87
|
+
color="currentColor"
|
|
88
|
+
icon={icon}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
81
91
|
|
|
82
92
|
const commonProps = {
|
|
83
93
|
"data-test-id": testId,
|
|
@@ -133,8 +143,6 @@ const sharedStyles = StyleSheet.create({
|
|
|
133
143
|
alignItems: "center",
|
|
134
144
|
justifyContent: "center",
|
|
135
145
|
boxSizing: "border-box",
|
|
136
|
-
height: 40,
|
|
137
|
-
width: 40,
|
|
138
146
|
padding: 0,
|
|
139
147
|
cursor: "pointer",
|
|
140
148
|
border: "none",
|
|
@@ -150,9 +158,6 @@ const sharedStyles = StyleSheet.create({
|
|
|
150
158
|
WebkitTapHighlightColor: "rgba(0,0,0,0)",
|
|
151
159
|
},
|
|
152
160
|
},
|
|
153
|
-
disabled: {
|
|
154
|
-
cursor: "default",
|
|
155
|
-
},
|
|
156
161
|
});
|
|
157
162
|
|
|
158
163
|
const styles: Record<string, any> = {};
|
|
@@ -161,8 +166,9 @@ const _generateStyles = (
|
|
|
161
166
|
color: string,
|
|
162
167
|
kind: "primary" | "secondary" | "tertiary",
|
|
163
168
|
light: boolean,
|
|
169
|
+
size: IconButtonSize,
|
|
164
170
|
) => {
|
|
165
|
-
const buttonType = color
|
|
171
|
+
const buttonType = `${color}-${kind}-${light}-${size}`;
|
|
166
172
|
if (styles[buttonType]) {
|
|
167
173
|
return styles[buttonType];
|
|
168
174
|
}
|
|
@@ -172,9 +178,26 @@ const _generateStyles = (
|
|
|
172
178
|
}
|
|
173
179
|
|
|
174
180
|
const {white, offBlack32, offBlack64, offBlack} = Color;
|
|
181
|
+
const defaultColor = ((): string => {
|
|
182
|
+
switch (kind) {
|
|
183
|
+
case "primary":
|
|
184
|
+
return light ? white : color;
|
|
185
|
+
case "secondary":
|
|
186
|
+
return offBlack;
|
|
187
|
+
case "tertiary":
|
|
188
|
+
return offBlack64;
|
|
189
|
+
default:
|
|
190
|
+
throw new Error("IconButton kind not recognized");
|
|
191
|
+
}
|
|
192
|
+
})();
|
|
193
|
+
const pixelsForSize = targetPixelsForSize(size);
|
|
175
194
|
|
|
176
195
|
const newStyles = {
|
|
177
|
-
default: {
|
|
196
|
+
default: {
|
|
197
|
+
height: pixelsForSize,
|
|
198
|
+
width: pixelsForSize,
|
|
199
|
+
color: defaultColor,
|
|
200
|
+
},
|
|
178
201
|
focus: {
|
|
179
202
|
color: light ? white : color,
|
|
180
203
|
borderWidth: 2,
|
|
@@ -198,24 +221,6 @@ const _generateStyles = (
|
|
|
198
221
|
cursor: "default",
|
|
199
222
|
},
|
|
200
223
|
} as const;
|
|
201
|
-
if (kind === "primary") {
|
|
202
|
-
// @ts-expect-error [FEI-5019] - TS2540 - Cannot assign to 'default' because it is a read-only property.
|
|
203
|
-
newStyles["default"] = {
|
|
204
|
-
color: light ? white : color,
|
|
205
|
-
};
|
|
206
|
-
} else if (kind === "secondary") {
|
|
207
|
-
// @ts-expect-error [FEI-5019] - TS2540 - Cannot assign to 'default' because it is a read-only property.
|
|
208
|
-
newStyles["default"] = {
|
|
209
|
-
color: offBlack,
|
|
210
|
-
};
|
|
211
|
-
} else if (kind === "tertiary") {
|
|
212
|
-
// @ts-expect-error [FEI-5019] - TS2540 - Cannot assign to 'default' because it is a read-only property.
|
|
213
|
-
newStyles["default"] = {
|
|
214
|
-
color: offBlack64,
|
|
215
|
-
};
|
|
216
|
-
} else {
|
|
217
|
-
throw new Error("IconButton kind not recognized");
|
|
218
|
-
}
|
|
219
224
|
|
|
220
225
|
styles[buttonType] = StyleSheet.create(newStyles);
|
|
221
226
|
return styles[buttonType];
|
|
@@ -7,6 +7,8 @@ import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
|
|
|
7
7
|
import {Link} from "react-router-dom";
|
|
8
8
|
import IconButtonCore from "./icon-button-core";
|
|
9
9
|
|
|
10
|
+
export type IconButtonSize = "xsmall" | "small" | "medium";
|
|
11
|
+
|
|
10
12
|
export type SharedProps = Partial<Omit<AriaProps, "aria-disabled">> & {
|
|
11
13
|
/**
|
|
12
14
|
* A Wonder Blocks icon asset, an object specifing paths for one or more of
|
|
@@ -40,6 +42,12 @@ export type SharedProps = Partial<Omit<AriaProps, "aria-disabled">> & {
|
|
|
40
42
|
* Test ID used for e2e testing.
|
|
41
43
|
*/
|
|
42
44
|
testId?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Size of the icon button.
|
|
47
|
+
* One of `xsmall` (16 icon, 20 target), `small` (24, 32), or `medium (24, 40).
|
|
48
|
+
* Defaults to `medium`.
|
|
49
|
+
*/
|
|
50
|
+
size?: IconButtonSize;
|
|
43
51
|
/**
|
|
44
52
|
* Optional custom styles.
|
|
45
53
|
*/
|
|
@@ -158,15 +166,16 @@ const IconButton: React.ForwardRefExoticComponent<
|
|
|
158
166
|
SharedProps
|
|
159
167
|
>(function IconButton(props: SharedProps, ref) {
|
|
160
168
|
const {
|
|
161
|
-
|
|
169
|
+
color = "default",
|
|
170
|
+
disabled = false,
|
|
162
171
|
href,
|
|
172
|
+
kind = "primary",
|
|
173
|
+
light = false,
|
|
174
|
+
onClick,
|
|
175
|
+
size = "medium",
|
|
163
176
|
skipClientNav,
|
|
164
177
|
tabIndex,
|
|
165
178
|
target,
|
|
166
|
-
color = "default",
|
|
167
|
-
kind = "primary",
|
|
168
|
-
light = false,
|
|
169
|
-
disabled = false,
|
|
170
179
|
...sharedProps
|
|
171
180
|
} = props;
|
|
172
181
|
const renderClickableBehavior = (router: any): React.ReactNode => {
|
|
@@ -191,14 +200,15 @@ const IconButton: React.ForwardRefExoticComponent<
|
|
|
191
200
|
{...state}
|
|
192
201
|
{...childrenProps}
|
|
193
202
|
color={color}
|
|
203
|
+
disabled={disabled}
|
|
204
|
+
href={href}
|
|
194
205
|
kind={kind}
|
|
195
206
|
light={light}
|
|
196
|
-
|
|
207
|
+
ref={ref}
|
|
197
208
|
skipClientNav={skipClientNav}
|
|
198
|
-
|
|
209
|
+
size={size}
|
|
199
210
|
target={target}
|
|
200
211
|
tabIndex={tabIndex}
|
|
201
|
-
ref={ref}
|
|
202
212
|
/>
|
|
203
213
|
);
|
|
204
214
|
}}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {iconSizeForButtonSize, targetPixelsForSize} from "./icon-button-util";
|
|
2
|
+
|
|
3
|
+
describe("iconSizeForButtonSize", () => {
|
|
4
|
+
test.each`
|
|
5
|
+
buttonSize | expectedIconSize
|
|
6
|
+
${"xsmall"} | ${"small"}
|
|
7
|
+
${"small"} | ${"medium"}
|
|
8
|
+
${"medium"} | ${"medium"}
|
|
9
|
+
`(
|
|
10
|
+
"should return $expectedIconSize icon for $buttonSize icon button",
|
|
11
|
+
({buttonSize, expectedIconSize}) => {
|
|
12
|
+
expect(iconSizeForButtonSize(buttonSize)).toBe(expectedIconSize);
|
|
13
|
+
},
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("targetPixelsForSize", () => {
|
|
18
|
+
test.each`
|
|
19
|
+
size | expectedTargetPixels
|
|
20
|
+
${"xsmall"} | ${24}
|
|
21
|
+
${"small"} | ${32}
|
|
22
|
+
${"medium"} | ${40}
|
|
23
|
+
`(
|
|
24
|
+
"should return $expectedTargetPixels for $size icon button",
|
|
25
|
+
({size, expectedTargetPixels}) => {
|
|
26
|
+
expect(targetPixelsForSize(size)).toBe(expectedTargetPixels);
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {IconSize} from "@khanacademy/wonder-blocks-icon";
|
|
2
|
+
import {IconButtonSize} from "../components/icon-button";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A function that returns the icon size for a given icon button size.
|
|
6
|
+
*/
|
|
7
|
+
export const iconSizeForButtonSize = (size: IconButtonSize): IconSize => {
|
|
8
|
+
switch (size) {
|
|
9
|
+
case "xsmall":
|
|
10
|
+
return "small";
|
|
11
|
+
case "small":
|
|
12
|
+
return "medium";
|
|
13
|
+
case "medium":
|
|
14
|
+
return "medium";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A function that returns the size of the touch target in pixels for a given icon button size.
|
|
20
|
+
*/
|
|
21
|
+
export const targetPixelsForSize = (size: IconButtonSize): number =>
|
|
22
|
+
({xsmall: 24, small: 32, medium: 40}[size]);
|