@khanacademy/wonder-blocks-button 4.0.13 → 4.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,35 @@
1
1
  # @khanacademy/wonder-blocks-button
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0992ff2b: Add ref forwarding to Button and IconButton
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [0423a440]
12
+ - Updated dependencies [c37b99aa]
13
+ - Updated dependencies [afd5a801]
14
+ - Updated dependencies [13c48aa0]
15
+ - Updated dependencies [cade62f3]
16
+ - Updated dependencies [c4cef3e6]
17
+ - Updated dependencies [4c900085]
18
+ - @khanacademy/wonder-blocks-typography@2.1.0
19
+ - @khanacademy/wonder-blocks-core@5.3.0
20
+ - @khanacademy/wonder-blocks-clickable@3.1.1
21
+ - @khanacademy/wonder-blocks-icon@2.0.14
22
+ - @khanacademy/wonder-blocks-progress-spinner@2.0.14
23
+
24
+ ## 4.0.14
25
+
26
+ ### Patch Changes
27
+
28
+ - e0265198: Updated focus state for tertiary button to a border instead of underline
29
+ - 0f21caaa: Truncate text for buttons with icons
30
+ - Updated dependencies [ad8beb23]
31
+ - @khanacademy/wonder-blocks-clickable@3.1.0
32
+
3
33
  ## 4.0.13
4
34
 
5
35
  ### Patch Changes
@@ -1,9 +1,7 @@
1
1
  import * as React from "react";
2
+ import { Link } from "react-router-dom";
2
3
  import type { ChildrenProps, ClickableState } from "@khanacademy/wonder-blocks-clickable";
3
4
  import type { SharedProps } from "./button";
4
5
  type Props = SharedProps & ChildrenProps & ClickableState;
5
- export default class ButtonCore extends React.Component<Props> {
6
- renderInner(router: any): React.ReactNode;
7
- render(): React.ReactNode;
8
- }
9
- export {};
6
+ declare const ButtonCore: React.ForwardRefExoticComponent<Props & React.RefAttributes<typeof Link | HTMLButtonElement | HTMLAnchorElement>>;
7
+ export default ButtonCore;
@@ -5,13 +5,15 @@
5
5
  * @flow
6
6
  */
7
7
  import * as React from "react";
8
+ import { Link } from "react-router-dom";
8
9
  import type {
9
10
  ChildrenProps,
10
11
  ClickableState,
11
12
  } from "@khanacademy/wonder-blocks-clickable";
12
13
  import type { SharedProps } from "./button";
13
14
  declare type Props = {| ...SharedProps, ...ChildrenProps, ...ClickableState |};
14
- declare export default class ButtonCore extends React.Component<Props> {
15
- renderInner(router: any): React.Node;
16
- render(): React.Node;
17
- }
15
+ declare var ButtonCore: React.AbstractComponent<
16
+ Props,
17
+ typeof Link | HTMLButtonElement | HTMLAnchorElement
18
+ >;
19
+ declare export default typeof ButtonCore;
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import type { AriaProps, StyleType } from "@khanacademy/wonder-blocks-core";
3
3
  import type { IconAsset } from "@khanacademy/wonder-blocks-icon";
4
+ import { Link } from "react-router-dom";
4
5
  export type SharedProps =
5
6
  /**
6
7
  * aria-label should be used when `spinner={true}` to let people using screen
@@ -23,11 +24,11 @@ Partial<Omit<AriaProps, "aria-disabled">> & {
23
24
  *
24
25
  * TODO(kevinb): support spinner + light once we have designs
25
26
  */
26
- spinner: boolean;
27
+ spinner?: boolean;
27
28
  /**
28
29
  * The color of the button, either blue or red.
29
30
  */
30
- color: "default" | "destructive";
31
+ color?: "default" | "destructive";
31
32
  /**
32
33
  * The kind of the button, either primary, secondary, or tertiary.
33
34
  *
@@ -37,23 +38,23 @@ Partial<Omit<AriaProps, "aria-disabled">> & {
37
38
  * - Secondary buttons have a border and no background color
38
39
  * - Tertiary buttons have no background or border
39
40
  */
40
- kind: "primary" | "secondary" | "tertiary";
41
+ kind?: "primary" | "secondary" | "tertiary";
41
42
  /**
42
43
  * Whether the button is on a dark/colored background.
43
44
  *
44
45
  * Sets primary button background color to white, and secondary and
45
46
  * tertiary button title to color.
46
47
  */
