Skip to content

Commit

Permalink
WB-1614: Add PhosphorIcon support to Link (#2099)
Browse files Browse the repository at this point in the history
## Summary:

Replaces `Icon` with the new `PhosphorIcon` component in `Link`. Now `Link` only
should accept PhosphorIcon instances in the `startIcon` and `endIcon` props.

Issue: https://khanacademy.atlassian.net/browse/WB-1614

## Test plan:
In Storybook, verify that the `Link` icon stories look correct.

- `startIcon` and `endIcon`: /?path=/story/link--start-and-end-icons
- External link: /?path=/story/link--opens-in-a-new-tab

Also compare the changes in Chromatic.

Author: jandrade

Reviewers: jeresig, nishasy

Required Reviewers:

Approved By: jeresig, nishasy

Checks: ✅ codecov/project, ✅ Chromatic - Get results on regular PRs (ubuntu-latest, 16.x), ✅ Lint (ubuntu-latest, 16.x), ✅ Test (ubuntu-latest, 16.x, 2/2), ✅ Check build sizes (ubuntu-latest, 16.x), ✅ Test (ubuntu-latest, 16.x, 1/2), ✅ gerald, ✅ Chromatic - Build on regular PRs / chromatic (ubuntu-latest, 16.x), ⏭  Chromatic - Skip on Release PR (changesets), ✅ Publish npm snapshot (ubuntu-latest, 16.x), ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 16.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 16.x), ⏭  dependabot

Pull Request URL: #2099
  • Loading branch information
jandrade authored Oct 27, 2023
1 parent 3f854fe commit d65b600
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 72 deletions.
5 changes: 5 additions & 0 deletions .changeset/shiny-pugs-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-blocks-link": major
---

Add support to PhosphorIcon in Link
13 changes: 7 additions & 6 deletions __docs__/wonder-blocks-link/link.argtypes.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as React from "react";

import type {InputType} from "@storybook/csf";
import Icon, {icons} from "@khanacademy/wonder-blocks-icon";
import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
import {IconMappings} from "../wonder-blocks-icon/phosphor-icon.argtypes";

const iconsMap: Record<string, React.ReactElement<typeof Icon>> = {};
const iconsMap: Record<string, React.ReactElement<typeof PhosphorIcon>> = {};

