Skip to content

Commit e50582e

Browse files
authored
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
1 parent 65deefa commit e50582e

File tree

77 files changed

+1706
-429
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1706
-429
lines changed

.prettierignore

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ pnpm-lock.yaml
22

33
# docs examples
44
docs/**/examples/
5+
docs/.vitepress/.temp
6+
docs/.vitepress/cache
7+
docs/.vitepress/data
8+
docs/.nitro
59

610
# lucide-angular
711
packages/lucide-angular/.angular/cache

docs/guide/packages/lucide-angular.md

+17
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,20 @@ import { icons } from 'lucide-angular';
115115

116116
LucideAngularModule.pick(icons)
117117
```
118+
119+
## With Lucide lab or custom icons
120+
121+
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
122+
They can be used in the same way as the official icons.
123+
124+
```js
125+
import { LucideAngularModule } from 'lucide-angular';
126+
import { burger } from '@lucide/lab';
127+
128+
@NgModule({
129+
imports: [
130+
LucideAngularModule.pick({ burger })
131+
]
132+
})
133+
export class AppModule { }
134+
```

docs/guide/packages/lucide-preact.md

+20
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ const App = () => {
6767

6868
> 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).
6969
70+
## With Lucide lab or custom icons
71+
72+
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
73+
74+
They can be used by using the `Icon` component.
75+
All props like regular lucide icons can be passed to adjust the icon appearance.
76+
77+
### Using the `Icon` component
78+
79+
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
80+
81+
```jsx
82+
import { Icon } from 'lucide-preact';
83+
import { burger } from '@lucide/lab';
84+
85+
const App = () => (
86+
<Icon iconNode={burger} />
87+
);
88+
```
89+
7090
## One generic icon component
7191

7292
It is possible to create one generic icon component to load icons, but it is not recommended.

docs/guide/packages/lucide-react-native.md

+20
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,26 @@ const App = () => {
6161
};
6262
```
6363

64+
## With Lucide lab or custom icons
65+
66+
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
67+
68+
They can be used by using the `Icon` component.
69+
All props like regular lucide icons can be passed to adjust the icon appearance.
70+
71+
### Using the `Icon` component
72+
73+
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
74+
75+
```jsx
76+
import { Icon } from 'lucide-react-native';
77+
import { burger } from '@lucide/lab';
78+
79+
const App = () => (
80+
<Icon iconNode={burger} />
81+
);
82+
```
83+
6484
## One generic icon component
6585

6686
It is possible to create one generic icon component to load icons, but it is not recommended.

docs/guide/packages/lucide-react.md

+20
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,26 @@ const App = () => {
6161
};
6262
```
6363

64+
## With Lucide lab or custom icons
65+
66+
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
67+
68+
They can be used by using the `Icon` component.
69+
All props like regular lucide icons can be passed to adjust the icon appearance.
70+
71+
### Using the `Icon` component
72+
73+
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
74+
75+
```jsx
76+
import { Icon } from 'lucide-react';
77+
import { burger } from '@lucide/lab';
78+
79+
const App = () => (
80+
<Icon iconNode={burger} />
81+
);
82+
```
83+
6484
## One generic icon component
6585

6686
It is possible to create one generic icon component to load icons, but it is not recommended.

docs/guide/packages/lucide-solid.md

+20
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,26 @@ const App = () => {
6161
};
6262
```
6363

64+
## With Lucide lab or custom icons
65+
66+
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
67+
68+
They can be used by using the `Icon` component.
69+
All props like the regular Lucide icons can be passed to adjust the icon appearance.
70+
71+
### Using the `Icon` component
72+
73+
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
74+
75+
```jsx
76+
import { Icon } from 'lucide-solid';
77+
import { burger, sausage } from '@lucide/lab';
78+
79+
const App = () => (
80+
<Icon iconNode={sausage} color="red"/>
81+
);
82+
```
83+
6484
## One generic icon component
6585

