Compare commits

...

10 Commits

Author SHA1 Message Date
Eric Fennis
ba3d66dc53 Merge branch 'main' of https://github.com/lucide-icons/lucide into backwards-compatible-classnames 2025-05-02 15:44:38 +02:00
Eric Fennis
c663a141b6 Update snapshots 2025-04-04 16:27:24 +02:00
Eric Fennis
35a1ec15cc Merge branch 'main' of https://github.com/lucide-icons/lucide into backwards-compatible-classnames 2025-04-04 14:46:42 +02:00
Eric Fennis
8a44e63e80 Adjust classes lucide vue next 2025-03-22 11:26:25 +01:00
Eric Fennis
35afa59c99 Add support for icon alias classnames in lucide-svelte 2025-03-21 11:18:03 +01:00
Eric Fennis
c42676b0fd Add support for alias classNames in lucide-static 2025-03-21 10:56:54 +01:00
Eric Fennis
613cac84d0 Add support for alias classnames in lucide-solid 2025-03-19 11:38:26 +01:00
Eric Fennis
cf48dbb2d3 Add support for preact 2025-03-19 11:14:07 +01:00
Eric Fennis
4edcf24fc9 Support aliased class names in lucide-react 2025-03-19 10:58:24 +01:00
Eric Fennis
1efc9eb8b5 Add support for aliases names in export template 2025-03-19 10:51:36 +01:00
39 changed files with 152 additions and 72 deletions

View File

@@ -14,5 +14,5 @@
<path d="M9 9h.01" />
<circle cx="20" cy="16" r="2" />
<circle cx="9" cy="9" r="7" />
<rect x="4" y="16" width="10" height="6" rx="2" />
<rect width="10" height="6" x="4" y="16" rx="2" />
</svg>

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 439 B

View File

@@ -13,5 +13,5 @@
<path d="M16 4h2a2 2 0 0 1 2 2v1.344" />
<path d="m17 18 4-4-4-4" />
<path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 1.793-1.113" />
<rect x="8" y="2" width="8" height="4" rx="1" />
<rect width="8" height="4" x="8" y="2" rx="1" />
</svg>

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 435 B

View File

@@ -21,6 +21,6 @@
<path d="M20 7h2" />
<path d="M7 20v2" />
<path d="M7 2v2" />
<rect x="4" y="4" width="16" height="16" rx="2" />
<rect x="8" y="8" width="8" height="8" rx="1" />
<rect width="16" height="16" x="4" y="4" rx="2" />
<rect width="8" height="8" x="8" y="8" rx="1" />
</svg>

Before

Width:  |  Height:  |  Size: 590 B

After

Width:  |  Height:  |  Size: 590 B

View File

@@ -12,5 +12,5 @@
<path d="m13 21-3-3 3-3" />
<path d="M20 18H10" />
<path d="M3 11h.01" />
<rect x="6" y="3" width="5" height="8" rx="2.5" />
<rect width="5" height="8" x="6" y="3" rx="2.5" />
</svg>

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 341 B

View File

@@ -12,6 +12,6 @@
<path d="M10 18h10" />
<path d="m17 21 3-3-3-3" />
<path d="M3 11h.01" />
<rect x="15" y="3" width="5" height="8" rx="2.5" />
<rect x="6" y="3" width="5" height="8" rx="2.5" />
<rect width="5" height="8" x="15" y="3" rx="2.5" />
<rect width="5" height="8" x="6" y="3" rx="2.5" />
</svg>

Before

Width:  |  Height:  |  Size: 395 B

After

Width:  |  Height:  |  Size: 395 B

View File

@@ -13,5 +13,5 @@
<path d="M18 9V6a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v14" />
<path d="M2 20h8" />
<path d="M20 17v-2a2 2 0 1 0-4 0v2" />
<rect x="14" y="17" width="8" height="5" rx="1" />
<rect width="8" height="5" x="14" y="17" rx="1" />
</svg>

Before

Width:  |  Height:  |  Size: 407 B

After

Width:  |  Height:  |  Size: 407 B

View File

@@ -10,5 +10,5 @@
stroke-linejoin="round"
>
<path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" />
<rect x="2" y="4" width="20" height="16" rx="2" />
<rect width="20" height="16" x="2" y="4" rx="2" />
</svg>

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 316 B

View File

@@ -10,8 +10,8 @@
stroke-linejoin="round"
>
<path d="M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z" />
<circle cx="13.5" cy="6.5" r=".5" fill="currentColor" />
<circle cx="17.5" cy="10.5" r=".5" fill="currentColor" />
<circle cx="6.5" cy="12.5" r=".5" fill="currentColor" />
<circle cx="8.5" cy="7.5" r=".5" fill="currentColor" />
<circle cx="13.5" cy="6.5" r=".5" />
<circle cx="17.5" cy="10.5" r=".5" />
<circle cx="6.5" cy="12.5" r=".5" />
<circle cx="8.5" cy="7.5" r=".5" />
</svg>

