@khanacademy/wonder-blocks-clickable 3.1.3 → 4.0.1

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,28 @@
1
1
  # @khanacademy/wonder-blocks-clickable
2
2
 
3
+ ## 4.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - f230b267: Allow 'safeWithNav()' to be used on its own without 'beforeNav()' in prop types
8
+
9
+ ## 4.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - 674a1e5c: Props are using discriminated union types to prevent invalid combinations of props
14
+
15
+ ### Minor Changes
16
+
17
+ - 8c77f29d: Create new Switch component and add 'switch' role to ClickableRole
18
+
19
+ ### Patch Changes
20
+
21
+ - 674a1e5c: We're no longer building flow types
22
+ - Updated dependencies [674a1e5c]
23
+ - Updated dependencies [674a1e5c]
24
+ - @khanacademy/wonder-blocks-core@6.0.0
25
+
3
26
  ## 3.1.3
4
27
 
5
28
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
- export type ClickableRole = "button" | "link" | "checkbox" | "radio" | "listbox" | "option" | "menuitem" | "menu" | "tab";
3
- type Props = Readonly<{
2
+ export type ClickableRole = "button" | "checkbox" | "link" | "listbox" | "menu" | "menuitem" | "option" | "radio" | "switch" | "tab";
3
+ type CommonProps = Readonly<{
4
4
  /**
5
5
  * A function that returns the a React `Element`.
6
6
  *
@@ -74,11 +74,15 @@ type Props = Readonly<{
74
74
  * Respond to raw "keyup" event.
75
75
  */
76
76
  onKeyUp?: (e: React.KeyboardEvent) => unknown;
77
+ }>;
78
+ type Props = (CommonProps & Readonly<{
77
79
  /**
78
80
  * A target destination window for a link to open in. Should only be used
79
81
  * when `href` is specified.
80
82
  */
81
83
  target?: "_blank";
84
+ beforeNav?: never;
85
+ }>) | (CommonProps & Readonly<{
82
86
  /**
83
87
  * Run async code before navigating to the URL passed to `href`. If the
84
88
  * promise returned rejects then navigation will not occur.
@@ -93,7 +97,8 @@ type Props = Readonly<{
93
97
  * navigation.
94
98
  */
95
99
  beforeNav?: () => Promise<unknown>;
96
- }>;
100
+ target?: never;
101
+ }>);
97
102
  export type ClickableState = Readonly<{
98
103
  /**
99
104
  * Whether the component is hovered.
@@ -2,55 +2,28 @@ import * as React from "react";
2
2
  import { Link } from "react-router-dom";
3
3
  import type { AriaProps, StyleType } from "@khanacademy/wonder-blocks-core";
4
4
  import type { ClickableRole, ClickableState } from "./clickable-behavior";
5
+ type CommonProps =
5
6
  /**
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
- * ```
7
+ * aria-label should be used when `spinner={true}` to let people using screen
8
+ * readers that the action taken by clicking the button will take some
9
+ * time to complete.
37
10
  */
38
- declare const Clickable: React.ForwardRefExoticComponent<Partial<Omit<AriaProps, "aria-disabled">> & {
11
+ Partial<Omit<AriaProps, "aria-disabled">> & {
39
12
  /**
40
13
  * The child of Clickable must be a function which returns the component
41
14
  * which should be made Clickable. The function is passed an object with
42
15
  * three boolean properties: hovered, focused, and pressed.
43
16
  */
44
- children: (arg1: ClickableState) => React.ReactNode;
17
+ children: (clickableState: ClickableState) => React.ReactNode;
45
18
  /**
46
19
  * An onClick function which Clickable can execute when clicked
47
20
  */
48
- onClick?: ((e: React.SyntheticEvent) => unknown) | undefined;
21
+ onClick?: (e: React.SyntheticEvent) => unknown;
49
22
  /**
50
23
  * Optional href which Clickable should direct to, uses client-side routing
51
24
  * by default if react-router is present
52
25
  */
53
- href?: string | undefined;
26
+ href?: string;
54
27
  /**
55
28
  * Styles to apply to the Clickable component
56
29
  */
@@ -58,63 +31,57 @@ declare const Clickable: React.ForwardRefExoticComponent<Partial<Omit<AriaProps,
58
31
  /**
59
32
  * Adds CSS classes to the Clickable.
60
33
  */
61
- className?: string | undefined;
34
+ className?: string;
62
35
  /**
63
36
  * Whether the Clickable is on a dark colored background.
64
37
  * Sets the default focus ring color to white, instead of blue.
65
38
  * Defaults to false.
66
39
  */
67
- light?: boolean | undefined;
40
+ light?: boolean;
68
41
  /**
69
42
  * Disables or enables the child; defaults to false
70
43
  */
71
- disabled?: boolean | undefined;
44
+ disabled?: boolean;
72
45
  /**
73
46
  * An optional id attribute.
74
47
  */
75
- id?: string | undefined;
48
+ id?: string;
76
49
  /**
77
50
  * Specifies the type of relationship between the current document and the
78
51
  * linked document. Should only be used when `href` is specified. This
79
52
  * defaults to "noopener noreferrer" when `target="_blank"`, but can be
80
53
  * overridden by setting this prop to something else.
81
54
  */
82
- rel?: string | undefined;
55
+ rel?: string;
83
56
  /**
84
57
  * The role of the component, can be a role of type ClickableRole
85
58
  */
86
- role?: ClickableRole | undefined;
59
+ role?: ClickableRole;
87
60
  /**
88
61
  * Avoids client-side routing in the presence of the href prop
89
62
  */
90
- skipClientNav?: boolean | undefined;
63
+ skipClientNav?: boolean;
91
64
  /**
92
65
  * Test ID used for e2e testing.
93
66
  */
94
- testId?: string | undefined;
67
+ testId?: string;
95
68
  /**
96
69
  * Respond to raw "keydown" event.
97
70
  */
98
- onKeyDown?: ((e: React.KeyboardEvent) => unknown) | undefined;
71
+ onKeyDown?: (e: React.KeyboardEvent) => unknown;
99
72
  /**
100
73
  * Respond to raw "keyup" event.
101
74
  */
102
- onKeyUp?: ((e: React.KeyboardEvent) => unknown) | undefined;
75
+ onKeyUp?: (e: React.KeyboardEvent) => unknown;
103
76
  /**
104
77
  * Don't show the default focus ring. This should be used when implementing
105
78
  * a custom focus ring within your own component that uses Clickable.
106
79
  */
107
- hideDefaultFocusRing?: boolean | undefined;
108
- /**
109
- * A target destination window for a link to open in.
110
- *
111
- * TODO(WB-1262): only allow this prop when `href` is also set.t
112
- */
113
- target?: "_blank" | undefined;
80
+ hideDefaultFocusRing?: boolean;
114
81
  /**
115
82
  * Set the tabindex attribute on the rendered element.
116
83
  */
117
- tabIndex?: number | undefined;
84
+ tabIndex?: number;
118
85
  /**
119
86
  * Run async code before navigating. If the promise returned rejects then
120
87
  * navigation will not occur.
@@ -125,13 +92,84 @@ declare const Clickable: React.ForwardRefExoticComponent<Partial<Omit<AriaProps,
125
92
  * WARNING: This prop must be used with `href` and should not be used with
126
93
  * `target="blank"`.
127
94
  */
128
- beforeNav?: (() => Promise<unknown>) | undefined;
95
+ beforeNav?: () => Promise<unknown>;
96
+ /**
97
+ * Run async code in the background while client-side navigating. If the
98
+ * browser does a full page load navigation, the callback promise must be
99
+ * settled before the navigation will occur. Errors are ignored so that
100
+ * navigation is guaranteed to succeed.
101
+ */
102
+ safeWithNav?: () => Promise<unknown>;
103
+ };
104
+ type Props = (CommonProps & {
105
+ target?: never;
106
+ beforeNav?: never;
107
+ safeWithNav?: never;
108
+ }) | (CommonProps & {
109
+ href: string;
110
+ /**
111
+ * Run async code in the background while client-side navigating. If the
112
+ * browser does a full page load navigation, the callback promise must be
113
+ * settled before the navigation will occur. Errors are ignored so that
114
+ * navigation is guaranteed to succeed.
115
+ */
116
+ safeWithNav?: () => Promise<unknown>;
117
+ /**
118
+ * A target destination window for a link to open in.
119
+ */
120
+ target?: "_blank";
121
+ beforeNav?: never;
122
+ }) | (CommonProps & {
123
+ href: string;
124
+ /**
125
+ * Run async code before navigating. If the promise returned rejects then
126
+ * navigation will not occur.
127
+ *
128
+ * If both safeWithNav and beforeNav are provided, beforeNav will be run
129
+ * first and safeWithNav will only be run if beforeNav does not reject.
130
+ */
131
+ beforeNav?: () => Promise<unknown>;
129
132
  /**
130
133
  * Run async code in the background while client-side navigating. If the
131
134
  * browser does a full page load navigation, the callback promise must be
132
135
  * settled before the navigation will occur. Errors are ignored so that
133
136
  * navigation is guaranteed to succeed.
134
137
  */
135
- safeWithNav?: (() => Promise<unknown>) | undefined;
136
- } & React.RefAttributes<HTMLAnchorElement | HTMLButtonElement | typeof Link>>;
138
+ safeWithNav?: () => Promise<unknown>;
139
+ target?: never;
140
+ });
141
+ /**
142
+ * A component to turn any custom component into a clickable one.
143
+ *
144
+ * Works by wrapping `ClickableBehavior` around the child element and styling
145
+ * the child appropriately and encapsulates routing logic which can be
146
+ * customized. Expects a function which returns an element as its child.
147
+ *
148
+ * Clickable allows your components to:
149
+ *
150
+ * - Handle mouse / touch / keyboard events
151
+ * - Match the standard behavior of the given role
152
+ * - Apply custom styles based on pressed / focused / hovered state
153
+ * - Perform Client Side Navigation when href is passed and the component is a
154
+ * descendent of a react-router Router.
155
+ *
156
+ * ### Usage
157
+ *
158
+ * ```jsx
159
+ * <Clickable onClick={() => alert("You clicked me!")}>
160
+ * {({hovered, focused, pressed}) =>
161
+ * <div
162
+ * style={[
163
+ * hovered && styles.hovered,
164
+ * focused && styles.focused,
165
+ * pressed && styles.pressed,
166
+ * ]}
167
+ * >
168
+ * Click Me!
169
+ * </div>
170
+ * }
171
+ * </Clickable>
172
+ * ```
173
+ */
174
+ declare const Clickable: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLAnchorElement | HTMLButtonElement | typeof Link>>;
137
175
  export default Clickable;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-clickable",
3
- "version": "3.1.3",
3
+ "version": "4.0.1",
4
4
  "design": "v1",
5
5
  "description": "Clickable component for Wonder-Blocks.",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,7 @@
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.18.6",
19
19
  "@khanacademy/wonder-blocks-color": "^2.0.1",
20
- "@khanacademy/wonder-blocks-core": "^5.4.0"
20
+ "@khanacademy/wonder-blocks-core": "^6.0.0"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "aphrodite": "^1.2.5",
@@ -27,6 +27,6 @@
27
27
  "react-router-dom": "5.3.0"
28
28
  },
