fix(lucide): Support for iconNodes from other libs like Lab (#2752)

* Add support for iconNodes

* formatting
This commit is contained in:
Eric Fennis
2025-02-07 15:44:54 +01:00
committed by GitHub
parent 410ae434fa
commit eb158561d3
9 changed files with 38 additions and 35 deletions
+1
View File
@@ -39,6 +39,7 @@
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mjs --iconFileExtension=.ts --withAliases --aliasNamesOnly --aliasesFileExtension=.ts --exportFileName=index.ts",
"build:bundle": "rollup -c rollup.config.mjs",
"test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false"
},
"devDependencies": {
+1 -5
View File
@@ -19,11 +19,7 @@ import type { IconNode } from '../types';
* @returns {Array}
* ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/
const ${componentName}: IconNode = [
'svg',
defaultAttributes,
${JSON.stringify(children)}
];
const ${componentName}: IconNode = ${JSON.stringify(children)}
export default ${componentName};
`;
+7 -10
View File
@@ -1,4 +1,6 @@
import { IconNode, IconNodeChild, SVGProps } from './types';
import { IconNode, SVGProps } from './types';
type CreateElementParams = [tag: string, attrs: SVGProps, children?: IconNode];
/**
* Creates a new HTMLElement from icon node
@@ -7,16 +9,16 @@ import { IconNode, IconNodeChild, SVGProps } from './types';
* @param {array} children
* @returns {HTMLElement}
*/
const createElement = (tag: string, attrs: SVGProps, children: IconNodeChild[] = []) => {
const createElement = ([tag, attrs, children]: CreateElementParams) => {
const element = document.createElementNS('http://www.w3.org/2000/svg', tag);
Object.keys(attrs).forEach((name) => {
element.setAttribute(name, String(attrs[name]));
});
if (children.length) {
if (children?.length) {
children.forEach((child) => {
const childElement = createElement(...child);
const childElement = createElement(child);
element.appendChild(childElement);
});
@@ -25,9 +27,4 @@ const createElement = (tag: string, attrs: SVGProps, children: IconNodeChild[] =
return element;
};
/**
* Creates a new HTMLElement from icon node
* @param {[tag: string, attrs: object, children: array]} iconNode
* @returns {HTMLElement}
*/
export default ([tag, attrs, children]: IconNode) => createElement(tag, attrs, children);
export default createElement;
+9 -5
View File
@@ -1,4 +1,5 @@
import createElement from './createElement';
import defaultAttributes from './defaultAttributes';
import { Icons } from './types';
export type CustomAttrs = { [attr: string]: any };
@@ -19,7 +20,9 @@ export const getAttrs = (element: Element): Record<string, string> =>
* @param {Object} attrs
* @returns {Array}
*/
export const getClassNames = (attrs: Record<string, string> | string): string | string[] => {
export const getClassNames = (
attrs: Record<string, string | string[]> | string,
): string | string[] => {
if (typeof attrs === 'string') return attrs;
if (!attrs || !attrs.class) return '';
if (attrs.class && typeof attrs.class === 'string') {
@@ -36,7 +39,9 @@ export const getClassNames = (attrs: Record<string, string> | string): string |
* @param {array} arrayOfClassnames
* @returns {string}
*/
export const combineClassNames = (arrayOfClassnames: (string | Record<string, string>)[]) => {
export const combineClassNames = (
arrayOfClassnames: (string | Record<string, string | string[]>)[],
) => {
const classNameArray = arrayOfClassnames.flatMap(getClassNames);
return classNameArray
@@ -77,10 +82,9 @@ const replaceElement = (element: Element, { nameAttr, icons, attrs }: ReplaceEle
}
const elementAttrs = getAttrs(element);
const [tag, iconAttributes, children] = iconNode;
const iconAttrs = {
...iconAttributes,
...defaultAttributes,
'data-lucide': iconName,
...attrs,
...elementAttrs,
@@ -94,7 +98,7 @@ const replaceElement = (element: Element, { nameAttr, icons, attrs }: ReplaceEle
});
}
const svgElement = createElement([tag, iconAttrs, children]);
const svgElement = createElement(['svg', iconAttrs, iconNode]);
return element.parentNode?.replaceChild(svgElement, element);
};
+1 -2
View File
@@ -1,6 +1,5 @@
// className is not supported in svg elements
export type SVGProps = Record<string, string | number>;
export type IconNodeChild = readonly [tag: string, attrs: SVGProps];
export type IconNode = readonly [tag: string, attrs: SVGProps, children?: IconNodeChild[]];
export type IconNode = [tag: string, attrs: SVGProps][];
export type Icons = { [key: string]: IconNode };
@@ -6,7 +6,7 @@ import { parseSync, stringify } from 'svgson';
const ICONS_DIR = path.resolve(__dirname, '../../../icons');
const getOriginalSvg = (iconName, aliasName) => {
const getOriginalSvg = (iconName: string, aliasName?: string) => {
const svgContent = fs.readFileSync(path.join(ICONS_DIR, `${iconName}.svg`), 'utf8');
const svgParsed = parseSync(svgContent);
@@ -51,14 +51,17 @@ describe('createIcons', () => {
createIcons({ icons, attrs });
const element = document.querySelector('svg');
const element = document.querySelector('svg') as SVGSVGElement;
const attributes = element.getAttributeNames();
const attributesAndValues = attributes.reduce((acc, item) => {
acc[item] = element.getAttribute(item);
const attributesAndValues = attributes.reduce(
(acc, item) => {
acc[item] = element.getAttribute(item);
return acc;
}, {});
return acc;
},
{} as Record<string, string | null>,
);
expect(document.body.innerHTML).toMatchSnapshot();
@@ -74,14 +77,17 @@ describe('createIcons', () => {
createIcons({ icons });
const element = document.querySelector('svg');
const element = document.querySelector('svg') as SVGSVGElement;
const attributes = element.getAttributeNames();
const attributesAndValues = attributes.reduce((acc, item) => {
acc[item] = element.getAttribute(item);
const attributesAndValues = attributes.reduce(
(acc, item) => {
acc[item] = element.getAttribute(item);
return acc;
}, {});
return acc;
},
{} as Record<string, string | null>,
);
expect(attributesAndValues).toEqual(expect.objectContaining(attrs));
});
@@ -16,7 +16,7 @@ describe('getAtts', () => {
],
};
const attrs = getAttrs(element);
const attrs = getAttrs(element as unknown as Element);
expect(attrs.class).toBe(element.attributes[0].value);
});
@@ -44,7 +44,7 @@ describe('getClassNames', () => {
describe('combineClassNames', () => {
it('should returns a string of classNames', () => {
const arrayOfClassnames = [
const arrayOfClassnames: (string | Record<string, string[]>)[] = [
'item',
{
class: ['item1', 'item2', 'item3'],