@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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # @khanacademy/wonder-blocks-icon-button
2
2
 
3
+ ## 5.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ae97aeb6: Add theming support to the IconButton component
8
+ - 22ccc5a9: Switch to pseudo-classes (`:hover, :focus-visible, :active`)
9
+ - 0d875501: Add khanmigo theme to `IconButton`
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [3f854fe8]
14
+ - @khanacademy/wonder-blocks-theming@1.2.0
15
+
16
+ ## 5.0.1
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies [7055ca94]
21
+ - @khanacademy/wonder-blocks-core@6.3.0
22
+ - @khanacademy/wonder-blocks-clickable@4.0.9
23
+ - @khanacademy/wonder-blocks-icon@2.2.1
24
+
3
25
  ## 5.0.0
4
26
 
5
27
  ### Major Changes
@@ -1,8 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { Link } from "react-router-dom";
3
- import type { ChildrenProps, ClickableState } from "@khanacademy/wonder-blocks-clickable";
4
3
  import type { SharedProps } from "./icon-button";
5
- type Props = SharedProps & ChildrenProps & ClickableState & {
4
+ type Props = SharedProps & {
6
5
  /**
7
6
  * URL to navigate to.
8
7
  *
package/dist/es/index.js CHANGED
@@ -1,15 +1,11 @@
1
1
  import * as React from 'react';
2
- import { __RouterContext } from 'react-router';
3
- import { isClientSideUrl, getClickableBehavior } from '@khanacademy/wonder-blocks-clickable';
4
2
  import { StyleSheet } from 'aphrodite';
5
3
  import { Link } from 'react-router-dom';
6
- import Color, { SemanticColor, mix, fade } from '@khanacademy/wonder-blocks-color';
4
+ import { __RouterContext } from 'react-router';
7
5
  import { addStyle } from '@khanacademy/wonder-blocks-core';
6
+ import { isClientSideUrl } from '@khanacademy/wonder-blocks-clickable';
8
7
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
9
-
10
- function _objectDestructuringEmpty(obj) {
11
- if (obj == null) throw new TypeError("Cannot destructure " + obj);
12
- }
8
+ import { tokens, mergeTheme, createThemeContext, ThemeSwitcherContext, useScopedTheme } from '@khanacademy/wonder-blocks-theming';
13
9
 
14
10
  function _extends() {
15
11
  _extends = Object.assign ? Object.assign.bind() : function (target) {
@@ -55,7 +51,155 @@ const targetPixelsForSize = size => ({
55
51
  medium: 40
56
52
  })[size];
57
53
 
58
- const _excluded$1 = ["color", "disabled", "focused", "hovered", "href", "icon", "kind", "light", "pressed", "size", "skipClientNav", "style", "testId", "waiting"];
54
+ const theme$1 = {
55
+ color: {
56
+ bg: {
57
+ hovered: "transparent",
58
+ active: "transparent",
59
+ disabled: "transparent",
60
+ filled: {
61
+ action: {
62
+ hovered: "transparent",
63
+ active: "transparent"
64
+ },
65
+ critical: {
66
+ hovered: "transparent",
67
+ active: "transparent"
68
+ }
69
+ }
70
+ },
71
+ stroke: {
72
+ disabled: {
73
+ default: tokens.color.offBlack32,
74
+ inverse: tokens.color.white50
75
+ },
76
+ inverse: tokens.color.white,
77
+ action: {
78
+ default: tokens.color.blue,
79
+ active: tokens.color.activeBlue,
80
+ inverse: tokens.color.fadedBlue
81
+ },
82
+ critical: {
83
+ default: tokens.color.red,
84
+ active: tokens.color.activeRed,
85
+ inverse: tokens.color.fadedRed
86
+ },
87
+ primary: {
88
+ action: {
89
+ hovered: tokens.color.blue,
90
+ active: tokens.color.activeBlue
91
+ },
92
+ critical: {
93
+ hovered: tokens.color.red,
94
+ active: tokens.color.activeRed
95
+ },
96
+ inverse: {
97
+ default: tokens.color.white,
98
+ hovered: tokens.color.white
99
+ }
100
+ },
101
+ secondary: {
102
+ default: tokens.color.offBlack
103
+ },
104
+ tertiary: {
105
+ default: tokens.color.offBlack64
106
+ },
107
+ filled: {
108
+ action: {
109
+ hovered: tokens.color.blue,
110
+ active: tokens.color.activeBlue
111
+ },
112
+ critical: {
113
+ hovered: tokens.color.red,
114
+ active: tokens.color.activeRed
115
+ }
116
+ }
117
+ }
118
+ },
119
+ border: {
120
+ width: {
121
+ default: tokens.border.width.thin,
122
+ active: tokens.border.width.none,
123
+ hovered: tokens.border.width.thin,
124
+ hoveredInverse: tokens.border.width.thin
125
+ },
126
+ radius: {
127
+ default: tokens.border.radius.medium_4
128
+ }
129
+ }
130
+ };
131
+
132
+ const theme = mergeTheme(theme$1, {
133
+ color: {
134
+ bg: {
135
+ hovered: tokens.color.white,
136
+ active: tokens.color.white64,
137
+ filled: {
138
+ action: {
139
+ hovered: tokens.color.blue,
140
+ active: tokens.color.activeBlue
141
+ },
142
+ critical: {
143
+ hovered: tokens.color.red,
144
+ active: tokens.color.activeRed
145
+ }
146
+ }
147
+ },
148
+ stroke: {
149
+ action: {
150
+ inverse: tokens.color.eggplant
151
+ },
152
+ critical: {
153
+ inverse: tokens.color.eggplant
154
+ },
155
+ primary: {
156
+ action: {
157
+ hovered: tokens.color.eggplant,
158
+ active: tokens.color.eggplant
159
+ },
160
+ critical: {
161
+ hovered: tokens.color.eggplant,
162
+ active: tokens.color.eggplant
163
+ },
164
+ inverse: {
165
+ hovered: tokens.color.eggplant
166
+ }
167
+ },
168
+ filled: {
169
+ action: {
170
+ hovered: tokens.color.white,
171
+ active: tokens.color.white
172
+ },
173
+ critical: {
174
+ hovered: tokens.color.white,
175
+ active: tokens.color.white
176
+ }
177
+ }
178
+ }
179
+ },
180
+ border: {
181
+ width: {
182
+ hovered: tokens.border.width.none,
183
+ hoveredInverse: tokens.border.width.none
184
+ }
185
+ }
186
+ });
187
+
188
+ const themes = {
189
+ default: theme$1,
190
+ khanmigo: theme
191
+ };
192
+ const IconButtonThemeContext = createThemeContext(theme$1);
193
+ function ThemedIconButton(props) {
194
+ var _themes$currentTheme;
195
+ const currentTheme = React.useContext(ThemeSwitcherContext);
196
+ const theme = (_themes$currentTheme = themes[currentTheme]) != null ? _themes$currentTheme : theme$1;
197
+ return React.createElement(IconButtonThemeContext.Provider, {
198
+ value: theme
199
+ }, props.children);
200
+ }
201
+
202
+ const _excluded$1 = ["color", "disabled", "href", "icon", "kind", "light", "size", "skipClientNav", "style", "testId"];
59
203
  function IconChooser({
60
204
  icon,
61
205
  size
@@ -84,23 +228,23 @@ const IconButtonCore = React.forwardRef(function IconButtonCore(props, ref) {
84
228
  const {
85
229
  color,
86
230
  disabled,
87
- focused,
88
- hovered,
89
231
  href,
90
232
  icon,
91
233
  kind = "primary",
92
234
  light = false,
93
- pressed,
94
235
  size = "medium",
95
236
  skipClientNav,
96
237
  style,
97
238
  testId
98
239
  } = props,
99
240
  restProps = _objectWithoutPropertiesLoose(props, _excluded$1);
241
+ const {
242
+ theme,
243
+ themeName
244
+ } = useScopedTheme(IconButtonThemeContext);
100
245
  const renderInner = router => {
101
- const buttonColor = color === "destructive" ? SemanticColor.controlDestructive : SemanticColor.controlDefault;
102
- const buttonStyles = _generateStyles(buttonColor, kind, light, size);
103
- const defaultStyle = [sharedStyles.shared, buttonStyles.default, disabled && buttonStyles.disabled, !disabled && (pressed ? buttonStyles.active : (hovered || focused) && buttonStyles.focus)];
246
+ const buttonStyles = _generateStyles(color, kind, light, size, theme, themeName);
247
+ const defaultStyle = [sharedStyles.shared, buttonStyles.default, disabled && buttonStyles.disabled];
104
248
  const child = React.createElement(IconChooser, {
105
249
  size: size,
106
250
  icon: icon
@@ -121,7 +265,8 @@ const IconButtonCore = React.forwardRef(function IconButtonCore(props, ref) {
121
265
  return React.createElement(StyledButton, _extends({
122
266
  type: "button"
123
267
  }, commonProps, {
124
- disabled: disabled,
268
+ onClick: disabled ? undefined : restProps.onClick,
269
+ "aria-disabled": disabled,
125
270
  ref: ref
126
271
  }), child);
127
272
  }
@@ -142,70 +287,141 @@ const sharedStyles = StyleSheet.create({
142
287
  textDecoration: "none",
143
288
  background: "none",
144
289
  margin: -8,
145
- touchAction: "manipulation",
146
- ":focus": {
147
- WebkitTapHighlightColor: "rgba(0,0,0,0)"
148
- }
290
+ touchAction: "manipulation"
149
291
  }
150
292
  });
151
293
  const styles = {};
152
- const _generateStyles = (color, kind, light, size) => {
153
- const buttonType = `${color}-${kind}-${light}-${size}`;
294
+ function getStylesByKind(kind, theme, color, light, buttonColor) {
295
+ switch (kind) {
296
+ case "primary":
297
+ const primaryHoveredColor = buttonColor === "destructive" ? theme.color.stroke.primary.critical.hovered : theme.color.stroke.primary.action.hovered;
298
+ return {
299
+ ":hover": {
300
+ backgroundColor: theme.color.bg.hovered,
301
+ color: light ? theme.color.stroke.primary.inverse.hovered : primaryHoveredColor,
302
+ outlineColor: light ? theme.color.stroke.inverse : color,
303
+ outlineOffset: 1,
304
+ outlineStyle: "solid",
305
+ outlineWidth: light ? theme.border.width.hoveredInverse : theme.border.width.hovered
306
+ },
307
+ ":active": {
308
+ backgroundColor: theme.color.bg.active
309
+ }
310
+ };
311
+ case "secondary":
312
+ case "tertiary":
313
+ return {
314
+ ":hover": {
315
+ backgroundColor: buttonColor === "destructive" ? theme.color.bg.filled.critical.hovered : theme.color.bg.filled.action.hovered,
316
+ color: buttonColor === "destructive" ? theme.color.stroke.filled.critical.hovered : theme.color.stroke.filled.action.hovered,
317
+ outlineWidth: theme.border.width.active
318
+ },
319
+ ":active": {
320
+ backgroundColor: buttonColor === "destructive" ? theme.color.bg.filled.critical.active : theme.color.bg.filled.action.active,
321
+ color: buttonColor === "destructive" ? theme.color.stroke.filled.critical.active : theme.color.stroke.filled.action.active,
322
+ outlineWidth: theme.border.width.active
323
+ }
324
+ };
325
+ default:
326
+ return {
327
+ ":focus-visible": {},
328
+ ":hover": {},
329
+ ":active": {}
330
+ };
331
+ }
332
+ }
333
+ const _generateStyles = (buttonColor = "default", kind, light, size, theme, themeName) => {
334
+ const color = buttonColor === "destructive" ? theme.color.stroke.critical.default : theme.color.stroke.action.default;
335
+ const buttonType = `${color}-${kind}-${light}-${size}-${themeName}`;
154
336
  if (styles[buttonType]) {
155
337
  return styles[buttonType];
156
338
  }
157
339
  if (light && kind !== "primary") {
158
340
  throw new Error("Light is only supported for primary IconButtons");
159
341
  }
160
- const {
161
- white,
162
- offBlack32,
163
- offBlack64,
164
- offBlack
165
- } = Color;
166
342
  const defaultColor = (() => {
167
343
  switch (kind) {
168
344
  case "primary":
169
- return light ? white : color;
345
+ return light ? theme.color.stroke.primary.inverse.default : color;
170
346
  case "secondary":
171
- return offBlack;
347
+ return theme.color.stroke.secondary.default;
172
348
  case "tertiary":
173
- return offBlack64;
349
+ return theme.color.stroke.tertiary.default;
174
350
  default:
175
351
  throw new Error("IconButton kind not recognized");
176
352
  }
177
353
  })();
178
354
  const pixelsForSize = targetPixelsForSize(size);
355
+ const kindOverrides = getStylesByKind(kind, theme, color, light, buttonColor);
356
+ const activeInverseColor = buttonColor === "destructive" ? theme.color.stroke.critical.inverse : theme.color.stroke.action.inverse;
357
+ const activeColor = buttonColor === "destructive" ? theme.color.stroke.critical.active : theme.color.stroke.action.active;
358
+ const defaultStrokeColor = light ? theme.color.stroke.inverse : color;
359
+ const disabledStrokeColor = light ? theme.color.stroke.disabled.inverse : theme.color.stroke.disabled.default;
360
+ const disabledStatesStyles = {
361
+ backgroundColor: theme.color.bg.disabled,
362
+ color: disabledStrokeColor,
363
+ outlineColor: disabledStrokeColor
364
+ };
179
365
  const newStyles = {
180
366
  default: {
181
367
  height: pixelsForSize,
182
368
  width: pixelsForSize,
183
- color: defaultColor
184
- },
185
- focus: {
186
- color: light ? white : color,
187
- borderWidth: 2,
188
- borderColor: light ? white : color,
189
- borderStyle: "solid",
190
- borderRadius: 4
191
- },
192
- active: {
193
- color: light ? mix(fade(color, 0.32), white) : mix(offBlack32, color),
194
- borderWidth: 2,
195
- borderColor: light ? mix(fade(color, 0.32), white) : mix(offBlack32, color),
196
- borderStyle: "solid",
197
- borderRadius: 4
369
+ color: defaultColor,
370
+ borderRadius: theme.border.radius.default,
371
+ ":hover": _extends({
372
+ boxShadow: "none",
373
+ color: defaultStrokeColor,
374
+ borderRadius: theme.border.radius.default,
375
+ outlineWidth: theme.border.width.default
376
+ }, kindOverrides[":hover"]),
377
+ ":focus": {
378
+ boxShadow: `0 0 0 ${theme.border.width.default}px ${defaultStrokeColor}`,
379
+ borderRadius: theme.border.radius.default
380
+ },
381
+ ":focus:not(:focus-visible)": {
382
+ boxShadow: "none"
383
+ },
384
+ ":focus-visible": _extends({
385
+ boxShadow: "none",
386
+ outlineWidth: theme.border.width.default,
387
+ outlineColor: defaultStrokeColor,
388
+ outlineOffset: 1,
389
+ outlineStyle: "solid",
390
+ borderRadius: theme.border.radius.default
391
+ }, kindOverrides[":focus-visible"]),
392
+ ":active": _extends({
393
+ color: light ? activeInverseColor : activeColor,
394
+ outlineWidth: theme.border.width.default,
395
+ outlineColor: light ? activeInverseColor : activeColor,
396
+ outlineOffset: 1,
397
+ outlineStyle: "solid",
398
+ borderRadius: theme.border.radius.default
399
+ }, kindOverrides[":active"])
198
400
  },
199
401
  disabled: {
200
- color: light ? mix(fade(white, 0.32), color) : offBlack32,
201
- cursor: "default"
402
+ color: disabledStrokeColor,
403
+ cursor: "not-allowed",
404
+ ":hover": _extends({}, disabledStatesStyles, {
405
+ outline: "none"
406
+ }),
407
+ ":active": _extends({}, disabledStatesStyles, {
408
+ outline: "none"
409
+ }),
410
+ ":focus": {
411
+ boxShadow: `0 0 0 ${theme.border.width.default}px ${disabledStrokeColor}`,
412
+ borderRadius: theme.border.radius.default
413
+ },
414
+ ":focus:not(:focus-visible)": {
415
+ boxShadow: "none"
416
+ },
417
+ ":focus-visible": disabledStatesStyles
202
418
  }
203
419
  };
204
420
  styles[buttonType] = StyleSheet.create(newStyles);
205
421
  return styles[buttonType];
206
422
  };
207
423
 
208
- const _excluded = ["color", "disabled", "href", "kind", "light", "onClick", "size", "skipClientNav", "tabIndex", "target"];
424
+ const _excluded = ["color", "disabled", "href", "kind", "light", "size", "skipClientNav", "tabIndex", "target"];
209
425
  const IconButton = React.forwardRef(function IconButton(props, ref) {
210
426
  const {
211
427
  color = "default",
@@ -213,38 +429,24 @@ const IconButton = React.forwardRef(function IconButton(props, ref) {
213
429
  href,
214
430
  kind = "primary",
215
431
  light = false,
216
- onClick,
217
432
  size = "medium",
218
433
  skipClientNav,
219
434
  tabIndex,
220
435
  target
221
436
  } = props,
222
437
  sharedProps = _objectWithoutPropertiesLoose(props, _excluded);
223
- const renderClickableBehavior = router => {
224
- const ClickableBehavior = getClickableBehavior(href, skipClientNav, router);
225
- return React.createElement(ClickableBehavior, {
226
- disabled: disabled,
227
- href: href,
228
- onClick: onClick,
229
- role: "button",
230
- target: target
231
- }, (state, _ref) => {
232
- let childrenProps = _extends({}, (_objectDestructuringEmpty(_ref), _ref));
233
- return React.createElement(IconButtonCore, _extends({}, sharedProps, state, childrenProps, {
234
- color: color,
235
- disabled: disabled,
236
- href: href,
237
- kind: kind,
238
- light: light,
239
- ref: ref,
240
- skipClientNav: skipClientNav,
241
- size: size,
242
- target: target,
243
- tabIndex: tabIndex
244
- }));
245
- });
246
- };
247
- return React.createElement(__RouterContext.Consumer, null, router => renderClickableBehavior(router));
438
+ return React.createElement(ThemedIconButton, null, React.createElement(IconButtonCore, _extends({}, sharedProps, {
439
+ color: color,
440
+ disabled: disabled,
441
+ href: href,
442
+ kind: kind,
443
+ light: light,
444
+ ref: ref,
445
+ skipClientNav: skipClientNav,
446
+ size: size,
447
+ target: target,
448
+ tabIndex: tabIndex
449
+ })));
248
450
  });
249
451
 
250
452
  export { IconButton as default };