29
29
  "devDependencies": {
30
- "wb-dev-build-settings": "^0.9.7"
30
+ "@khanacademy/wb-dev-build-settings": "^1.0.0"
31
31
  }
32
32
  }
@@ -0,0 +1,20 @@
1
+ import * as React from "react";
2
+
3
+ import ClickableBehavior from "../clickable-behavior";
4
+
5
+ <ClickableBehavior>
6
+ {(_, childrenProps) => <div {...childrenProps} />}
7
+ </ClickableBehavior>;
8
+
9
+ <ClickableBehavior target="_blank">
10
+ {(_, childrenProps) => <div {...childrenProps} />}
11
+ </ClickableBehavior>;
12
+
13
+ <ClickableBehavior beforeNav={() => Promise.resolve()}>
14
+ {(_, childrenProps) => <div {...childrenProps} />}
15
+ </ClickableBehavior>;
16
+
17
+ // @ts-expect-error `target` and `beforeNav` can't be used together.
18
+ <ClickableBehavior target="_blank" beforeNav={() => Promise.resolve()}>
19
+ {(_, childrenProps) => <div {...childrenProps} />}
20
+ </ClickableBehavior>;
@@ -0,0 +1,49 @@
1
+ import * as React from "react";
2
+
3
+ import Clickable from "../clickable";
4
+
5
+ <Clickable>{(_) => "Hello, world!"}</Clickable>;
6
+
7
+ <Clickable href="/foo">{(_) => "Hello, world!"}</Clickable>;
8
+
9
+ <Clickable href="/foo" target="_blank">
10
+ {(_) => "Hello, world!"}
11
+ </Clickable>;
12
+
13
+ // @ts-expect-error - `href` must be set when using `target="_blank"`.
14
+ <Clickable target="_blank">{(_) => "Hello, world!"}</Clickable>;
15
+
16
+ <Clickable href="/foo" beforeNav={() => Promise.resolve()}>
17
+ {(_) => "Hello, world!"}
18
+ </Clickable>;
19
+
20
+ <Clickable href="/foo" safeWithNav={() => Promise.resolve()}>
21
+ {(_) => "Hello, world!"}
22
+ </Clickable>;
23
+
24
+ <Clickable href="/foo" target="_blank" safeWithNav={() => Promise.resolve()}>
25
+ {(_) => "Hello, world!"}
26
+ </Clickable>;
27
+
28
+ // @ts-expect-error - `target="_blank"` cannot be used with `beforeNav`.
29
+ <Clickable href="/foo" target="_blank" beforeNav={() => Promise.resolve()}>
30
+ {(_) => "Hello, world!"}
31
+ </Clickable>;
32
+
33
+ // @ts-expect-error - `target="_blank"` cannot be used with `beforeNav`.
34
+ <Clickable
35
+ href="/foo"
36
+ target="_blank"
37
+ beforeNav={() => Promise.resolve()}
38
+ safeWithNav={() => Promise.resolve()}
39
+ >
40
+ {(_) => "Hello, world!"}
41
+ </Clickable>;
42
+
43
+ <Clickable
44
+ href="/foo"
45
+ beforeNav={() => Promise.resolve()}
46
+ safeWithNav={() => Promise.resolve()}
47
+ >
48
+ {(_) => "Hello, world!"}
49
+ </Clickable>;
@@ -3,13 +3,14 @@ import * as React from "react";
3
3
  // NOTE: Potentially add to this as more cases come up.
