feat(icon-component): Creating icons with iconNodes (#1997)

* Add useIconComponent, lucide-react

* Add concept useIconComponent

* add useIconComponents to packages

* Add icon component

* Add icon component

* Add tests for react packages

* Reset changes in icons

* Add types

* Add support for Icon components in Lucide Vue Next

* update tests

* Update tests

* Enable Svelte component

* Fix lucide-react-native tests

* Update Solid package

* update snapshots

* Add docs

* add docs

* Update tests

* Formatting

* Formatting

* Update package lock

* Remove `useIconComponent`

* Update guides

* Update exports preact and solid package

* Formatting

* Format createIcons.ts

* Add lucide lab repo link in docs
This commit is contained in:
Eric Fennis
2024-04-26 17:59:04 +02:00
committed by GitHub
parent 65deefa53c
commit e50582e93e
77 changed files with 1705 additions and 428 deletions

View File

@@ -2,6 +2,10 @@ pnpm-lock.yaml
# docs examples # docs examples
docs/**/examples/ docs/**/examples/
docs/.vitepress/.temp
docs/.vitepress/cache
docs/.vitepress/data
docs/.nitro
# lucide-angular # lucide-angular
packages/lucide-angular/.angular/cache packages/lucide-angular/.angular/cache

View File

@@ -115,3 +115,20 @@ import { icons } from 'lucide-angular';
LucideAngularModule.pick(icons) LucideAngularModule.pick(icons)
``` ```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used in the same way as the official icons.
```js
import { LucideAngularModule } from 'lucide-angular';
import { burger } from '@lucide/lab';
@NgModule({
imports: [
LucideAngularModule.pick({ burger })
]
})
export class AppModule { }
```

View File

@@ -67,6 +67,26 @@ const App = () => {
> SVG attributes in Preact aren't transformed, so if you want to change for example the `stroke-linejoin` you need to pass it in kebabcase. Basically how the SVG spec want you to write it. See this topic in the [Preact documentation](https://preactjs.com/guide/v10/differences-to-react/#svg-inside-jsx). > SVG attributes in Preact aren't transformed, so if you want to change for example the `stroke-linejoin` you need to pass it in kebabcase. Basically how the SVG spec want you to write it. See this topic in the [Preact documentation](https://preactjs.com/guide/v10/differences-to-react/#svg-inside-jsx).
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```jsx
import { Icon } from 'lucide-preact';
import { burger } from '@lucide/lab';
const App = () => (
<Icon iconNode={burger} />
);
```
## One generic icon component ## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended. It is possible to create one generic icon component to load icons, but it is not recommended.

View File

@@ -61,6 +61,26 @@ const App = () => {
}; };
``` ```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```jsx
import { Icon } from 'lucide-react-native';
import { burger } from '@lucide/lab';
const App = () => (
<Icon iconNode={burger} />
);
```
## One generic icon component ## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended. It is possible to create one generic icon component to load icons, but it is not recommended.

View File

@@ -61,6 +61,26 @@ const App = () => {
}; };
``` ```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```jsx
import { Icon } from 'lucide-react';
import { burger } from '@lucide/lab';
const App = () => (
<Icon iconNode={burger} />
);
```
## One generic icon component ## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended. It is possible to create one generic icon component to load icons, but it is not recommended.

View File

@@ -61,6 +61,26 @@ const App = () => {
}; };
``` ```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like the regular Lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```jsx
import { Icon } from 'lucide-solid';
import { burger, sausage } from '@lucide/lab';
const App = () => (
<Icon iconNode={sausage} color="red"/>
);
```
## One generic icon component ## One generic icon component
It is possible to create one generic icon component to load icons. It's not recommended. It is possible to create one generic icon component to load icons. It's not recommended.

View File

@@ -166,6 +166,27 @@ The package includes type definitions for all icons. This is useful if you want
For more details about typing the `svelte:component` directive, see the [Svelte documentation](https://svelte.dev/docs/typescript#types-componenttype). For more details about typing the `svelte:component` directive, see the [Svelte documentation](https://svelte.dev/docs/typescript#types-componenttype).
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like the regular Lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```svelte
<script>
import { Icon } from 'lucide-svelte';
import { burger, sausage } from '@lucide/lab';
</script>
<Icon iconNode={burger} />
<Icon iconNode={sausage} color="red"/>
```
## One generic icon component ## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended. It is possible to create one generic icon component to load icons, but it is not recommended.

View File

@@ -37,16 +37,16 @@ Each icon can be imported as a Vue component, which renders an inline SVG Elemen
You can pass additional props to adjust the icon. You can pass additional props to adjust the icon.
```vue ```vue
<script setup>
import { Camera } from 'lucide-vue-next';
</script>
<template> <template>
<Camera <Camera
color="red" color="red"
:size="32" :size="32"
/> />
</template> </template>
<script setup>
import { Camera } from 'lucide-vue-next';
</script>
``` ```
## Props ## Props
@@ -69,6 +69,28 @@ To customize the appearance of an icon, you can pass custom properties as props
</template> </template>
``` ```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```vue
<script setup>
import { Icon } from 'lucide-vue-next';
import { burger } from '@lucide/lab';
</script>
<template>
<Icon :iconNode={burger} />
</template>
```
## One generic icon component ## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended. It is possible to create one generic icon component to load icons, but it is not recommended.

View File

@@ -130,3 +130,18 @@ menuIcon.classList.add('my-icon-class');
const myApp = document.getElementById('app'); const myApp = document.getElementById('app');
myApp.appendChild(menuIcon); myApp.appendChild(menuIcon);
``` ```
### With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used in the same way as the official icons.
```js
import { burger } from '@lucide/lab';
createIcons({
icons: {
burger
}
});
```

View File

@@ -0,0 +1,50 @@
import { h, toChildArray } from 'preact';
import defaultAttributes from './defaultAttributes';
import type { IconNode, LucideProps } from './types';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
}
/**
* Lucide icon component
*
* @component Icon
* @param {object} props
* @param {string} props.color - The color of the icon
* @param {number} props.size - The size of the icon
* @param {number} props.strokeWidth - The stroke width of the icon
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
* @param {string} props.class - The class name of the icon
* @param {IconNode} props.children - The children of the icon
* @param {IconNode} props.iconNode - The icon node of the icon
*
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const Icon = ({
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
children,
iconNode,
class: classes = '',
...rest
}: IconComponentProps) =>
h(
'svg',
{
...defaultAttributes,
width: String(size),
height: size,
stroke: color,
['stroke-width' as 'strokeWidth']: absoluteStrokeWidth
? (Number(strokeWidth) * 24) / Number(size)
: strokeWidth,
class: ['lucide', classes].join(' '),
...rest,
},
[...iconNode.map(([tag, attrs]) => h(tag, attrs)), ...toChildArray(children)],
);
export default Icon;

View File

@@ -1,17 +1,7 @@
import { type FunctionComponent, h, type JSX, toChildArray } from 'preact'; import { h, type JSX } from 'preact';
import defaultAttributes from './defaultAttributes'; import { mergeClasses, toKebabCase } from '@lucide/shared';
import { toKebabCase } from '@lucide/shared'; import Icon from './Icon';
import type { IconNode, LucideIcon, LucideProps } from './types';
export type IconNode = [elementName: keyof JSX.IntrinsicElements, attrs: Record<string, string>][];
export interface LucideProps extends Partial<Omit<JSX.SVGAttributes, 'ref' | 'size'>> {
color?: string;
size?: string | number;
strokeWidth?: string | number;
absoluteStrokeWidth?: boolean;
}
export type LucideIcon = FunctionComponent<LucideProps>;
/** /**
* Create a Lucide icon component * Create a Lucide icon component
@@ -20,29 +10,18 @@ export type LucideIcon = FunctionComponent<LucideProps>;
* @returns {FunctionComponent} LucideIcon * @returns {FunctionComponent} LucideIcon
*/ */
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => { const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
const Component = ({ const Component = ({ class: classes = '', children, ...props }: LucideProps) =>
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
children,
class: classes = '',
...rest
}: LucideProps) =>
h( h(
'svg', Icon,
{ {
...defaultAttributes, ...props,
width: String(size), iconNode,
height: size, class: mergeClasses<string | JSX.SignalLike<string | undefined>>(
stroke: color, `lucide-${toKebabCase(iconName)}`,
['stroke-width' as 'strokeWidth']: absoluteStrokeWidth classes,
? (Number(strokeWidth) * 24) / Number(size) ),
: strokeWidth,
class: ['lucide', `lucide-${toKebabCase(iconName)}`, classes].join(' '),
...rest,
}, },
[...iconNode.map(([tag, attrs]) => h(tag, attrs)), ...toChildArray(children)], children,
); );
Component.displayName = `${iconName}`; Component.displayName = `${iconName}`;

View File

@@ -1,4 +1,7 @@
export * from './icons'; export * from './icons';
export * as icons from './icons'; export * as icons from './icons';
export * from './aliases'; export * from './aliases';
export * from './types';
export { default as createLucideIcon } from './createLucideIcon'; export { default as createLucideIcon } from './createLucideIcon';
export { default as Icon } from './Icon';

View File

@@ -0,0 +1,12 @@
import { type FunctionComponent, type JSX } from 'preact';
export type IconNode = [elementName: keyof JSX.IntrinsicElements, attrs: Record<string, string>][];
export interface LucideProps extends Partial<Omit<JSX.SVGAttributes, 'ref' | 'size'>> {
color?: string;
size?: string | number;
strokeWidth?: string | number;
absoluteStrokeWidth?: boolean;
}
export type LucideIcon = FunctionComponent<LucideProps>;

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/preact';
import { airVent } from './testIconNodes';
import { Icon } from '../src/lucide-preact';
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
class="lucide "
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
`;

View File

@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using createLucideIcon > should create a component from an iconNode 1`] = `
<svg
class="lucide lucide-air-vent"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
`;

View File

