@khanacademy/wonder-blocks-clickable 3.0.13 → 3.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,11 @@
1
1
  # @khanacademy/wonder-blocks-clickable
2
2
 
3
+ ## 3.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ad8beb23: Added new tab index and ref props
8
+
3
9
  ## 3.0.13
4
10
 
5
11
  ### Patch Changes
@@ -1,13 +1,41 @@
1
1
  import * as React from "react";
2
+ import { Link } from "react-router-dom";
2
3
  import type { AriaProps, StyleType } from "@khanacademy/wonder-blocks-core";
3
4
  import type { ClickableRole, ClickableState } from "./clickable-behavior";
4
- type Props =
5
5
  /**
6
- * aria-label should be used when `spinner={true}` to let people using screen
7
- * readers that the action taken by clicking the button will take some
8
- * time to complete.
6
+ * A component to turn any custom component into a clickable one.
7
+ *
8
+ * Works by wrapping `ClickableBehavior` around the child element and styling
9
+ * the child appropriately and encapsulates routing logic which can be
10
+ * customized. Expects a function which returns an element as its child.
11
+ *
12
+ * Clickable allows your components to:
13
+ *
14
+ * - Handle mouse / touch / keyboard events
15
+ * - Match the standard behavior of the given role
16
+ * - Apply custom styles based on pressed / focused / hovered state
17
+ * - Perform Client Side Navigation when href is passed and the component is a
18
+ * descendent of a react-router Router.
19
+ *
20
+ * ### Usage
21
+ *
22
+ * ```jsx
23
+ * <Clickable onClick={() => alert("You clicked me!")}>
24
+ * {({hovered, focused, pressed}) =>
25
+ * <div
26
+ * style={[
27
+ * hovered && styles.hovered,
28
+ * focused && styles.focused,
29
+ * pressed && styles.pressed,
30
+ * ]}
31
+ * >
32
+ * Click Me!
33
+ * </div>
34
+ * }
35
+ * </Clickable>
36
+ * ```
9
37
  */