4
4
  export type ClickableRole =
5
5
  | "button"
6
- | "link"
7
6
  | "checkbox"
8
- | "radio"
7
+ | "link"
9
8
  | "listbox"
10
- | "option"
11
- | "menuitem"
12
9
  | "menu"
10
+ | "menuitem"
11
+ | "option"
12
+ | "radio"
13
+ | "switch"
13
14
  | "tab";
14
15
 
15
16
  const getAppropriateTriggersForRole = (role?: ClickableRole | null) => {
@@ -41,8 +42,7 @@ const getAppropriateTriggersForRole = (role?: ClickableRole | null) => {
41
42
  }
42
43
  };
43
44
 
44
- // TODO(FEI-5000): Convert back to conditional props after TS migration is complete.
45
- type Props = Readonly<{
45
+ type CommonProps = Readonly<{
46
46
  /**
47
47
  * A function that returns the a React `Element`.
48
48
  *
@@ -119,28 +119,40 @@ type Props = Readonly<{
119
119
  * Respond to raw "keyup" event.
120
120
  */
121
121
  onKeyUp?: (e: React.KeyboardEvent) => unknown;
122
- /**
123
- * A target destination window for a link to open in. Should only be used
124
- * when `href` is specified.
125
- */
126
- // TODO(WB-1262): only allow this prop when `href` is also set.
127
- target?: "_blank";
128
- /**
129
- * Run async code before navigating to the URL passed to `href`. If the
130
- * promise returned rejects then navigation will not occur.
131
- *
132
- * If both safeWithNav and beforeNav are provided, beforeNav will be run
133
- * first and safeWithNav will only be run if beforeNav does not reject.
134
- *
135
- * WARNING: Using this with `target="_blank"` will trigger built-in popup
136
- * blockers in Firefox and Safari. This is because we do navigation
137
- * programmatically and `beforeNav` causes a delay which means that the
138
- * browser can't make a directly link between a user action and the
139
- * navigation.
140
- */
141
- beforeNav?: () => Promise<unknown>;
142
122
  }>;
143
123
 
124
+ type Props =
125
+ | (CommonProps &
126
+ Readonly<{
127
+ /**
128
+ * A target destination window for a link to open in. Should only be used
129
+ * when `href` is specified.
130
+ */
131
+ // TODO(WB-1262): only allow this prop when `href` is also set.
132
+ target?: "_blank";
133
+
134
+ beforeNav?: never; // disallow beforeNav when target="_blank"
135
+ }>)
136
+ | (CommonProps &
137
+ Readonly<{
138
+ /**
139
+ * Run async code before navigating to the URL passed to `href`. If the
140
+ * promise returned rejects then navigation will not occur.
141
+ *
142
+ * If both safeWithNav and beforeNav are provided, beforeNav will be run
143
+ * first and safeWithNav will only be run if beforeNav does not reject.
144
+ *
145
+ * WARNING: Using this with `target="_blank"` will trigger built-in popup
146
+ * blockers in Firefox and Safari. This is because we do navigation
147
+ * programmatically and `beforeNav` causes a delay which means that the
148
+ * browser can't make a directly link between a user action and the
149
+ * navigation.
150
+ */
151
+ beforeNav?: () => Promise<unknown>;
152
+
153
+ target?: never; // disallow target="_blank" when beforeNav is set
154
+ }>);
155
+
144
156
  export type ClickableState = Readonly<{
145
157
  /**
146
158
  * Whether the component is hovered.
@@ -11,8 +11,7 @@ import getClickableBehavior from "../util/get-clickable-behavior";
11
11
  import type {ClickableRole, ClickableState} from "./clickable-behavior";
12
12
  import {isClientSideUrl} from "../util/is-client-side-url";
13
13
 
14
- // TODO(FEI-5000): Convert back to conditional props after TS migration is complete.
15
- type Props =
14
+ type CommonProps =
16
15
  /**
17
16
  * aria-label should be used when `spinner={true}` to let people using screen
18
17
  * readers that the action taken by clicking the button will take some
@@ -24,7 +23,7 @@ type Props =
24
23
  * which should be made Clickable. The function is passed an object with
25
24
  * three boolean properties: hovered, focused, and pressed.
26
25
  */
27
- children: (arg1: ClickableState) => React.ReactNode;
26
+ children: (clickableState: ClickableState) => React.ReactNode;
28
27
  /**
29
28
  * An onClick function which Clickable can execute when clicked
30
29
  */
@@ -88,12 +87,6 @@ type Props =
88
87
  * a custom focus ring within your own component that uses Clickable.
89
88
  */
90
89
  hideDefaultFocusRing?: boolean;
91
- /**
92
- * A target destination window for a link to open in.
93
- *
94
- * TODO(WB-1262): only allow this prop when `href` is also set.t
95
- */
96
- target?: "_blank";
97
90
  /**
98
91
  * Set the tabindex attribute on the rendered element.
99
92
  */
@@ -118,6 +111,53 @@ type Props =
118
111
  safeWithNav?: () => Promise<unknown>;
119
112
  };
120
113
 
114
+ type Props =
115
+ | (CommonProps & {
116
+ target?: never;
117
+ beforeNav?: never;
118
+ safeWithNav?: never;
119
+ })
120
+ | (CommonProps & {
121
+ href: string;
122
+
123
+ /**
124
+ * Run async code in the background while client-side navigating. If the
125
+ * browser does a full page load navigation, the callback promise must be
126
+ * settled before the navigation will occur. Errors are ignored so that
127
+ * navigation is guaranteed to succeed.
128
+ */
129
+ safeWithNav?: () => Promise<unknown>;
130
+
131
+ /**
132
+ * A target destination window for a link to open in.
133
+ */
134
+ target?: "_blank";
135
+
136
+ beforeNav?: never;
137
+ })
138
+ | (CommonProps & {
139
+ href: string;
140
+
141
+ /**
142
+ * Run async code before navigating. If the promise returned rejects then
143
+ * navigation will not occur.
144
+ *
145
+ * If both safeWithNav and beforeNav are provided, beforeNav will be run
146
+ * first and safeWithNav will only be run if beforeNav does not reject.
147
+ */
148
+ beforeNav?: () => Promise<unknown>;
149
+
150
+ /**
151
+ * Run async code in the background while client-side navigating. If the
152
+ * browser does a full page load navigation, the callback promise must be
153
+ * settled before the navigation will occur. Errors are ignored so that
154
+ * navigation is guaranteed to succeed.
155
+ */
156
+ safeWithNav?: () => Promise<unknown>;
157
+
158
+ target?: never;
159
+ });
160
+
121
161
  const StyledAnchor = addStyle("a");
122
162
  const StyledButton = addStyle("button");
123
163
  const StyledLink = addStyle(Link);