47
- light: boolean;
48
+ light?: boolean;
48
49
  /**
49
50
  * The size of the button. "medium" = height: 40; "small" = height: 32;
50
51
  * "large" = height: 56;
51
52
  */
52
- size: "medium" | "small" | "large";
53
+ size?: "medium" | "small" | "large";
53
54
  /**
54
55
  * Whether the button is disabled.
55
56
  */
56
- disabled: boolean;
57
+ disabled?: boolean;
57
58
  /**
58
59
  * An optional id attribute.
59
60
  */
@@ -142,14 +143,6 @@ Partial<Omit<AriaProps, "aria-disabled">> & {
142
143
  safeWithNav?: () => Promise<unknown>;
143
144
  };
144
145
  type Props = SharedProps;
145
- type DefaultProps = {
146
- color: Props["color"];
147
- kind: Props["kind"];
148
- light: Props["light"];
149
- size: Props["size"];
150
- disabled: Props["disabled"];
151
- spinner: Props["spinner"];
152
- };
153
146
  /**
154
147
  * Reusable button component.
155
148
  *
@@ -170,9 +163,5 @@ type DefaultProps = {
170
163
  * </Button>
171
164
  * ```
172
165
  */
173
- export default class Button extends React.Component<Props> {
174
- static defaultProps: DefaultProps;
175
- renderClickableBehavior(router: any): React.ReactNode;
176
- render(): React.ReactNode;
177
- }
178
- export {};
166
+ declare const Button: React.ForwardRefExoticComponent<Props & React.RefAttributes<typeof Link | HTMLButtonElement | HTMLAnchorElement>>;
167
+ export default Button;
@@ -7,6 +7,7 @@
7
7
  import * as React from "react";
8
8
  import type { AriaProps, StyleType } from "@khanacademy/wonder-blocks-core";
9
9
  import type { IconAsset } from "@khanacademy/wonder-blocks-icon";