@@ -10,8 +10,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
stroke-width="4" stroke-width="4"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="lucide lucide-grid3x3 " class="lucide lucide-grid3x3"
data-testid="grid-icon"
> >
<rect width="18" <rect width="18"
height="18" height="18"
@@ -41,8 +40,7 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
stroke-width="1" stroke-width="1"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="lucide lucide-grid3x3 " class="lucide lucide-grid3x3"
data-testid="grid-icon"
> >
<rect width="18" <rect width="18"
height="18" height="18"
@@ -72,7 +70,7 @@ exports[`Using lucide icon components > should render an component 1`] = `
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="lucide lucide-grid3x3 " class="lucide lucide-grid3x3"
> >
<rect width="18" <rect width="18"
height="18" height="18"

View File

@@ -0,0 +1,15 @@
import { describe, it, expect } from 'vitest';
import { createLucideIcon } from '../src/lucide-preact';
import { airVent } from './testIconNodes';
import { render } from '@testing-library/preact';
describe('Using createLucideIcon', () => {
it('should create a component from an iconNode', () => {
const AirVent = createLucideIcon('AirVent', airVent);
const { container } = render(<AirVent />);
expect(container.firstChild).toMatchSnapshot();
expect(container.firstChild).toBeDefined();
});
});

View File

@@ -1,6 +1,7 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { render, cleanup } from '@testing-library/preact'; import { render, cleanup } from '@testing-library/preact';
import { Pen, Edit2, Grid, Droplet } from '../src/lucide-preact'; import { Pen, Edit2, Grid, Droplet } from '../src/lucide-preact';
import defaultAttributes from '../src/defaultAttributes';
type AttributesAssertion = { attributes: Record<string, { value: string }> }; type AttributesAssertion = { attributes: Record<string, { value: string }> };
@@ -11,30 +12,43 @@ describe('Using lucide icon components', () => {
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
}); });
it('should render the icon with the default attributes', () => {
const { container } = render(<Grid />);
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('xmlns', defaultAttributes.xmlns);
expect(SVGElement).toHaveAttribute('width', String(defaultAttributes.width));
expect(SVGElement).toHaveAttribute('height', String(defaultAttributes.height));
expect(SVGElement).toHaveAttribute('viewBox', defaultAttributes.viewBox);
expect(SVGElement).toHaveAttribute('fill', defaultAttributes.fill);
expect(SVGElement).toHaveAttribute('stroke', defaultAttributes.stroke);
expect(SVGElement).toHaveAttribute('stroke-width', String(defaultAttributes['stroke-width']));
expect(SVGElement).toHaveAttribute('stroke-linecap', defaultAttributes['stroke-linecap']);
expect(SVGElement).toHaveAttribute('stroke-linejoin', defaultAttributes['stroke-linejoin']);
});
it('should adjust the size, stroke color and stroke width', () => { it('should adjust the size, stroke color and stroke width', () => {
const testId = 'grid-icon'; const { container } = render(
const { container, getByTestId } = render(
<Grid <Grid
data-testid={testId}
size={48} size={48}
stroke="red" stroke="red"
strokeWidth={4} strokeWidth={4}
/>, />,
); );
const { attributes } = getByTestId(testId) as unknown as AttributesAssertion; const SVGElement = container.firstElementChild;
expect(attributes.stroke.value).toBe('red');
expect(attributes.width.value).toBe('48'); expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(attributes.height.value).toBe('48'); expect(SVGElement).toHaveAttribute('width', '48');
expect(attributes['stroke-width'].value).toBe('4'); expect(SVGElement).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '4');
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
}); });
it('should render the alias icon', () => { it('should render the alias icon', () => {
const testId = 'pen-icon';
const { container } = render( const { container } = render(
<Pen <Pen
data-testid={testId}
size={48} size={48}
stroke="red" stroke="red"
strokeWidth={4} strokeWidth={4}
@@ -47,7 +61,6 @@ describe('Using lucide icon components', () => {
const { container: Edit2Container } = render( const { container: Edit2Container } = render(
<Edit2 <Edit2
data-testid={testId}
size={48} size={48}
stroke="red" stroke="red"
strokeWidth={4} strokeWidth={4}
@@ -58,22 +71,21 @@ describe('Using lucide icon components', () => {
}); });
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => { it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'grid-icon'; const { container } = render(
const { container, getByTestId } = render(
<Grid <Grid
data-testid={testId}
size={48} size={48}
stroke="red" stroke="red"
absoluteStrokeWidth absoluteStrokeWidth
/>, />,
); );
const { attributes } = getByTestId(testId) as unknown as AttributesAssertion; const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(SVGElement).toHaveAttribute('width', '48');
expect(SVGElement).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '1');
expect(attributes.stroke.value).toBe('red');
expect(attributes.width.value).toBe('48');
expect(attributes.height.value).toBe('48');
expect(attributes['stroke-width'].value).toBe('1');
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
}); });

View File

@@ -1,5 +1,10 @@
import { expect } from 'vitest'; import { expect, afterEach } from 'vitest';
import '@testing-library/jest-dom'; import { cleanup } from '@testing-library/preact';
import '@testing-library/jest-dom/vitest';
import htmlSerializer from 'jest-serializer-html'; import htmlSerializer from 'jest-serializer-html';
expect.addSnapshotSerializer(htmlSerializer); expect.addSnapshotSerializer(htmlSerializer);
afterEach(() => {
cleanup();
});

View File

@@ -0,0 +1,22 @@
import { IconNode } from '../src/createLucideIcon';
export const airVent: IconNode = [
[
'path',
{
d: 'M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2',
key: 'larmp2',
},
],
['path', { d: 'M6 8h12', key: '6g4wlu' }],
['path', { d: 'M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12', key: '1bo8pg' }],
['path', { d: 'M6.6 15.6A2 2 0 1 0 10 17v-5', key: 't9h90c' }],
];
export const coffee: IconNode = [
['path', { d: 'M17 8h1a4 4 0 1 1 0 8h-1', key: 'jx4kbh' }],
['path', { d: 'M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z', key: '1bxrl0' }],
['line', { x1: '6', x2: '6', y1: '2', y2: '4', key: '1cr9l3' }],
['line', { x1: '10', x2: '10', y1: '2', y2: '4', key: '170wym' }],
['line', { x1: '14', x2: '14', y1: '2', y2: '4', key: '1c5f70' }],
];

View File

@@ -45,6 +45,7 @@
"devDependencies": { "devDependencies": {
"@lucide/rollup-plugins": "workspace:*", "@lucide/rollup-plugins": "workspace:*",
"@lucide/build-icons": "workspace:*", "@lucide/build-icons": "workspace:*",
"@lucide/shared": "workspace:*",
"@testing-library/jest-dom": "^6.1.6", "@testing-library/jest-dom": "^6.1.6",
"@testing-library/react": "^14.1.2", "@testing-library/react": "^14.1.2",
"@types/prop-types": "^15.7.5", "@types/prop-types": "^15.7.5",

View File

@@ -0,0 +1,69 @@
import { createElement, forwardRef, type FunctionComponent } from 'react';
import * as NativeSvg from 'react-native-svg';
import defaultAttributes, { childDefaultAttributes } from './defaultAttributes';
import { IconNode, LucideProps } from './types';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
}
/**
* Lucide icon component
*
* @component Icon
* @param {object} props
* @param {string} props.color - The color of the icon
* @param {number} props.size - The size of the icon
* @param {number} props.strokeWidth - The stroke width of the icon
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
* @param {string} props.className - The class name of the icon
* @param {IconNode} props.children - The children of the icon
* @param {IconNode} props.iconNode - The icon node of the icon
*
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
(
{
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
children,
iconNode,
...rest
},
ref,
) => {
const customAttrs = {
stroke: color,
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
...rest,
};
return createElement(
NativeSvg.Svg as unknown as string,
{
ref,
...defaultAttributes,
width: size,
height: size,
...customAttrs,
},
[
...iconNode.map(([tag, attrs]) => {
const upperCasedTag = (tag.charAt(0).toUpperCase() +
tag.slice(1)) as keyof typeof NativeSvg;
// duplicating the attributes here because generating the OTA update bundles don't inherit the SVG properties from parent (codepush, expo-updates)
return createElement(
NativeSvg[upperCasedTag] as FunctionComponent<LucideProps>,
{ ...childDefaultAttributes, ...customAttrs, ...attrs } as LucideProps,
);
}),
...((Array.isArray(children) ? children : [children]) || []),
],
);
},
);
export default Icon;

View File

@@ -7,17 +7,7 @@ import {
} from 'react'; } from 'react';
import * as NativeSvg from 'react-native-svg'; import * as NativeSvg from 'react-native-svg';
import defaultAttributes, { childDefaultAttributes } from './defaultAttributes'; import defaultAttributes, { childDefaultAttributes } from './defaultAttributes';
import type { SvgProps } from 'react-native-svg'; import { IconNode, LucideIcon, LucideProps } from './types';
export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][];
export interface LucideProps extends SvgProps {
size?: string | number;
absoluteStrokeWidth?: boolean;
'data-testid'?: string;
}
export type LucideIcon = ForwardRefExoticComponent<LucideProps>;
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => { const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
const Component = forwardRef( const Component = forwardRef(

View File

@@ -1,9 +1,7 @@
export * from './icons'; export * from './icons';
export * as icons from './icons'; export * as icons from './icons';
export * from './aliases'; export * from './aliases';
export { export * from './types';
default as createLucideIcon,
type IconNode, export { default as createLucideIcon } from './createLucideIcon';
type LucideProps, export { default as Icon } from './Icon';
type LucideIcon,
} from './createLucideIcon';

View File

@@ -0,0 +1,12 @@
import type { ForwardRefExoticComponent, ReactSVG } from 'react';
import type { SvgProps } from 'react-native-svg';
export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][];
export interface LucideProps extends SvgProps {
size?: string | number;
absoluteStrokeWidth?: boolean;
'data-testid'?: string;
}
export type LucideIcon = ForwardRefExoticComponent<LucideProps>;

View File

@@ -0,0 +1,35 @@
import { describe, it, expect, vi } from 'vitest';
import { render } from '@testing-library/react';
import { airVent } from './testIconNodes';
import { Icon } from '../src/lucide-react-native';
vi.mock('react-native-svg');
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,48 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
/>
<path
d="M6 8h12"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
/>
</svg>
`;

View File

@@ -10,7 +10,6 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
stroke-width="4" stroke-width="4"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
data-testid="grid-icon"
> >
<rect fill="none" <rect fill="none"
stroke="red" stroke="red"
@@ -128,7 +127,6 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
stroke-width="1" stroke-width="1"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
data-testid="grid-icon"
> >
<rect fill="none" <rect fill="none"
stroke="red" stroke="red"

View File

@@ -14,21 +14,20 @@ describe('Using lucide icon components', () => {
}); });
it('should adjust the size, stroke color and stroke width', () => { it('should adjust the size, stroke color and stroke width', () => {
const testId = 'grid-icon'; const { container } = render(
const { container, getByTestId } = render(
<Grid <Grid
data-testid={testId}
size={48} size={48}
stroke="red" stroke="red"
strokeWidth={4} strokeWidth={4}
/>, />,
); );
const { attributes } = getByTestId(testId); const SVGElement = container.firstElementChild;
expect((attributes as unknown as Attributes).stroke.value).toBe('red');
expect((attributes as unknown as Attributes).width.value).toBe('48'); expect(SVGElement).toHaveAttribute('stroke', 'red');
expect((attributes as unknown as Attributes).height.value).toBe('48'); expect(SVGElement).toHaveAttribute('width', '48');
expect((attributes as unknown as Attributes)['stroke-width'].value).toBe('4'); expect(SVGElement).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '4');
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
}); });
@@ -61,23 +60,20 @@ describe('Using lucide icon components', () => {
}); });
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => { it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'grid-icon'; const { container } = render(
const { container, getByTestId } = render(
<Grid <Grid
data-testid={testId}
size={48} size={48}
stroke="red" stroke="red"
absoluteStrokeWidth absoluteStrokeWidth
/>, />,
); );
const { attributes } = getByTestId(testId) as unknown as { const SVGElement = container.firstElementChild;
attributes: Record<string, { value: string }>;
}; expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(attributes.stroke.value).toBe('red'); expect(SVGElement).toHaveAttribute('width', '48');
expect(attributes.width.value).toBe('48'); expect(SVGElement).toHaveAttribute('height', '48');
expect(attributes.height.value).toBe('48'); expect(SVGElement).toHaveAttribute('stroke-width', '1');
expect(attributes['stroke-width'].value).toBe('1');
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
}); });
@@ -91,8 +87,8 @@ describe('Using lucide icon components', () => {
<Grid data-testid={childId} /> <Grid data-testid={childId} />
</Grid>, </Grid>,
); );
const { children } = getByTestId(testId) as unknown as { children: HTMLCollection }; const { children } = container.firstElementChild ?? {};
const lastChild = children[children.length - 1]; const lastChild = children?.[children.length - 1];
expect(lastChild).toEqual(getByTestId(childId)); expect(lastChild).toEqual(getByTestId(childId));
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();

