@khanacademy/wonder-blocks-link 3.8.17 → 3.9.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.
@@ -13,7 +13,7 @@ import type {
13
13
  ClickableState,
14
14
  } from "@khanacademy/wonder-blocks-clickable";
15
15
  import type {StyleDeclaration} from "aphrodite";
16
- import type {SharedProps} from "./link.js";
16
+ import type {SharedProps} from "./link";
17
17
 
18
18
  type Props = {|
19
19
  ...SharedProps,
@@ -33,6 +33,7 @@ export default class LinkCore extends React.Component<Props> {
33
33
  focused,
34
34
  hovered,
35
35
  href,
36
+ inline,
36
37
  kind,
37
38
  light,
38
39
  visitable,
@@ -43,14 +44,22 @@ export default class LinkCore extends React.Component<Props> {
43
44
  ...restProps
44
45
  } = this.props;
45
46
 
46
- const linkStyles = _generateStyles(kind, light, visitable);
47
+ const linkStyles = _generateStyles(inline, kind, light, visitable);
48
+ const restingStyles = inline
49
+ ? linkStyles.restingInline
50
+ : linkStyles.resting;
47
51
 
48
52
  const defaultStyles = [
49
53
  sharedStyles.shared,
50
- !(hovered || focused || pressed) && linkStyles.default,
51
- pressed
52
- ? linkStyles.active
53
- : (hovered || focused) && linkStyles.focus,
54
+ !(hovered || focused || pressed) && restingStyles,
55
+ pressed && linkStyles.active,
56
+ // A11y: The focus ring should always be present when the
57
+ // the link has focus, even the link is being hovered over.
58
+ // TODO(WB-1498): Udpate ClickableBehavior so that focus doesn't
59
+ // stop on mouseleave. We want the focus ring to remain on a
60
+ // focused link even after hovering and un-hovering on it.
61
+ !pressed && hovered && linkStyles.hover,
62
+ !pressed && focused && linkStyles.focus,
54
63
  ];
55
64
 
56
65
  const commonProps = {
@@ -86,11 +95,14 @@ const sharedStyles = StyleSheet.create({
86
95
  cursor: "pointer",
87
96
  textDecoration: "none",
88
97
  outline: "none",
98
+ display: "inline-flex",
99
+ fontSize: 16,
100
+ lineHeight: "22px",
89
101
  },
90
102
  });
91
103
 
92
- const _generateStyles = (kind, light, visitable) => {
93
- const buttonType = kind + light.toString() + visitable.toString();
104
+ const _generateStyles = (inline, kind, light, visitable) => {
105
+ const buttonType = `${kind}-${inline.toString()}-${light.toString()}-${visitable.toString()}`;
94
106
  if (styles[buttonType]) {
95
107
  return styles[buttonType];
96
108
  }
@@ -99,49 +111,77 @@ const _generateStyles = (kind, light, visitable) => {
99
111
  throw new Error("Secondary Light links are not supported");
100
112
  }
101
113
 
102
- if (visitable && (kind !== "primary" || (kind === "primary" && light))) {
103
- throw new Error("Only primary (not light) link is visitable");
114
+ if (visitable && kind !== "primary") {
115
+ throw new Error("Only primary link is visitable");
104
116
  }
105
117
 
106
- const {blue, purple, white, offBlack, offBlack32} = Color;
107
- const linkPurple = mix(fade(offBlack, 0.08), purple);
118
+ const {blue, pink, purple, white, offBlack, offBlack32, offBlack64} = Color;
108
119
 
120
+ // Standard purple
121
+ const linkPurple = mix(fade(offBlack, 0.08), purple);
122
+ // Light blue
123
+ const fadedBlue = mix(fade(blue, 0.32), white);
124
+ // Light pink
125
+ const activeLightVisited = mix(fade(white, 0.32), pink);
126
+ // Dark blue
127
+ const activeDefaultPrimary = mix(offBlack32, blue);
128
+
129
+ const primaryDefaultTextColor = light ? white : blue;
130
+ const secondaryDefaultTextColor = inline ? offBlack : offBlack64;
109
131
  const defaultTextColor =
110
- kind === "primary" ? (light ? white : blue) : offBlack;
132
+ kind === "primary"
133
+ ? primaryDefaultTextColor
134
+ : secondaryDefaultTextColor;
111
135
 
112
- const focusColor: string = light ? white : blue;
113
- const activeColor: string = light
114
- ? mix(fade(blue, 0.32), white)
115
- : mix(offBlack32, blue);
136
+ const primaryActiveColor = light ? fadedBlue : activeDefaultPrimary;
137
+ const secondaryActiveColor = inline ? activeDefaultPrimary : offBlack;
138
+ const activeColor =
139
+ kind === "primary" ? primaryActiveColor : secondaryActiveColor;
116
140
 
117
141
  const defaultVisited = visitable
118
142
  ? {
119
143
  ":visited": {
120
- color: linkPurple,
144
+ color: light ? pink : linkPurple,
121
145
  },
122
146
  }
123
147
  : Object.freeze({});
124
148
  const activeVisited = visitable
125
149
  ? {
126
150
  ":visited": {
127
- color: mix(offBlack32, linkPurple),
151
+ color: light
152
+ ? activeLightVisited
153
+ : mix(offBlack32, linkPurple),
128
154
  },
129
155
  }
130
156
  : Object.freeze({});
131
157
 
132
158
  const newStyles: StyleDeclaration = {
133
- default: {
159
+ resting: {
160
+ color: defaultTextColor,
161
+ ...defaultVisited,
162
+ },
163
+ restingInline: {
134
164
  color: defaultTextColor,
165
+ textDecoration: "underline currentcolor solid 1px",
166
+ textUnderlineOffset: 4,
167
+ ...defaultVisited,
168
+ },
169
+ hover: {
170
+ textDecoration: "underline currentcolor dashed 2px",
171
+ color: defaultTextColor,
172
+ textUnderlineOffset: 4,
135
173
  ...defaultVisited,
136
174
  },
137
175
  focus: {
138
- textDecoration: "underline currentcolor solid",
139
- color: focusColor,
176
+ color: defaultTextColor,
177
+ outline: `1px solid ${light ? white : blue}`,
178
+ borderRadius: 3,
140
179
  ...defaultVisited,
141
180
  },
142
181
  active: {
143
182
  color: activeColor,
144
- textDecoration: "underline currentcolor solid",
183
+ textDecoration: "underline currentcolor solid 1px",
184
+ textUnderlineOffset: 4,
145
185
  ...activeVisited,
146
186
  },
147
187
  };
@@ -5,9 +5,10 @@ import {getClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
5
5
 
6
6
  import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
7
7
  import type {Typography} from "@khanacademy/wonder-blocks-typography";
8
- import LinkCore from "./link-core.js";
8
+ import LinkCore from "./link-core";
9
9
 
10
- type CommonProps = {|
10
+ // TODO(FEI-5000): Convert back to conditional props after TS migration is complete.
11
+ export type SharedProps = {|
11
12
  ...AriaProps,
12
13
 
13
14
  /**
@@ -25,6 +26,13 @@ type CommonProps = {|
25
26
  */
26
27
  id?: string,
27
28
 
29
+ /**
30
+ * Indicates that this link is used within a body of text.
31
+ * This styles the link with an underline to distinguish it
32
+ * from surrounding text.
33
+ */
34
+ inline: boolean,
35
+
28
36
  /**
29
37
  * Kind of Link. Note: Secondary light Links are not supported.
30
38
  */
@@ -125,40 +133,33 @@ type CommonProps = {|
125
133
  * Respond to raw "keyup" event.
126
134
  */
127
135
  onKeyUp?: (e: SyntheticKeyboardEvent<>) => mixed,
128
- |};
129
136
 
130
- export type SharedProps =
131
- | {|
132
- ...CommonProps,
133
-
134
- /**
135
- * A target destination window for a link to open in. We only support
136
- * "_blank" which opens the URL in a new tab.
137
- *
138
- * TODO(WB-1262): only allow this prop when `href` is also set.t
139
- */
140
- target?: "_blank",
141
- |}
142
- | {|
143
- ...CommonProps,
137
+ /**
138
+ * A target destination window for a link to open in. We only support
139
+ * "_blank" which opens the URL in a new tab.
140
+ *
141
+ * TODO(WB-1262): only allow this prop when `href` is also set.t
142
+ */
143
+ target?: "_blank",
144
144
 
145
- /**
146
- * Run async code before navigating to the URL passed to `href`. If the
147
- * promise returned rejects then navigation will not occur.
148
- *
149
- * If both safeWithNav and beforeNav are provided, beforeNav will be run
150
- * first and safeWithNav will only be run if beforeNav does not reject.
151
- *
152
- * WARNING: Using this with `target="_blank"` will trigger built-in popup
153
- * blockers in Firefox and Safari. This is because we do navigation
154
- * programmatically and `beforeNav` causes a delay which means that the
155
- * browser can't make a directly link between a user action and the
156
- * navigation.
157
- */
158
- beforeNav?: () => Promise<mixed>,
159
- |};
145
+ /**
146
+ * Run async code before navigating to the URL passed to `href`. If the
147
+ * promise returned rejects then navigation will not occur.
148
+ *
149
+ * If both safeWithNav and beforeNav are provided, beforeNav will be run
150
+ * first and safeWithNav will only be run if beforeNav does not reject.
151
+ *
152
+ * WARNING: Using this with `target="_blank"` will trigger built-in popup
153
+ * blockers in Firefox and Safari. This is because we do navigation
154
+ * programmatically and `beforeNav` causes a delay which means that the
155
+ * browser can't make a directly link between a user action and the
156
+ * navigation.
157
+ */
158
+ beforeNav?: () => Promise<mixed>,
159
+ |};
160
160
 
161
161
  type DefaultProps = {|
162
+ inline: $PropertyType<SharedProps, "inline">,
162
163
  kind: $PropertyType<SharedProps, "kind">,
163
164
  light: $PropertyType<SharedProps, "light">,
164
165
  visitable: $PropertyType<SharedProps, "visitable">,
@@ -184,6 +185,7 @@ type DefaultProps = {|
184
185
  */
185
186
  export default class Link extends React.Component<SharedProps> {
186
187
  static defaultProps: DefaultProps = {
188
+ inline: false,
187
189
  kind: "primary",
188
190
  light: false,
189
191
  visitable: false,
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // @flow
2
- import Link from "./components/link.js";
2
+ import Link from "./components/link";
3
3
 
4
4
  export {Link as default};