@khanacademy/wonder-blocks-link 4.2.7 → 4.3.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/dist/index.js CHANGED
@@ -70,32 +70,29 @@ function _objectWithoutPropertiesLoose(source, excluded) {
70
70
  const _excluded$1 = ["children", "skipClientNav", "focused", "hovered", "href", "inline", "kind", "light", "visitable", "pressed", "style", "testId", "waiting", "target", "startIcon", "endIcon"];
71
71
  const StyledAnchor = wonderBlocksCore.addStyle("a");
72
72
  const StyledLink = wonderBlocksCore.addStyle(reactRouterDom.Link);
73
- const StyledSpan = wonderBlocksCore.addStyle("span");
74
- class LinkCore extends React__namespace.Component {
75
- renderInner(router) {
76
- const _this$props = this.props,
77
- {
73
+ const LinkCore = React__namespace.forwardRef((props, ref) => {
74
+ const renderInner = router => {
75
+ const {
78
76
  children,
79
77
  skipClientNav,
80
78
  focused,
81
79
  hovered,
82
80
  href,
83
- inline,
84
- kind,
85
- light,
86
- visitable,
81
+ inline = false,
82
+ kind = "primary",
83
+ light = false,
84
+ visitable = false,
87
85
  pressed,
88
86
  style,
89
87
  testId,
90
88
  target,
91
89
  startIcon,
92
90
  endIcon
93
- } = _this$props,
94
- restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$1);
95
- const withIcon = startIcon || endIcon || target === "_blank";
91
+ } = props,
92
+ restProps = _objectWithoutPropertiesLoose(props, _excluded$1);
96
93
  const linkStyles = _generateStyles(inline, kind, light, visitable);
97
94
  const restingStyles = inline ? linkStyles.restingInline : linkStyles.resting;
98
- const defaultStyles = [sharedStyles.shared, restingStyles, pressed && linkStyles.active, !pressed && hovered && linkStyles.hover, !pressed && focused && linkStyles.focus, withIcon && sharedStyles.withIcon];
95
+ const defaultStyles = [sharedStyles.shared, restingStyles, pressed && linkStyles.active, !pressed && hovered && linkStyles.hover, !pressed && focused && linkStyles.focus];
99
96
  const commonProps = _extends({
100
97
  "data-test-id": testId,
101
98
  style: [defaultStyles, style],
@@ -118,9 +115,7 @@ class LinkCore extends React__namespace.Component {
118
115
  style: [linkContentStyles.startIcon, linkContentStyles.centered],
119
116
  testId: "start-icon",
120
117
  "aria-hidden": "true"
121
- }), withIcon ? React__namespace.createElement(StyledSpan, {
122
- style: linkContentStyles.centered
123
- }, children) : children, endIcon ? React__namespace.createElement(Icon__default["default"], {
118
+ }), children, endIcon ? React__namespace.createElement(Icon__default["default"], {
124
119
  icon: endIcon,
125
120
  size: "small",
126
121
  style: [linkContentStyles.endIcon, linkContentStyles.centered],
@@ -128,15 +123,15 @@ class LinkCore extends React__namespace.Component {
128
123
  "aria-hidden": "true"
129
124
  }) : isExternalLink && target === "_blank" && externalIcon);
130
125
  return router && !skipClientNav && wonderBlocksClickable.isClientSideUrl(href) ? React__namespace.createElement(StyledLink, _extends({}, commonProps, {
131
- to: href
126
+ to: href,
127
+ ref: ref
132
128
  }), linkContent) : React__namespace.createElement(StyledAnchor, _extends({}, commonProps, {
133
- href: href
129
+ href: href,
130
+ ref: ref
134
131
  }), linkContent);
135
- }
136
- render() {
137
- return React__namespace.createElement(reactRouter.__RouterContext.Consumer, null, router => this.renderInner(router));
138
- }
139
- }
132
+ };
133
+ return React__namespace.createElement(reactRouter.__RouterContext.Consumer, null, router => renderInner(router));
134
+ });
140
135
  const styles = {};
141
136
  const linkContentStyles = aphrodite.StyleSheet.create({
142
137
  startIcon: {
@@ -146,7 +141,7 @@ const linkContentStyles = aphrodite.StyleSheet.create({
146
141
  marginInlineStart: Spacing__default["default"].xxxSmall_4
147
142
  },
148
143
  centered: {
149
- verticalAlign: "middle"
144
+ verticalAlign: "-10%"
150
145
  }
151
146
  });
152
147
  const sharedStyles = aphrodite.StyleSheet.create({
@@ -155,10 +150,6 @@ const sharedStyles = aphrodite.StyleSheet.create({
155
150
  textDecoration: "none",
156
151
  outline: "none",
157
152
  alignItems: "center"
158
- },
159
- withIcon: {
160
- verticalAlign: "bottom",
161
- textUnderlineOffset: 4
162
153
  }
163
154
  });
164
155
  const _generateStyles = (inline, kind, light, visitable) => {
@@ -230,23 +221,26 @@ const _generateStyles = (inline, kind, light, visitable) => {
230
221
  return styles[buttonType];
231
222
  };
232
223
 
233
- const _excluded = ["onClick", "beforeNav", "safeWithNav", "href", "skipClientNav", "children", "tabIndex", "onKeyDown", "onKeyUp", "target"];
234
- class Link extends React__namespace.Component {
235
- renderClickableBehavior(router) {
236
- const _this$props = this.props,
237
- {
238
- onClick,
239
- beforeNav = undefined,
240
- safeWithNav,
241
- href,
242
- skipClientNav,
243
- children,
244
- tabIndex,
245
- onKeyDown,
246
- onKeyUp,
247
- target = undefined
248
- } = _this$props,
249
- sharedProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
224
+ const _excluded = ["onClick", "beforeNav", "safeWithNav", "href", "skipClientNav", "children", "tabIndex", "onKeyDown", "onKeyUp", "target", "inline", "kind", "light", "visitable"];
225
+ const Link = React__namespace.forwardRef((props, ref) => {
226
+ const {
227
+ onClick,
228
+ beforeNav = undefined,
229
+ safeWithNav,
230
+ href,
231
+ skipClientNav,
232
+ children,
233
+ tabIndex,
234
+ onKeyDown,
235
+ onKeyUp,
236
+ target = undefined,
237
+ inline = false,
238
+ kind = "primary",
239
+ light = false,
240
+ visitable = false
241
+ } = props,
242
+ sharedProps = _objectWithoutPropertiesLoose(props, _excluded);
243
+ const renderClickableBehavior = router => {
250
244
  const ClickableBehavior = wonderBlocksClickable.getClickableBehavior(href, skipClientNav, router);
251
245
  if (beforeNav) {
252
246
  return React__namespace.createElement(ClickableBehavior, {
@@ -264,7 +258,12 @@ class Link extends React__namespace.Component {
264
258
  skipClientNav: skipClientNav,
265
259
  href: href,
266
260
  target: target,
267
- tabIndex: tabIndex
261
+ tabIndex: tabIndex,
262
+ inline: inline,
263
+ kind: kind,
264
+ light: light,
265
+ visitable: visitable,
266
+ ref: ref
268
267
  }), children);
269
268
  });
270
269
  } else {
@@ -283,20 +282,17 @@ class Link extends React__namespace.Component {
283
282
  skipClientNav: skipClientNav,
284
283
  href: href,
285
284
  target: target,
286
- tabIndex: tabIndex
285
+ tabIndex: tabIndex,
286
+ inline: inline,
287
+ kind: kind,
288
+ light: light,
289
+ visitable: visitable,
290
+ ref: ref
287
291
  }), children);
288
292
  });
289
293
  }
290
- }
291
- render() {
292
- return React__namespace.createElement(reactRouter.__RouterContext.Consumer, null, router => this.renderClickableBehavior(router));
293
- }
294
- }
295
- Link.defaultProps = {
296
- inline: false,
297
- kind: "primary",
298
- light: false,
299
- visitable: false
300
- };
294
+ };
295
+ return React__namespace.createElement(reactRouter.__RouterContext.Consumer, null, router => renderClickableBehavior(router));
296
+ });
301
297
 