10
- Partial<Omit<AriaProps, "aria-disabled">> & {
38
+ declare const Clickable: React.ForwardRefExoticComponent<Partial<Omit<AriaProps, "aria-disabled">> & {
11
39
  /**
12
40
  * The child of Clickable must be a function which returns the component
13
41
  * which should be made Clickable. The function is passed an object with
@@ -17,12 +45,12 @@ Partial<Omit<AriaProps, "aria-disabled">> & {
17
45
  /**
18
46
  * An onClick function which Clickable can execute when clicked
19
47
  */
20
- onClick?: (e: React.SyntheticEvent) => unknown;
48
+ onClick?: ((e: React.SyntheticEvent) => unknown) | undefined;
21
49
  /**
22
50
  * Optional href which Clickable should direct to, uses client-side routing
23
51
  * by default if react-router is present
24
52
  */
25
- href?: string;
53
+ href?: string | undefined;
26
54
  /**
27
55
  * Styles to apply to the Clickable component
28
56
  */
@@ -30,59 +58,63 @@ Partial<Omit<AriaProps, "aria-disabled">> & {
30
58
  /**
31
59
  * Adds CSS classes to the Clickable.
32
60
  */
33
- className?: string;
61
+ className?: string | undefined;
34
62
  /**
35
63
  * Whether the Clickable is on a dark colored background.
36
64
  * Sets the default focus ring color to white, instead of blue.
37
65
  * Defaults to false.
38
66
  */
39
- light: boolean;
67
+ light?: boolean | undefined;
40
68
  /**
41
69
  * Disables or enables the child; defaults to false
42
70
  */
43
- disabled: boolean;
71
+ disabled?: boolean | undefined;
44
72
  /**
45
73
  * An optional id attribute.
46
74
  */
47
- id?: string;
75
+ id?: string | undefined;
48
76
  /**
49
77
  * Specifies the type of relationship between the current document and the
50
78
  * linked document. Should only be used when `href` is specified. This
51
79
  * defaults to "noopener noreferrer" when `target="_blank"`, but can be
52
80
  * overridden by setting this prop to something else.
53
81
  */
54
- rel?: string;
82
+ rel?: string | undefined;
55
83
  /**
56
84
  * The role of the component, can be a role of type ClickableRole
57
85
  */
58
- role?: ClickableRole;
86
+ role?: ClickableRole | undefined;
59
87
  /**
60
88
  * Avoids client-side routing in the presence of the href prop
61
89
  */
62
- skipClientNav?: boolean;
90
+ skipClientNav?: boolean | undefined;
63
91
  /**
64
92
  * Test ID used for e2e testing.
65
93
  */
66
- testId?: string;
94
+ testId?: string | undefined;
67
95
  /**
68
96
  * Respond to raw "keydown" event.
69
97
  */
70
- onKeyDown?: (e: React.KeyboardEvent) => unknown;
98
+ onKeyDown?: ((e: React.KeyboardEvent) => unknown) | undefined;
71
99
  /**
72
100
  * Respond to raw "keyup" event.
73
101
  */
74
- onKeyUp?: (e: React.KeyboardEvent) => unknown;
102
+ onKeyUp?: ((e: React.KeyboardEvent) => unknown) | undefined;
75
103
  /**
76
104
  * Don't show the default focus ring. This should be used when implementing
77
105
  * a custom focus ring within your own component that uses Clickable.
78
106
  */
79
- hideDefaultFocusRing?: boolean;
107
+ hideDefaultFocusRing?: boolean | undefined;
80
108
  /**
81
109
  * A target destination window for a link to open in.
82
110
  *
83
111
  * TODO(WB-1262): only allow this prop when `href` is also set.t
84
112
  */
85
- target?: "_blank";
113
+ target?: "_blank" | undefined;
114
+ /**
115
+ * Set the tabindex attribute on the rendered element.
116
+ */
117
+ tabIndex?: number | undefined;
86
118
  /**
87
119
  * Run async code before navigating. If the promise returned rejects then
88
120
  * navigation will not occur.
@@ -93,58 +125,13 @@ Partial<Omit<AriaProps, "aria-disabled">> & {
93
125
  * WARNING: This prop must be used with `href` and should not be used with
94
126
  * `target="blank"`.
95
127
  */
96
- beforeNav?: () => Promise<unknown>;
128
+ beforeNav?: (() => Promise<unknown>) | undefined;
97
129
  /**
98
130
  * Run async code in the background while client-side navigating. If the
99
131
  * browser does a full page load navigation, the callback promise must be
100
132
  * settled before the navigation will occur. Errors are ignored so that
101
133
  * navigation is guaranteed to succeed.
102
134
  */
103
- safeWithNav?: () => Promise<unknown>;
104
- };
105
- type DefaultProps = {
106
- light: Props["light"];
107
- disabled: Props["disabled"];
108
- };
109
- /**
110
- * A component to turn any custom component into a clickable one.
111
- *
112
- * Works by wrapping `ClickableBehavior` around the child element and styling
113
- * the child appropriately and encapsulates routing logic which can be
114
- * customized. Expects a function which returns an element as its child.
115
- *
116
- * Clickable allows your components to:
117
- *
118
- * - Handle mouse / touch / keyboard events
119
- * - Match the standard behavior of the given role
120
- * - Apply custom styles based on pressed / focused / hovered state
121
- * - Perform Client Side Navigation when href is passed and the component is a
122
- * descendent of a react-router Router.
123
- *
124
- * ### Usage
125
- *
126
- * ```jsx
127
- * <Clickable onClick={() => alert("You clicked me!")}>
128
- * {({hovered, focused, pressed}) =>
129
- * <div
130
- * style={[
131
- * hovered && styles.hovered,
132
- * focused && styles.focused,
133
- * pressed && styles.pressed,
134
- * ]}
135
- * >
136
- * Click Me!
137
- * </div>
138
- * }
139
- * </Clickable>
140
- * ```
141
- */
142
- export default class Clickable extends React.Component<Props> {
143
- static defaultProps: DefaultProps;
144
- getCorrectTag: (clickableState: ClickableState, router: any, commonProps: {
145
- [key: string]: any;
146
- }) => React.ReactElement;
147
- renderClickableBehavior(router: any): React.ReactNode;
148
- render(): React.ReactNode;
149
- }
150
- export {};
135
+ safeWithNav?: (() => Promise<unknown>) | undefined;
136
+ } & React.RefAttributes<HTMLAnchorElement | HTMLButtonElement | typeof Link>>;
137
+ export default Clickable;
@@ -5,9 +5,44 @@
5
5
  * @flow
6
6
  */
7
7
  import * as React from "react";
8
+ import { Link } from "react-router-dom";
8
9
  import type { AriaProps, StyleType } from "@khanacademy/wonder-blocks-core";
9
10
  import type { ClickableRole, ClickableState } from "./clickable-behavior";
10
- declare type Props = {|
11
+
12
+ /**
13
+ * A component to turn any custom component into a clickable one.
14
+ *
15
+ * Works by wrapping `ClickableBehavior` around the child element and styling
16
+ * the child appropriately and encapsulates routing logic which can be
17
+ * customized. Expects a function which returns an element as its child.
18
+ *
19
+ * Clickable allows your components to:
20
+ *
21
+ * - Handle mouse / touch / keyboard events
22
+ * - Match the standard behavior of the given role
23
+ * - Apply custom styles based on pressed / focused / hovered state
24
+ * - Perform Client Side Navigation when href is passed and the component is a
25
+ * descendent of a react-router Router.
26
+ *
27
+ * ### Usage
28
+ *
29
+ * ```jsx
30
+ * <Clickable onClick={() => alert("You clicked me!")}>
31
+ * {({hovered, focused, pressed}) =>
32
+ * <div
33
+ * style={[
34
+ * hovered && styles.hovered,
35
+ * focused && styles.focused,
36
+ * pressed && styles.pressed,
37
+ * ]}
38
+ * >
39
+ * Click Me!
40
+ * </div>
41
+ * }
42
+ * </Clickable>
43
+ * ```
44
+ */
45
+ declare var Clickable: React.ForwardRefExoticComponent<{|
11
46
  ...$Rest<$Diff<AriaProps, { "aria-disabled": any }>, {}>,
12
47
  ...{|
13
48
  /**
@@ -20,13 +55,13 @@ declare type Props = {|
20
55
  /**
21
56
  * An onClick function which Clickable can execute when clicked
22
57
  */
23
- onClick?: (e: SyntheticEvent<>) => mixed,
58
+ onClick?: ((e: SyntheticEvent<>) => mixed) | void,
24
59
 
25
60
  /**
26
61
  * Optional href which Clickable should direct to, uses client-side routing
27
62
  * by default if react-router is present
28
63
  */
29
- href?: string,
64
+ href?: string | void,
30
65
 
31
66
  /**
32
67
  * Styles to apply to the Clickable component
@@ -36,24 +71,24 @@ declare type Props = {|
36
71
  /**
37
72
  * Adds CSS classes to the Clickable.
38
73
  */
39
- className?: string,
74
+ className?: string | void,
40
75
 
41
76
  /**
42
77
  * Whether the Clickable is on a dark colored background.
43
78
  * Sets the default focus ring color to white, instead of blue.
44
79
  * Defaults to false.
45
80
  */
46
- light: boolean,
81
+ light?: boolean | void,
47
82
 
48
83
  /**
49
84
  * Disables or enables the child; defaults to false
50
85
  */
51
- disabled: boolean,
86
+ disabled?: boolean | void,
52
87
 
53
88
  /**
54
89
  * An optional id attribute.
55
90
  */
56
- id?: string,
91
+ id?: string | void,
57
92
 
58
93
  /**
59
94
  * Specifies the type of relationship between the current document and the
@@ -61,45 +96,50 @@ declare type Props = {|
61
96
  * defaults to "noopener noreferrer" when `target="_blank"`, but can be
62
97
  * overridden by setting this prop to something else.
63
98
  */
64
- rel?: string,
99
+ rel?: string | void,
65
100
 
66
101
  /**
67
102
  * The role of the component, can be a role of type ClickableRole
68
103
  */
69
- role?: ClickableRole,
104
+ role?: ClickableRole | void,
70
105
 
71
106
  /**
72
107
  * Avoids client-side routing in the presence of the href prop
73
108
  */
74
- skipClientNav?: boolean,
109
+ skipClientNav?: boolean | void,
75
110
 
76
111
  /**
77
112
  * Test ID used for e2e testing.
78
113
  */
79
- testId?: string,
114
+ testId?: string | void,
80
115
 
81
116
  /**
82
117
  * Respond to raw "keydown" event.
83
118
  */
84
- onKeyDown?: (e: SyntheticKeyboardEvent<>) => mixed,
119
+ onKeyDown?: ((e: SyntheticKeyboardEvent<>) => mixed) | void,
85
120
 
86
121
  /**
87
122
  * Respond to raw "keyup" event.
88
123
  */
89
- onKeyUp?: (e: SyntheticKeyboardEvent<>) => mixed,
124
+ onKeyUp?: ((e: SyntheticKeyboardEvent<>) => mixed) | void,
90
125
 
91
126
  /**
92
127
  * Don't show the default focus ring. This should be used when implementing
93
128
  * a custom focus ring within your own component that uses Clickable.
94
129
  */
95
- hideDefaultFocusRing?: boolean,
130
+ hideDefaultFocusRing?: boolean | void,
96
131
 
97
132
  /**
98
133
  * A target destination window for a link to open in.
99
134
  *
100
135
  * TODO(WB-1262): only allow this prop when `href` is also set.t
101
136
  */
102
- target?: "_blank",
137
+ target?: "_blank" | void,
138
+
139
+ /**
140
+ * Set the tabindex attribute on the rendered element.
141
+ */
142
+ tabIndex?: number | void,
103
143
 
104
144
  /**
105
145
  * Run async code before navigating. If the promise returned rejects then
@@ -111,7 +151,7 @@ declare type Props = {|
111
151
  * WARNING: This prop must be used with `href` and should not be used with
112
152
  * `target="blank"`.
113
153
  */
114
- beforeNav?: () => Promise<mixed>,
154
+ beforeNav?: (() => Promise<mixed>) | void,
115
155
 
116
156
  /**
117
157
  * Run async code in the background while client-side navigating. If the
@@ -119,55 +159,8 @@ declare type Props = {|
119
159
  * settled before the navigation will occur. Errors are ignored so that
120
160
  * navigation is guaranteed to succeed.
121
161
  */
122
- safeWithNav?: () => Promise<mixed>,
162
+ safeWithNav?: (() => Promise<mixed>) | void,
123
163
  |},
124
- |};
125
- declare type DefaultProps = {|
126
- light: $PropertyType<Props, "light">,
127
- disabled: $PropertyType<Props, "disabled">,
128
- |};
129
- /**
130
- * A component to turn any custom component into a clickable one.
131
- *
132
- * Works by wrapping `ClickableBehavior` around the child element and styling
133
- * the child appropriately and encapsulates routing logic which can be
134
- * customized. Expects a function which returns an element as its child.
135
- *
136
- * Clickable allows your components to:
137
- *
138
- * - Handle mouse / touch / keyboard events
139
- * - Match the standard behavior of the given role
140
- * - Apply custom styles based on pressed / focused / hovered state
141
- * - Perform Client Side Navigation when href is passed and the component is a
142
- * descendent of a react-router Router.
143
- *
144
- * ### Usage
145
- *
146
- * ```jsx
147
- * <Clickable onClick={() => alert("You clicked me!")}>
148
- * {({hovered, focused, pressed}) =>
149
- * <div
150
- * style={[
151
- * hovered && styles.hovered,
152
- * focused && styles.focused,
153
- * pressed && styles.pressed,
154
- * ]}
155
- * >
156
- * Click Me!
157
- * </div>
158
- * }
159
- * </Clickable>
160
- * ```
161
- */
162
- declare export default class Clickable extends React.Component<Props> {
163
- static defaultProps: DefaultProps;
164
- getCorrectTag: (
165
- clickableState: ClickableState,
166
- router: any,
167
- commonProps: {
168
- [key: string]: any,
169
- }
170
- ) => React.Element<any>;
171
- renderClickableBehavior(router: any): React.Node;
172
- render(): React.Node;
173
- }
164
+ ...React.RefAttributes<HTMLAnchorElement | HTMLButtonElement | typeof Link>,
165
+ |}>;
166
+ declare export default typeof Clickable;
package/dist/es/index.js CHANGED
@@ -364,41 +364,40 @@ function getClickableBehavior(href, skipClientNav, router) {
364
364
  return ClickableBehavior;
365
365
  }
366
366
 
367
- const _excluded = ["href", "onClick", "skipClientNav", "beforeNav", "safeWithNav", "style", "target", "testId", "onKeyDown", "onKeyUp", "hideDefaultFocusRing", "light", "disabled"];
367
+ const _excluded = ["href", "onClick", "skipClientNav", "beforeNav", "safeWithNav", "style", "target", "testId", "onKeyDown", "onKeyUp", "hideDefaultFocusRing", "light", "disabled", "tabIndex"];
368
368
  const StyledAnchor = addStyle("a");
369
369
  const StyledButton = addStyle("button");
370
370
  const StyledLink = addStyle(Link);
371
- class Clickable extends React.Component {
372
- constructor(...args) {
373
- super(...args);
374
- this.getCorrectTag = (clickableState, router, commonProps) => {
375
- const activeHref = this.props.href && !this.props.disabled;
376
- const useClient = router && !this.props.skipClientNav && isClientSideUrl(this.props.href || "");
377
- if (activeHref && useClient && this.props.href) {
378
- return React.createElement(StyledLink, _extends({}, commonProps, {
379
- to: this.props.href,
380
- role: this.props.role,
381
- target: this.props.target || undefined,
382
- "aria-disabled": this.props.disabled ? "true" : undefined
383
- }), this.props.children(clickableState));
384
- } else if (activeHref && !useClient) {
385
- return React.createElement(StyledAnchor, _extends({}, commonProps, {
386
- href: this.props.href,
387
- role: this.props.role,
388
- target: this.props.target || undefined,
389
- "aria-disabled": this.props.disabled ? "true" : undefined
390
- }), this.props.children(clickableState));
391
- } else {
392
- return React.createElement(StyledButton, _extends({}, commonProps, {
393
- type: "button",
394
- "aria-disabled": this.props.disabled
395
- }), this.props.children(clickableState));
396
- }
397
- };
398
- }
399
- renderClickableBehavior(router) {
400
- const _this$props = this.props,
401
- {
371
+ const Clickable = React.forwardRef(function Clickable(props, ref) {
372
+ const getCorrectTag = (clickableState, router, commonProps) => {
373
+ const activeHref = props.href && !props.disabled;
374
+ const useClient = router && !props.skipClientNav && isClientSideUrl(props.href || "");
375
+ if (activeHref && useClient && props.href) {
376
+ return React.createElement(StyledLink, _extends({}, commonProps, {
377
+ to: props.href,
378
+ role: props.role,
379
+ target: props.target || undefined,
380
+ "aria-disabled": props.disabled ? "true" : undefined,
381
+ ref: ref
382
+ }), props.children(clickableState));
383
+ } else if (activeHref && !useClient) {
384
+ return React.createElement(StyledAnchor, _extends({}, commonProps, {
385
+ href: props.href,
386
+ role: props.role,
387
+ target: props.target || undefined,
388
+ "aria-disabled": props.disabled ? "true" : undefined,
389
+ ref: ref
390
+ }), props.children(clickableState));
391
+ } else {
392
+ return React.createElement(StyledButton, _extends({}, commonProps, {
393
+ type: "button",
394
+ "aria-disabled": props.disabled,
395
+ ref: ref
396
+ }), props.children(clickableState));
397
+ }
398
+ };
399
+ const renderClickableBehavior = router => {
400
+ const {
402
401
  href,
403
402
  onClick,
404
403
  skipClientNav,
@@ -411,9 +410,10 @@ class Clickable extends React.Component {
411
410
  onKeyUp,
412
411
  hideDefaultFocusRing,
413
412
  light,
414
- disabled
415
- } = _this$props,
416
- restProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
413
+ disabled,
414
+ tabIndex
415
+ } = props,
416
+ restProps = _objectWithoutPropertiesLoose(props, _excluded);
417
417
  const ClickableBehavior = getClickableBehavior(href, skipClientNav, router);
418
418
  const getStyle = state => [styles.reset, styles.link, !hideDefaultFocusRing && state.focused && (light ? styles.focusedLight : styles.focused), disabled && styles.disabled, style];
419
419
  if (beforeNav) {
@@ -424,8 +424,9 @@ class Clickable extends React.Component {
424
424
  safeWithNav: safeWithNav,
425
425
  onKeyDown: onKeyDown,
426
426
  onKeyUp: onKeyUp,
427
- disabled: disabled
428
- }, (state, childrenProps) => this.getCorrectTag(state, router, _extends({}, restProps, {
427
+ disabled: disabled,
428
+ tabIndex: tabIndex
429
+ }, (state, childrenProps) => getCorrectTag(state, router, _extends({}, restProps, {
429
430
  "data-test-id": testId,
430
431
  style: getStyle(state)
431
432
  }, childrenProps)));
@@ -437,17 +438,16 @@ class Clickable extends React.Component {
437
438
  onKeyDown: onKeyDown,
438
439
  onKeyUp: onKeyUp,
439
440
  target: target,
440
- disabled: disabled
441
- }, (state, childrenProps) => this.getCorrectTag(state, router, _extends({}, restProps, {
441
+ disabled: disabled,
442
+ tabIndex: tabIndex
443
+ }, (state, childrenProps) => getCorrectTag(state, router, _extends({}, restProps, {
442
444
  "data-test-id": testId,
443
445
  style: getStyle(state)
444
446
  }, childrenProps)));
445
447
  }
446
- }
447
- render() {
448
- return React.createElement(__RouterContext.Consumer, null, router => this.renderClickableBehavior(router));
449
- }
450
- }
448
+ };
449
+ return React.createElement(__RouterContext.Consumer, null, router => renderClickableBehavior(router));
450
+ });
451
451
  Clickable.defaultProps = {
452
452
  light: false,
453
453
  disabled: false