Skip to content

Commit 6341b25

Browse files
authored
refactor(icons)!: align API with RAC (#1411)
* refactor(icons)!: align API with RAC * fix: check for fill * feat: add custom example * chore: add changeset * fix: ts * refactor: composition
1 parent d26c1c9 commit 6341b25

File tree

20 files changed

+149
-176
lines changed

20 files changed

+149
-176
lines changed

.changeset/angry-pears-return.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@launchpad-ui/icons": minor
3+
"@launchpad-ui/avatar": patch
4+
"@launchpad-ui/chip": patch
5+
"@launchpad-ui/filter": patch
6+
"@launchpad-ui/core": patch
7+
---
8+
9+
Align API with RAC components:
10+
- Remove `IconContext`
11+
- Remove outer `span`
12+
- Remove props `title` and `description` in favor of composition
13+
- Prop `subtle` -> `variant`
14+
- Prop `children` for custom icons
15+
- Consolidate sizes

.storybook/custom.svg

Lines changed: 11 additions & 0 deletions
Loading

.storybook/main.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@ const config: StorybookConfig = {
2424
core: {
2525
disableTelemetry: true,
2626
},
27-
staticDirs: [
28-
'.',
29-
{ from: '../packages/icons/src/img', to: '/static' },
30-
{ from: '../packages/card/src/img', to: '/static' },
31-
],
27+
staticDirs: ['.', { from: '../packages/card/src/img', to: '/static' }],
3228
env: (config) => {
3329
const packageStatuses = getPackageStatusEnvVars();
3430

.storybook/preview.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import type { ReactNode } from 'react';
44

55
import { Box } from '@launchpad-ui/box';
66
import { RouterProvider as AriaRouterProvider, useHref } from '@launchpad-ui/components';
7+
import sprite from '@launchpad-ui/icons/img/sprite.svg';
78
import { withThemeByDataAttribute } from '@storybook/addon-themes';
89
import { themes } from '@storybook/theming';
910
import { BrowserRouter, useNavigate } from 'react-router-dom';
1011

12+
import custom from './custom.svg';
1113
import { allModes } from './modes';
1214

1315
import '../packages/components/src/styles/themes.css';
@@ -16,6 +18,32 @@ import '../packages/tokens/dist/index.css';
1618
import '../packages/tokens/dist/media-queries.css';
1719
import '../packages/tokens/dist/themes.css';
1820

21+
fetch(sprite)
22+
.then(async (response) => response.text())
23+
.then((data) => {
24+
const parser = new DOMParser();
25+
const content = parser.parseFromString(data, 'image/svg+xml').documentElement;
26+
content.id = 'lp-icons-sprite';
27+
content.style.display = 'none';
28+
document.body.appendChild(content);
29+
})
30+
.catch((err) => {
31+
console.log('Failed to fetch sprite', err);
32+
});
33+
34+
fetch(custom)
35+
.then(async (response) => response.text())
36+
.then((data) => {
37+
const parser = new DOMParser();
38+
const content = parser.parseFromString(data, 'image/svg+xml').documentElement;
39+
content.id = 'app-icons-sprite';
40+
content.style.display = 'none';
41+
document.body.appendChild(content);
42+
})
43+
.catch((err) => {
44+
console.log('Failed to fetch sprite', err);
45+
});
46+
1947
const RouterProvider = ({ children }: { children: ReactNode }) => {
2048
const navigate = useNavigate();
2149
return (

packages/avatar/src/Avatar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ const Avatar = ({
8080
}
8181
return cloneElement(defaultIcon, {
8282
className: classes,
83+
// @ts-expect-error pass through
8384
'data-test-id': testId,
84-
size,
85+
...(size !== 'tiny' && { size }),
8586
...(rest as IconProps),
8687
});
8788
}

packages/chip/src/Chip.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ const Chip = ({
3535
icon &&
3636
cloneElement(icon, {
3737
key: 'icon',
38-
size,
38+
...(size === 'small' && { size }),
3939
'aria-hidden': true,
40-
className: cx(icon.props.className, styles.icon),
40+
className: cx(icon.props.className, styles.icon, size === 'tiny' && styles.tinyIcon),
4141
});
4242

4343
const classes = cx(

packages/chip/src/styles/Chip.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,8 @@
7575
.icon {
7676
margin-right: 0.125rem;
7777
}
78+
79+
.tinyIcon {
80+
width: 0.625rem;
81+
height: 0.625rem;
82+
}

packages/components/stories/Modal.stories.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ const renderModal = (args: Story['args'], destructive = false) => (
5656
<>
5757
<div slot="header">
5858
{destructive && (
59-
<Icon name="warning" size="medium" fill="var(--lp-color-fill-feedback-warning)" />
59+
<Icon
60+
name="warning"
61+
size="medium"
62+
variant={null}
63+
fill="var(--lp-color-fill-feedback-warning)"
64+
/>
6065
)}
6166
<Heading slot="title">Title</Heading>
6267
<IconButton

packages/filter/src/FilterButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const FilterButton = forwardRef<Ref, FilterButtonProps>((props, ref) => {
9292
aria-label={ariaLabel}
9393
className={styles.clear}
9494
data-test-id="clear-filter-button"
95-
icon={<Icon name="cancel" size="tiny" />}
95+
icon={<Icon name="cancel" size="small" />}
9696
size="small"
9797
onClick={onClear}
9898
/>

packages/icons/README.md

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,57 +17,24 @@ $ npm install @launchpad-ui/icons
1717

1818
### Basic
1919

20-
By default, the `Icon` component expects the package's provided `sprite.svg` file to be located in `/static/sprite.svg` of your app. The `name` prop specifies which icon to render.
20+
The `name` prop specifies which icon to render.
2121

2222
```js
2323
import { Icon } from '@launchpad-ui/icons';
2424

2525
const MyIcon = () => <Icon name="info" />;
2626
```
2727

28-
### Custom static location
29-
30-
A custom path to the sprite can be set via the `IconContext` provider. For example, if importing a static asset returns a resolved URL ([like in Vite](https://vitejs.dev/guide/assets.html#importing-asset-as-url) or [Remix](https://remix.run/docs/en/1.18.1/other-api/asset-imports#asset-url-imports)) you can do the following in your app to load the icons:
31-
32-
```js
33-
import { IconContext } from '@launchpad-ui/icons';
34-
import icons from '@launchpad-ui/icons/sprite.svg';
35-
import { createRoot } from 'react-dom/client';
36-
37-
const domNode = document.getElementById('root');
38-
const root = createRoot(domNode);
39-
40-
root.render(
41-
<IconContext.Provider value={{ path: icons }}>
42-
<App />
43-
</IconContext.Provider>
44-
);
45-
```
46-
4728
### CORS limitation
4829

49-
Unfortunately SVG sprites [cannot be accessed cross-domain](https://oreillymedia.github.io/Using_SVG/extras/ch10-cors.html). If you are hosting the sprite file in a CDN or different domain you will have to fetch the file and inject it into the document to access the icons directly.
30+
Unfortunately SVG sprites [cannot be accessed cross-domain](https://oreillymedia.github.io/Using_SVG/extras/ch10-cors.html). The workaround is to fetch the file and inject it into the document to access the icons directly.
5031

51-
First set the `Icon` context path to an empty string to indicate the symbols are available in the DOM:
32+
Fetch and inject the sprite for `Icon` to render icons correctly:
5233

5334
```js
54-
import { IconContext } from '@launchpad-ui/icons';
55-
import { createRoot } from 'react-dom/client';
35+
import sprite from '@launchpad-ui/icons/sprite.svg';
5636

57-
const domNode = document.getElementById('root');
58-
const root = createRoot(domNode);
59-
60-
root.render(
61-
<IconContext.Provider value={{ path: '' }}>
62-
<App />
63-
</IconContext.Provider>
64-
);
65-
```
66-
67-
Then fetch and inject the sprite for `Icon` to render icons correctly:
68-
69-
```js
70-
fetch('https://cdn.example.com/sprite.svg')
37+
fetch(sprite)
7138
.then(async (response) => response.text())
7239
.then((data) => {
7340
const parser = new DOMParser();
@@ -86,7 +53,7 @@ To minimize latency, you can preload the sprite file accordingly:
8653
```html
8754
<link
8855
rel="preload"
89-
href="https://cdn.example.com/sprite.svg"
56+
href="{assets_location}/sprite.svg"
9057
as="fetch"
9158
type="image/svg+xml"
9259
crossorigin

0 commit comments

Comments
 (0)