@khanacademy/wonder-blocks-link 4.3.0 → 4.4.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.
@@ -26,135 +26,132 @@ type Props = SharedProps &
26
26
  const StyledAnchor = addStyle("a");
27
27
  const StyledLink = addStyle(Link);
28
28
 
29
- const LinkCore = React.forwardRef(
30
- (
31
- props: Props,
32
- ref: React.ForwardedRef<typeof Link | HTMLAnchorElement>,
33
- ) => {
34
- const renderInner = (router: any): React.ReactNode => {
35
- const {
36
- children,
37
- skipClientNav,
38
- focused,
39
- hovered,
40
- href,
41
- inline = false,
42
- kind = "primary",
43
- light = false,
44
- visitable = false,
45
- pressed,
46
- style,
47
- testId,
48
- waiting: _,
49
- target,
50
- startIcon,
51
- endIcon,
52
- ...restProps
53
- } = props;
54
-
55
- const linkStyles = _generateStyles(inline, kind, light, visitable);
56
- const restingStyles = inline
57
- ? linkStyles.restingInline
58
- : linkStyles.resting;
59
-
60
- const defaultStyles = [
61
- sharedStyles.shared,
62
- restingStyles,
63
- pressed && linkStyles.active,
64
- // A11y: The focus ring should always be present when the
65
- // the link has focus, even the link is being hovered over.
66
- // TODO(WB-1498): Udpate ClickableBehavior so that focus doesn't
67
- // stop on mouseleave. We want the focus ring to remain on a
68
- // focused link even after hovering and un-hovering on it.
69
- !pressed && hovered && linkStyles.hover,
70
- !pressed && focused && linkStyles.focus,
71
- ];
72
-
73
- const commonProps = {
74
- "data-test-id": testId,
75
- style: [defaultStyles, style],
76
- target,
77
- ...restProps,
78
- } as const;
79
-
80
- const linkUrl = new URL(href, window.location.origin);
81
-
82
- const isExternalLink = linkUrl.origin !== window.location.origin;
83
-
84
- const externalIconPath: IconAsset = {
85
- small: "M14 6.5C14 6.63261 13.9473 6.75979 13.8536 6.85355C13.7598 6.94732 13.6326 7 13.5 7C13.3674 7 13.2402 6.94732 13.1464 6.85355C13.0527 6.75979 13 6.63261 13 6.5V3.7075L8.85437 7.85375C8.76055 7.94757 8.63331 8.00028 8.50062 8.00028C8.36794 8.00028 8.2407 7.94757 8.14688 7.85375C8.05306 7.75993 8.00035 7.63268 8.00035 7.5C8.00035 7.36732 8.05306 7.24007 8.14688 7.14625L12.2925 3H9.5C9.36739 3 9.24021 2.94732 9.14645 2.85355C9.05268 2.75979 9 2.63261 9 2.5C9 2.36739 9.05268 2.24021 9.14645 2.14645C9.24021 2.05268 9.36739 2 9.5 2H13.5C13.6326 2 13.7598 2.05268 13.8536 2.14645C13.9473 2.24021 14 2.36739 14 2.5V6.5ZM11.5 8C11.3674 8 11.2402 8.05268 11.1464 8.14645C11.0527 8.24021 11 8.36739 11 8.5V13H3V5H7.5C7.63261 5 7.75979 4.94732 7.85355 4.85355C7.94732 4.75979 8 4.63261 8 4.5C8 4.36739 7.94732 4.24021 7.85355 4.14645C7.75979 4.05268 7.63261 4 7.5 4H3C2.73478 4 2.48043 4.10536 2.29289 4.29289C2.10536 4.48043 2 4.73478 2 5V13C2 13.2652 2.10536 13.5196 2.29289 13.7071C2.48043 13.8946 2.73478 14 3 14H11C11.2652 14 11.5196 13.8946 11.7071 13.7071C11.8946 13.5196 12 13.2652 12 13V8.5C12 8.36739 11.9473 8.24021 11.8536 8.14645C11.7598 8.05268 11.6326 8 11.5 8Z",
86
- };
87
-
88
- const externalIcon = (
89
- <Icon
90
- icon={externalIconPath}
91
- size="small"
92
- style={[
93
- linkContentStyles.endIcon,
94
- linkContentStyles.centered,
95
- ]}
96
- testId="external-icon"
97
- />
98
- );
99
-
100
- const linkContent = (
101
- <>
102
- {startIcon && (
103
- <Icon
104
- icon={startIcon}
105
- size="small"
106
- style={[
107
- linkContentStyles.startIcon,
108
- linkContentStyles.centered,
109
- ]}
110
- testId="start-icon"
111
- aria-hidden="true"
112
- />
113
- )}
114
- {children}
115
- {endIcon ? (
116
- <Icon
117
- icon={endIcon}
118
- size="small"
119
- style={[
120
- linkContentStyles.endIcon,
121
- linkContentStyles.centered,
122
- ]}
123
- testId="end-icon"
124
- aria-hidden="true"
125
- />
126
- ) : (
127
- isExternalLink && target === "_blank" && externalIcon
128
- )}
129
- </>
130
- );
131
-
132
- return router && !skipClientNav && isClientSideUrl(href) ? (
133
- <StyledLink
134
- {...commonProps}
135
- to={href}
136
- ref={ref as React.ForwardedRef<typeof Link>}
137
- >
138
- {linkContent}
139
- </StyledLink>
140
- ) : (
141
- <StyledAnchor
142
- {...commonProps}
143
- href={href}
144
- ref={ref as React.ForwardedRef<HTMLAnchorElement>}
145
- >
146
- {linkContent}
147
- </StyledAnchor>
148
- );
29
+ const LinkCore = React.forwardRef(function LinkCore(
30
+ props: Props,
31
+ ref: React.ForwardedRef<typeof Link | HTMLAnchorElement>,
32
+ ) {
33
+ const renderInner = (router: any): React.ReactNode => {
34
+ const {
35
+ children,
36
+ skipClientNav,
37
+ focused,
38
+ hovered,
39
+ href,
40
+ inline = false,
41
+ kind = "primary",
42
+ light = false,
43
+ visitable = false,
44
+ pressed,
45
+ style,
46
+ testId,
47
+ waiting: _,
48
+ target,
49
+ startIcon,
50
+ endIcon,
51
+ ...restProps
52
+ } = props;
53
+
54
+ const linkStyles = _generateStyles(inline, kind, light, visitable);
55
+ const restingStyles = inline
56
+ ? linkStyles.restingInline
57
+ : linkStyles.resting;
58
+
59
+ const defaultStyles = [
60
+ sharedStyles.shared,
61
+ restingStyles,
62
+ pressed && linkStyles.active,
63
+ // A11y: The focus ring should always be present when the
64
+ // the link has focus, even the link is being hovered over.
65
+ // TODO(WB-1498): Udpate ClickableBehavior so that focus doesn't
66
+ // stop on mouseleave. We want the focus ring to remain on a
67
+ // focused link even after hovering and un-hovering on it.
68
+ !pressed && hovered && linkStyles.hover,
69
+ !pressed && focused && linkStyles.focus,
70
+ ];
71
+
72
+ const commonProps = {
73
+ "data-test-id": testId,
74
+ style: [defaultStyles, style],
75
+ target,
76
+ ...restProps,
77
+ } as const;
78
+
79
+ const linkUrl = new URL(href, window.location.origin);
80
+
81
+ const isExternalLink = linkUrl.origin !== window.location.origin;
82
+
83
+ const externalIconPath: IconAsset = {
84
+ small: "M14 6.5C14 6.63261 13.9473 6.75979 13.8536 6.85355C13.7598 6.94732 13.6326 7 13.5 7C13.3674 7 13.2402 6.94732 13.1464 6.85355C13.0527 6.75979 13 6.63261 13 6.5V3.7075L8.85437 7.85375C8.76055 7.94757 8.63331 8.00028 8.50062 8.00028C8.36794 8.00028 8.2407 7.94757 8.14688 7.85375C8.05306 7.75993 8.00035 7.63268 8.00035 7.5C8.00035 7.36732 8.05306 7.24007 8.14688 7.14625L12.2925 3H9.5C9.36739 3 9.24021 2.94732 9.14645 2.85355C9.05268 2.75979 9 2.63261 9 2.5C9 2.36739 9.05268 2.24021 9.14645 2.14645C9.24021 2.05268 9.36739 2 9.5 2H13.5C13.6326 2 13.7598 2.05268 13.8536 2.14645C13.9473 2.24021 14 2.36739 14 2.5V6.5ZM11.5 8C11.3674 8 11.2402 8.05268 11.1464 8.14645C11.0527 8.24021 11 8.36739 11 8.5V13H3V5H7.5C7.63261 5 7.75979 4.94732 7.85355 4.85355C7.94732 4.75979 8 4.63261 8 4.5C8 4.36739 7.94732 4.24021 7.85355 4.14645C7.75979 4.05268 7.63261 4 7.5 4H3C2.73478 4 2.48043 4.10536 2.29289 4.29289C2.10536 4.48043 2 4.73478 2 5V13C2 13.2652 2.10536 13.5196 2.29289 13.7071C2.48043 13.8946 2.73478 14 3 14H11C11.2652 14 11.5196 13.8946 11.7071 13.7071C11.8946 13.5196 12 13.2652 12 13V8.5C12 8.36739 11.9473 8.24021 11.8536 8.14645C11.7598 8.05268 11.6326 8 11.5 8Z",
149
85
  };
150
86
 
151
- return (
152
- <__RouterContext.Consumer>
153
- {(router) => renderInner(router)}
154
- </__RouterContext.Consumer>
87
+ const externalIcon = (
88
+ <Icon
89
+ icon={externalIconPath}
90
+ size="small"
91
+ style={[linkContentStyles.endIcon, linkContentStyles.centered]}
92
+ testId="external-icon"
93
+ />
155
94
  );
156
- },
157
- );
95
+
96
+ let startIconElement;
97
+ let endIconElement;
98
+
99
+ if (startIcon) {
100
+ startIconElement = React.cloneElement(startIcon, {
101
+ style: [
102
+ linkContentStyles.startIcon,
103
+ linkContentStyles.centered,
104
+ ],
105
+ testId: "start-icon",
106
+ "aria-hidden": "true",
107
+ ...startIcon.props,
108
+ } as Partial<React.ComponentProps<typeof Icon>>);
109
+ }
110
+
111
+ if (endIcon) {
112
+ endIconElement = React.cloneElement(endIcon, {
113
+ style: [linkContentStyles.endIcon, linkContentStyles.centered],
114
+ testId: "end-icon",
115
+ "aria-hidden": "true",
116
+ ...endIcon.props,
117
+ } as Partial<React.ComponentProps<typeof Icon>>);
118
+ }
119
+
120
+ const linkContent = (
121
+ <>
122
+ {startIcon && startIconElement}
123
+ {children}
124
+ {endIcon
125
+ ? endIconElement
126
+ : isExternalLink && target === "_blank" && externalIcon}
127
+ </>
128
+ );
129
+
130
+ return router && !skipClientNav && isClientSideUrl(href) ? (
131
+ <StyledLink
132
+ {...commonProps}
133
+ to={href}
134
+ ref={ref as React.ForwardedRef<typeof Link>}
135
+ >
136
+ {linkContent}
137
+ </StyledLink>
138
+ ) : (
139
+ <StyledAnchor
140
+ {...commonProps}
141
+ href={href}
142
+ ref={ref as React.ForwardedRef<HTMLAnchorElement>}
143
+ >
144
+ {linkContent}
145
+ </StyledAnchor>
146
+ );
147
+ };
148
+
149
+ return (
150
+ <__RouterContext.Consumer>
151
+ {(router) => renderInner(router)}
152
+ </__RouterContext.Consumer>
153
+ );
154
+ });
158
155
 
159
156
  const styles: Record<string, any> = {};
160
157
 
@@ -4,8 +4,8 @@ import {Link as ReactRouterLink} from "react-router-dom";
4
4
  import {getClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
5
5
 
6
6
  import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
7
+ import Icon from "@khanacademy/wonder-blocks-icon";
7
8
  import type {Typography} from "@khanacademy/wonder-blocks-typography";
8
- import type {IconAsset} from "@khanacademy/wonder-blocks-icon";
9
9
  import LinkCore from "./link-core";
10
10
 
11
11
  // TODO(FEI-5000): Convert back to conditional props after TS migration is complete.
@@ -145,13 +145,13 @@ export type SharedProps = AriaProps & {
145
145
  /**
146
146
  * An optional icon displayed before the link label.
147
147
  */
148
- startIcon?: IconAsset;
148
+ startIcon?: React.ReactElement<typeof Icon>;
149
149
  /**
150
150
  * An optional icon displayed after the link label.
151
151
  * If `target="_blank"` and `endIcon` is passed in, `endIcon` will override
152
152
  * the default `externalIcon`.
153
153
  */
154
- endIcon?: IconAsset;
154
+ endIcon?: React.ReactElement<typeof Icon>;
155
155
  };
156
156
 
157
157
  /**
@@ -172,113 +172,111 @@ export type SharedProps = AriaProps & {
172
172
  * </Link>
173
173
  * ```
174
174
  */
175
- const Link = React.forwardRef(
176
- (
177
- props: SharedProps,
178
- ref: React.ForwardedRef<typeof ReactRouterLink | HTMLAnchorElement>,
179
- ) => {
180
- const {
181
- onClick,
182
- beforeNav = undefined,
183
- safeWithNav,
175
+ const Link = React.forwardRef(function Link(
176
+ props: SharedProps,
177
+ ref: React.ForwardedRef<typeof ReactRouterLink | HTMLAnchorElement>,
178
+ ) {
179
+ const {
180
+ onClick,
181
+ beforeNav = undefined,
182
+ safeWithNav,
183
+ href,
184
+ skipClientNav,
185
+ children,
186
+ tabIndex,
187
+ onKeyDown,
188
+ onKeyUp,
189
+ target = undefined,
190
+ inline = false,
191
+ kind = "primary",
192
+ light = false,
193
+ visitable = false,
194
+ ...sharedProps
195
+ } = props;
196
+
197
+ const renderClickableBehavior = (router: any): React.ReactNode => {
198
+ const ClickableBehavior = getClickableBehavior(
184
199
  href,
185
200
  skipClientNav,
186
- children,
187
- tabIndex,
188
- onKeyDown,
189
- onKeyUp,
190
- target = undefined,
191
- inline = false,
192
- kind = "primary",
193
- light = false,
194
- visitable = false,
195
- ...sharedProps
196
- } = props;
201
+ router,
202
+ );
197
203
 
198
- const renderClickableBehavior = (router: any): React.ReactNode => {
199
- const ClickableBehavior = getClickableBehavior(
200
- href,
201
- skipClientNav,
202
- router,
204
+ if (beforeNav) {
205
+ return (
206
+ <ClickableBehavior
207
+ disabled={false}
208
+ href={href}
209
+ role="link"
210
+ onClick={onClick}
211
+ beforeNav={beforeNav}
212
+ safeWithNav={safeWithNav}
213
+ onKeyDown={onKeyDown}
214
+ onKeyUp={onKeyUp}
215
+ >
216
+ {(state, {...childrenProps}) => {
217
+ return (
218
+ <LinkCore
219
+ {...sharedProps}
220
+ {...state}
221
+ {...childrenProps}
222
+ skipClientNav={skipClientNav}
223
+ href={href}
224
+ target={target}
225
+ tabIndex={tabIndex}
226
+ inline={inline}
227
+ kind={kind}
228
+ light={light}
229
+ visitable={visitable}
230
+ ref={ref}
231
+ >
232
+ {children}
233
+ </LinkCore>
234
+ );
235
+ }}
236
+ </ClickableBehavior>
203
237
  );
238
+ } else {
239
+ return (
240
+ <ClickableBehavior
241
+ disabled={false}
242
+ href={href}
243
+ role="link"
244
+ onClick={onClick}
245
+ safeWithNav={safeWithNav}
246
+ target={target}
247
+ onKeyDown={onKeyDown}
248
+ onKeyUp={onKeyUp}
249
+ >
250
+ {(state, {...childrenProps}) => {
251
+ return (
252
+ <LinkCore
253
+ {...sharedProps}
254
+ {...state}
255
+ {...childrenProps}
256
+ skipClientNav={skipClientNav}
257
+ href={href}
258
+ target={target}
259
+ tabIndex={tabIndex}
260
+ inline={inline}
261
+ kind={kind}
262
+ light={light}
263
+ visitable={visitable}
264
+ ref={ref}
265
+ >
266
+ {children}
267
+ </LinkCore>
268
+ );
269
+ }}
270
+ </ClickableBehavior>
271
+ );
272
+ }
273
+ };
204
274
 
205
- if (beforeNav) {
206
- return (
207
- <ClickableBehavior
208
- disabled={false}
209
- href={href}
210
- role="link"
211
- onClick={onClick}
212
- beforeNav={beforeNav}
213
- safeWithNav={safeWithNav}
214
- onKeyDown={onKeyDown}
215
- onKeyUp={onKeyUp}
216
- >
217
- {(state, {...childrenProps}) => {
218
- return (
219
- <LinkCore
220
- {...sharedProps}
221
- {...state}
222
- {...childrenProps}
223
- skipClientNav={skipClientNav}
224
- href={href}
225
- target={target}
226
- tabIndex={tabIndex}
227
- inline={inline}
228
- kind={kind}
229
- light={light}
230
- visitable={visitable}
231
- ref={ref}
232
- >
233
- {children}
234
- </LinkCore>
235
- );
236
- }}
237
- </ClickableBehavior>
238
- );
239
- } else {
240
- return (
241
- <ClickableBehavior
242
- disabled={false}
243
- href={href}
244
- role="link"
245
- onClick={onClick}
246
- safeWithNav={safeWithNav}
247
- target={target}
248
- onKeyDown={onKeyDown}
249
- onKeyUp={onKeyUp}
250
- >
251
- {(state, {...childrenProps}) => {
252
- return (
253
- <LinkCore
254
- {...sharedProps}
255
- {...state}
256
- {...childrenProps}
257
- skipClientNav={skipClientNav}
258
- href={href}
259
- target={target}
260
- tabIndex={tabIndex}
261
- inline={inline}
262
- kind={kind}
263
- light={light}
264
- visitable={visitable}
265
- ref={ref}
266
- >
267
- {children}
268
- </LinkCore>
269
- );
270
- }}
271
- </ClickableBehavior>
272
- );
273
- }
274
- };
275
-
276
- return (
277
- <__RouterContext.Consumer>
278
- {(router) => renderClickableBehavior(router)}
279
- </__RouterContext.Consumer>
280
- );
281
- },
282
- );
275
+ return (
276
+ <__RouterContext.Consumer>
277
+ {(router) => renderClickableBehavior(router)}
278
+ </__RouterContext.Consumer>
279
+ );
280
+ });
283
281
 
284
282
  export default Link;