fix(lucide): Lucide create element function returning SVG Element (#2816)

* Fix create element function

* Formatting

* Adjust documentation
This commit is contained in:
Eric Fennis
2025-02-24 10:10:28 +01:00
committed by GitHub
parent 1250fc276d
commit 3f2ad5b2a6
7 changed files with 116 additions and 28 deletions
+16 -4
View File
@@ -126,11 +126,23 @@ import { createElement, Menu } from 'lucide';
const menuIcon = createElement(Menu); // Returns HTMLElement (svg)
// set custom attributes with browser native functions
menuIcon.setAttribute('stroke', '#333');
menuIcon.classList.add('my-icon-class');
// Append HTMLElement in the DOM
const myApp = document.getElementById('app');
myApp.appendChild(menuIcon);
```
// Append HTMLElement in webpage
#### Custom Element binding with custom attributes
```js
import { createElement, Menu } from 'lucide';
const menuIcon = createElement(Menu, {
class: ['my-custom-class', 'icon'],
'stroke-width': 1,
stroke: '#333'
}); // Returns HTMLElement (svg)
// Append HTMLElement in the DOM
const myApp = document.getElementById('app');
myApp.appendChild(menuIcon);
```
+25 -8
View File
@@ -1,15 +1,16 @@
import defaultAttributes from './defaultAttributes';
import { IconNode, SVGProps } from './types';
type CreateElementParams = [tag: string, attrs: SVGProps, children?: IconNode];
type CreateSVGElementParams = [tag: string, attrs: SVGProps, children?: IconNode];
/**
* Creates a new HTMLElement from icon node
* @param {string} tag
* @param {object} attrs
* @param {array} children
* @returns {HTMLElement}
* Creates a new SVGElement
* @param {string} tag - Tag name of the element
* @param {object} attrs - Attributes of the element
* @param {array} children - Children of the element
* @returns {SVGElement}
*/
const createElement = ([tag, attrs, children]: CreateElementParams) => {
const createSVGElement = ([tag, attrs, children]: CreateSVGElementParams) => {
const element = document.createElementNS('http://www.w3.org/2000/svg', tag);
Object.keys(attrs).forEach((name) => {
@@ -18,7 +19,7 @@ const createElement = ([tag, attrs, children]: CreateElementParams) => {
if (children?.length) {
children.forEach((child) => {
const childElement = createElement(child);
const childElement = createSVGElement(child);
element.appendChild(childElement);
});
@@ -27,4 +28,20 @@ const createElement = ([tag, attrs, children]: CreateElementParams) => {
return element;
};
/**
* Creates a new HTMLElement from icon node
* @param {array} iconNode - Icon node to be converted to an element
* @param {object} customAttrs - Custom attributes to be added to the element
* @returns {HTMLElement}
*/
const createElement = (iconNode: IconNode, customAttrs: SVGProps = {}) => {
const tag = 'svg';
const attrs = {
...defaultAttributes,
...customAttrs,
};
return createSVGElement([tag, attrs, iconNode]);
};
export default createElement;
+1 -1
View File
@@ -98,7 +98,7 @@ const replaceElement = (element: Element, { nameAttr, icons, attrs }: ReplaceEle
});
}
const svgElement = createElement(['svg', iconAttrs, iconNode]);
const svgElement = createElement(iconNode, iconAttrs);
return element.parentNode?.replaceChild(svgElement, element);
};
@@ -0,0 +1,19 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`createElement > should match the snapshot 1`] = `
<svg xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewbox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8">
</path>
<path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">
</path>
</svg>
`;
@@ -0,0 +1,37 @@
import { describe, it, expect } from 'vitest';
import { House, createElement } from '../src/lucide';
import { getOriginalSvg } from './helpers';
describe('createElement', () => {
it('should create SVG Element', () => {
const HomeSVG = createElement(House);
expect(HomeSVG.tagName).toBe('svg');
});
it('should match the snapshot', () => {
const HomeSVG = createElement(House);
expect(HomeSVG.outerHTML).toMatchSnapshot();
});
it('should create SVG Element with attributes', () => {
const HomeSVG = createElement(House, { fill: 'red' });
expect(HomeSVG.getAttribute('fill')).toBe('red');
});
it('should create SVG Element with class name', () => {
const HomeSVG = createElement(House, { class: 'icon' });
expect(HomeSVG.getAttribute('class')).toBe('icon');
});
it('should create the correct svg element', () => {
const HomeSVG = createElement(House);
const svg = getOriginalSvg('house', undefined, false);
expect(HomeSVG.outerHTML).toBe(svg);
});
});
+17
View File
@@ -0,0 +1,17 @@
import fs from 'fs';
import path from 'path';
import { parseSync, stringify } from 'svgson';
const ICONS_DIR = path.resolve(__dirname, '../../../icons');
export const getOriginalSvg = (iconName: string, aliasName?: string, setAttrs = true) => {
const svgContent = fs.readFileSync(path.join(ICONS_DIR, `${iconName}.svg`), 'utf8');
const svgParsed = parseSync(svgContent);
if (setAttrs) {
svgParsed.attributes['data-lucide'] = aliasName ?? iconName;
svgParsed.attributes['class'] = `lucide lucide-${aliasName ?? iconName}`;
}
return stringify(svgParsed, { selfClose: false });
};
+1 -15
View File
@@ -1,20 +1,6 @@
import { describe, it, expect } from 'vitest';
import { createIcons, icons } from '../src/lucide';
import fs from 'fs';
import path from 'path';
import { parseSync, stringify } from 'svgson';
const ICONS_DIR = path.resolve(__dirname, '../../../icons');
const getOriginalSvg = (iconName: string, aliasName?: string) => {
const svgContent = fs.readFileSync(path.join(ICONS_DIR, `${iconName}.svg`), 'utf8');
const svgParsed = parseSync(svgContent);
svgParsed.attributes['data-lucide'] = aliasName ?? iconName;
svgParsed.attributes['class'] = `lucide lucide-${aliasName ?? iconName}`;
return stringify(svgParsed, { selfClose: false });
};
import { getOriginalSvg } from './helpers';
describe('createIcons', () => {
it('should read elements from DOM and replace it with icons', () => {