@khanacademy/wonder-blocks-link 3.9.1 → 3.9.3

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.
@@ -1,151 +0,0 @@
1
- // @flow
2
-
3
- export default {
4
- children: {
5
- control: {type: "text"},
6
- description:
7
- "Text to appear on the link. It can be a plain text or Typography element.",
8
- table: {type: {summary: "string | React.Element<Typography>"}},
9
- type: {required: true},
10
- },
11
- href: {
12
- control: {type: "text"},
13
- description: "URL to navigate to.",
14
- table: {type: {summary: "string"}},
15
- type: {required: true},
16
- },
17
- id: {
18
- control: {type: "text"},
19
- description: "An optional id attribute.",
20
- table: {type: {summary: "string"}},
21
- type: {required: false},
22
- },
23
- inline: {
24
- control: {type: "boolean"},
25
- description: `Indicates that this link is used within a body of text.
26
- This styles the link with an underline to distinguish it
27
- from surrounding text.`,
28
- table: {type: {summary: "boolean"}},
29
- type: {required: false},
30
- },
31
- kind: {
32
- control: {type: "select"},
33
- description:
34
- "Kind of Link. Note: Secondary light Links are not supported.",
35
- options: ["primary", "secondary"],
36
- table: {
37
- type: {summary: `"primary" | "secondary"`},
38
- },
39
- type: {required: false},
40
- },
41
- light: {
42
- control: {type: "boolean"},
43
- description: "Whether the button is on a dark/colored background.",
44
- table: {
45
- type: {summary: "boolean"},
46
- },
47
- },
48
- visitable: {
49
- control: {type: "boolean"},
50
- description:
51
- "Whether the link should change color once it's visited. Secondary or primary (light) links are not allowed to be visitable.",
52
- table: {
53
- type: {summary: "boolean"},
54
- },
55
- },
56
- rel: {
57
- control: {type: "text"},
58
- description: `Specifies the type of relationship between the current
59
- document and the linked document. Should only be used when
60
- \`href\` is specified. This defaults to "noopener noreferrer"
61
- when \`target="_blank"\`, but can be overridden by setting this
62
- prop to something else.`,
63
- table: {
64
- type: {summary: "string"},
65
- },
66
- },
67
- tabIndex: {
68
- control: {type: "number"},
69
- description: "Set the tabindex attribute on the rendered element.",
70
- table: {
71
- defaultValue: {summary: 0},
72
- type: {summary: "number"},
73
- },
74
- },
75
- testId: {
76
- control: {type: "text"},
77
- description: "Test ID used for e2e testing.",
78
- table: {
79
- type: {summary: "string"},
80
- },
81
- },
82
-
83
- style: {
84
- control: {type: "object"},
85
- description: "custom styles.",
86
- table: {type: {summary: "StyleType"}},
87
- },
88
- className: {
89
- control: {type: "text"},
90
- description: "Adds CSS classes to the Link.",
91
- table: {type: {summary: "string"}},
92
- },
93
- beforeNav: {
94
- description: `Run async code before navigating to the URL passed to
95
- \`href\`. If the promise returned rejects then navigation will not
96
- occur. If both safeWithNav and beforeNav are provided, beforeNav
97
- will be run first and safeWithNav will only be run if beforeNav
98
- does not reject.`,
99
- table: {
100
- category: "Navigation",
101
- type: {summary: "() => Promise<mixed>"},
102
- },
103
- },
104
- safeWithNav: {
105
- description: `Run async code in the background while client-side
106
- navigating. If the browser does a full page load navigation, the
107
- callback promise must be settled before the navigation will occur.
108
- Errors are ignored so that navigation is guaranteed to succeed.`,
109
- table: {
110
- category: "Navigation",
111
- type: {summary: "() => Promise<mixed>"},
112
- },
113
- },
114
- skipClientNav: {
115
- control: {type: "boolean"},
116
- description: `Whether to avoid using client-side navigation.
117
- If the URL passed to href is local to the client-side, e.g.
118
- /math/algebra/eval-exprs, then it tries to use react-router-dom's
119
- Link component which handles the client-side navigation. You can set
120
- \`skipClientNav\` to true avoid using client-side nav entirely.`,
121
- table: {
122
- category: "Navigation",
123
- type: {summary: "boolean"},
124
- },
125
- },
126
- onClick: {
127
- description: `Function to call when button is clicked.
128
- This should NOT be used to redirect to a different URL or to
129
- prevent navigation via e.preventDefault(). The event passed to this
130
- handler will have its preventDefault() and stopPropagation() methods
131
- stubbed out.`,
132
- table: {
133
- category: "Events",
134
- type: {summary: "(e: SyntheticEvent<>) => mixed"},
135
- },
136
- },
137
- onKeyDown: {
138
- description: `Respond to raw "keydown" event.`,
139
- table: {
140
- category: "Events",
141
- type: {summary: "(e: SyntheticKeyboardEvent<>) => mixed"},
142
- },
143
- },
144
- onKeyUp: {
145
- description: `Respond to raw "keyup" event.`,
146
- table: {
147
- category: "Events",
148
- type: {summary: "(e: SyntheticKeyboardEvent<>) => mixed"},
149
- },
150
- },
151
- };
@@ -1,487 +0,0 @@
1
- // We need to use fireEvent for mouseDown in these tests, none of the userEvent
2
- // alternatives work. Click includes mouseUp, which removes the pressed style.
3
- /* eslint-disable testing-library/prefer-user-event */
4
- // @flow
5
- import {expect} from "@storybook/jest";
6
- import * as React from "react";
7
- import {within, userEvent, fireEvent} from "@storybook/testing-library";
8
- import {StyleSheet} from "aphrodite";
9
- import {MemoryRouter, Route, Switch} from "react-router-dom";
10
-
11
- import Color from "@khanacademy/wonder-blocks-color";
12
- import {View} from "@khanacademy/wonder-blocks-core";
13
- import {Strut} from "@khanacademy/wonder-blocks-layout";
14
- import Link from "@khanacademy/wonder-blocks-link";
15
- import Spacing from "@khanacademy/wonder-blocks-spacing";
16
- import {
17
- Body,
18
- HeadingSmall,
19
- LabelLarge,
20
- } from "@khanacademy/wonder-blocks-typography";
21
- import type {StoryComponentType} from "@storybook/react";
22
-
23
- import LinkArgTypes from "./link.argtypes";
24
- import ComponentInfo from "../../../../../.storybook/components/component-info";
25
- import {name, version} from "../../../package.json";
26
-
27
- export default {
28
- title: "Link",
29
- component: Link,
30
- parameters: {
31
- componentSubtitle: ((
32
- <ComponentInfo name={name} version={version} />
33
- ): any),
34
- },
35
- argTypes: LinkArgTypes,
36
- };
37
-
38
- const activeBlue = "#1b50b3";
39
- const fadedBlue = "#b5cefb";
40
-
41
- export const Default: StoryComponentType = (args) => (
42
- <Link target="_blank" {...args} />
43
- );
44
-
45
- Default.args = {
46
- href: "/",
47
- children: "Hello, world!",
48
- };
49
-
50
- export const Primary: StoryComponentType = () => (
51
- <Link href="#">The quick brown fox jumps over the lazy dog.</Link>
52
- );
53
-
54
- Primary.parameters = {
55
- docs: {
56
- storyDescription: `Minimal link usage.
57
- This links to the top of the page.`,
58
- },
59
- };
60
-
61
- Primary.play = async ({canvasElement}) => {
62
- const canvas = within(canvasElement);
63
-
64
- const link = canvas.getByRole("link");
65
-
66
- await expect(link).toHaveStyle(`color: ${Color.blue}`);
67
-
68
- await userEvent.hover(link);
69
- await expect(link).toHaveStyle(
70
- `text-decoration: underline ${Color.blue} dashed 2px`,
71
- );
72
-
73
- await fireEvent.mouseDown(link);
74
- await expect(link).toHaveStyle(
75
- `text-decoration: underline solid ${activeBlue} 1px`,
76
- );
77
- };
78
-
79
- export const Secondary: StoryComponentType = () => (
80
- <Link href="#" kind="secondary">
81
- The quick brown fox jumps over the lazy dog.
82
- </Link>
83
- );
84
-
85
- Secondary.parameters = {
86
- docs: {
87
- storyDescription: `Minimal secondary link usage. A secondary link
88
- has lighter text. This links to the top of the page.`,
89
- },
90
- };
91
-
92
- Secondary.play = async ({canvasElement}) => {
93
- const canvas = within(canvasElement);
94
-
95
- const link = canvas.getByRole("link");
96
-
97
- await expect(link).toHaveStyle(`color: ${Color.offBlack64}`);
98
-
99
- await userEvent.hover(link);
100
- await expect(link).toHaveStyle(
101
- `text-decoration: underline ${Color.offBlack64} dashed 2px`,
102
- );
103
-
104
- await fireEvent.mouseDown(link);
105
- await expect(link).toHaveStyle(
106
- `text-decoration: underline solid ${Color.offBlack} 1px`,
107
- );
108
- };
109
-
110
- export const Visitable: StoryComponentType = () => (
111
- <Link href="#" visitable={true}>
112
- The quick brown fox jumps over the lazy dog.
113
- </Link>
114
- );
115
-
116
- Visitable.parameters = {
117
- docs: {
118
- storyDescription: `This is a visitable link. It changes color after
119
- it has been clicked on to indicate that it's been visited before.
120
- This link's \`visitable\` prop is set to true.
121
- It links to the top of the page.`,
122
- },
123
- };
124
-
125
- export const LightPrimary: StoryComponentType = () => (
126
- <Link href="#" light={true}>
127
- The quick brown fox jumps over the lazy dog.
128
- </Link>
129
- );
130
-
131
- LightPrimary.parameters = {
132
- docs: {
133
- storyDescription: `Minimal link usage on a dark background. This
134
- link has its \`light\` prop set to true. It links to the top
135
- of the page.`,
136
- },
137
- backgrounds: {
138
- default: "darkBlue",
139
- },
140
- };
141
-
142
- LightPrimary.play = async ({canvasElement}) => {
143
- const canvas = within(canvasElement);
144
-
145
- const link = canvas.getByRole("link");
146
-
147
- await userEvent.hover(link);
148
- await expect(link).toHaveStyle(
149
- `text-decoration: underline ${Color.white} dashed 2px`,
150
- );
151
-
152
- await fireEvent.mouseDown(link);
153
- await expect(link).toHaveStyle(
154
- `text-decoration: underline solid ${fadedBlue} 1px`,
155
- );
156
- };
157
-
158
- export const LightVisitable: StoryComponentType = () => (
159
- <Link href="#" light={true} visitable={true}>
160
- The quick brown fox jumps over the lazy dog.
161
- </Link>
162
- );
163
-
164
- LightVisitable.parameters = {
165
- backgrounds: {
166
- default: "darkBlue",
167
- },
168
- docs: {
169
- storyDescription: `This is a visitable link on a dark background.
170
- It changes color after it has been clicked on to indicate
171
- that it's been visited before. This link's \`visitable\` prop
172
- is set to true. It links to the top of the page.`,
173
- },
174
- };
175
-
176
- export const Inline: StoryComponentType = () => (
177
- <Body>
178
- This is an inline{" "}
179
- <Link href="#" inline={true}>
180
- Primary link
181
- </Link>
182
- , whereas this is an inline{" "}
183
- <Link href="#" kind="secondary" inline={true}>
184
- Secondary link
185
- </Link>
186
- , and this is an inline{" "}
187
- <Link href="#" visitable={true} inline={true}>
188
- Visitable link (Primary only)
189
- </Link>
190
- .
191
- </Body>
192
- );
193
-
194
- Inline.parameters = {
195
- docs: {
196
- storyDescription: `Inline links include an underline to distinguish
197
- them from the surrounding text. Make a link inline by setting the
198
- \`inline\` prop to \`true\`. It is recommended to use inline
199
- links within paragraphs and sentences.`,
200
- },
201
- };
202
-
203
- Inline.play = async ({canvasElement}) => {
204
- const canvas = within(canvasElement);
205
-
206
- const primaryLink = canvas.getByText("Primary link");
207
- const secondaryLink = canvas.getByText("Secondary link");
208
-
209
- // Primary link styles
210
- await expect(primaryLink).toHaveStyle(`color: ${Color.blue}`);
211
-
212
- await userEvent.hover(primaryLink);
213
- await expect(primaryLink).toHaveStyle(
214
- `text-decoration: underline ${Color.blue} dashed 2px`,
215
- );
216
-
217
- await fireEvent.mouseDown(primaryLink);
218
- await expect(primaryLink).toHaveStyle(
219
- `text-decoration: underline solid ${activeBlue} 1px`,
220
- );
221
-
222
- // Secondary link styles
223
- await expect(secondaryLink).toHaveStyle(`color: ${Color.offBlack}`);
224
-
225
- await userEvent.hover(secondaryLink);
226
- await expect(secondaryLink).toHaveStyle(
227
- `text-decoration: underline ${Color.offBlack} dashed 2px`,
228
- );
229
-
230
- await fireEvent.mouseDown(secondaryLink);
231
- await expect(secondaryLink).toHaveStyle(
232
- `text-decoration: underline solid ${activeBlue} 1px`,
233
- );
234
- };
235
-
236
- export const InlineLight: StoryComponentType = () => (
237
- <Body style={{color: Color.white}}>
238
- This is an inline{" "}
239
- <Link href="#" inline={true} light={true}>
240
- Primary link
241
- </Link>
242
- , whereas this is an inline{" "}
243
- <Link href="#" visitable={true} inline={true} light={true}>
244
- Visitable link (Primary only)
245
- </Link>
246
- . Secondary light links are not supported.
247
- </Body>
248
- );
249
-
250
- InlineLight.parameters = {
251
- backgrounds: {
252
- default: "darkBlue",
253
- },
254
- docs: {
255
- storyDescription: `Inline links include an underline to distinguish
256
- them from the surrounding text. If the link is on a
257
- dark background, set the \`light\` prop to true for it to
258
- be appropriately visible.\n\n**NOTE:** Secondary light links are
259
- not supported.`,
260
- },
261
- };
262
-
263
- InlineLight.play = async ({canvasElement}) => {
264
- const canvas = within(canvasElement);
265
-
266
- const primaryLink = canvas.getByText("Primary link");
267
-
268
- await expect(primaryLink).toHaveStyle(`color: ${Color.white}`);
269
-
270
- await userEvent.hover(primaryLink);
271
- await expect(primaryLink).toHaveStyle(
272
- `text-decoration: underline ${Color.white} dashed 2px`,
273
- );
274
-
275
- await fireEvent.mouseDown(primaryLink);
276
- await expect(primaryLink).toHaveStyle(
277
- `text-decoration: underline solid ${fadedBlue} 1px`,
278
- );
279
- };
280
-
281
- export const Variants: StoryComponentType = () => (
282
- <View>
283
- {/* Default (dark) */}
284
- <View style={{padding: Spacing.large_24}}>
285
- {/* Standalone */}
286
- <View>
287
- <View style={styles.standaloneLinkWrapper}>
288
- <Link href="#nonexistent-link">
289
- Standalone Primary Link
290
- </Link>
291
- </View>
292
- <View style={styles.standaloneLinkWrapper}>
293
- <Link href="#secondary-nonexistent-link" kind="secondary">
294
- Standalone Secondary Link
295
- </Link>
296
- </View>
297
- <View style={styles.standaloneLinkWrapper}>
298
- <Link href="#" visitable={true}>
299
- Standalone Visitable Link (Primary only)
300
- </Link>
301
- </View>
302
- </View>
303
- <Strut size={Spacing.xSmall_8} />
304
- {/* Inline */}
305
- <Body>
306
- This is an{" "}
307
- <Link href="#" inline={true}>
308
- Inline Primary link
309
- </Link>
310
- , whereas this is an{" "}
311
- <Link href="#" kind="secondary" inline={true}>
312
- Inline Secondary link
313
- </Link>
314
- , and this is an{" "}
315
- <Link href="#" visitable={true} inline={true}>
316
- Inline Visitable link (Primary only)
317
- </Link>
318
- .
319
- </Body>
320
- </View>
321
- {/* Light */}
322
- <View
323
- style={{
324
- backgroundColor: Color.darkBlue,
325
- padding: Spacing.large_24,
326
- }}
327
- >
328
- {/* Standalone */}
329
- <View>
330
- <View style={styles.standaloneLinkWrapper}>
331
- <Link href="#nonexistent-link" light={true}>
332
- Standalone Light Link (Primary only)
333
- </Link>
334
- </View>
335
- <View style={styles.standaloneLinkWrapper}>
336
- <Link href="#" visitable={true} light={true}>
337
- Standalone Light Visitable Link (Primary only)
338
- </Link>
339
- </View>
340
- </View>
341
- <Strut size={Spacing.xSmall_8} />
342
- {/* Inline */}
343
- <Body style={{color: Color.white}}>
344
- This is an{" "}
345
- <Link href="#" inline={true} light={true}>
346
- Inline Primary link
347
- </Link>
348
- , whereas this is an{" "}
349
- <Link href="#" visitable={true} inline={true} light={true}>
350
- Inline Visitable link (Primary only)
351
- </Link>
352
- . Secondary light links are not supported.
353
- </Body>
354
- </View>
355
- </View>
356
- );
357
-
358
- Variants.parameters = {
359
- docs: {
360
- storyDescription: `By default, primary links are blue, secondary
361
- links are gray, and visitable links turn purple after they've
362
- been clicked on. Default inline links are underlined, and the
363
- secondary kind is black to match surrounding text color.
364
- Light standalone and inline links have the same colors - white
365
- with visited visitable links being pink. Light inline links are
366
- also underlined like default inline links. Light secondary links
367
- are not supported and will result in an error.`,
368
- },
369
- };
370
-
371
- export const WithTypography: StoryComponentType = () => (
372
- <Link href="#nonexistent-link" id="typography-link">
373
- <HeadingSmall>Heading inside a Link element</HeadingSmall>
374
- </Link>
375
- );
376
-
377
- WithTypography.parameters = {
378
- docs: {
379
- storyDescription: `Wonder Blocks Typography elements can also be used
380
- inside Links instead of plain text. Here, we have a \`Link\`
381
- containing a \`HeadingSmall\`.`,
382
- },
383
- };
384
-
385
- WithTypography.play = async ({canvasElement}) => {
386
- const canvas = within(canvasElement);
387
-
388
- const heading = canvas.getByText("Heading inside a Link element");
389
-
390
- // Confirm that the default font size (16px) and line height (22px)
391
- // are successfully overridden by typography.
392
- await expect(heading).toHaveStyle("font-size: 20px");
393
- await expect(heading).toHaveStyle("lineHeight: 24px");
394
- };
395
-
396
- export const WithStyle: StoryComponentType = () => (
397
- <Link href="#" style={styles.pinkLink}>
398
- This link has a style.
399
- </Link>
400
- );
401
-
402
- WithStyle.parameters = {
403
- docs: {
404
- storyDescription: `Link can take a \`style\` prop. Here, the
405
- Link has been given a style in which the \`color\` field has
406
- been set to \`Colors.pink\`.`,
407
- },
408
- };
409
-
410
- export const Navigation: StoryComponentType = () => (
411
- <MemoryRouter>
412
- <View>
413
- <View style={styles.row}>
414
- <Link
415
- href="/foo"
416
- style={styles.heading}
417
- onClick={() => {
418
- // eslint-disable-next-line no-console
419
- console.log("I'm still on the same page!");
420
- }}
421
- >
422
- <LabelLarge>Uses Client-side Nav</LabelLarge>
423
- </Link>
424
- <Link
425
- href="/iframe.html?id=link--default&viewMode=story"
426
- style={styles.heading}
427
- skipClientNav
428
- >
429
- <LabelLarge>Avoids Client-side Nav</LabelLarge>
430
- </Link>
431
- </View>
432
- <View style={styles.navigation}>
433
- <Switch>
434
- <Route path="/foo">
435
- <View id="foo">
436
- The first link does client-side navigation here.
437
- </View>
438
- </Route>
439
- <Route path="*">See navigation changes here</Route>
440
- </Switch>
441
- </View>
442
- </View>
443
- </MemoryRouter>
444
- );
445
-
446
- Navigation.parameters = {
447
- docs: {
448
- storyDescription: `If you want to navigate to an external URL
449
- and/or reload the window, make sure to use \`href\` and
450
- \`skipClientNav={true}\`, as shown in this example.
451
- **For navigation callbacks:** The \`onClick\`, \`beforeNav\`, and
452
- \`safeWithNav\` props can be used to run callbacks when navigating
453
- to the new URL. Which prop to use depends on the use case. See the
454
- [Button documentation](/story/button-navigation-callbacks--before-nav-callbacks&viewMode=docs)
455
- for details.`,
456
- },
457
- };
458
-
459
- const styles = StyleSheet.create({
460
- darkBackground: {
461
- backgroundColor: Color.darkBlue,
462
- color: Color.white,
463
- padding: 10,
464
- },
465
- heading: {
466
- marginRight: Spacing.large_24,
467
- },
468
- navigation: {
469
- border: `1px dashed ${Color.lightBlue}`,
470
- marginTop: Spacing.large_24,
471
- padding: Spacing.large_24,
472
- },
473
- pinkLink: {
474
- color: Color.pink,
475
- },
476
- row: {
477
- flexDirection: "row",
478
- alignItems: "center",
479
- },
480
- standaloneLinkWrapper: {
481
- // Use inline-block so the outline wraps only the text
482
- // instead of taking the full width of the parent
483
- // container.
484
- display: "inline-block",
485
- marginBottom: Spacing.xSmall_8,
486
- },
487
- });