Skip to content

Commit

Permalink
fix(lucide): Support for iconNodes from other libs like Lab (#2752)
Browse files Browse the repository at this point in the history
* Add support for iconNodes

* formatting
  • Loading branch information
ericfennis authored Feb 7, 2025
1 parent 410ae43 commit eb15856
Show file tree
Hide file tree
Showing 9 changed files with 38 additions and 35 deletions.
1 change: 1 addition & 0 deletions packages/lucide/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
6 changes: 1 addition & 5 deletions packages/lucide/scripts/exportTemplate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
`;
Expand Down
17 changes: 7 additions & 10 deletions packages/lucide/src/createElement.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);
});
Expand All @@ -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;
14 changes: 9 additions & 5 deletions packages/lucide/src/replaceElement.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import createElement from './createElement';
import defaultAttributes from './defaultAttributes';
import { Icons } from './types';

export type CustomAttrs = { [attr: string]: any };
Expand All @@ -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') {
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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);
};
Expand Down
3 changes: 1 addition & 2 deletions packages/lucide/src/types.ts
Original file line number Diff line number Diff line change
@@ -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 };
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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();

Expand All @@ -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));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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'],
Expand Down

0 comments on commit eb15856

Please sign in to comment.