302
298
  module.exports = Link;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-link",
3
- "version": "4.2.7",
3
+ "version": "4.3.0",
4
4
  "design": "v1",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -16,10 +16,10 @@
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.18.6",
19
- "@khanacademy/wonder-blocks-clickable": "^3.0.13",
19
+ "@khanacademy/wonder-blocks-clickable": "^3.1.1",
20
20
  "@khanacademy/wonder-blocks-color": "^2.0.1",
21
- "@khanacademy/wonder-blocks-core": "^5.2.3",
22
- "@khanacademy/wonder-blocks-icon": "^2.0.13",
21
+ "@khanacademy/wonder-blocks-core": "^5.3.0",
22
+ "@khanacademy/wonder-blocks-icon": "^2.0.14",
23
23
  "@khanacademy/wonder-blocks-spacing": "^4.0.1"
24
24
  },
25
25
  "peerDependencies": {
@@ -25,135 +25,136 @@ type Props = SharedProps &
25
25
 
26
26
  const StyledAnchor = addStyle("a");
27
27
  const StyledLink = addStyle(Link);
28
- const StyledSpan = addStyle("span");
29
28
 
30
- export default class LinkCore extends React.Component<Props> {
31
- renderInner(router: any): React.ReactNode {
32
- const {
33
- children,
34
- skipClientNav,
35
- focused,
36
- hovered,
37
- href,
38
- inline,
39
- kind,
40
- light,
41
- visitable,
42
- pressed,
43
- style,
44
- testId,
45
- waiting: _,
46
- target,
47
- startIcon,
48
- endIcon,
49
- ...restProps
50
- } = this.props;
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;
51
54
 
52
- // If icon props are present, link `children` will render inside a
53
- // `StyledSpan` element to vertically align text with icons and add
54
- // `textUnderlineOffset`
55
- const withIcon = startIcon || endIcon || target === "_blank";
55
+ const linkStyles = _generateStyles(inline, kind, light, visitable);
56
+ const restingStyles = inline
57
+ ? linkStyles.restingInline
58
+ : linkStyles.resting;
56
59
 
57
- const linkStyles = _generateStyles(inline, kind, light, visitable);
58
- const restingStyles = inline
59
- ? linkStyles.restingInline
60
- : linkStyles.resting;
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
+ ];
61
72
 
62
- const defaultStyles = [
63
- sharedStyles.shared,
64
- restingStyles,
65
- pressed && linkStyles.active,
66
- // A11y: The focus ring should always be present when the
67
- // the link has focus, even the link is being hovered over.
68
- // TODO(WB-1498): Udpate ClickableBehavior so that focus doesn't
69
- // stop on mouseleave. We want the focus ring to remain on a
70
- // focused link even after hovering and un-hovering on it.
71
- !pressed && hovered && linkStyles.hover,
72
- !pressed && focused && linkStyles.focus,
73
- withIcon && sharedStyles.withIcon,
74
- ];
73
+ const commonProps = {
74
+ "data-test-id": testId,
75
+ style: [defaultStyles, style],
76
+ target,
77
+ ...restProps,
78
+ } as const;
75
79
 
76
- const commonProps = {
77
- "data-test-id": testId,
78
- style: [defaultStyles, style],
79
- target,
80
- ...restProps,
81
- } as const;
80
+ const linkUrl = new URL(href, window.location.origin);
82
81
 
83
- const linkUrl = new URL(href, window.location.origin);
82
+ const isExternalLink = linkUrl.origin !== window.location.origin;
84
83
 
85
- const isExternalLink = linkUrl.origin !== window.location.origin;
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
+ };
86
87
 
87
- const externalIconPath: IconAsset = {
88
- 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",
89
- };
90
-
91
- const externalIcon = (
92
- <Icon
93
- icon={externalIconPath}
94
- size="small"
95
- style={[linkContentStyles.endIcon, linkContentStyles.centered]}
96
- testId="external-icon"
97
- />
98
- );
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
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
- {withIcon ? (
115
- <StyledSpan style={linkContentStyles.centered}>
116
- {children}
117
- </StyledSpan>
118
- ) : (
119
- children
120
- )}
121
- {endIcon ? (
122
- <Icon
123
- icon={endIcon}
124
- size="small"
125
- style={[
126
- linkContentStyles.endIcon,
127
- linkContentStyles.centered,
128
- ]}
129
- testId="end-icon"
130
- aria-hidden="true"
131
- />
132
- ) : (
133
- isExternalLink && target === "_blank" && externalIcon
134
- )}
135
- </>
136
- );
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
+ );
137
131
 
