Skip to content

Commit

Permalink
Merge pull request #112 from secundant/92-neodxsvg-generate-sizes-and…
Browse files Browse the repository at this point in the history
…-maybe-viewbox-for-every-icon

`@neodx/svg` New `metadata` API
  • Loading branch information
secundant authored Jul 24, 2023
2 parents 0b2f246 + 311c3a7 commit 5e2ed3d
Show file tree
Hide file tree
Showing 38 changed files with 2,138 additions and 594 deletions.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-camels-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@neodx/svg': minor
---

Introduce new `metadata` API
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ We have a some ideas for future development, so stay tuned and feel free to requ

### [@neodx/svg](./libs/svg)

Are you converting every SVG icon to React component with SVGR or something similar? It's so ease to use!
Are you converting every SVG icon to a React component with SVGR or something similar? It's so ease to use!

But wait, did you know that SVG sprites are native approach for icons? It's even easier to use!
But wait; did you know that SVG sprites are a native approach for icons? It's even easier to use!

```typescript jsx
import { Icon } from '@/shared/ui';
Expand Down Expand Up @@ -75,7 +75,7 @@ export default defineConfig({
svg({
root: 'assets',
output: 'public',
definitions: 'src/shared/ui/icon/sprite.h.ts'
metadata: 'src/shared/ui/icon/sprite.gen.ts'
})
]
});
Expand All @@ -94,7 +94,7 @@ export default {
svg({
root: 'assets',
output: 'public',
definitions: 'src/shared/ui/icon/sprite.h.ts'
metadata: 'src/shared/ui/icon/sprite.gen.ts'
})
]
};
Expand All @@ -113,7 +113,7 @@ export default {
svg({
root: 'assets',
output: 'public',
definitions: 'src/shared/ui/icon/sprite.h.ts'
metadata: 'src/shared/ui/icon/sprite.gen.ts'
})
]
};
Expand All @@ -132,7 +132,7 @@ export default {
svg({
root: 'assets',
output: 'public',
definitions: 'src/shared/ui/icon/sprite.h.ts'
metadata: 'src/shared/ui/icon/sprite.gen.ts'
})
]
};
Expand All @@ -144,7 +144,7 @@ export default {
<summary>CLI</summary>

```shell
npx @neodx/svg --group --root assets --output public --definition src/shared/ui/icon/sprite.h.ts
npx @neodx/svg --group --root assets --output public --definition src/shared/ui/icon/sprite.gen.ts
# --root - root folder with SVGs
# --group - group icons by folders (assets/common/add.svg -> common/add, assets/other/cut.svg -> other/cut)
# --output (-o) - output folder for sprites
Expand All @@ -165,7 +165,7 @@ await buildSprites({
root: 'assets',
input: '**/*.svg',
output: 'public',
definition: 'src/shared/ui/icon/sprite.h.ts'
metadata: 'src/shared/ui/icon/sprite.gen.ts'
});
```

Expand All @@ -185,7 +185,7 @@ src/
shared/
ui/
icon/
+ sprite.h.ts // sprite definitions - types, metadata, etc.
+ sprite.gen.ts // sprite definitions - types, metadata, etc.
public/
+ sprite/
+ common.svg
Expand All @@ -208,7 +208,7 @@ Having trouble finding a suitable logging library because they're too heavy, pla

I faced the same issues, which led me to create `@neodx/log`.
It's simple, efficient, and avoids most critical drawbacks.
Furthermore, it's easily replaceable and extensible, making it the great fit for your development needs.
Furthermore, it's easily replaceable and extensible, making it a great fit for your development needs.

<div align="center">
<img alt="Header" src="libs/log/docs/preview-intro.png" width="1458">
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/svg/frameworks-and-bundlers.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default defineConfig({
root: 'assets',
group: true,
output: 'public',
definitions: 'src/shared/ui/icon/sprite.h.ts',
metadata: 'src/shared/ui/icon/sprite.gen.ts',
resetColors: {
replaceUnknown: 'currentColor'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function Icon({ name, className, viewBox, ...props }: IconProps) {
aria-hidden
{...props}
>
<use xlinkHref={`/${spriteName}.svg#${iconName}`} />
<use href={`/${spriteName}.svg#${iconName}`} />
</svg>
);
}
4 changes: 2 additions & 2 deletions examples/svg-magic-with-figma-export/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export default defineConfig({
root: 'assets/icons',
output: 'public',
group: true,
metadata: 'src/shared/ui/icon/sprite.gen.ts',
resetColors: {
replace: ['#6C707E', '#A8ADBD', '#818594']
},
definitions: 'src/shared/ui/icon/sprite.gen.ts'
}
})
]
});
2 changes: 1 addition & 1 deletion examples/svg-next/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const nextConfig = {
svg({
root: 'assets',
output: 'public',
definitions: 'src/shared/ui/icon/sprite.gen.ts',
metadata: 'src/shared/ui/icon/sprite.gen.ts',
resetColors: {
replaceUnknown: 'currentColor'
}
Expand Down
2 changes: 1 addition & 1 deletion examples/svg-next/src/shared/ui/icon/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function Icon({ name, className, viewBox, ...props }: IconProps) {
aria-hidden
{...props}
>
<use xlinkHref={`/sprite.svg#${name}`} />
<use href={`/sprite.svg#${name}`} />
</svg>
);
}
52 changes: 41 additions & 11 deletions examples/svg-vite/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Example of using `@neodx/svg` Vite plugin

> **Warning** In this example was used `experimentalRuntime` option and advanced `fileName` feature, API will be changed in the nearest future.
This example shows how to use `@neodx/svg` as Vite plugin and simple step-by-step setup for React.

In the addition you can see how to use multicolored icons with TailwindCSS and CSS variable (it's not very pleasant, but it works 🌝).
In addition, you can see how to use multicolored icons with TailwindCSS and CSS variable
(it's not very pleasant, but it works 🌝).

![result](./docs/result.png)

Expand Down Expand Up @@ -37,7 +36,14 @@ export default defineConfig(({ command }) => ({
root: 'assets', // Root folder for SVG files, all source paths will be relative to this folder
group: true, // Group SVG files by folder
output: 'public', // Output folder for generated files
definitions: 'src/shared/ui/icon/sprite.gen.ts', // Output file for generated TypeScript definitions
metadata: {
path: 'src/shared/ui/icon/sprite.gen.ts', // Output file for generated TypeScript definitions
runtime: {
// Generate additional runtime information
size: true,
viewBox: true
}
},
resetColors: {
replace: ['#000', '#eee', '#6C707E'], // Resets all known colors to `currentColor`
replaceUnknown: 'var(--icon-color)' // Replaces unknown colors with custom CSS variable
Expand All @@ -47,14 +53,14 @@ export default defineConfig(({ command }) => ({
}));
```

## Create Icon component and describe basic styles
## Create an Icon component and describe basic styles

[shared/ui/icon/icon.tsx](./src/shared/ui/icon/icon.tsx):

```tsx
import clsx from 'clsx';
import type { SVGProps } from 'react';
import type { SpritesMap } from './sprite.gen';
import { SPRITES_META, type SpritesMap } from './sprite.gen';

// Merging all icons as `SPRITE_NAME/ICON_NAME`
export type SpriteKey = {
Expand All @@ -66,18 +72,30 @@ export interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'name' | 'type'
}

export function Icon({ name, className, viewBox, ...props }: IconProps) {
const [spriteName, iconName] = name.split('/');
const [spriteName, iconName] = name.split('/') as [
keyof SpritesMap,
SpritesMap[keyof SpritesMap]
];
const { filePath, items } = SPRITES_META[spriteName];
// @ts-expect-error mixed structures are confusing TS
const { viewBox, width, height } = items[iconName];
const rect = width === height ? 'xy' : width > height ? 'x' : 'y';

return (
<svg
// We recommend to use specific component class for avoid collisions with other styles and simple override it
className={clsx('icon', className)}
/**
* this prop is used by the "icon" class to set the icon's scaled size
* @see https://github.com/secundant/neodx/issues/92 - Issue with original motivation
*/
data-icon-aspect-ratio={rect}
viewBox={viewBox}
focusable="false"
aria-hidden
{...props}
>
<use xlinkHref={`/${spriteName}.svg#${iconName}`} />
<use href={`/${spriteName}.svg#${iconName}`} />
</svg>
);
}
Expand All @@ -100,7 +118,19 @@ export function Icon({ name, className, viewBox, ...props }: IconProps) {
@layer components {
/* Our base class for all icons */
.icon {
@apply select-none fill-current w-[1em] h-[1em] inline-block text-inherit box-content;
@apply select-none fill-current inline-block text-inherit box-content;
}

.icon[data-icon-aspect-ratio='xy'] {
@apply w-[1em] h-[1em];
}

.icon[data-icon-aspect-ratio='x'] {
@apply w-[1em];
}

.icon[data-icon-aspect-ratio='y'] {
@apply h-[1em];
}
}
```
Expand All @@ -125,8 +155,8 @@ Under this example I want to cover all planned features of `@neodx/svg`, you can
- [x] Colors: replace known to CSS variables
- [x] Colors: exclude specific icons
- [x] Colors: exclude specific colors
- [ ] Non-standard sizes: generate `viewBox` and `width`/`height` attributes
- [ ] Non-standard sizes: example of enhanced `Icon` component
- [x] Non-standard sizes: generate `viewBox` and `width`/`height` attributes
- [x] Non-standard sizes: example of enhanced `Icon` component
- [ ] Inline SVG: auto-detection of internal references
- [ ] Inline SVG: injection into HTML
- [ ] Remove unnecessary attributes
Expand Down
14 changes: 10 additions & 4 deletions examples/svg-vite/src/shared/ui/icon/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,30 @@ export interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'name' | 'type'
name: IconName;
}

export function Icon({ name, className, viewBox: viewBoxFromProps, ...props }: IconProps) {
export function Icon({ name, className, ...props }: IconProps) {
const [spriteName, iconName] = name.split('/') as [
keyof SpritesMap,
SpritesMap[keyof SpritesMap]
];
const { filePath, items } = SPRITES_META[spriteName];
// TODO Fix types
const { viewBox } = (items as any)[iconName] as { viewBox: string };
const { viewBox, width, height } = (items as any)[iconName] as any;
const rect = width === height ? 'xy' : width > height ? 'x' : 'y';

return (
<svg
className={clsx('icon', className)}
viewBox={viewBoxFromProps ?? viewBox}
/**
* this prop is used by the "icon" class to set the icon's scaled size
* @see https://github.com/secundant/neodx/issues/92
*/
data-icon-aspect-ratio={rect}
viewBox={viewBox}
focusable="false"
aria-hidden
{...props}
>
<use xlinkHref={`/${filePath}#${iconName}`} />
<use href={`/${filePath}#${iconName}`} />
</svg>
);
}
Loading

0 comments on commit 5e2ed3d

Please sign in to comment.