fix(packages): consistent icon name class (#2878)

* fix: consistent icon name class

* merge classes

* fix vue-next

* update test snapshots

* fix vue-next

* fix test

* fix solid

* proper deduplication

* update snapshots

* preact

* refactor

* deprecated

* refactor tests

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
This commit is contained in:
Dante Issaias
2025-03-21 10:35:54 +00:00
committed by GitHub
parent 6d6aa4c4cc
commit 4835ae67a9
16 changed files with 190 additions and 27 deletions

View File

@@ -26,7 +26,7 @@ import createLucideIcon from '../createLucideIcon';
* @returns {JSX.Element} JSX Element * @returns {JSX.Element} JSX Element
* ${deprecated ? `@deprecated ${deprecationReason}` : ''} * ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/ */
const ${componentName} = createLucideIcon('${componentName}', ${JSON.stringify(children)}); const ${componentName} = createLucideIcon('${iconName}', ${JSON.stringify(children)});
export default ${componentName}; export default ${componentName};
`; `;

View File

@@ -1,5 +1,5 @@
import { h, type JSX } from 'preact'; import { h, type JSX } from 'preact';
import { mergeClasses, toKebabCase } from '@lucide/shared'; import { mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import Icon from './Icon'; import Icon from './Icon';
import type { IconNode, LucideIcon, LucideProps } from './types'; import type { IconNode, LucideIcon, LucideProps } from './types';
@@ -17,6 +17,7 @@ const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
...props, ...props,
iconNode, iconNode,
class: mergeClasses<string | JSX.SignalLike<string | undefined>>( class: mergeClasses<string | JSX.SignalLike<string | undefined>>(
`lucide-${toKebabCase(toPascalCase(iconName))}`,
`lucide-${toKebabCase(iconName)}`, `lucide-${toKebabCase(iconName)}`,
classes, classes,
), ),
@@ -24,7 +25,7 @@ const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
children, children,
); );
Component.displayName = `${iconName}`; Component.displayName = toPascalCase(iconName);
return Component; return Component;
}; };

View File

