Skip to content

Commit

Permalink
feat(Icon): add (official) support for image symbols (#1411)
Browse files Browse the repository at this point in the history
## πŸ“ Changes

We had support for images in `Icon` but this makes it official and
adjusts types/docs.

- Adds example of using `img` as `Icon` symbol
- Update docs
- Update types

## βœ… Checklist

Easy UI has certain UX standards that must be met. In general,
non-trivial changes should meet the following criteria:

- [ ] ~Visuals match Design Specs in Figma~
- [x] Stories accompany any component changes
- [x] Code is in accordance with our style guide
- [ ] ~Design tokens are utilized~
- [x] Unit tests accompany any component changes
- [x] TSDoc is written for any API surface area
- [x] Specs are up-to-date
- [x] Console is free from warnings
- [x] No accessibility violations are reported
- [x] Cross-browser check is performed (Chrome, Safari, Firefox)
- [x] Changeset is added

~Strikethrough~ any items that are not applicable to this pull request.
  • Loading branch information
stephenjwatkins authored Oct 17, 2024
1 parent af4520b commit b190fa6
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/clever-feet-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@easypost/easy-ui": minor
---

feat(Icon): add support for images
2 changes: 1 addition & 1 deletion documentation/specs/Icon.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type ResponsiveIconSize = {
type IconColor = TokenNamespace<"color">;

interface Icon {
symbol: ReactElement<IconSymbol>;
symbol: ReactElement<IconSymbol> | ReactElement<ComponentProps<"img">>;
size?: IconSize | ResponsiveIconSize;
color?: IconColor;
accessibilityLabel?: string;
Expand Down
2 changes: 1 addition & 1 deletion easy-ui-react/src/Icon/Icon.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
}
}

.Svg {
.symbol {
position: relative;
display: block;
width: 100%;
Expand Down
12 changes: 11 additions & 1 deletion easy-ui-react/src/Icon/Icon.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ErrorIcon from "@easypost/easy-ui-icons/Error";
import InfoIcon from "@easypost/easy-ui-icons/Info";
import WarningIcon from "@easypost/easy-ui-icons/Warning";
import { Meta, StoryObj } from "@storybook/react";
import React from "react";
import React, { ComponentProps } from "react";
import {
createColorTokensControl,
createLabelledOptionsControl,
Expand All @@ -23,6 +23,7 @@ const meta: Meta<typeof Icon> = {
Info: InfoIcon,
Warning: WarningIcon,
Error: ErrorIcon,
FedExLogoImg: FedExLogoImg,
}),
size: createLabelledOptionsControl({
md: "md",
Expand Down Expand Up @@ -54,3 +55,12 @@ export const Controls: Story = {
symbol: CheckCircleIcon,
},
};

function FedExLogoImg(props: ComponentProps<"img">) {
return (
<img
src="data:image/svg+xml;base64,PHN2ZyBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA3OS42IDM0LjYiIHZpZXdCb3g9IjAgMCA3OS42IDM0LjYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibS0xMy41LTdoOTl2NDguMmgtOTl6IiBmaWxsPSJub25lIi8+PGcgZmlsbD0iI2Y2MCI+PHBhdGggZD0ibTc5LjEgMjUuM2MwLTEuMi0uOS0yLjEtMi4xLTIuMXMtMi4xLjktMi4xIDIuMS45IDIuMSAyLjEgMi4xYzEuMyAwIDIuMS0uOSAyLjEtMi4xem0tMi42LjF2MS40aC0uNHYtMy4xaDEuMWMuNyAwIDEgLjMgMSAuOCAwIC4zLS4yLjYtLjUuNy4zIDAgLjQuMy40LjcgMCAuMy4xLjcuMi45aC0uNWMtLjEtLjMtLjEtLjctLjItMXMtLjItLjQtLjUtLjR6bS42LS40Yy40IDAgLjYtLjIuNi0uNHMtLjEtLjQtLjYtLjRoLS42di44em0tMi42LjNjMC0xLjUgMS4yLTIuNSAyLjYtMi41czIuNiAxIDIuNiAyLjUtMS4yIDIuNS0yLjYgMi41LTIuNi0xLTIuNi0yLjV6Ii8+PHBhdGggZD0ibTY1LjYgMjcuOC0yLjktMy4zLTIuOSAzLjNoLTYuMmw2LTYuNy02LTYuOGg2LjRsMi45IDMuMyAyLjktMy4zaDYuMWwtNiA2LjcgNi4xIDYuOHoiLz48cGF0aCBkPSJtNDEuOCAyNy44di0yMS4zaDExLjh2NC44aC02Ljh2M2g2Ljh2NC42aC02Ljh2NC4yaDYuOHY0Ljd6Ii8+PC9nPjxwYXRoIGQ9Im0zNi44IDYuNXY4LjdoLS4xYy0xLjEtMS4zLTIuNS0xLjctNC4xLTEuNy0zLjMgMC01LjcgMi4yLTYuNiA1LjItMS0zLjItMy41LTUuMi03LjMtNS4yLTMuMSAwLTUuNSAxLjQtNi44IDMuNnYtMi44aC02LjJ2LTNoNi45di00LjhoLTEyLjZ2MjEuM2g1Ljd2LTguOWg1LjZjLS4yLjYtLjMgMS4zLS4zIDIuMSAwIDQuNCAzLjQgNy42IDcuNyA3LjYgMy42IDAgNi0xLjcgNy4zLTQuOGgtNC44Yy0uNy45LTEuMiAxLjItMi41IDEuMi0xLjUgMC0yLjgtMS4zLTIuOC0yLjloOS45Yy40IDMuNSAzLjIgNi42IDYuOSA2LjYgMS42IDAgMy4xLS44IDQtMi4yaC4xdjEuNGg1di0yMS40em0tMjAuNyAxMi40Yy4zLTEuNCAxLjQtMi4yIDIuNy0yLjIgMS40IDAgMi40LjkgMi43IDIuMnptMTcuNyA1LjdjLTEuOCAwLTMtMS43LTMtMy41IDAtMS45IDEtMy43IDMtMy43IDIuMSAwIDIuOSAxLjggMi45IDMuNyAwIDEuOC0uOSAzLjUtMi45IDMuNXoiIGZpbGw9IiM0NjE0OGMiLz48L3N2Zz4="
{...props}
/>
);
}
11 changes: 10 additions & 1 deletion easy-ui-react/src/Icon/Icon.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import CheckCircleIcon from "@easypost/easy-ui-icons/CheckCircle";
import { render, screen } from "@testing-library/react";
import React from "react";
import React, { ComponentProps } from "react";
import {
getResponsiveDesignToken,
getComponentThemeToken,
Expand Down Expand Up @@ -39,4 +39,13 @@ describe("<Icon />", () => {
getResponsiveDesignToken("icon", "size", "size.icon", "sm"),
);
});

it("should render an image", () => {
const CarrierLogo = (props: ComponentProps<"img">) => (
<img src="/carrier-logo.png" {...props} />
);
render(<Icon symbol={CarrierLogo} accessibilityLabel="Carrier name" />);
expect(screen.getByRole("img", { hidden: false })).toBeInTheDocument();
expect(screen.getByTitle("Carrier name")).toBeInTheDocument();
});
});
19 changes: 17 additions & 2 deletions easy-ui-react/src/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ export type IconColor =
| "primary-inverse";

export type IconProps = {
/** Icon symbol SVG source from @easypost/easy-ui-icons */
/**
* Icon symbol.
*
* This can be an SVG source from @easypost/easy-ui-icons or a React
* element that renders to an img.
*/
symbol: IconSymbol;
/** Size of the icon */
size?: ResponsiveProp<IconSize>;
Expand Down Expand Up @@ -82,6 +87,16 @@ export type IconProps = {
* return <Icon symbol={AddIcon} size={{ sm: "sm", md: "md", lg: "lg" }} />;
* }
* ```
* @example
* _With image symbol:_
* ```tsx
* import { Icon } from "@easypost/easy-ui/Icon";
*
* export function Component() {
* const CarrierLogo = (props) => <img src="/carrier-logo.png" {...props} />;
* return <Icon symbol={CarrierLogo} />;
* }
* ```
*/
export function Icon({
symbol: Symbol,
Expand All @@ -101,7 +116,7 @@ export function Icon({
return (
<span className={styles.Icon} style={style}>
<Symbol
className={styles.Svg}
className={styles.symbol}
focusable="false"
role="img"
title={accessibilityLabel}
Expand Down
8 changes: 5 additions & 3 deletions easy-ui-react/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type tokens from "@easypost/easy-ui-tokens/js/tokens";
import React from "react";
import React, { ComponentProps } from "react";
import { ResponsiveProp } from "./utilities/css";

export type DesignTokens = typeof tokens;
Expand Down Expand Up @@ -57,12 +57,14 @@ export type ThemeTokenNamespace<Needle extends string> = Namespace<

export type ThemeColorAliases = ThemeTokenNamespace<"color">;

type IconSymbolProps = React.SVGProps<SVGSVGElement> & {
type IconSvgSymbolProps = React.SVGProps<SVGSVGElement> & {
title?: string;
titleId?: string;
};

export type IconSymbol = React.FunctionComponent<IconSymbolProps>;
export type IconSymbol =
| React.FunctionComponent<IconSvgSymbolProps>
| React.FunctionComponent<ComponentProps<"img">>;

export type SpaceScale = DesignTokenNamespace<"space">;
export type ResponsiveSpaceScale = ResponsiveProp<SpaceScale>;
Expand Down

0 comments on commit b190fa6

Please sign in to comment.