6686
It is possible to create one generic icon component to load icons. It's not recommended.

docs/guide/packages/lucide-svelte.md

+21
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,27 @@ The package includes type definitions for all icons. This is useful if you want
166166

167167
For more details about typing the `svelte:component` directive, see the [Svelte documentation](https://svelte.dev/docs/typescript#types-componenttype).
168168

169+
## With Lucide lab or custom icons
170+
171+
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
172+
173+
They can be used by using the `Icon` component.
174+
All props like the regular Lucide icons can be passed to adjust the icon appearance.
175+
176+
### Using the `Icon` component
177+
178+
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
179+
180+
```svelte
181+
<script>
182+
import { Icon } from 'lucide-svelte';
183+
import { burger, sausage } from '@lucide/lab';
184+
</script>
185+
186+
<Icon iconNode={burger} />
187+
<Icon iconNode={sausage} color="red"/>
188+
```
189+
169190
## One generic icon component
170191

171192
It is possible to create one generic icon component to load icons, but it is not recommended.

docs/guide/packages/lucide-vue-next.md

+26-4
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ Each icon can be imported as a Vue component, which renders an inline SVG Elemen
3737
You can pass additional props to adjust the icon.
3838

3939
```vue
40+
<script setup>
41+
import { Camera } from 'lucide-vue-next';
42+
</script>
43+
4044
<template>
4145
<Camera
4246
color="red"
4347
:size="32"
4448
/>
4549
</template>
46-
47-
<script setup>
48-
import { Camera } from 'lucide-vue-next';
49-
</script>
5050
```
5151

5252
## Props
@@ -69,6 +69,28 @@ To customize the appearance of an icon, you can pass custom properties as props
6969
</template>
7070
```
7171

72+
## With Lucide lab or custom icons
73+
74+
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
75+
76+
They can be used by using the `Icon` component.
77+
All props like regular lucide icons can be passed to adjust the icon appearance.
78+
79+
### Using the `Icon` component
80+
81+
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
82+
83+
```vue
84+
<script setup>
85+
import { Icon } from 'lucide-vue-next';
86+
import { burger } from '@lucide/lab';
87+
</script>
88+
89+
<template>
90+
<Icon :iconNode={burger} />
91+
</template>
92+
```
93+
7294
## One generic icon component
7395

7496
It is possible to create one generic icon component to load icons, but it is not recommended.

docs/guide/packages/lucide.md

+15
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,18 @@ menuIcon.classList.add('my-icon-class');
130130
const myApp = document.getElementById('app');
131131
myApp.appendChild(menuIcon);
132132
```
133+
134+
### With Lucide lab or custom icons
135+
136+
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
137+
They can be used in the same way as the official icons.
138+
139+
```js
140+
import { burger } from '@lucide/lab';
141+
142+
createIcons({
143+
icons: {
144+
burger
145+
}
146+
});
147+
```

packages/lucide-preact/src/Icon.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { h, toChildArray } from 'preact';
2+
import defaultAttributes from './defaultAttributes';
3+
import type { IconNode, LucideProps } from './types';
4+
5+
interface IconComponentProps extends LucideProps {
6+
iconNode: IconNode;
7+
}
8+
9+
/**
10+
* Lucide icon component
11+
*
12+
* @component Icon
13+
* @param {object} props
14+
* @param {string} props.color - The color of the icon
15+
* @param {number} props.size - The size of the icon
16+
* @param {number} props.strokeWidth - The stroke width of the icon
17+
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
18+
* @param {string} props.class - The class name of the icon
19+
* @param {IconNode} props.children - The children of the icon
20+
* @param {IconNode} props.iconNode - The icon node of the icon
21+
*
22+
* @returns {ForwardRefExoticComponent} LucideIcon
23+
*/
24+
const Icon = ({
25+
color = 'currentColor',
26+
size = 24,
27+
strokeWidth = 2,
28+
absoluteStrokeWidth,
29+
children,
30+
iconNode,
31+
class: classes = '',
32+
...rest
33+
}: IconComponentProps) =>
34+
h(
35+
'svg',
36+
{
37+
...defaultAttributes,
38+
width: String(size),
39+
height: size,
40+
stroke: color,
41+
['stroke-width' as 'strokeWidth']: absoluteStrokeWidth
42+
? (Number(strokeWidth) * 24) / Number(size)
43+
: strokeWidth,
44+
class: ['lucide', classes].join(' '),
45+
...rest,
46+
},
47+
[...iconNode.map(([tag, attrs]) => h(tag, attrs)), ...toChildArray(children)],
48+
);
49+
50+
export default Icon;

packages/lucide-preact/src/createLucideIcon.ts

+13-34
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
1-
import { type FunctionComponent, h, type JSX, toChildArray } from 'preact';
2-
import defaultAttributes from './defaultAttributes';
3-
import { toKebabCase } from '@lucide/shared';
4-
5-
export type IconNode = [elementName: keyof JSX.IntrinsicElements, attrs: Record<string, string>][];
6-
7-
export interface LucideProps extends Partial<Omit<JSX.SVGAttributes, 'ref' | 'size'>> {
8-
color?: string;
9-
size?: string | number;
10-
strokeWidth?: string | number;
11-
absoluteStrokeWidth?: boolean;
12-
}
13-
14-
export type LucideIcon = FunctionComponent<LucideProps>;
1+
import { h, type JSX } from 'preact';
2+
import { mergeClasses, toKebabCase } from '@lucide/shared';
3+
import Icon from './Icon';
4+
import type { IconNode, LucideIcon, LucideProps } from './types';
155

166
/**
177
* Create a Lucide icon component
@@ -20,29 +10,18 @@ export type LucideIcon = FunctionComponent<LucideProps>;
2010
* @returns {FunctionComponent} LucideIcon
2111
*/
2212
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
23-
const Component = ({
24-
color = 'currentColor',
25-
size = 24,
26-
strokeWidth = 2,
27-
absoluteStrokeWidth,
28-
children,
29-
class: classes = '',
30-
...rest
31-
}: LucideProps) =>
13+
const Component = ({ class: classes = '', children, ...props }: LucideProps) =>
3214
h(
33-
'svg',
15+
Icon,
3416
{
35-
...defaultAttributes,
36-
width: String(size),
37-
height: size,
38-
stroke: color,
39-
['stroke-width' as 'strokeWidth']: absoluteStrokeWidth
40-
? (Number(strokeWidth) * 24) / Number(size)
41-
: strokeWidth,
42-
class: ['lucide', `lucide-${toKebabCase(iconName)}`, classes].join(' '),
43-
...rest,
17+
...props,
18+
iconNode,
19+
class: mergeClasses<string | JSX.SignalLike<string | undefined>>(
20+
`lucide-${toKebabCase(iconName)}`,
21+
classes,
22+
),
4423
},
45-
[...iconNode.map(([tag, attrs]) => h(tag, attrs)), ...toChildArray(children)],
24+
children,
4625
);
4726

4827
Component.displayName = `${iconName}`;
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
export * from './icons';
22
export * as icons from './icons';
33
export * from './aliases';
4+
export * from './types';
5+
46
export { default as createLucideIcon } from './createLucideIcon';
7+
export { default as Icon } from './Icon';

packages/lucide-preact/src/types.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { type FunctionComponent, type JSX } from 'preact';
2+
3+
export type IconNode = [elementName: keyof JSX.IntrinsicElements, attrs: Record<string, string>][];
4+
5+
export interface LucideProps extends Partial<Omit<JSX.SVGAttributes, 'ref' | 'size'>> {
6+
color?: string;
7+
size?: string | number;
8+
strokeWidth?: string | number;
9+
absoluteStrokeWidth?: boolean;
10+
}
11+
12+
export type LucideIcon = FunctionComponent<LucideProps>;

0 commit comments

Comments
 (0)