@@ -27,3 +27,59 @@ exports[`Using createLucideIcon > should create a component from an iconNode 1`]
/> />
</svg> </svg>
`; `;
exports[`Using createLucideIcon > should create a component from an iconNode with iconName 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>
`;
exports[`Using createLucideIcon > should include backwards compatible className 1`] = `
<svg
class="lucide lucide-layout2 lucide-layout-2"
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,7 +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 lucide-grid-3x3"
> >
<rect width="18" <rect width="18"
height="18" height="18"
@@ -40,7 +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 lucide-grid-3x3"
> >
<rect width="18" <rect width="18"
height="18" height="18"
@@ -70,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 lucide-grid-3x3"
> >
<rect width="18" <rect width="18"
height="18" height="18"

View File

@@ -12,4 +12,22 @@ describe('Using createLucideIcon', () => {
expect(container.firstChild).toMatchSnapshot(); expect(container.firstChild).toMatchSnapshot();
expect(container.firstChild).toBeDefined(); expect(container.firstChild).toBeDefined();
}); });
it('should create a component from an iconNode with iconName', () => {
const AirVent = createLucideIcon('air-vent', airVent);
const { container } = render(<AirVent />);
expect(container.firstChild).toMatchSnapshot();
expect(container.firstChild).toBeDefined();
});
it('should include backwards compatible className', () => {
const Layout2 = createLucideIcon('layout-2', airVent);
const { container } = render(<Layout2 />);
expect(container.firstChild).toMatchSnapshot();
expect(container.firstChild).toBeDefined();
});
}); });

View File

@@ -29,7 +29,7 @@ export const __iconNode: IconNode = ${JSON.stringify(children)}
* @returns {JSX.Element} JSX Element * @returns {JSX.Element} JSX Element
* ${deprecated ? `@deprecated ${deprecationReason}` : ''} * ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/ */
const ${componentName} = createLucideIcon('${componentName}', __iconNode); const ${componentName} = createLucideIcon('${iconName}', __iconNode);
export default ${componentName}; export default ${componentName};
`; `;

View File

@@ -1,5 +1,5 @@
import { createElement, forwardRef } from 'react'; import { createElement, forwardRef } from 'react';
import { mergeClasses, toKebabCase } from '@lucide/shared'; import { mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import { IconNode, LucideProps } from './types'; import { IconNode, LucideProps } from './types';
import Icon from './Icon'; import Icon from './Icon';
@@ -14,12 +14,16 @@ const createLucideIcon = (iconName: string, iconNode: IconNode) => {
createElement(Icon, { createElement(Icon, {
ref, ref,
iconNode, iconNode,
className: mergeClasses(`lucide-${toKebabCase(iconName)}`, className), className: mergeClasses(
`lucide-${toKebabCase(toPascalCase(iconName))}`,
`lucide-${iconName}`,
className,
),
...props, ...props,
}), }),
); );
Component.displayName = `${iconName}`; Component.displayName = toPascalCase(iconName);
return Component; return Component;
}; };

View File

@@ -1,6 +1,34 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using createLucideIcon > should create a component from an iconNode 1`] = ` exports[`Using createLucideIcon > should create a component from an iconNode 1`] = `
<svg
class="lucide lucide-air-vent lucide-AirVent"
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>
`;
exports[`Using createLucideIcon > should create a component from an iconNode with iconName 1`] = `
<svg <svg
class="lucide lucide-air-vent" class="lucide lucide-air-vent"
fill="none" fill="none"
@@ -27,3 +55,31 @@ exports[`Using createLucideIcon > should create a component from an iconNode 1`]
/> />
</svg> </svg>
`; `;
exports[`Using createLucideIcon > should include backwards compatible className 1`] = `
<svg
class="lucide lucide-layout2 lucide-layout-2"
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,7 +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 lucide-grid-3x3"
> >
<rect width="18" <rect width="18"
height="18" height="18"
@@ -40,7 +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 lucide-grid-3x3"
> >
<rect width="18" <rect width="18"
height="18" height="18"
@@ -70,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 lucide-grid-3x3"
> >
<rect width="18" <rect width="18"
height="18" height="18"

View File

@@ -12,4 +12,22 @@ describe('Using createLucideIcon', () => {
expect(container.firstChild).toMatchSnapshot(); expect(container.firstChild).toMatchSnapshot();
expect(container.firstChild).toBeDefined(); expect(container.firstChild).toBeDefined();
}); });
it('should create a component from an iconNode with iconName', () => {
const AirVent = createLucideIcon('air-vent', airVent);
const { container } = render(<AirVent />);
expect(container.firstChild).toMatchSnapshot();
expect(container.firstChild).toBeDefined();
});
it('should include backwards compatible className', () => {
const Layout2 = createLucideIcon('layout-2', airVent);
const { container } = render(<Layout2 />);
expect(container.firstChild).toMatchSnapshot();
expect(container.firstChild).toBeDefined();
});
}); });

View File

@@ -30,7 +30,7 @@ const iconNode: IconNode = ${JSON.stringify(children)};
* ${deprecated ? `@deprecated ${deprecationReason}` : ''} * ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/ */
const ${componentName} = (props: LucideProps) => ( const ${componentName} = (props: LucideProps) => (
<Icon {...props} name="${componentName}" iconNode={iconNode} /> <Icon {...props} iconNode={iconNode} name="${iconName}" />
) )
export default ${componentName}; export default ${componentName};

View File

@@ -2,7 +2,7 @@ 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 { mergeClasses, toKebabCase } from '@lucide/shared'; import { mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
interface IconProps { interface IconProps {
name?: string; name?: string;
@@ -36,7 +36,12 @@ const Icon = (props: LucideProps & IconProps) => {
class={mergeClasses( class={mergeClasses(
'lucide', 'lucide',
'lucide-icon', 'lucide-icon',
localProps.name != null ? `lucide-${toKebabCase(localProps?.name)}` : undefined, ...(localProps.name != null
? [
`lucide-${toKebabCase(toPascalCase(localProps.name))}`,
`lucide-${toKebabCase(localProps.name)}`,
]
: []),
localProps.class != null ? localProps.class : '', localProps.class != null ? localProps.class : '',
)} )}
{...rest} {...rest}

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-icon lucide-grid3x3" class="lucide lucide-icon lucide-grid3x3 lucide-grid-3x3"
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-icon lucide-grid3x3" class="lucide lucide-icon lucide-grid3x3 lucide-grid-3x3"
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-icon lucide-grid3x3" class="lucide lucide-icon lucide-grid3x3 lucide-grid-3x3"
> >
<rect width="18" <rect width="18"
height="18" height="18"

View File

@@ -26,7 +26,7 @@ import createLucideIcon from '../createLucideIcon';
* @returns {FunctionalComponent} Vue component * @returns {FunctionalComponent} Vue component
* ${deprecated ? `@deprecated ${deprecationReason}` : ''} * ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/ */
const ${componentName} = createLucideIcon('${componentName}Icon', ${JSON.stringify(children)}); const ${componentName} = createLucideIcon('${iconName}', ${JSON.stringify(children)});
export default ${componentName}; export default ${componentName};
`; `;

View File

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

View File

@@ -3,7 +3,7 @@
exports[`Using lucide icon components > should add a class to the element 1`] = ` exports[`Using lucide icon components > should add a class to the element 1`] = `
<div> <div>
<svg <svg
class="lucide lucide-smile-icon my-icon" class="lucide lucide-smile-icon lucide-smile my-icon"
fill="none" fill="none"
height="24" height="24"
stroke="currentColor" stroke="currentColor"
@@ -41,7 +41,7 @@ exports[`Using lucide icon components > should add a class to the element 1`] =
exports[`Using lucide icon components > should add a style attribute to the element 1`] = ` exports[`Using lucide icon components > should add a style attribute to the element 1`] = `
<div> <div>
<svg <svg
class="lucide lucide-smile-icon" class="lucide lucide-smile-icon lucide-smile"
fill="none" fill="none"
height="24" height="24"
stroke="currentColor" stroke="currentColor"
@@ -80,7 +80,7 @@ exports[`Using lucide icon components > should add a style attribute to the elem
exports[`Using lucide icon components > should adjust the size, stroke color and stroke width 1`] = ` exports[`Using lucide icon components > should adjust the size, stroke color and stroke width 1`] = `
<div> <div>
<svg <svg
class="lucide lucide-smile-icon" class="lucide lucide-smile-icon lucide-smile"
fill="none" fill="none"
height="48" height="48"
stroke="red" stroke="red"
@@ -118,7 +118,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
exports[`Using lucide icon components > should pass children to the icon slot 1`] = ` exports[`Using lucide icon components > should pass children to the icon slot 1`] = `
<div> <div>
<svg <svg
class="lucide lucide-smile-icon" class="lucide lucide-smile-icon lucide-smile"
fill="none" fill="none"
height="24" height="24"
stroke="currentColor" stroke="currentColor"
@@ -161,7 +161,7 @@ exports[`Using lucide icon components > should pass children to the icon slot 1`
exports[`Using lucide icon components > should render an component 1`] = ` exports[`Using lucide icon components > should render an component 1`] = `
<div> <div>
<svg <svg
class="lucide lucide-smile-icon" class="lucide lucide-smile-icon lucide-smile"
fill="none" fill="none"
height="24" height="24"
stroke="currentColor" stroke="currentColor"