10
+ import { Link } from "react-router-dom";
10
11
  export type SharedProps = {|
11
12
  ...$Rest<$Diff<AriaProps, { "aria-disabled": any }>, {}>,
12
13
  ...{|
@@ -27,12 +28,12 @@ export type SharedProps = {|
27
28
  *
28
29
  * TODO(kevinb): support spinner + light once we have designs
29
30
  */
30
- spinner: boolean,
31
+ spinner?: boolean,
31
32
 
32
33
  /**
33
34
  * The color of the button, either blue or red.
34
35
  */
35
- color: "default" | "destructive",
36
+ color?: "default" | "destructive",
36
37
 
37
38
  /**
38
39
  * The kind of the button, either primary, secondary, or tertiary.
@@ -43,7 +44,7 @@ export type SharedProps = {|
43
44
  * - Secondary buttons have a border and no background color
44
45
  * - Tertiary buttons have no background or border
45
46
  */
46
- kind: "primary" | "secondary" | "tertiary",
47
+ kind?: "primary" | "secondary" | "tertiary",
47
48
 
48
49
  /**
49
50
  * Whether the button is on a dark/colored background.
@@ -51,18 +52,18 @@ export type SharedProps = {|
51
52
  * Sets primary button background color to white, and secondary and
52
53
  * tertiary button title to color.
53
54
  */
54
- light: boolean,
55
+ light?: boolean,
55
56
 
56
57
  /**
57
58
  * The size of the button. "medium" = height: 40; "small" = height: 32;
58
59
  * "large" = height: 56;
59
60
  */
60
- size: "medium" | "small" | "large",
61
+ size?: "medium" | "small" | "large",
61
62
 
62
63
  /**
63
64
  * Whether the button is disabled.
64
65
  */
65
- disabled: boolean,
66
+ disabled?: boolean,
66
67
 
67
68
  /**
68
69
  * An optional id attribute.
@@ -165,14 +166,6 @@ export type SharedProps = {|
165
166
  |},
166
167
  |};
167
168
  declare type Props = SharedProps;
168
- declare type DefaultProps = {|
169
- color: $PropertyType<Props, "color">,
170
- kind: $PropertyType<Props, "kind">,
171
- light: $PropertyType<Props, "light">,
172
- size: $PropertyType<Props, "size">,
173
- disabled: $PropertyType<Props, "disabled">,
174
- spinner: $PropertyType<Props, "spinner">,
175
- |};
176
169
  /**
177
170
  * Reusable button component.
178
171
  *
@@ -193,8 +186,8 @@ declare type DefaultProps = {|
193
186
  * </Button>
194
187
  * ```
195
188
  */
196
- declare export default class Button extends React.Component<Props> {
197
- static defaultProps: DefaultProps;
198
- renderClickableBehavior(router: any): React.Node;
199
- render(): React.Node;
200
- }
189
+ declare var Button: React.AbstractComponent<
190
+ Props,
191
+ typeof Link | HTMLButtonElement | HTMLAnchorElement
192
+ >;
193
+ declare export default typeof Button;
package/dist/es/index.js CHANGED
@@ -42,10 +42,9 @@ const _excluded$1 = ["children", "skipClientNav", "color", "disabled", "focused"
42
42
  const StyledAnchor = addStyle("a");
43
43
  const StyledButton = addStyle("button");
44
44
  const StyledLink = addStyle(Link);
45
- class ButtonCore extends React.Component {
46
- renderInner(router) {
47
- const _this$props = this.props,
48
- {
45
+ const ButtonCore = React.forwardRef((props, ref) => {
46
+ const renderInner = router => {
47
+ const {
49
48
  children,
50
49
  skipClientNav,
51
50
  color,
@@ -53,23 +52,23 @@ class ButtonCore extends React.Component {
53
52
  focused,
54
53
  hovered,
55
54
  href = undefined,
56
- kind,
57
- light,
55
+ kind = "primary",
56
+ light = false,
58
57
  pressed,
59
- size,
58
+ size = "medium",
60
59
  style,
61
60
  testId,
62
61
  type = undefined,
63
62
  spinner,
64
63
  icon,
65
64
  id
66
- } = _this$props,
67
- restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$1);
65
+ } = props,
66
+ restProps = _objectWithoutPropertiesLoose(props, _excluded$1);
68
67
  const buttonColor = color === "destructive" ? SemanticColor.controlDestructive : SemanticColor.controlDefault;
69
68
  const iconWidth = icon ? (size === "small" ? 16 : 24) + 8 : 0;
70
69
  const buttonStyles = _generateStyles(buttonColor, kind, light, iconWidth, size);
71
70
  const disabled = spinner || disabledProp;
72
- const defaultStyle = [sharedStyles.shared, disabled && sharedStyles.disabled, icon && sharedStyles.withIcon, buttonStyles.default, disabled && buttonStyles.disabled, kind !== "tertiary" && !disabled && (pressed ? buttonStyles.active : (hovered || focused) && buttonStyles.focus), size === "small" && sharedStyles.small, size === "large" && sharedStyles.large];
71
+ const defaultStyle = [sharedStyles.shared, disabled && sharedStyles.disabled, icon && sharedStyles.withIcon, buttonStyles.default, disabled && buttonStyles.disabled, kind !== "tertiary" && !disabled && (pressed ? buttonStyles.active : (hovered || focused) && buttonStyles.focus), kind === "tertiary" && !pressed && focused && [buttonStyles.focus, disabled && buttonStyles.disabledFocus], size === "small" && sharedStyles.small, size === "large" && sharedStyles.large];
73
72
  const commonProps = _extends({
74
73
  "data-test-id": testId,
75
74
  id: id,
@@ -79,19 +78,22 @@ class ButtonCore extends React.Component {
79
78
  const Label = size === "small" ? LabelSmall : LabelLarge;
80
79
  const iconSize = size === "small" ? "small" : "medium";
81
80
  const label = React.createElement(Label, {
82
- style: [sharedStyles.text, size === "large" && sharedStyles.largeText, icon && sharedStyles.textWithIcon, spinner && sharedStyles.hiddenText, kind === "tertiary" && sharedStyles.textWithFocus, kind === "tertiary" && !disabled && (pressed ? buttonStyles.active : (hovered || focused) && buttonStyles.focus), kind === "tertiary" && disabled && focused && [buttonStyles.focus, buttonStyles.disabledFocus]]
83
- }, icon && React.createElement(Icon, {
84
- size: iconSize,
85
- color: "currentColor",
86
- icon: icon,
87
- style: sharedStyles.icon
88
- }), children);
81
+ style: [sharedStyles.text, size === "large" && sharedStyles.largeText, spinner && sharedStyles.hiddenText, kind === "tertiary" && sharedStyles.textWithFocus, kind === "tertiary" && !disabled && (pressed ? [buttonStyles.hover, buttonStyles.active] : hovered && buttonStyles.hover)],
82
+ testId: testId ? `${testId}-inner-label` : undefined
83
+ }, children);
89
84
  const sizeMapping = {
90
85
  medium: "small",
91
86
  small: "xsmall",
92
87
  large: "medium"
93
88
  };
94
- const contents = React.createElement(React.Fragment, null, label, spinner && React.createElement(CircularSpinner, {
89
+ const contents = React.createElement(React.Fragment, null, icon && React.createElement(Icon, {
90
+ size: iconSize,
91
+ color: "currentColor",
92
+ icon: icon,
93
+ style: sharedStyles.icon,
94
+ "aria-hidden": "true",
95
+ testId: testId ? `${testId}-icon` : undefined
96
+ }), label, spinner && React.createElement(CircularSpinner, {
95
97
  style: sharedStyles.spinner,
96
98
  size: sizeMapping[size],
97
99
  light: kind === "primary",
@@ -99,22 +101,23 @@ class ButtonCore extends React.Component {
99
101
  }));
100
102
  if (href && !disabled) {
101
103
  return router && !skipClientNav && isClientSideUrl(href) ? React.createElement(StyledLink, _extends({}, commonProps, {
102
- to: href
104
+ to: href,
105
+ ref: ref
103
106
  }), contents) : React.createElement(StyledAnchor, _extends({}, commonProps, {
104
- href: href
107
+ href: href,
108
+ ref: ref
105
109
  }), contents);
106
110
  } else {
107
111
  return React.createElement(StyledButton, _extends({
108
112
  type: type || "button"
109
113
  }, commonProps, {
110
- "aria-disabled": disabled
114
+ "aria-disabled": disabled,
115
+ ref: ref
111
116
  }), contents);
112
117
  }
113
- }
114
- render() {
115
- return React.createElement(__RouterContext.Consumer, null, router => this.renderInner(router));
116
- }
117
- }
118
+ };
119
+ return React.createElement(__RouterContext.Consumer, null, router => renderInner(router));
120
+ });
118
121
  const sharedStyles = StyleSheet.create({
119
122
  shared: {
120
123
  position: "relative",
@@ -164,9 +167,6 @@ const sharedStyles = StyleSheet.create({
164
167
  fontSize: 18,
165
168
  lineHeight: "20px"
166
169
  },
167
- textWithIcon: {
168
- display: "flex"
169
- },
170
170
  textWithFocus: {
171
171
  position: "relative"
172
172
  },
@@ -231,14 +231,14 @@ const _generateStyles = (color, kind, light, iconWidth, size) => {
231
231
  borderColor: light ? white50 : offBlack50,
232
232
  borderStyle: "solid",
233
233
  borderWidth: 1,
234
- paddingLeft: iconWidth ? padding - 4 : padding,
234
+ paddingLeft: padding,
235
235
  paddingRight: padding
236
236
  },
237
237
  focus: {
238
238
  background: light ? "transparent" : white,
239
239
  borderColor: light ? white : color,
240
240
  borderWidth: 2,
241
- paddingLeft: iconWidth ? padding - 5 : padding - 1,
241
+ paddingLeft: padding - 1,
242
242
  paddingRight: padding - 1
243
243
  },
244
244
  active: {
@@ -246,7 +246,7 @@ const _generateStyles = (color, kind, light, iconWidth, size) => {
246
246
  color: light ? fadedColor : activeColor,
247
247
  borderColor: light ? fadedColor : activeColor,
248
248
  borderWidth: 2,
249
- paddingLeft: iconWidth ? padding - 5 : padding - 1,
249
+ paddingLeft: padding - 1,
250
250
  paddingRight: padding - 1
251
251
  },
252
252
  disabled: {
@@ -256,7 +256,7 @@ const _generateStyles = (color, kind, light, iconWidth, size) => {
256
256
  ":focus": {
257
257
  borderColor: light ? white50 : offBlack32,
258
258
  borderWidth: 2,
259
- paddingLeft: iconWidth ? padding - 5 : padding - 1,
259
+ paddingLeft: padding - 1,
260
260
  paddingRight: padding - 1
261
261
  }
262
262
  }
@@ -269,29 +269,35 @@ const _generateStyles = (color, kind, light, iconWidth, size) => {
269
269
  paddingLeft: 0,
270
270
  paddingRight: 0
271
271
  },
272
- focus: {
272
+ hover: {
273
273
  ":after": {
274
274
  content: "''",
275
275
  position: "absolute",
276
276
  height: 2,
277
- width: `calc(100% - ${iconWidth}px)`,
277
+ width: "100%",
278
278
  right: 0,
279
279
  bottom: 0,
280
280
  background: light ? white : color,
281
281
  borderRadius: 2
282
282
  }
283
283
  },
284
- active: {
285
- color: light ? fadedColor : activeColor,
284
+ focus: {
286
285
  ":after": {
287
286
  content: "''",
288
287
  position: "absolute",
289
- height: 2,
290
- width: `calc(100% - ${iconWidth}px)`,
291
- right: 0,
292
- bottom: -1,
293
- background: light ? fadedColor : activeColor,
294
- borderRadius: 2
288
+ width: `calc(100% + ${Spacing.xxxSmall_4}px)`,
289
+ height: `calc(100% - ${Spacing.xxxSmall_4}px)`,
290
+ borderStyle: "solid",
291
+ borderColor: light ? white : color,
292
+ borderWidth: Spacing.xxxxSmall_2,
293
+ borderRadius: Spacing.xxxSmall_4
294
+ }
295
+ },
296
+ active: {
297
+ color: light ? fadedColor : activeColor,
298
+ ":after": {
299
+ height: 1,
300
+ background: light ? fadedColor : activeColor
295
301
  }
296
302
  },
297
303
  disabled: {
@@ -300,7 +306,7 @@ const _generateStyles = (color, kind, light, iconWidth, size) => {
300
306
  },
301
307
  disabledFocus: {
302
308
  ":after": {
303
- background: light ? white : offBlack32
309
+ borderColor: light ? white50 : offBlack32
304
310
  }
305
311
  }
306
312
  };
@@ -311,35 +317,43 @@ const _generateStyles = (color, kind, light, iconWidth, size) => {
311
317
  return styles[buttonType];
312
318
  };
313
319
 
314
- const _excluded = ["href", "type", "children", "skipClientNav", "spinner", "disabled", "onClick", "beforeNav", "safeWithNav", "tabIndex", "target", "rel"];
315
- class Button extends React.Component {
316
- renderClickableBehavior(router) {
317
- const _this$props = this.props,
318
- {
319
- href = undefined,
320
- type = undefined,
321
- children,
322
- skipClientNav,
323
- spinner,
324
- disabled,
325
- onClick,
326
- beforeNav = undefined,
327
- safeWithNav = undefined,
328
- tabIndex,
329
- target,
330
- rel
331
- } = _this$props,
332
- sharedButtonCoreProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
320
+ const _excluded = ["href", "type", "children", "skipClientNav", "onClick", "beforeNav", "safeWithNav", "tabIndex", "target", "rel", "color", "kind", "light", "size", "disabled", "spinner"];
321
+ const Button = React.forwardRef((props, ref) => {
322
+ const {
323
+ href = undefined,
324
+ type = undefined,
325
+ children,
326
+ skipClientNav,
327
+ onClick,
328
+ beforeNav = undefined,
329
+ safeWithNav = undefined,
330
+ tabIndex,
331
+ target,
332
+ rel,
333
+ color = "default",
334
+ kind = "primary",
335
+ light = false,
336
+ size = "medium",
337
+ disabled = false,
338
+ spinner = false
339
+ } = props,
340
+ sharedButtonCoreProps = _objectWithoutPropertiesLoose(props, _excluded);
341
+ const renderClickableBehavior = router => {
333
342
  const ClickableBehavior = getClickableBehavior(href, skipClientNav, router);
334
343
  const renderProp = (state, restChildProps) => {
335
344
  return React.createElement(ButtonCore, _extends({}, sharedButtonCoreProps, state, restChildProps, {
336
345
  disabled: disabled,
337
346
  spinner: spinner || state.waiting,
347
+ color: color,
348
+ kind: kind,
349
+ light: light,
350
+ size: size,
338
351
  skipClientNav: skipClientNav,
339
352
  href: href,
340
353
  target: target,
341
354
  type: type,
342
- tabIndex: tabIndex
355
+ tabIndex: tabIndex,
356
+ ref: ref
343
357
  }), children);
344
358
  };
345
359
  if (beforeNav) {
@@ -365,18 +379,8 @@ class Button extends React.Component {
365
379
  rel: rel
366
380
  }, renderProp);
367
381
  }
368
- }
369
- render() {
370
- return React.createElement(__RouterContext.Consumer, null, router => this.renderClickableBehavior(router));
371
- }
372
- }
373
- Button.defaultProps = {
374
- color: "default",
375
- kind: "primary",
376
- light: false,
377
- size: "medium",
378
- disabled: false,
379
- spinner: false
380
- };
382
+ };
383
+ return React.createElement(__RouterContext.Consumer, null, router => renderClickableBehavior(router));
384
+ });
381
385
 
382
386
  export { Button as default };