Before

Width:  |  Height:  |  Size: 571 B

After

Width:  |  Height:  |  Size: 491 B

View File

@@ -15,5 +15,5 @@
<path d="M20 8V4" />
<path d="M4 8V4" />
<path d="M8 15v-3.014" />
<rect x="3" y="12" width="18" height="7" rx="1" />
<rect width="18" height="7" x="3" y="12" rx="1" />
</svg>

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 415 B

View File

@@ -39,6 +39,7 @@
"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",
"test": "pnpm build:icons && vitest run",
"test:update": "vitest run -u",
"version": "pnpm version --git-tag-version=false"
},
"devDependencies": {

View File

@@ -8,6 +8,7 @@ export default async ({
getSvg,
deprecated,
deprecationReason,
iconNameAliases,
}) => {
const svgContents = await getSvg();
const svgBase64 = base64SVG(svgContents);
@@ -26,7 +27,9 @@ import createLucideIcon from '../createLucideIcon';
* @returns {JSX.Element} JSX Element
* ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/
const ${componentName} = createLucideIcon('${iconName}', ${JSON.stringify(children)});
const ${componentName} = createLucideIcon('${componentName}', ${JSON.stringify(children)}${
iconNameAliases != null ? `, ${JSON.stringify(iconNameAliases)}` : ''
});
export default ${componentName};
`;

View File

@@ -1,5 +1,5 @@
import { h, type JSX } from 'preact';
import { mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import { createLucideClassNames, mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import Icon from './Icon';
import type { IconNode, LucideIcon, LucideProps } from './types';
@@ -9,7 +9,12 @@ import type { IconNode, LucideIcon, LucideProps } from './types';
* @param {array} iconNode
* @returns {FunctionComponent} LucideIcon
*/
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
const createLucideIcon = (iconName: string, iconNode: IconNode, aliasNames: string[] = []): LucideIcon => {
const lucideClassNames = createLucideClassNames([
iconName,
...aliasNames,
]);
const Component = ({ class: classes = '', children, ...props }: LucideProps) =>
h(
Icon,
@@ -17,8 +22,8 @@ const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
...props,
iconNode,
class: mergeClasses<string | JSX.SignalLike<string | undefined>>(
lucideClassNames,
`lucide-${toKebabCase(toPascalCase(iconName))}`,
`lucide-${toKebabCase(iconName)}`,
classes,
),
},

View File

@@ -58,7 +58,7 @@ exports[`Using createLucideIcon > should create a component from an iconNode wit
exports[`Using createLucideIcon > should include backwards compatible className 1`] = `
<svg
class="lucide lucide-layout2 lucide-layout-2"
class="lucide lucide-layout-2 lucide-layout2"
fill="none"
height="24"
stroke="currentColor"

View File

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

View File

@@ -70,6 +70,16 @@ describe('Using lucide icon components', () => {
expect(PenIconRenderedHTML).toBe(Edit2Container.innerHTML);
});
it('should render the alias icon name classNames', () => {
const { container } = render(
<Pen />,
);
const PenIcon = container.firstChild;
expect(PenIcon).toHaveClass('lucide-edit-2');
})
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const { container } = render(
<Grid

View File

@@ -47,6 +47,7 @@
"typecheck": "tsc",
"typecheck:watch": "tsc -w",
"test": "pnpm build:icons && vitest run",
"test:update": "vitest run -u",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false"
},

View File

@@ -8,6 +8,7 @@ export default async ({
getSvg,
deprecated,
deprecationReason,
iconNameAliases,
}) => {
const svgContents = await getSvg();
const svgBase64 = base64SVG(svgContents);
@@ -29,7 +30,9 @@ export const __iconNode: IconNode = ${JSON.stringify(children)}
* @returns {JSX.Element} JSX Element
* ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/
const ${componentName} = createLucideIcon('${iconName}', __iconNode);
const ${componentName} = createLucideIcon('${iconName}', __iconNode${
iconNameAliases != null ? `, ${JSON.stringify(iconNameAliases)}` : ''
});
export default ${componentName};
`;

View File

@@ -1,5 +1,5 @@
import { createElement, forwardRef } from 'react';
import { mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import { mergeClasses, createLucideClassNames, toPascalCase, toKebabCase } from '@lucide/shared';
import { IconNode, LucideProps } from './types';
import Icon from './Icon';
@@ -9,14 +9,19 @@ import Icon from './Icon';
* @param {array} iconNode
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const createLucideIcon = (iconName: string, iconNode: IconNode) => {
const createLucideIcon = (iconName: string, iconNode: IconNode, aliasNames: string[] = []) => {
const lucideClassNames = createLucideClassNames([
iconName,
...aliasNames,
]);
const Component = forwardRef<SVGSVGElement, LucideProps>(({ className, ...props }, ref) =>
createElement(Icon, {
ref,
iconNode,
className: mergeClasses(
`lucide-${toKebabCase(toPascalCase(iconName))}`,
`lucide-${iconName}`,
lucideClassNames,
className,
),
...props,

View File

@@ -2,8 +2,7 @@
exports[`Using createLucideIcon > should create a component from an iconNode 1`] = `
<svg
aria-hidden="true"
class="lucide lucide-air-vent lucide-AirVent"
class="lucide lucide-air-vent"
fill="none"
height="24"
stroke="currentColor"

View File

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

View File

@@ -69,6 +69,16 @@ describe('Using lucide icon components', () => {
expect(PenIconRenderedHTML).toBe(Edit2Container.innerHTML);
});
it('should render the alias icon name classNames', () => {
const { container } = render(
<Pen />,
);
const PenIcon = container.firstChild;
expect(PenIcon).toHaveClass('lucide-edit-2');
})
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const { container, getByTestId } = render(
<Grid

View File

@@ -63,6 +63,7 @@
"build:bundle": "rollup -c rollup.config.mjs",
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mjs --renderUniqueKey --withAliases --separateAliasesFile --aliasesFileExtension=.ts --iconFileExtension=.tsx --exportFileName=index.ts",
"test": "pnpm build:icons && vitest run",
"test:update": "vitest run -u",
"version": "pnpm version --git-tag-version=false"
},
"devDependencies": {

View File

@@ -8,6 +8,7 @@ export default async ({
getSvg,
deprecated,
deprecationReason,
iconNameAliases,
}) => {
const svgContents = await getSvg();
const svgBase64 = base64SVG(svgContents);
@@ -30,7 +31,9 @@ const iconNode: IconNode = ${JSON.stringify(children)};
* ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/
const ${componentName} = (props: LucideProps) => (
<Icon {...props} iconNode={iconNode} name="${iconName}" />
<Icon {...props} name="${iconName}" iconNode={iconNode}${
iconNameAliases != null ? ` aliasNames={${JSON.stringify(iconNameAliases)}}` : ''
} />
)
export default ${componentName};

View File

@@ -2,11 +2,12 @@ import { For, splitProps } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types';
import { mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import { mergeClasses, toKebabCase, createLucideClassNames, toPascalCase } from '@lucide/shared';
interface IconProps {
name?: string;
iconNode: IconNode;
aliasNames?: string[];
}
const Icon = (props: LucideProps & IconProps) => {
@@ -19,6 +20,7 @@ const Icon = (props: LucideProps & IconProps) => {
'name',
'iconNode',
'absoluteStrokeWidth',
'aliasNames'
]);
return (
@@ -42,6 +44,7 @@ const Icon = (props: LucideProps & IconProps) => {
`lucide-${toKebabCase(localProps.name)}`,
]
: []),
localProps.aliasNames != null ? createLucideClassNames(localProps.aliasNames) : undefined,
localProps.class != null ? localProps.class : '',
)}
{...rest}

View File

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

View File

@@ -10,31 +10,27 @@ describe('Using lucide icon components', () => {
});
it('should adjust the size, stroke color and stroke width', async () => {
const testId = 'grid-icon';
const { container, getByTestId } = render(() => (
<Grid
data-testid={testId}
size={48}
color="red"
strokeWidth={4}
/>
));
const { attributes } = (await getByTestId(testId)) as unknown as {
attributes: Record<string, { value: string }>;
};
expect(attributes.stroke.value).toBe('red');
expect(attributes.width.value).toBe('48');
expect(attributes.height.value).toBe('48');
expect(attributes['stroke-width'].value).toBe('4');
const gridIcon = container.firstChild as SVGElement;
expect(gridIcon).toHaveAttribute('width', '48');
expect(gridIcon).toHaveAttribute('height', '48');
expect(gridIcon).toHaveAttribute('stroke', 'red');
expect(gridIcon).toHaveAttribute('stroke-width', '4');
expect(container.innerHTML).toMatchSnapshot();
});
it('should render the alias icon', () => {
const testId = 'pen-icon';
const { container } = render(() => (
<Pen
data-testid={testId}
size={48}
stroke="red"
strokeWidth={4}
@@ -47,7 +43,6 @@ describe('Using lucide icon components', () => {
const { container: Edit2Container } = render(() => (
<Edit2
data-testid={testId}
size={48}
stroke="red"
strokeWidth={4}
@@ -57,24 +52,31 @@ describe('Using lucide icon components', () => {
expect(PenIconRenderedHTML).toBe(Edit2Container.innerHTML);
});
it('should render the alias icon name classNames', () => {
const { container } = render(() => (
<Pen />
));
const PenIcon = container.firstChild;
expect(PenIcon).toHaveClass('lucide-edit-2');
})
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'grid-icon';
const { container, getByTestId } = render(() => (
<Grid
data-testid={testId}
size={48}
color="red"
absoluteStrokeWidth
/>
));
const { attributes } = getByTestId(testId) as unknown as {
attributes: Record<string, { value: string }>;
};
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');
const gridIcon = container.firstChild as SVGElement;
expect(gridIcon).toHaveAttribute('stroke-width', '1');
expect(gridIcon).toHaveAttribute('width', '48');
expect(gridIcon).toHaveAttribute('height', '48');
expect(gridIcon).toHaveAttribute('stroke', 'red');
expect(container.innerHTML).toMatchSnapshot();
});

View File

@@ -1,15 +1,17 @@
/* eslint-disable import/no-extraneous-dependencies */
import base64SVG from '@lucide/build-icons/utils/base64SVG.mjs';
export default async ({ componentName, iconName, getSvg, deprecated, deprecationReason }) => {
export default async ({ componentName, iconName, getSvg, deprecated, deprecationReason, iconNameAliases = [] }) => {
let svgContents = await getSvg();
const svgBase64 = base64SVG(svgContents);
const iconClassNames = [iconName, ...iconNameAliases].map((aliasName) => `lucide-${aliasName}`).join(' ')
svgContents = svgContents.replace(
'<svg',
`
<svg
class="lucide lucide-${iconName}"`,
class="lucide ${iconClassNames}"`,
);
return `

View File

@@ -9,6 +9,7 @@ export default async ({
getSvg,
deprecated,
deprecationReason,
iconNameAliases
}) => {
const svgContents = await getSvg();
const svgBase64 = base64SVG(svgContents);
@@ -36,7 +37,9 @@ const iconNode: IconNode = ${JSON.stringify(children)};
*/
</script>
<Icon name="${iconName}" {...$$props} iconNode={iconNode}>
<Icon name="${iconName}" {...$$props} iconNode={iconNode}${
iconNameAliases != null ? ` aliasNames={${JSON.stringify(iconNameAliases)}}` : ''
}>
<slot/>
</Icon>
`;

View File

@@ -8,6 +8,7 @@
export let strokeWidth: number | string = 2
export let absoluteStrokeWidth: boolean = false
export let iconNode: IconNode = []
export let aliasNames: string[] = []
const mergeClasses = <ClassType = string | undefined | null>(
...classes: ClassType[]
@@ -34,6 +35,7 @@
'lucide-icon',
'lucide',
name ? `lucide-${name}`: '',
...aliasNames.map(alias => `lucide-${alias}`),
$$props.class
)
}

View File

@@ -70,6 +70,14 @@ describe('Using lucide icon components', () => {
expect(PenIconRenderedHTML).toBe(Edit2Container.innerHTML);
});
it('should render the alias icon name classNames', () => {
const { container } = render(Pen);
const PenIcon = container.firstChild;
expect(PenIcon).toHaveClass('lucide-edit-2');
})
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'smile-icon';
const { container, getByTestId } = render(Smile, {

View File

@@ -12,7 +12,7 @@ import Icon from './Icon';
* @returns {FunctionalComponent} LucideIcon
*/
const createLucideIcon =
(iconName: string, iconNode: IconNode): FunctionalComponent<LucideProps> =>
(iconName: string, iconNode: IconNode, aliasNames: string[] = []): FunctionalComponent<LucideProps> =>
(props, { slots }) =>
h(
Icon,

View File

@@ -50,6 +50,22 @@ export const mergeClasses = <ClassType = string | undefined | null>(...classes:
.join(' ')
.trim();
/**
* Create list of lucide icon names
*
* @param {array} classes
* @returns {string} A string of classes
*/
export const createLucideClassNames = (iconNames?: string[]) => {
if (iconNames == null) {
return '';
}
return iconNames
.map((aliasName) => `lucide-${toKebabCase(aliasName)}`)
.join(' ')
.trim();
/**
* Check if a component has an accessibility prop
*

View File

@@ -40,6 +40,13 @@ function generateIconFiles({
})
: '';
const iconNameAliases = iconMetaData[iconName]?.aliases?.map((alias) => {
if (typeof alias === 'string') {
return alias;
}
return alias.name;
});
const elementTemplate = await template({
componentName,
iconName,
@@ -47,6 +54,7 @@ function generateIconFiles({
getSvg,
deprecated,
deprecationReason,
iconNameAliases
});
const output = pretty