138
- return router && !skipClientNav && isClientSideUrl(href) ? (
139
- <StyledLink {...commonProps} to={href}>
140
- {linkContent}
141
- </StyledLink>
142
- ) : (
143
- <StyledAnchor {...commonProps} href={href}>
144
- {linkContent}
145
- </StyledAnchor>
146
- );
147
- }
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
+ );
149
+ };
148
150
 
149
- render(): React.ReactNode {
150
151
  return (
151
152
  <__RouterContext.Consumer>
152
- {(router) => this.renderInner(router)}
153
+ {(router) => renderInner(router)}
153
154
  </__RouterContext.Consumer>
154
155
  );
155
- }
156
- }
156
+ },
157
+ );
157
158
 
158
159
  const styles: Record<string, any> = {};
159
160
 
@@ -165,7 +166,8 @@ const linkContentStyles = StyleSheet.create({
165
166
  marginInlineStart: Spacing.xxxSmall_4,
166
167
  },
167
168
  centered: {
168
- verticalAlign: "middle",
169
+ // Manually align the bottom of start/end icons with the text baseline.
170
+ verticalAlign: "-10%",
169
171
  },
170
172
  });
171
173
 
@@ -176,10 +178,6 @@ const sharedStyles = StyleSheet.create({
176
178
  outline: "none",
177
179
  alignItems: "center",
178
180
  },
179
- withIcon: {
180
- verticalAlign: "bottom",
181
- textUnderlineOffset: 4,
182
- },
183
181
  });
184
182
 
185
183
  const _generateStyles = (
@@ -292,3 +290,5 @@ const _generateStyles = (
292
290
  styles[buttonType] = StyleSheet.create(newStyles);
293
291
  return styles[buttonType];
294
292
  };
293
+
294
+ export default LinkCore;