Object.entries(icons).forEach(([iconLabel, iconValue]) => {
iconsMap[iconLabel] = <Icon icon={iconValue} />;
Object.entries(IconMappings).forEach(([iconLabel, iconValue]) => {
iconsMap[iconLabel] = <PhosphorIcon icon={iconValue} />;
});

export default {
Expand All @@ -27,7 +28,7 @@ export default {
mapping: iconsMap,
table: {
category: "Icons",
type: {summary: "Icon"},
type: {summary: "PhosphorIcon"},
},
},

Expand Down Expand Up @@ -127,7 +128,7 @@ export default {
mapping: iconsMap,
table: {
category: "Icons",
type: {summary: "Icon"},
type: {summary: "PhosphorIcon"},
},
},

Expand Down
66 changes: 43 additions & 23 deletions __docs__/wonder-blocks-link/link.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable max-lines */
// We need to use fireEvent for mouseDown in these tests, none of the userEvent
// alternatives work. Click includes mouseUp, which removes the pressed style.
/* eslint-disable testing-library/prefer-user-event */
Expand All @@ -10,7 +11,7 @@ import type {Meta, StoryObj} from "@storybook/react";

import Color from "@khanacademy/wonder-blocks-color";
import {View} from "@khanacademy/wonder-blocks-core";
import Icon, {icons} from "@khanacademy/wonder-blocks-icon";
import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
import {Strut} from "@khanacademy/wonder-blocks-layout";
import Spacing from "@khanacademy/wonder-blocks-spacing";
import {
Expand All @@ -23,6 +24,7 @@ import packageConfig from "../../packages/wonder-blocks-link/package.json";

import ComponentInfo from "../../.storybook/components/component-info";
import LinkArgTypes from "./link.argtypes";
import {IconMappings} from "../wonder-blocks-icon/phosphor-icon.argtypes";

export default {
title: "Link",
Expand Down Expand Up @@ -257,22 +259,24 @@ export const StartAndEndIcons: StoryComponentType = () => (
<View style={{padding: Spacing.large_24}}>
<Link
href="#"
startIcon={<Icon icon={icons.add} />}
startIcon={<PhosphorIcon icon={IconMappings.plusCircleBold} />}
style={styles.standaloneLinkWrapper}
>
This link has a start icon
</Link>
<Link
href="#"
endIcon={<Icon icon={icons.search} />}
endIcon={
<PhosphorIcon icon={IconMappings.magnifyingGlassBold} />
}
kind="secondary"
style={styles.standaloneLinkWrapper}
>
This link has an end icon
</Link>
<Link
href="https://stuffonmycat.com/"
endIcon={<Icon icon={icons.info} />}
endIcon={<PhosphorIcon icon={IconMappings.infoBold} />}
target="_blank"
style={styles.standaloneLinkWrapper}
>
Expand All @@ -281,17 +285,17 @@ export const StartAndEndIcons: StoryComponentType = () => (
</Link>
<Link
href="#"
startIcon={<Icon icon={icons.caretLeft} />}
endIcon={<Icon icon={icons.caretRight} />}
startIcon={<PhosphorIcon icon={IconMappings.caretLeftBold} />}
endIcon={<PhosphorIcon icon={IconMappings.caretRightBold} />}
kind="secondary"
style={styles.standaloneLinkWrapper}
>
This link has a start icon and an end icon
</Link>
<Link
href="#"
startIcon={<Icon icon={icons.caretLeft} />}
endIcon={<Icon icon={icons.caretRight} />}
startIcon={<PhosphorIcon icon={IconMappings.caretLeftBold} />}
endIcon={<PhosphorIcon icon={IconMappings.caretRightBold} />}
style={styles.multiLine}
>
This is a multi-line link with start and end icons
Expand All @@ -301,7 +305,9 @@ export const StartAndEndIcons: StoryComponentType = () => (
<Link
href="#"
inline={true}
startIcon={<Icon icon={icons.caretLeft} />}
startIcon={
<PhosphorIcon icon={IconMappings.caretLeftBold} />
}
>
link with a start icon
</Link>{" "}
Expand All @@ -310,7 +316,9 @@ export const StartAndEndIcons: StoryComponentType = () => (
href="#"
inline={true}
target="_blank"
endIcon={<Icon icon={icons.caretRight} />}
endIcon={
<PhosphorIcon icon={IconMappings.caretRightBold} />
}
>
link with an end icon
</Link>
Expand All @@ -326,23 +334,25 @@ export const StartAndEndIcons: StoryComponentType = () => (
>
<Link
href="#"
startIcon={<Icon icon={icons.add} />}
startIcon={<PhosphorIcon icon={IconMappings.plusCircleBold} />}
light={true}
style={styles.standaloneLinkWrapper}
>
This link has a start icon
</Link>
<Link
href="#"
endIcon={<Icon icon={icons.search} />}
endIcon={
<PhosphorIcon icon={IconMappings.magnifyingGlassBold} />
}
light={true}
style={styles.standaloneLinkWrapper}
>
This link has an end icon
</Link>
<Link
href="https://stuffonmycat.com/"
endIcon={<Icon icon={icons.info} />}
endIcon={<PhosphorIcon icon={IconMappings.infoBold} />}
target="_blank"
light={true}
style={styles.standaloneLinkWrapper}
Expand All @@ -352,17 +362,17 @@ export const StartAndEndIcons: StoryComponentType = () => (
</Link>
<Link
href="#"
startIcon={<Icon icon={icons.caretLeft} />}
endIcon={<Icon icon={icons.caretRight} />}
startIcon={<PhosphorIcon icon={IconMappings.caretLeftBold} />}
endIcon={<PhosphorIcon icon={IconMappings.caretRightBold} />}
light={true}
style={styles.standaloneLinkWrapper}
>
This link has a start icon and an end icon
</Link>
<Link
href="#"
startIcon={<Icon icon={icons.caretLeft} />}
endIcon={<Icon icon={icons.caretRight} />}
startIcon={<PhosphorIcon icon={IconMappings.caretLeftBold} />}
endIcon={<PhosphorIcon icon={IconMappings.caretRightBold} />}
light={true}
style={styles.multiLine}
>
Expand All @@ -372,7 +382,9 @@ export const StartAndEndIcons: StoryComponentType = () => (
This is an inline{" "}
<Link
href="#"
startIcon={<Icon icon={icons.caretLeft} />}
startIcon={
<PhosphorIcon icon={IconMappings.caretLeftBold} />
}
inline={true}
light={true}
>
Expand All @@ -381,7 +393,9 @@ export const StartAndEndIcons: StoryComponentType = () => (
and an inline{" "}
<Link
href="#"
endIcon={<Icon icon={icons.caretRight} />}
endIcon={
<PhosphorIcon icon={IconMappings.caretRightBold} />
}
inline={true}
light={true}
target="_blank"
Expand Down Expand Up @@ -914,18 +928,24 @@ WithTitle.play = async ({canvasElement}) => {
export const RightToLeftWithIcons: StoryComponentType = () => (
<View style={{padding: Spacing.medium_16}}>
<View style={styles.rightToLeft}>
<Link href="/" startIcon={<Icon icon={icons.caretRight} />}>
<Link
href="/"
startIcon={<PhosphorIcon icon={IconMappings.caretRightBold} />}
>
هذا الرابط مكتوب باللغة العربية
</Link>
<Strut size={Spacing.medium_16} />
<Link href="/" endIcon={<Icon icon={icons.caretLeft} />}>
<Link
href="/"
endIcon={<PhosphorIcon icon={IconMappings.caretLeftBold} />}
>
هذا الرابط مكتوب باللغة العربية
</Link>
<Strut size={Spacing.medium_16} />
<Link
href="/"
startIcon={<Icon icon={icons.caretRight} />}
endIcon={<Icon icon={icons.caretLeft} />}
startIcon={<PhosphorIcon icon={IconMappings.caretRightBold} />}
endIcon={<PhosphorIcon icon={IconMappings.caretLeftBold} />}
>
هذا الرابط مكتوب باللغة العربية
</Link>
Expand Down
47 changes: 17 additions & 30 deletions packages/wonder-blocks-link/src/components/__tests__/link.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {MemoryRouter, Route, Switch} from "react-router-dom";
import {fireEvent, render, screen, waitFor} from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import Icon, {icons} from "@khanacademy/wonder-blocks-icon";
import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
import plusIcon from "@phosphor-icons/core/bold/plus-bold.svg";

import Link from "../link";

Expand Down Expand Up @@ -362,12 +363,12 @@ describe("Link", () => {
);

// Act
const link = screen.getByRole("link");
const icon = screen.getByTestId("external-icon");

// Assert
expect(link.innerHTML).toEqual(expect.stringContaining("<svg"));
expect(icon).toBeInTheDocument();
expect(icon).toHaveStyle({
maskImage: "url(arrow-square-out-bold.svg)",
});
});

test("does not render external icon when `target=_blank` and link is relative", () => {
Expand Down Expand Up @@ -414,19 +415,17 @@ describe("Link", () => {
render(
<Link
href="https://www.khanacademy.org/"
startIcon={<Icon icon={icons.add} />}
startIcon={<PhosphorIcon icon={plusIcon} />}
>
Add new item
</Link>,
);

// Act
const link = screen.getByRole("link");
const icon = screen.getByTestId("start-icon");

// Assert
expect(link.innerHTML).toEqual(expect.stringContaining("<svg"));
expect(icon).toBeInTheDocument();
expect(icon).toHaveStyle({maskImage: "url(plus-bold.svg)"});
});

test("does not render icon when startIcon prop is not passed in", () => {
Expand All @@ -445,41 +444,35 @@ describe("Link", () => {
render(
<Link
href="https://www.khanacademy.org/"
startIcon={<Icon icon={icons.add} />}
startIcon={<PhosphorIcon icon={plusIcon} />}
>
Add new item
</Link>,
);

// Act
const icon = screen.getByTestId("start-icon");
const iconToExpect =
"M11 11V7a1 1 0 0 1 2 0v4h4a1 1 0 0 1 0 2h-4v4a1 1 0 0 1-2 0v-4H7a1 1 0 0 1 0-2h4zm1 13C5.373 24 0 18.627 0 12S5.373 0 12 0s12 5.373 12 12-5.373 12-12 12zm0-2c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z";

// Assert
expect(icon.innerHTML).toEqual(
expect.stringContaining(iconToExpect),
);
expect(icon).toHaveStyle({maskImage: "url(plus-bold.svg)"});
});

test("render icon with link when endIcon prop is passed in", () => {
// Arrange
render(
<Link
href="https://www.khanacademy.org/"
endIcon={<Icon icon={icons.caretRight} />}
endIcon={<PhosphorIcon icon={plusIcon} />}
>
Click to go back
</Link>,
);

// Act
const link = screen.getByRole("link");
const icon = screen.getByTestId("end-icon");

// Assert
expect(link.innerHTML).toEqual(expect.stringContaining("<svg"));
expect(icon).toBeInTheDocument();
expect(icon).toHaveStyle({maskImage: "url(plus-bold.svg)"});
});

test("does not render icon when endIcon prop is not passed in", () => {
Expand All @@ -498,7 +491,7 @@ describe("Link", () => {
render(
<Link
href="https://www.google.com/"
endIcon={<Icon icon={icons.caretRight} />}
endIcon={<PhosphorIcon icon={plusIcon} />}
target="_blank"
>
Open a new tab
Expand All @@ -517,39 +510,33 @@ describe("Link", () => {
render(
<Link
href="https://www.google.com/"
endIcon={<Icon icon={icons.caretRight} />}
endIcon={<PhosphorIcon icon={plusIcon} />}
target="_blank"
>
Open a new tab
</Link>,
);

// Act
const link = screen.getByRole("link");
const endIcon = screen.getByTestId("end-icon");
const icon = screen.getByTestId("end-icon");

// Assert
expect(link.innerHTML).toEqual(expect.stringContaining("<svg"));
expect(endIcon).toBeInTheDocument();
expect(icon).toHaveStyle({maskImage: "url(plus-bold.svg)"});
});

test("endIcon prop passed down correctly", () => {
// Arrange
render(
<Link href="/" endIcon={<Icon icon={icons.caretRight} />}>
<Link href="/" endIcon={<PhosphorIcon icon={plusIcon} />}>
Click to go back
</Link>,
);

// Act
const icon = screen.getByTestId("end-icon");
const iconToExpect =
"M8.586 8L5.293 4.707a1 1 0 0 1 1.414-1.414l4 4a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414-1.414L8.586 8z";

// Assert
expect(icon.innerHTML).toEqual(
expect.stringContaining(iconToExpect),
);
expect(icon).toHaveStyle({maskImage: "url(plus-bold.svg)"});
});
});
});
Loading

0 comments on commit d65b600

Please sign in to comment.