View File

@@ -0,0 +1,22 @@
import { IconNode } from '../src/types';
export const airVent: IconNode = [
[
'path',
{
d: 'M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2',
key: 'larmp2',
},
],
['path', { d: 'M6 8h12', key: '6g4wlu' }],
['path', { d: 'M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12', key: '1bo8pg' }],
['path', { d: 'M6.6 15.6A2 2 0 1 0 10 17v-5', key: 't9h90c' }],
];
export const coffee: IconNode = [
['path', { d: 'M17 8h1a4 4 0 1 1 0 8h-1', key: 'jx4kbh' }],
['path', { d: 'M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z', key: '1bxrl0' }],
['line', { x1: '6', x2: '6', y1: '2', y2: '4', key: '1cr9l3' }],
['line', { x1: '10', x2: '10', y1: '2', y2: '4', key: '170wym' }],
['line', { x1: '14', x2: '14', y1: '2', y2: '4', key: '1c5f70' }],
];

View File

@@ -0,0 +1,59 @@
import { createElement, forwardRef } from 'react';
import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types';
import { mergeClasses } from '@lucide/shared';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
}
/**
* Lucide icon component
*
* @component Icon
* @param {object} props
* @param {string} props.color - The color of the icon
* @param {number} props.size - The size of the icon
* @param {number} props.strokeWidth - The stroke width of the icon
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
* @param {string} props.className - The class name of the icon
* @param {IconNode} props.children - The children of the icon
* @param {IconNode} props.iconNode - The icon node of the icon
*
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
(
{
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
className = '',
children,
iconNode,
...rest
},
ref,
) => {
return createElement(
'svg',
{
ref,
...defaultAttributes,
width: size,
height: size,
stroke: color,
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
className: mergeClasses('lucide', className),
...rest,
},
[
...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
...(Array.isArray(children) ? children : [children]),
],
);
},
);
export default Icon;

View File

@@ -1,60 +1,22 @@
import { import { createElement, forwardRef } from 'react';
forwardRef, import { mergeClasses, toKebabCase } from '@lucide/shared';
createElement, import { IconNode, LucideProps } from './types';
ReactSVG, import Icon from './Icon';
SVGProps,
ForwardRefExoticComponent,
RefAttributes,
} from 'react';
import defaultAttributes from './defaultAttributes';
import { toKebabCase } from '@lucide/shared';
export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][]; /**
* Create a Lucide icon component
export type SVGAttributes = Partial<SVGProps<SVGSVGElement>>; * @param {string} iconName
type ComponentAttributes = RefAttributes<SVGSVGElement> & SVGAttributes; * @param {array} iconNode
* @returns {ForwardRefExoticComponent} LucideIcon
export interface LucideProps extends ComponentAttributes { */
size?: string | number; const createLucideIcon = (iconName: string, iconNode: IconNode) => {
absoluteStrokeWidth?: boolean; const Component = forwardRef<SVGSVGElement, LucideProps>(({ className, ...props }, ref) =>
} createElement(Icon, {
export type LucideIcon = ForwardRefExoticComponent<LucideProps>;
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
const Component = forwardRef<SVGSVGElement, LucideProps>(
(
{
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
className = '',
children,
...rest
},
ref, ref,
) => { iconNode,
return createElement( className: mergeClasses(`lucide-${toKebabCase(iconName)}`, className),
'svg', ...props,
{ }),
ref,
...defaultAttributes,
width: size,
height: size,
stroke: color,
strokeWidth: absoluteStrokeWidth
? (Number(strokeWidth) * 24) / Number(size)
: strokeWidth,
className: ['lucide', `lucide-${toKebabCase(iconName)}`, className].join(' '),
...rest,
},
[
...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
...(Array.isArray(children) ? children : [children]),
],
);
},
); );
Component.displayName = `${iconName}`; Component.displayName = `${iconName}`;

View File

@@ -1,9 +1,7 @@
export * from './icons'; export * from './icons';
export * as icons from './icons'; export * as icons from './icons';
export * from './aliases'; export * from './aliases';
export { export * from './types';
default as createLucideIcon,
type IconNode, export { default as createLucideIcon } from './createLucideIcon';
type LucideProps, export { default as Icon } from './Icon';
type LucideIcon,
} from './createLucideIcon';

View File

@@ -0,0 +1,15 @@
import { ReactSVG, SVGProps, ForwardRefExoticComponent, RefAttributes } from 'react';
export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][];
export type SVGAttributes = Partial<SVGProps<SVGSVGElement>>;
type ElementAttributes = RefAttributes<SVGSVGElement> & SVGAttributes;
export interface LucideProps extends ElementAttributes {
size?: string | number;
absoluteStrokeWidth?: boolean;
}
export type LucideIcon = ForwardRefExoticComponent<
Omit<LucideProps, 'ref'> & RefAttributes<SVGSVGElement>
>;

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/react';
import { airVent } from './testIconNodes';
import { Icon } from '../src/lucide-react';
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
class="lucide"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
`;

View File

@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using createLucideIcon > should create a component from an iconNode 1`] = `
<svg
class="lucide lucide-air-vent"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
`;

View File

@@ -0,0 +1,36 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using dynamicImports > should render icons dynamically by using the dynamicIconImports module 1`] = `
<svg xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
viewbox="0 0 24 24"
fill="none"
stroke="red"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-smile"
aria-label="smile"
>
<circle cx="12"
cy="12"
r="10"
>
</circle>
<path d="M8 14s1.5 2 4 2 4-2 4-2">
</path>
<line x1="9"
x2="9.01"
y1="9"
y2="9"
>
</line>
<line x1="15"
x2="15.01"
y1="9"
y2="9"
>
</line>
</svg>
`;

View File

@@ -10,8 +10,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
stroke-width="4" stroke-width="4"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="lucide lucide-grid3x3 " class="lucide lucide-grid3x3"
data-testid="grid-icon"
> >
<rect width="18" <rect width="18"
height="18" height="18"
@@ -41,8 +40,7 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
stroke-width="1" stroke-width="1"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="lucide lucide-grid3x3 " class="lucide lucide-grid3x3"
data-testid="grid-icon"
> >
<rect width="18" <rect width="18"
height="18" height="18"
@@ -72,7 +70,7 @@ exports[`Using lucide icon components > should render an component 1`] = `
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
class="lucide lucide-grid3x3 " class="lucide lucide-grid3x3"
> >
<rect width="18" <rect width="18"
height="18" height="18"
@@ -91,38 +89,3 @@ exports[`Using lucide icon components > should render an component 1`] = `
</path> </path>
</svg> </svg>
`; `;
exports[`Using lucide icon components > should render icons dynamically by using the dynamicIconImports module 1`] = `
<svg xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
viewbox="0 0 24 24"
fill="none"
stroke="red"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-smile "
aria-label="smile"
>
<circle cx="12"
cy="12"
r="10"
>
</circle>
<path d="M8 14s1.5 2 4 2 4-2 4-2">
</path>
<line x1="9"
x2="9.01"
y1="9"
y2="9"
>
</line>
<line x1="15"
x2="15.01"
y1="9"
y2="9"
>
</line>
</svg>
`;

View File

@@ -0,0 +1,15 @@
import { describe, it, expect } from 'vitest';
import { createLucideIcon } from '../src/lucide-react';
import { airVent } from './testIconNodes';
import { render } from '@testing-library/react';
describe('Using createLucideIcon', () => {
it('should create a component from an iconNode', () => {
const AirVent = createLucideIcon('AirVent', airVent);
const { container } = render(<AirVent />);
expect(container.firstChild).toMatchSnapshot();
expect(container.firstChild).toBeDefined();
});
});

View File

@@ -0,0 +1,38 @@
import { describe, it, expect } from 'vitest';
import { Suspense, lazy } from 'react';
import { render, waitFor } from '@testing-library/react';
import dynamicIconImports from '../src/dynamicIconImports';
import { LucideProps } from '../src/types';
describe('Using dynamicImports', () => {
it('should render icons dynamically by using the dynamicIconImports module', async () => {
interface IconProps extends Omit<LucideProps, 'ref'> {
name: keyof typeof dynamicIconImports;
}
const Icon = ({ name, ...props }: IconProps) => {
const LucideIcon = lazy(dynamicIconImports[name]);
return (
<Suspense fallback={null}>
<LucideIcon {...props} />
</Suspense>
);
};
const { container, getByLabelText } = render(
<Icon
aria-label="smile"
name="smile"
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
await waitFor(() => getByLabelText('smile'));
expect(container.innerHTML).toMatchSnapshot();
});
});

View File

@@ -1,8 +1,7 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { render, cleanup, waitFor } from '@testing-library/react'; import { render, cleanup } from '@testing-library/react';
import { Pen, Edit2, Grid, LucideProps, Droplet } from '../src/lucide-react'; import { Pen, Edit2, Grid, Droplet } from '../src/lucide-react';
import { Suspense, lazy } from 'react'; import defaultAttributes from '../src/defaultAttributes';
import dynamicIconImports from '../src/dynamicIconImports';
describe('Using lucide icon components', () => { describe('Using lucide icon components', () => {
it('should render an component', () => { it('should render an component', () => {
@@ -11,24 +10,37 @@ describe('Using lucide icon components', () => {
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
}); });
it('should render the icon with default attributes', () => {
const { container } = render(<Grid />);
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('xmlns', defaultAttributes.xmlns);
expect(SVGElement).toHaveAttribute('width', String(defaultAttributes.width));
expect(SVGElement).toHaveAttribute('height', String(defaultAttributes.height));
expect(SVGElement).toHaveAttribute('viewBox', defaultAttributes.viewBox);
expect(SVGElement).toHaveAttribute('fill', defaultAttributes.fill);
expect(SVGElement).toHaveAttribute('stroke', defaultAttributes.stroke);
expect(SVGElement).toHaveAttribute('stroke-width', String(defaultAttributes.strokeWidth));
expect(SVGElement).toHaveAttribute('stroke-linecap', defaultAttributes.strokeLinecap);
expect(SVGElement).toHaveAttribute('stroke-linejoin', defaultAttributes.strokeLinejoin);
});
it('should adjust the size, stroke color and stroke width', () => { it('should adjust the size, stroke color and stroke width', () => {
const testId = 'grid-icon'; const { container } = render(
const { container, getByTestId } = render(
<Grid <Grid
data-testid={testId}
size={48} size={48}
stroke="red" stroke="red"
strokeWidth={4} strokeWidth={4}
/>, />,
); );
const { attributes } = getByTestId(testId) as unknown as { const SVGElement = container.firstElementChild;
attributes: Record<string, { value: string }>;
}; expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(attributes.stroke.value).toBe('red'); expect(SVGElement).toHaveAttribute('width', '48');
expect(attributes.width.value).toBe('48'); expect(SVGElement).toHaveAttribute('height', '48');
expect(attributes.height.value).toBe('48'); expect(SVGElement).toHaveAttribute('stroke-width', '4');
expect(attributes['stroke-width'].value).toBe('4');
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
}); });
@@ -58,23 +70,20 @@ describe('Using lucide icon components', () => {
}); });
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => { it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'grid-icon';
const { container, getByTestId } = render( const { container, getByTestId } = render(
<Grid <Grid
data-testid={testId}
size={48} size={48}
stroke="red" stroke="red"
absoluteStrokeWidth absoluteStrokeWidth
/>, />,
); );
const { attributes } = getByTestId(testId) as unknown as { const SVGElement = container.firstElementChild;
attributes: Record<string, { value: string }>;
}; expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(attributes.stroke.value).toBe('red'); expect(SVGElement).toHaveAttribute('width', '48');
expect(attributes.width.value).toBe('48'); expect(SVGElement).toHaveAttribute('height', '48');
expect(attributes.height.value).toBe('48'); expect(SVGElement).toHaveAttribute('stroke-width', '1');
expect(attributes['stroke-width'].value).toBe('1');
expect(container.innerHTML).toMatchSnapshot(); expect(container.innerHTML).toMatchSnapshot();
}); });
@@ -87,34 +96,4 @@ describe('Using lucide icon components', () => {
expect(container.firstChild).toHaveClass('lucide'); expect(container.firstChild).toHaveClass('lucide');
expect(container.firstChild).toHaveClass('lucide-droplet'); expect(container.firstChild).toHaveClass('lucide-droplet');
}); });
it('should render icons dynamically by using the dynamicIconImports module', async () => {
interface IconProps extends Omit<LucideProps, 'ref'> {
name: keyof typeof dynamicIconImports;
}
const Icon = ({ name, ...props }: IconProps) => {
const LucideIcon = lazy(dynamicIconImports[name]);
return (
<Suspense fallback={null}>
<LucideIcon {...props} />
</Suspense>
);
};
const { container, getByLabelText } = render(
<Icon
aria-label="smile"
name="smile"
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
await waitFor(() => getByLabelText('smile'));
expect(container.innerHTML).toMatchSnapshot();
});
}); });

View File

@@ -0,0 +1,22 @@
import { IconNode } from '../src/types';
export const airVent: IconNode = [
[
'path',
{
d: 'M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2',
key: 'larmp2',
},
],
['path', { d: 'M6 8h12', key: '6g4wlu' }],
['path', { d: 'M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12', key: '1bo8pg' }],
['path', { d: 'M6.6 15.6A2 2 0 1 0 10 17v-5', key: 't9h90c' }],
];
export const coffee: IconNode = [
['path', { d: 'M17 8h1a4 4 0 1 1 0 8h-1', key: 'jx4kbh' }],
['path', { d: 'M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z', key: '1bxrl0' }],
['line', { x1: '6', x2: '6', y1: '2', y2: '4', key: '1cr9l3' }],
['line', { x1: '10', x2: '10', y1: '2', y2: '4', key: '170wym' }],
['line', { x1: '14', x2: '14', y1: '2', y2: '4', key: '1c5f70' }],
];

View File

@@ -2,10 +2,10 @@ import { For, splitProps } from 'solid-js';
import { Dynamic } from 'solid-js/web'; import { Dynamic } from 'solid-js/web';
import defaultAttributes from './defaultAttributes'; import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types'; import { IconNode, LucideProps } from './types';
import { toKebabCase } from '@lucide/shared'; import { mergeClasses, toKebabCase } from '@lucide/shared';
interface IconProps { interface IconProps {
name: string; name?: string;
iconNode: IconNode; iconNode: IconNode;
} }
@@ -33,9 +33,12 @@ const Icon = (props: LucideProps & IconProps) => {
Number(localProps.size) Number(localProps.size)
: Number(localProps.strokeWidth ?? defaultAttributes['stroke-width']) : Number(localProps.strokeWidth ?? defaultAttributes['stroke-width'])
} }
class={`lucide lucide-${toKebabCase(localProps?.name ?? 'icon')} ${ class={mergeClasses(
localProps.class != null ? localProps.class : '' 'lucide',
}`} 'lucide-icon',
localProps.name != null ? `lucide-${toKebabCase(localProps?.name)}` : undefined,
localProps.class != null ? localProps.class : '',
)}
{...rest} {...rest}
> >
<For each={localProps.iconNode}> <For each={localProps.iconNode}>

View File

@@ -1,3 +1,6 @@
export * from './icons'; export * from './icons';
export * as icons from './icons'; export * as icons from './icons';
export * from './aliases'; export * from './aliases';
export * from './types';
export { default as Icon } from './Icon';

View File

@@ -11,3 +11,5 @@ export interface LucideProps extends SVGAttributes {
class?: string; class?: string;
absoluteStrokeWidth?: boolean; absoluteStrokeWidth?: boolean;
} }
export type LucideIcon = (props: LucideProps) => JSX.Element;

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { render } from '@solidjs/testing-library';
import { airVent } from './testIconNodes';
import { Icon } from '../src/lucide-solid';
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(() => (
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>
));
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(() => (
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>
));
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,33 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
class="lucide lucide-icon"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
key="larmp2"
/>
<path
d="M6 8h12"
key="6g4wlu"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
key="1bo8pg"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
key="t9h90c"
/>
</svg>
`;

View File

@@ -10,7 +10,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
height="48" height="48"
stroke="red" stroke="red"
stroke-width="4" stroke-width="4"
class="lucide lucide-grid3x3 " class="lucide lucide-icon lucide-grid3x3"
data-testid="grid-icon" data-testid="grid-icon"
> >
<rect width="18" <rect width="18"
@@ -50,7 +50,7 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
height="48" height="48"
stroke="red" stroke="red"
stroke-width="1" stroke-width="1"
class="lucide lucide-grid3x3 " class="lucide lucide-icon lucide-grid3x3"
data-testid="grid-icon" data-testid="grid-icon"
> >
<rect width="18" <rect width="18"
@@ -90,7 +90,7 @@ exports[`Using lucide icon components > should render a component 1`] = `
height="24" height="24"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"
class="lucide lucide-grid3x3 " class="lucide lucide-icon lucide-grid3x3"
> >
<rect width="18" <rect width="18"
height="18" height="18"

View File

@@ -0,0 +1,22 @@
import { IconNode } from '../src/types';
export const airVent: IconNode = [
[
'path',
{
d: 'M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2',
key: 'larmp2',
},
],
['path', { d: 'M6 8h12', key: '6g4wlu' }],
['path', { d: 'M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12', key: '1bo8pg' }],
['path', { d: 'M6.6 15.6A2 2 0 1 0 10 17v-5', key: 't9h90c' }],
];
export const coffee: IconNode = [
['path', { d: 'M17 8h1a4 4 0 1 1 0 8h-1', key: 'jx4kbh' }],
['path', { d: 'M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z', key: '1bxrl0' }],
['line', { x1: '6', x2: '6', y1: '2', y2: '4', key: '1cr9l3' }],
['line', { x1: '10', x2: '10', y1: '2', y2: '4', key: '170wym' }],
['line', { x1: '14', x2: '14', y1: '2', y2: '4', key: '1c5f70' }],
];

View File

@@ -52,10 +52,12 @@
"build:package": "svelte-package --input ./src", "build:package": "svelte-package --input ./src",
"build:license": "node ./scripts/appendBlockComments.mjs", "build:license": "node ./scripts/appendBlockComments.mjs",
"test": "pnpm build:icons && vitest run", "test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false" "version": "pnpm version --git-tag-version=false"
}, },
"devDependencies": { "devDependencies": {
"@lucide/build-icons": "workspace:*", "@lucide/build-icons": "workspace:*",
"@lucide/shared": "workspace:*",
"@sveltejs/package": "^2.2.3", "@sveltejs/package": "^2.2.3",
"@sveltejs/vite-plugin-svelte": "^2.4.2", "@sveltejs/vite-plugin-svelte": "^2.4.2",
"@testing-library/jest-dom": "^6.1.4", "@testing-library/jest-dom": "^6.1.4",

View File

@@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import { mergeClasses } from '@lucide/shared'
import defaultAttributes from './defaultAttributes' import defaultAttributes from './defaultAttributes'
import type { IconNode } from './types'; import type { IconNode } from './types';
export let name: string export let name: string | undefined = undefined
export let color = 'currentColor' export let color = 'currentColor'
export let size: number | string = 24 export let size: number | string = 24
export let strokeWidth: number | string = 2 export let strokeWidth: number | string = 2
@@ -21,7 +22,14 @@
? Number(strokeWidth) * 24 / Number(size) ? Number(strokeWidth) * 24 / Number(size)
: strokeWidth : strokeWidth
} }
class={`lucide-icon lucide lucide-${name} ${$$props.class ?? ''}`} class={
mergeClasses(
'lucide-icon',
'lucide',
name ? `lucide-${name}`: '',
$$props.class
)
}
> >
{#each iconNode as [tag, attrs]} {#each iconNode as [tag, attrs]}
<svelte:element this={tag} {...attrs}/> <svelte:element this={tag} {...attrs}/>

View File

@@ -3,3 +3,4 @@ export * as icons from './icons/index.js';
export * from './aliases.js'; export * from './aliases.js';
export { default as defaultAttributes } from './defaultAttributes.js'; export { default as defaultAttributes } from './defaultAttributes.js';
export * from './types.js'; export * from './types.js';
export { default as Icon } from './Icon.svelte';

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/svelte';
import { Icon } from '../src/lucide-svelte';
import { airVent } from './testIconNodes';
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(Icon, {
props: {
iconNode: airVent,
size: 48,
color: 'red',
absoluteStrokeWidth: true,
},
});
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(Icon, {
props: {
iconNode: airVent,
size: 48,
color: 'red',
absoluteStrokeWidth: true,
},
});
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,36 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<div>
<svg
class="lucide-icon lucide"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
</div>
`;

View File

@@ -45,7 +45,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
<body> <body>
<div> <div>
<svg <svg
class="lucide-icon lucide lucide-smile " class="lucide-icon lucide lucide-smile"
fill="none" fill="none"
height="48" height="48"
stroke="red" stroke="red"
@@ -98,7 +98,7 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
data-testid="smile-icon" data-testid="smile-icon"
class="lucide-icon lucide lucide-smile " class="lucide-icon lucide lucide-smile"
> >
<circle cx="12" <circle cx="12"
cy="12" cy="12"
@@ -127,7 +127,7 @@ exports[`Using lucide icon components > should render an component 1`] = `
<body> <body>
<div> <div>
<svg <svg
class="lucide-icon lucide lucide-smile " class="lucide-icon lucide lucide-smile"
fill="none" fill="none"
height="24" height="24"
stroke="currentColor" stroke="currentColor"
@@ -172,7 +172,7 @@ exports[`Using lucide icon components > should render an icon slot 1`] = `
<body> <body>
<div> <div>
<svg <svg
class="lucide-icon lucide lucide-smile " class="lucide-icon lucide lucide-smile"
fill="none" fill="none"
height="24" height="24"
stroke="currentColor" stroke="currentColor"

View File

@@ -0,0 +1,21 @@
import type { IconNode } from '../src/lucide-svelte';
export const airVent: IconNode = [
[
'path',
{
d: 'M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2',
},
],
['path', { d: 'M6 8h12' }],
['path', { d: 'M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12' }],
['path', { d: 'M6.6 15.6A2 2 0 1 0 10 17v-5' }],
];
export const coffee: IconNode = [
['path', { d: 'M17 8h1a4 4 0 1 1 0 8h-1' }],
['path', { d: 'M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z' }],
['line', { x1: '6', x2: '6', y1: '2', y2: '4' }],
['line', { x1: '10', x2: '10', y1: '2', y2: '4' }],
['line', { x1: '14', x2: '14', y1: '2', y2: '4' }],
];

View File

@@ -41,6 +41,7 @@
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mjs --renderUniqueKey --withAliases --aliasesFileExtension=.ts --iconFileExtension=.ts --exportFileName=index.ts", "build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mjs --renderUniqueKey --withAliases --aliasesFileExtension=.ts --iconFileExtension=.ts --exportFileName=index.ts",
"build:bundles": "rollup -c ./rollup.config.mjs", "build:bundles": "rollup -c ./rollup.config.mjs",
"test": "pnpm build:icons && vitest run", "test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false" "version": "pnpm version --git-tag-version=false"
}, },
"devDependencies": { "devDependencies": {
@@ -48,14 +49,14 @@
"@lucide/rollup-plugins": "workspace:*", "@lucide/rollup-plugins": "workspace:*",
"@lucide/shared": "workspace:*", "@lucide/shared": "workspace:*",
"@testing-library/jest-dom": "^6.1.6", "@testing-library/jest-dom": "^6.1.6",
"@testing-library/vue": "^8.0.1", "@testing-library/vue": "^8.0.3",
"@vitejs/plugin-vue": "^4.6.2", "@vitejs/plugin-vue": "^4.6.2",
"@vue/test-utils": "2.4.3", "@vue/test-utils": "2.4.5",
"rollup": "^4.9.2", "rollup": "^4.9.2",
"rollup-plugin-dts": "^6.1.0", "rollup-plugin-dts": "^6.1.0",
"vite": "5.0.13", "vite": "5.0.13",
"vitest": "^1.1.1", "vitest": "^1.4.0",
"vue": "^3.0.1" "vue": "^3.4.21"
}, },
"peerDependencies": { "peerDependencies": {
"vue": ">=3.0.1" "vue": ">=3.0.1"

View File

@@ -0,0 +1,30 @@
import { type FunctionalComponent, h } from 'vue';
import { mergeClasses, toKebabCase } from '@lucide/shared';
import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types';
interface IconProps {
iconNode: IconNode;
name: string;
}
const Icon: FunctionalComponent<LucideProps & IconProps> = (
{ size, strokeWidth = 2, absoluteStrokeWidth, color, iconNode, name, class: classes, ...props },
{ slots },
) => {
return h(
'svg',
{
...defaultAttributes,
width: size || defaultAttributes.width,
height: size || defaultAttributes.height,
stroke: color || defaultAttributes.stroke,
'stroke-width': absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
class: ['lucide', `lucide-${toKebabCase(name ?? 'icon')}`],
...props,
},
[...iconNode.map((child) => h(...child)), ...(slots.default ? [slots.default()] : [])],
);
};
export default Icon;

View File

@@ -1,17 +1,9 @@
import { h } from 'vue'; import { h } from 'vue';
import type { SVGAttributes, FunctionalComponent } from 'vue'; import type { FunctionalComponent } from 'vue';
import defaultAttributes from './defaultAttributes'; import { IconNode, LucideProps } from './types';
import { toKebabCase } from '@lucide/shared'; import Icon from './Icon';
// Create interface extending SVGAttributes // Create interface extending SVGAttributes
export interface SVGProps extends Partial<SVGAttributes> {
size?: 24 | number;
strokeWidth?: number | string;
absoluteStrokeWidth?: boolean;
}
export type IconNode = [elementName: string, attrs: Record<string, string>][];
export type Icon = FunctionalComponent<SVGProps>;
/** /**
* Create a Lucide icon component * Create a Lucide icon component
@@ -20,27 +12,16 @@ export type Icon = FunctionalComponent<SVGProps>;
* @returns {FunctionalComponent} LucideIcon * @returns {FunctionalComponent} LucideIcon
*/ */
const createLucideIcon = const createLucideIcon =
(iconName: string, iconNode: IconNode): Icon => (iconName: string, iconNode: IconNode): FunctionalComponent<LucideProps> =>
( (props, { slots }) =>
{ size, strokeWidth = 2, absoluteStrokeWidth, color, class: classes, ...props }, // props h(
{ attrs, slots }, // context Icon,
) => {
return h(
'svg',
{ {
...defaultAttributes,
width: size || defaultAttributes.width,
height: size || defaultAttributes.height,
stroke: color || defaultAttributes.stroke,
'stroke-width': absoluteStrokeWidth
? (Number(strokeWidth) * 24) / Number(size)
: strokeWidth,
...attrs,
class: ['lucide', `lucide-${toKebabCase(iconName)}`],
...props, ...props,
iconNode,
name: iconName,
}, },
[...iconNode.map((child) => h(...child)), ...(slots.default ? [slots.default()] : [])], slots,
); );
};
export default createLucideIcon; export default createLucideIcon;

View File

@@ -1,3 +1,7 @@
export * from './icons'; export * from './icons';
export * as icons from './icons'; export * as icons from './icons';
export * from './aliases'; export * from './aliases';
export * from './types';
export { default as createLucideIcon } from './createLucideIcon';
export { default as Icon } from './Icon';

View File

@@ -0,0 +1,13 @@
import type { FunctionalComponent, SVGAttributes } from 'vue';
export interface LucideProps extends Partial<SVGAttributes> {
size?: 24 | number;
strokeWidth?: number | string;
absoluteStrokeWidth?: boolean;
}
export type IconNode = [elementName: string, attrs: Record<string, string>][];
export type LucideIcon = FunctionalComponent<LucideProps>;
// Legacy exports
export type SVGProps = LucideProps;

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/vue';
import { airVent } from './testIconNodes';
import { Icon } from '../src/lucide-vue-next';
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(Icon, {
props: {
iconNode: airVent,
size: 48,
color: 'red',
absoluteStrokeWidth: true,
},
});
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(Icon, {
props: {
iconNode: airVent,
size: 48,
color: 'red',
absoluteStrokeWidth: true,
},
});
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
class="lucide lucide-icon"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
`;

View File

@@ -81,10 +81,8 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
<div> <div>
<svg <svg
class="lucide lucide-smile-icon" class="lucide lucide-smile-icon"
color="red"
fill="none" fill="none"
height="48" height="48"
size="48"
stroke="red" stroke="red"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"

View File

@@ -1,6 +1,7 @@
import { describe, it, expect, vi, afterEach } from 'vitest'; import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, fireEvent, cleanup } from '@testing-library/vue'; import { render, fireEvent, cleanup } from '@testing-library/vue';
import { Smile, Edit2, Pen } from '../src/lucide-vue-next'; import { Smile, Edit2, Pen } from '../src/lucide-vue-next';
import defaultAttributes from '../src/defaultAttributes';
describe('Using lucide icon components', () => { describe('Using lucide icon components', () => {
afterEach(() => cleanup()); afterEach(() => cleanup());
@@ -10,6 +11,22 @@ describe('Using lucide icon components', () => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
it('should render the icon with the default attributes', () => {
const { container } = render(Smile);
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('xmlns', defaultAttributes.xmlns);
expect(SVGElement).toHaveAttribute('width', String(defaultAttributes.width));
expect(SVGElement).toHaveAttribute('height', String(defaultAttributes.height));
expect(SVGElement).toHaveAttribute('viewBox', defaultAttributes.viewBox);
expect(SVGElement).toHaveAttribute('fill', defaultAttributes.fill);
expect(SVGElement).toHaveAttribute('stroke', defaultAttributes.stroke);
expect(SVGElement).toHaveAttribute('stroke-width', String(defaultAttributes['stroke-width']));
expect(SVGElement).toHaveAttribute('stroke-linecap', defaultAttributes['stroke-linecap']);
expect(SVGElement).toHaveAttribute('stroke-linejoin', defaultAttributes['stroke-linejoin']);
});
it('should adjust the size, stroke color and stroke width', () => { it('should adjust the size, stroke color and stroke width', () => {
const { container } = render(Smile, { const { container } = render(Smile, {
props: { props: {
@@ -19,11 +36,11 @@ describe('Using lucide icon components', () => {
}, },
}); });
const [icon] = document.getElementsByClassName('lucide'); const SVGElement = container.firstElementChild;
expect(icon.getAttribute('width')).toBe('48'); expect(SVGElement).toHaveAttribute('width', '48');
expect(icon.getAttribute('stroke')).toBe('red'); expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(icon.getAttribute('stroke-width')).toBe('4'); expect(SVGElement).toHaveAttribute('stroke-width', '4');
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
@@ -37,7 +54,7 @@ describe('Using lucide icon components', () => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
const [icon] = document.getElementsByClassName('my-icon'); const icon = container.firstElementChild;
expect(icon).toHaveClass('my-icon'); expect(icon).toHaveClass('my-icon');
expect(icon).toHaveClass('lucide'); expect(icon).toHaveClass('lucide');
@@ -53,20 +70,20 @@ describe('Using lucide icon components', () => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
const [icon] = document.getElementsByClassName('lucide'); const icon = container.firstElementChild;
expect(icon).toHaveStyle({ position: 'absolute' }); expect(icon).toHaveStyle({ position: 'absolute' });
}); });
it('should call the onClick event', async () => { it('should call the onClick event', async () => {
const onClick = vi.fn(); const onClick = vi.fn();
render(Smile, { const { container } = render(Smile, {
attrs: { attrs: {
onClick, onClick,
}, },
}); });
const [icon] = document.getElementsByClassName('lucide'); const icon = container.firstElementChild;
await fireEvent.click(icon); await fireEvent.click(icon);
@@ -116,7 +133,7 @@ describe('Using lucide icon components', () => {
}); });
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => { it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
render(Pen, { const { container } = render(Pen, {
props: { props: {
size: 48, size: 48,
color: 'red', color: 'red',
@@ -124,10 +141,10 @@ describe('Using lucide icon components', () => {
}, },
}); });
const [icon] = document.getElementsByClassName('lucide'); const icon = container.firstElementChild;
expect(icon.getAttribute('width')).toBe('48'); expect(icon).toHaveAttribute('width', '48');
expect(icon.getAttribute('stroke')).toBe('red'); expect(icon).toHaveAttribute('stroke', 'red');
expect(icon.getAttribute('stroke-width')).toBe('1'); expect(icon).toHaveAttribute('stroke-width', '1');
}); });
}); });

View File

@@ -1 +1 @@
import '@testing-library/jest-dom'; import '@testing-library/jest-dom/vitest';

View File

@@ -0,0 +1,22 @@
import { IconNode } from '../src/createLucideIcon';
export const airVent: IconNode = [
[
'path',
{
d: 'M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2',
key: 'larmp2',
},
],
['path', { d: 'M6 8h12', key: '6g4wlu' }],
['path', { d: 'M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12', key: '1bo8pg' }],
['path', { d: 'M6.6 15.6A2 2 0 1 0 10 17v-5', key: 't9h90c' }],
];
export const coffee: IconNode = [
['path', { d: 'M17 8h1a4 4 0 1 1 0 8h-1', key: 'jx4kbh' }],
['path', { d: 'M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z', key: '1bxrl0' }],
['line', { x1: '6', x2: '6', y1: '2', y2: '4', key: '1cr9l3' }],
['line', { x1: '10', x2: '10', y1: '2', y2: '4', key: '170wym' }],
['line', { x1: '14', x2: '14', y1: '2', y2: '4', key: '1c5f70' }],
];

View File

@@ -0,0 +1,37 @@
import replaceElement from './replaceElement';
/**
* Replaces all elements with matching nameAttr with the defined icons
* @param {{ icons?: object, nameAttr?: string, attrs?: object }} options
*/
const createIcons = ({ icons = {}, nameAttr = 'data-lucide', attrs = {} } = {}) => {
if (!Object.values(icons).length) {
throw new Error(
"Please provide an icons object.\nIf you want to use all the icons you can import it like:\n `import { createIcons, icons } from 'lucide';\nlucide.createIcons({icons});`",
);
}
if (typeof document === 'undefined') {
throw new Error('`createIcons()` only works in a browser environment.');
}
const elementsToReplace = document.querySelectorAll(`[${nameAttr}]`);
Array.from(elementsToReplace).forEach((element) =>
replaceElement(element, { nameAttr, icons, attrs }),
);
/** @todo: remove this block in v1.0 */
if (nameAttr === 'data-lucide') {
const deprecatedElements = document.querySelectorAll('[icon-name]');
if (deprecatedElements.length > 0) {
console.warn(
'[Lucide] Some icons were found with the now deprecated icon-name attribute. These will still be replaced for backwards compatibility, but will no longer be supported in v1.0 and you should switch to data-lucide',
);
Array.from(deprecatedElements).forEach((element) =>
replaceElement(element, { nameAttr: 'icon-name', icons, attrs }),
);
}
}
};
export default createIcons;

View File

@@ -1,41 +1,9 @@
import replaceElement from './replaceElement';
import * as iconAndAliases from './iconsAndAliases'; import * as iconAndAliases from './iconsAndAliases';
/** /*
* Replaces all elements with matching nameAttr with the defined icons Create Icons function export.
* @param {{ icons?: object, nameAttr?: string, attrs?: object }} options */
*/ export { default as createIcons } from './createIcons';
const createIcons = ({ icons = {}, nameAttr = 'data-lucide', attrs = {} } = {}) => {
if (!Object.values(icons).length) {
throw new Error(
"Please provide an icons object.\nIf you want to use all the icons you can import it like:\n `import { createIcons, icons } from 'lucide';\nlucide.createIcons({icons});`",
);
}
if (typeof document === 'undefined') {
throw new Error('`createIcons()` only works in a browser environment.');
}
const elementsToReplace = document.querySelectorAll(`[${nameAttr}]`);
Array.from(elementsToReplace).forEach((element) =>
replaceElement(element, { nameAttr, icons, attrs }),
);
/** @todo: remove this block in v1.0 */
if (nameAttr === 'data-lucide') {
const deprecatedElements = document.querySelectorAll('[icon-name]');
if (deprecatedElements.length > 0) {
console.warn(
'[Lucide] Some icons were found with the now deprecated icon-name attribute. These will still be replaced for backwards compatibility, but will no longer be supported in v1.0 and you should switch to data-lucide',
);
Array.from(deprecatedElements).forEach((element) =>
replaceElement(element, { nameAttr: 'icon-name', icons, attrs }),
);
}
}
};
export { createIcons };
/* /*
Create Element function export. Create Element function export.

View File

@@ -1 +1,2 @@
export * from './utils'; export * from './utils';
export * from './utility-types';

View File

@@ -0,0 +1,16 @@
/**
* Convert a type string from camelCase to PascalCase
*
* @example
* type Test = CamelToPascal<'fooBar'> // 'FooBar'
*/
export type CamelToPascal<T extends string> = T extends `${infer FirstChar}${infer Rest}`
? `${Capitalize<FirstChar>}${Rest}`
: never;
/**
* Creates a list of components from a list of component names and a component type
*/
export type ComponentList<ComponentNames, ComponentType> = {
[Prop in keyof ComponentNames as CamelToPascal<Prop & string>]: ComponentType;
};

View File

@@ -1,8 +1,37 @@
import { CamelToPascal } from './utility-types';
/** /**
* Converts string to KebabCase * Converts string to kebab case
* *
* @param {string} string * @param {string} string
* @returns {string} A kebabized string * @returns {string} A kebabized string
*/ */
export const toKebabCase = (string: string) => export const toKebabCase = (string: string) =>
string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
/**
* Converts string to pascal case
*
* @param {string} string
* @returns {string} A pascalized string
*/
export const toPascalCase = <T extends string>(string: T): CamelToPascal<T> => {
const camelCase = string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) =>
p2 ? p2.toUpperCase() : p1.toLowerCase(),
);
return (camelCase.charAt(0).toUpperCase() + camelCase.slice(1)) as CamelToPascal<T>;
};
/**
* Merges classes into a single string
*
* @param {array} classes
* @returns {string} A string of classes
*/
export const mergeClasses = <ClassType = string | undefined | null>(...classes: ClassType[]) =>
classes
.filter((className, index, array) => {
return Boolean(className) && array.indexOf(className) === index;
})
.join(' ');

346
pnpm-lock.yaml generated
View File

@@ -457,6 +457,9 @@ importers:
'@lucide/rollup-plugins': '@lucide/rollup-plugins':
specifier: workspace:* specifier: workspace:*
version: link:../../tools/rollup-plugins version: link:../../tools/rollup-plugins
'@lucide/shared':
specifier: workspace:*
version: link:../shared
'@testing-library/jest-dom': '@testing-library/jest-dom':
specifier: ^6.1.6 specifier: ^6.1.6
version: 6.4.2(vitest@1.2.2) version: 6.4.2(vitest@1.2.2)
@@ -586,6 +589,9 @@ importers:
'@lucide/build-icons': '@lucide/build-icons':
specifier: workspace:* specifier: workspace:*
version: link:../../tools/build-icons version: link:../../tools/build-icons
'@lucide/shared':
specifier: workspace:*
version: link:../shared
'@sveltejs/package': '@sveltejs/package':
specifier: ^2.2.3 specifier: ^2.2.3
version: 2.2.6(svelte@4.1.2)(typescript@5.1.6) version: 2.2.6(svelte@4.1.2)(typescript@5.1.6)
@@ -681,16 +687,16 @@ importers:
version: link:../shared version: link:../shared
'@testing-library/jest-dom': '@testing-library/jest-dom':
specifier: ^6.1.6 specifier: ^6.1.6
version: 6.4.2(vitest@1.2.2) version: 6.4.2(vitest@1.4.0)
'@testing-library/vue': '@testing-library/vue':
specifier: ^8.0.1 specifier: ^8.0.3
version: 8.0.2(vue@3.4.18) version: 8.0.3(vue@3.4.21)
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^4.6.2 specifier: ^4.6.2
version: 4.6.2(vite@5.0.13)(vue@3.4.18) version: 4.6.2(vite@5.0.13)(vue@3.4.21)
'@vue/test-utils': '@vue/test-utils':
specifier: 2.4.3 specifier: 2.4.5
version: 2.4.3(vue@3.4.18) version: 2.4.5
rollup: rollup:
specifier: ^4.9.2 specifier: ^4.9.2
version: 4.9.6 version: 4.9.6
@@ -701,11 +707,11 @@ importers:
specifier: 5.0.13 specifier: 5.0.13
version: 5.0.13 version: 5.0.13
vitest: vitest:
specifier: ^1.1.1 specifier: ^1.4.0
version: 1.2.2(jsdom@20.0.3) version: 1.4.0
vue: vue:
specifier: ^3.0.1 specifier: ^3.4.21
version: 3.4.18(typescript@4.9.5) version: 3.4.21(typescript@4.9.5)
packages/shared: {} packages/shared: {}
@@ -7452,6 +7458,38 @@ packages:
vitest: 1.2.2(jsdom@20.0.3) vitest: 1.2.2(jsdom@20.0.3)
dev: true dev: true
/@testing-library/jest-dom@6.4.2(vitest@1.4.0):
resolution: {integrity: sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==}
engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
peerDependencies:
'@jest/globals': '>= 28'
'@types/bun': latest
'@types/jest': '>= 28'
jest: '>= 28'
vitest: '>= 0.32'
peerDependenciesMeta:
'@jest/globals':
optional: true
'@types/bun':
optional: true
'@types/jest':
optional: true
jest:
optional: true
vitest:
optional: true
dependencies:
'@adobe/css-tools': 4.3.3
'@babel/runtime': 7.23.9
aria-query: 5.3.0
chalk: 3.0.0
css.escape: 1.5.1
dom-accessibility-api: 0.6.3
lodash: 4.17.21
redent: 3.0.0
vitest: 1.4.0
dev: true
/@testing-library/preact@3.2.3(preact@10.19.4): /@testing-library/preact@3.2.3(preact@10.19.4):
resolution: {integrity: sha512-y6Kklp1XK3f1X2fWCbujmJyzkf+1BgLYXNgAx21j9+D4CoqMTz5qC4SQufb1L6q/jxLGACzrQ90ewVOTBvHOfg==} resolution: {integrity: sha512-y6Kklp1XK3f1X2fWCbujmJyzkf+1BgLYXNgAx21j9+D4CoqMTz5qC4SQufb1L6q/jxLGACzrQ90ewVOTBvHOfg==}
engines: {node: '>= 12'} engines: {node: '>= 12'}
@@ -7500,8 +7538,8 @@ packages:
vue-template-compiler: 2.7.14(vue@2.7.14) vue-template-compiler: 2.7.14(vue@2.7.14)
dev: true dev: true
/@testing-library/vue@8.0.2(vue@3.4.18): /@testing-library/vue@8.0.3(vue@3.4.21):
resolution: {integrity: sha512-A8wWX+qQn0o0izpQWnGCpwQt8wAdpsVP8vPP2h5Q/jcGhZ5yKXz9PPUqhQv+45LTFaWlyRf8bArTVaB/KFFd5A==} resolution: {integrity: sha512-wSsbNlZ69ZFQgVlHMtc/ZC/g9BHO7MhyDrd4nHyfEubtMr3kToN/w4/BsSBknGIF8w9UmPbsgbIuq/CbdBHzCA==}
engines: {node: '>=14'} engines: {node: '>=14'}
peerDependencies: peerDependencies:
'@vue/compiler-sfc': '>= 3' '@vue/compiler-sfc': '>= 3'
@@ -7512,10 +7550,8 @@ packages:
dependencies: dependencies:
'@babel/runtime': 7.23.9 '@babel/runtime': 7.23.9
'@testing-library/dom': 9.3.4 '@testing-library/dom': 9.3.4
'@vue/test-utils': 2.4.3(vue@3.4.18) '@vue/test-utils': 2.4.5
vue: 3.4.18(typescript@4.9.5) vue: 3.4.21(typescript@4.9.5)
transitivePeerDependencies:
- '@vue/server-renderer'
dev: true dev: true
/@tokenizer/token@0.3.0: /@tokenizer/token@0.3.0:
@@ -8378,7 +8414,7 @@ packages:
vue: 2.7.14 vue: 2.7.14
dev: true dev: true
/@vitejs/plugin-vue@4.6.2(vite@5.0.13)(vue@3.4.18): /@vitejs/plugin-vue@4.6.2(vite@5.0.13)(vue@3.4.21):
resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==} resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies: peerDependencies:
@@ -8386,7 +8422,7 @@ packages:
vue: ^3.2.25 vue: ^3.2.25
dependencies: dependencies:
vite: 5.0.13 vite: 5.0.13
vue: 3.4.18(typescript@4.9.5) vue: 3.4.21(typescript@4.9.5)
dev: true dev: true
/@vitejs/plugin-vue@5.0.3(vite@5.0.12)(vue@3.4.18): /@vitejs/plugin-vue@5.0.3(vite@5.0.12)(vue@3.4.18):
@@ -8416,6 +8452,14 @@ packages:
chai: 4.4.1 chai: 4.4.1
dev: true dev: true
/@vitest/expect@1.4.0:
resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==}
dependencies:
'@vitest/spy': 1.4.0
'@vitest/utils': 1.4.0
chai: 4.4.1
dev: true
/@vitest/runner@0.32.4: /@vitest/runner@0.32.4:
resolution: {integrity: sha512-cHOVCkiRazobgdKLnczmz2oaKK9GJOw6ZyRcaPdssO1ej+wzHVIkWiCiNacb3TTYPdzMddYkCgMjZ4r8C0JFCw==} resolution: {integrity: sha512-cHOVCkiRazobgdKLnczmz2oaKK9GJOw6ZyRcaPdssO1ej+wzHVIkWiCiNacb3TTYPdzMddYkCgMjZ4r8C0JFCw==}
dependencies: dependencies:
@@ -8432,6 +8476,14 @@ packages:
pathe: 1.1.2 pathe: 1.1.2
dev: true dev: true
/@vitest/runner@1.4.0:
resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==}
dependencies:
'@vitest/utils': 1.4.0
p-limit: 5.0.0
pathe: 1.1.2
dev: true
/@vitest/snapshot@0.32.4: /@vitest/snapshot@0.32.4:
resolution: {integrity: sha512-IRpyqn9t14uqsFlVI2d7DFMImGMs1Q9218of40bdQQgMePwVdmix33yMNnebXcTzDU5eiV3eUsoxxH5v0x/IQA==} resolution: {integrity: sha512-IRpyqn9t14uqsFlVI2d7DFMImGMs1Q9218of40bdQQgMePwVdmix33yMNnebXcTzDU5eiV3eUsoxxH5v0x/IQA==}
dependencies: dependencies:
@@ -8448,6 +8500,14 @@ packages:
pretty-format: 29.7.0 pretty-format: 29.7.0
dev: true dev: true
/@vitest/snapshot@1.4.0:
resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==}
dependencies:
magic-string: 0.30.7
pathe: 1.1.2
pretty-format: 29.7.0
dev: true
/@vitest/spy@0.32.4: /@vitest/spy@0.32.4:
resolution: {integrity: sha512-oA7rCOqVOOpE6rEoXuCOADX7Lla1LIa4hljI2MSccbpec54q+oifhziZIJXxlE/CvI2E+ElhBHzVu0VEvJGQKQ==} resolution: {integrity: sha512-oA7rCOqVOOpE6rEoXuCOADX7Lla1LIa4hljI2MSccbpec54q+oifhziZIJXxlE/CvI2E+ElhBHzVu0VEvJGQKQ==}
dependencies: dependencies:
@@ -8460,6 +8520,12 @@ packages:
tinyspy: 2.2.1 tinyspy: 2.2.1
dev: true dev: true
/@vitest/spy@1.4.0:
resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==}
dependencies:
tinyspy: 2.2.1
dev: true
/@vitest/utils@0.32.4: /@vitest/utils@0.32.4:
resolution: {integrity: sha512-Gwnl8dhd1uJ+HXrYyV0eRqfmk9ek1ASE/LWfTCuWMw+d07ogHqp4hEAV28NiecimK6UY9DpSEPh+pXBA5gtTBg==} resolution: {integrity: sha512-Gwnl8dhd1uJ+HXrYyV0eRqfmk9ek1ASE/LWfTCuWMw+d07ogHqp4hEAV28NiecimK6UY9DpSEPh+pXBA5gtTBg==}
dependencies: dependencies:
@@ -8477,6 +8543,15 @@ packages:
pretty-format: 29.7.0 pretty-format: 29.7.0
dev: true dev: true
/@vitest/utils@1.4.0:
resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==}
dependencies:
diff-sequences: 29.6.3
estree-walker: 3.0.3
loupe: 2.3.7
pretty-format: 29.7.0
dev: true
/@vue/compiler-core@3.4.18: /@vue/compiler-core@3.4.18:
resolution: {integrity: sha512-F7YK8lMK0iv6b9/Gdk15A67wM0KKZvxDxed0RR60C1z9tIJTKta+urs4j0RTN5XqHISzI3etN3mX0uHhjmoqjQ==} resolution: {integrity: sha512-F7YK8lMK0iv6b9/Gdk15A67wM0KKZvxDxed0RR60C1z9tIJTKta+urs4j0RTN5XqHISzI3etN3mX0uHhjmoqjQ==}
dependencies: dependencies:
@@ -8486,12 +8561,29 @@ packages:
estree-walker: 2.0.2 estree-walker: 2.0.2
source-map-js: 1.0.2 source-map-js: 1.0.2
/@vue/compiler-core@3.4.21:
resolution: {integrity: sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==}
dependencies:
'@babel/parser': 7.23.9
'@vue/shared': 3.4.21
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.0.2
dev: true
/@vue/compiler-dom@3.4.18: /@vue/compiler-dom@3.4.18:
resolution: {integrity: sha512-24Eb8lcMfInefvQ6YlEVS18w5Q66f4+uXWVA+yb7praKbyjHRNuKVWGuinfSSjM0ZIiPi++QWukhkgznBaqpEA==} resolution: {integrity: sha512-24Eb8lcMfInefvQ6YlEVS18w5Q66f4+uXWVA+yb7praKbyjHRNuKVWGuinfSSjM0ZIiPi++QWukhkgznBaqpEA==}
dependencies: dependencies:
'@vue/compiler-core': 3.4.18 '@vue/compiler-core': 3.4.18
'@vue/shared': 3.4.18 '@vue/shared': 3.4.18
/@vue/compiler-dom@3.4.21:
resolution: {integrity: sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==}
dependencies:
'@vue/compiler-core': 3.4.21
'@vue/shared': 3.4.21
dev: true
/@vue/compiler-sfc@2.7.14: /@vue/compiler-sfc@2.7.14:
resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==} resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==}
dependencies: dependencies:
@@ -8513,12 +8605,33 @@ packages:
postcss: 8.4.35 postcss: 8.4.35
source-map-js: 1.0.2 source-map-js: 1.0.2
/@vue/compiler-sfc@3.4.21:
resolution: {integrity: sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==}
dependencies:
'@babel/parser': 7.23.9
'@vue/compiler-core': 3.4.21
'@vue/compiler-dom': 3.4.21
'@vue/compiler-ssr': 3.4.21
'@vue/shared': 3.4.21
estree-walker: 2.0.2
magic-string: 0.30.7
postcss: 8.4.35
source-map-js: 1.0.2
dev: true
/@vue/compiler-ssr@3.4.18: /@vue/compiler-ssr@3.4.18:
resolution: {integrity: sha512-hSlv20oUhPxo2UYUacHgGaxtqP0tvFo6ixxxD6JlXIkwzwoZ9eKK6PFQN4hNK/R13JlNyldwWt/fqGBKgWJ6nQ==} resolution: {integrity: sha512-hSlv20oUhPxo2UYUacHgGaxtqP0tvFo6ixxxD6JlXIkwzwoZ9eKK6PFQN4hNK/R13JlNyldwWt/fqGBKgWJ6nQ==}
dependencies: dependencies:
'@vue/compiler-dom': 3.4.18 '@vue/compiler-dom': 3.4.18
'@vue/shared': 3.4.18 '@vue/shared': 3.4.18
/@vue/compiler-ssr@3.4.21:
resolution: {integrity: sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==}
dependencies:
'@vue/compiler-dom': 3.4.21
'@vue/shared': 3.4.21
dev: true
/@vue/devtools-api@6.5.1: /@vue/devtools-api@6.5.1:
resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==} resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==}
dev: true dev: true
@@ -8528,12 +8641,25 @@ packages:
dependencies: dependencies:
'@vue/shared': 3.4.18 '@vue/shared': 3.4.18
/@vue/reactivity@3.4.21:
resolution: {integrity: sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==}
dependencies:
'@vue/shared': 3.4.21
dev: true
/@vue/runtime-core@3.4.18: /@vue/runtime-core@3.4.18:
resolution: {integrity: sha512-7mU9diCa+4e+8/wZ7Udw5pwTH10A11sZ1nldmHOUKJnzCwvZxfJqAtw31mIf4T5H2FsLCSBQT3xgioA9vIjyDQ==} resolution: {integrity: sha512-7mU9diCa+4e+8/wZ7Udw5pwTH10A11sZ1nldmHOUKJnzCwvZxfJqAtw31mIf4T5H2FsLCSBQT3xgioA9vIjyDQ==}
dependencies: dependencies:
'@vue/reactivity': 3.4.18 '@vue/reactivity': 3.4.18
'@vue/shared': 3.4.18 '@vue/shared': 3.4.18
/@vue/runtime-core@3.4.21:
resolution: {integrity: sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==}
dependencies:
'@vue/reactivity': 3.4.21
'@vue/shared': 3.4.21
dev: true
/@vue/runtime-dom@3.4.18: /@vue/runtime-dom@3.4.18:
resolution: {integrity: sha512-2y1Mkzcw1niSfG7z3Qx+2ir9Gb4hdTkZe5p/I8x1aTIKQE0vY0tPAEUPhZm5tx6183gG3D/KwHG728UR0sIufA==} resolution: {integrity: sha512-2y1Mkzcw1niSfG7z3Qx+2ir9Gb4hdTkZe5p/I8x1aTIKQE0vY0tPAEUPhZm5tx6183gG3D/KwHG728UR0sIufA==}
dependencies: dependencies:
@@ -8541,6 +8667,14 @@ packages:
'@vue/shared': 3.4.18 '@vue/shared': 3.4.18
csstype: 3.1.3 csstype: 3.1.3
/@vue/runtime-dom@3.4.21:
resolution: {integrity: sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==}
dependencies:
'@vue/runtime-core': 3.4.21
'@vue/shared': 3.4.21
csstype: 3.1.3
dev: true
/@vue/server-renderer@3.4.18(vue@3.4.18): /@vue/server-renderer@3.4.18(vue@3.4.18):
resolution: {integrity: sha512-YJd1wa7mzUN3NRqLEsrwEYWyO+PUBSROIGlCc3J/cvn7Zu6CxhNLgXa8Z4zZ5ja5/nviYO79J1InoPeXgwBTZA==} resolution: {integrity: sha512-YJd1wa7mzUN3NRqLEsrwEYWyO+PUBSROIGlCc3J/cvn7Zu6CxhNLgXa8Z4zZ5ja5/nviYO79J1InoPeXgwBTZA==}
peerDependencies: peerDependencies:
@@ -8550,9 +8684,23 @@ packages:
'@vue/shared': 3.4.18 '@vue/shared': 3.4.18
vue: 3.4.18(typescript@4.9.5) vue: 3.4.18(typescript@4.9.5)
/@vue/server-renderer@3.4.21(vue@3.4.21):
resolution: {integrity: sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==}
peerDependencies:
vue: 3.4.21
dependencies:
'@vue/compiler-ssr': 3.4.21
'@vue/shared': 3.4.21
vue: 3.4.21(typescript@4.9.5)
dev: true
/@vue/shared@3.4.18: /@vue/shared@3.4.18:
resolution: {integrity: sha512-CxouGFxxaW5r1WbrSmWwck3No58rApXgRSBxrqgnY1K+jk20F6DrXJkHdH9n4HVT+/B6G2CAn213Uq3npWiy8Q==} resolution: {integrity: sha512-CxouGFxxaW5r1WbrSmWwck3No58rApXgRSBxrqgnY1K+jk20F6DrXJkHdH9n4HVT+/B6G2CAn213Uq3npWiy8Q==}
/@vue/shared@3.4.21:
resolution: {integrity: sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==}
dev: true
/@vue/test-utils@1.3.0(vue-template-compiler@2.7.14)(vue@2.7.14): /@vue/test-utils@1.3.0(vue-template-compiler@2.7.14)(vue@2.7.14):
resolution: {integrity: sha512-Xk2Xiyj2k5dFb8eYUKkcN9PzqZSppTlx7LaQWBbdA8tqh3jHr/KHX2/YLhNFc/xwDrgeLybqd+4ZCPJSGPIqeA==} resolution: {integrity: sha512-Xk2Xiyj2k5dFb8eYUKkcN9PzqZSppTlx7LaQWBbdA8tqh3jHr/KHX2/YLhNFc/xwDrgeLybqd+4ZCPJSGPIqeA==}
peerDependencies: peerDependencies:
@@ -8566,18 +8714,11 @@ packages:
vue-template-compiler: 2.7.14(vue@2.7.14) vue-template-compiler: 2.7.14(vue@2.7.14)
dev: true dev: true
/@vue/test-utils@2.4.3(vue@3.4.18): /@vue/test-utils@2.4.5:
resolution: {integrity: sha512-F4K7mF+ad++VlTrxMJVRnenKSJmO6fkQt2wpRDiKDesQMkfpniGWsqEi/JevxGBo2qEkwwjvTUAoiGJLNx++CA==} resolution: {integrity: sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==}
peerDependencies:
'@vue/server-renderer': ^3.0.1
vue: ^3.0.1
peerDependenciesMeta:
'@vue/server-renderer':
optional: true
dependencies: dependencies:
js-beautify: 1.14.9 js-beautify: 1.14.9
vue: 3.4.18(typescript@4.9.5) vue-component-type-helpers: 2.0.7
vue-component-type-helpers: 1.8.27
dev: true dev: true
/@vueuse/components@10.7.2(vue@3.4.18): /@vueuse/components@10.7.2(vue@3.4.18):
@@ -9211,13 +9352,6 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: false dev: false
/array-buffer-byte-length@1.0.0:
resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==}
dependencies:
call-bind: 1.0.6
is-array-buffer: 3.0.4
dev: true
/array-buffer-byte-length@1.0.1: /array-buffer-byte-length@1.0.1:
resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -11013,12 +11147,12 @@ packages:
/deep-equal@2.2.2: /deep-equal@2.2.2:
resolution: {integrity: sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==} resolution: {integrity: sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==}
dependencies: dependencies:
array-buffer-byte-length: 1.0.0 array-buffer-byte-length: 1.0.1
call-bind: 1.0.6 call-bind: 1.0.6
es-get-iterator: 1.1.3 es-get-iterator: 1.1.3
get-intrinsic: 1.2.4 get-intrinsic: 1.2.4
is-arguments: 1.1.1 is-arguments: 1.1.1
is-array-buffer: 3.0.2 is-array-buffer: 3.0.4
is-date-object: 1.0.5 is-date-object: 1.0.5
is-regex: 1.1.4 is-regex: 1.1.4
is-shared-array-buffer: 1.0.2 is-shared-array-buffer: 1.0.2
@@ -11026,11 +11160,11 @@ packages:
object-is: 1.1.5 object-is: 1.1.5
object-keys: 1.1.1 object-keys: 1.1.1
object.assign: 4.1.4 object.assign: 4.1.4
regexp.prototype.flags: 1.5.0 regexp.prototype.flags: 1.5.1
side-channel: 1.0.4 side-channel: 1.0.4
which-boxed-primitive: 1.0.2 which-boxed-primitive: 1.0.2
which-collection: 1.0.1 which-collection: 1.0.1
which-typed-array: 1.1.11 which-typed-array: 1.1.14
dev: true dev: true
/deep-is@0.1.4: /deep-is@0.1.4:
@@ -14156,14 +14290,6 @@ packages:
has-tostringtag: 1.0.2 has-tostringtag: 1.0.2
dev: true dev: true
/is-array-buffer@3.0.2:
resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==}
dependencies:
call-bind: 1.0.6
get-intrinsic: 1.2.4
is-typed-array: 1.1.12
dev: true
/is-array-buffer@3.0.4: /is-array-buffer@3.0.4:
resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -14763,6 +14889,10 @@ packages:
/js-tokens@4.0.0: /js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
/js-tokens@8.0.3:
resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==}
dev: true
/js-yaml@3.14.1: /js-yaml@3.14.1:
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
hasBin: true hasBin: true
@@ -18754,15 +18884,6 @@ packages:
resolution: {integrity: sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==} resolution: {integrity: sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==}
dev: true dev: true
/regexp.prototype.flags@1.5.0:
resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.6
define-properties: 1.2.1
functions-have-names: 1.2.3
dev: true
/regexp.prototype.flags@1.5.1: /regexp.prototype.flags@1.5.1:
resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -20217,6 +20338,12 @@ packages:
acorn: 8.11.3 acorn: 8.11.3
dev: true dev: true
/strip-literal@2.0.0:
resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==}
dependencies:
js-tokens: 8.0.3
dev: true
/strnum@1.0.5: /strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
dev: true dev: true
@@ -21569,6 +21696,27 @@ packages:
- terser - terser
dev: true dev: true
/vite-node@1.4.0:
resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
dependencies:
cac: 6.7.14
debug: 4.3.4
pathe: 1.1.2
picocolors: 1.0.0
vite: 5.0.12
transitivePeerDependencies:
- '@types/node'
- less
- lightningcss
- sass
- stylus
- sugarss
- supports-color
- terser
dev: true
/vite-plugin-singlefile@0.5.1(vite@5.0.13): /vite-plugin-singlefile@0.5.1(vite@5.0.13):
resolution: {integrity: sha512-yA9lWd6bSet0Br4/s34YPNnVBlDl2MbxlHDRrLrBCncD7q+HO5GGsw29Ymp+ydZ3eb4UU2GECgX2MJZW+qnoeQ==} resolution: {integrity: sha512-yA9lWd6bSet0Br4/s34YPNnVBlDl2MbxlHDRrLrBCncD7q+HO5GGsw29Ymp+ydZ3eb4UU2GECgX2MJZW+qnoeQ==}
peerDependencies: peerDependencies:
@@ -21909,6 +22057,61 @@ packages:
- terser - terser
dev: true dev: true
/vitest@1.4.0:
resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@types/node': ^18.0.0 || >=20.0.0
'@vitest/browser': 1.4.0
'@vitest/ui': 1.4.0
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@types/node':
optional: true
'@vitest/browser':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
dependencies:
'@vitest/expect': 1.4.0
'@vitest/runner': 1.4.0
'@vitest/snapshot': 1.4.0
'@vitest/spy': 1.4.0
'@vitest/utils': 1.4.0
acorn-walk: 8.3.2
chai: 4.4.1
debug: 4.3.4
execa: 8.0.1
local-pkg: 0.5.0
magic-string: 0.30.7
pathe: 1.1.2
picocolors: 1.0.0
std-env: 3.7.0
strip-literal: 2.0.0
tinybench: 2.6.0
tinypool: 0.8.2
vite: 5.0.12
vite-node: 1.4.0
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
- lightningcss
- sass
- stylus
- sugarss
- supports-color
- terser
dev: true
/vlq@1.0.1: /vlq@1.0.1:
resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==}
dev: true dev: true
@@ -21918,8 +22121,8 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/vue-component-type-helpers@1.8.27: /vue-component-type-helpers@2.0.7:
resolution: {integrity: sha512-0vOfAtI67UjeO1G6UiX5Kd76CqaQ67wrRZiOe7UAb9Jm6GzlUr/fC7CV90XfwapJRjpCMaZFhv1V0ajWRmE9Dg==} resolution: {integrity: sha512-7e12Evdll7JcTIocojgnCgwocX4WzIYStGClBQ+QuWPinZo/vQolv2EMq4a3lg16TKfwWafLimG77bxb56UauA==}
dev: true dev: true
/vue-demi@0.14.5(vue@3.4.18): /vue-demi@0.14.5(vue@3.4.18):
@@ -21983,6 +22186,22 @@ packages:
'@vue/shared': 3.4.18 '@vue/shared': 3.4.18
typescript: 4.9.5 typescript: 4.9.5
/vue@3.4.21(typescript@4.9.5):
resolution: {integrity: sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@vue/compiler-dom': 3.4.21
'@vue/compiler-sfc': 3.4.21
'@vue/runtime-dom': 3.4.21
'@vue/server-renderer': 3.4.21(vue@3.4.21)
'@vue/shared': 3.4.21
typescript: 4.9.5
dev: true
/w3c-keyname@2.2.8: /w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
dev: false dev: false
@@ -22243,17 +22462,6 @@ packages:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
dev: true dev: true
/which-typed-array@1.1.11:
resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==}
engines: {node: '>= 0.4'}
dependencies:
available-typed-arrays: 1.0.6
call-bind: 1.0.6
for-each: 0.3.3
gopd: 1.0.1
has-tostringtag: 1.0.2
dev: true
/which-typed-array@1.1.14: /which-typed-array@1.1.14:
resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==} resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}

View File

@@ -5,10 +5,10 @@
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "node ./main.mjs" "start": "node ./cli.mjs"
}, },
"bin": { "bin": {
"build-icons": "./main.mjs" "build-icons": "./cli.mjs"
}, },
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"