feat(packages): added lucide icons package skeleton

This commit is contained in:
Karsa
2025-04-01 17:25:10 +02:00
parent e8f64d8dcc
commit 708d5114d6
23 changed files with 645 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
stats
node_modules
tests
scripts
build
src
babel.config.js
jest.config.js
rollup.config.js
yarn.error.log
+83
View File
@@ -0,0 +1,83 @@
<p align="center">
<a href="https://github.com/lucide-icons/lucide">
<img src="https://lucide.dev/package-logos/lucide.svg" alt="Lucide icon library for web applications." width="540">
</a>
</p>
<p align="center">
Lucide icon library for web applications.
</p>
<div align="center">
[![npm](https://img.shields.io/npm/v/lucide?color=blue)](https://www.npmjs.com/package/lucide)
![NPM Downloads](https://img.shields.io/npm/dw/lucide)
[![GitHub](https://img.shields.io/github/license/lucide-icons/lucide)](https://lucide.dev/license)
</div>
<p align="center">
<a href="https://lucide.dev/guide/">About</a>
·
<a href="https://lucide.dev/icons/">Icons</a>
·
<a href="https://lucide.dev/guide/packages/lucide">Documentation</a>
·
<a href="https://lucide.dev/license">License</a>
</p>
# Lucide
Implementation of the lucide icon library for web applications.
## Installation
```sh
pnpm add lucide
```
```sh
npm install lucide
```
```sh
yarn add lucide
```
```sh
bun add lucide
```
### CDN
```html
<!-- Development version -->
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
<!-- Production version -->
<script src="https://unpkg.com/lucide@latest"></script>
```
## Documentation
For full documentation, visit [lucide.dev](https://lucide.dev/guide/packages/lucide)
## Community
Join the [Discord server](https://discord.gg/EH6nSts) to chat with the maintainers and other users.
## License
Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/license).
## Sponsors
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
<img src="https://lucide.dev/vercel.svg" alt="Powered by Vercel" width="200" />
</a>
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="https://lucide.dev/digitalocean.svg" width="200" alt="DigitalOcean Referral Badge" /></a>
### Awesome backers 🍺
<a href="https://www.scipress.io?utm_source=lucide"><img src="https://lucide.dev/sponsors/scipress.svg" width="180" alt="Scipress sponsor badge" /></a>
<a href="https://github.com/pdfme/pdfme"><img src="../../docs/public/sponsors/pdfme.svg" width="180" alt="pdfme sponsor badge" /></a>
+57
View File
@@ -0,0 +1,57 @@
{
"name": "@lucide/icons",
"description": "A Lucide icon library that contains icon data in a standard Lucide format.",
"version": "0.0.1",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",
"repository": {
"type": "git",
"url": "https://github.com/lucide-icons/lucide.git",
"directory": "packages/lucide"
},
"keywords": [
"Lucide",
"HTML",
"Feather",
"Icons",
"Icon",
"SVG",
"Feather Icons",
"Fontawesome",
"Font Awesome"
],
"amdName": "lucide-icons",
"source": "src/lucide-icons.js",
"main": "dist/cjs/lucide-icons.js",
"main:umd": "dist/umd/lucide-icons.js",
"module": "dist/esm/lucide-icons.js",
"unpkg": "dist/umd/lucide-icons.min.js",
"typings": "dist/lucide-icons.d.ts",
"sideEffects": false,
"files": [
"dist"
],
"scripts": {
"build": "pnpm clean && pnpm copy:license && pnpm build:icons && pnpm build:bundle",
"copy:license": "cp ../../LICENSE ./LICENSE",
"clean": "rm -rf dist && rm -rf stats && rm -rf ./src/icons/*.ts",
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mjs --renderUniqueKey --withAliases --withDynamicImports --separateAliasesFile --aliasesFileExtension=.ts --iconFileExtension=.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": {
"@lucide/build-icons": "workspace:*",
"@lucide/rollup-plugins": "workspace:*",
"@rollup/plugin-replace": "^6.0.1",
"@testing-library/jest-dom": "^6.1.6",
"jest-serializer-html": "^7.1.0",
"rollup": "^4.22.4",
"rollup-plugin-dts": "^6.1.0",
"typescript": "^4.9.3",
"vite": "5.4.14",
"vitest": "^1.1.1"
}
}
+83
View File
@@ -0,0 +1,83 @@
import plugins from '@lucide/rollup-plugins';
import replace from '@rollup/plugin-replace';
import dts from 'rollup-plugin-dts';
import pkg from './package.json' with { type: 'json' };
const outputFileName = pkg.name;
const outputDir = 'dist';
const inputs = ['src/lucide.ts'];
const bundles = [
{
format: 'umd',
inputs,
outputDir,
minify: true,
},
{
format: 'umd',
inputs,
outputDir,
},
{
format: 'cjs',
inputs,
outputDir,
},
{
format: 'esm',
inputs,
outputDir,
preserveModules: true,
},
];
const configs = bundles
.map(({ inputs, outputDir, format, minify, preserveModules }) =>
inputs.map((input) => ({
input,
plugins: [
// This is for lucide plugin to replace an argument in createIcons so it is easier to use with UMD.
...(format === 'umd'
? [
replace({
'icons = {}': 'icons = iconAndAliases',
delimiters: ['', ''],
preventAssignment: false,
}),
]
: []),
...plugins({ pkg, minify }),
],
output: {
name: outputFileName,
...(preserveModules
? {
dir: `${outputDir}/${format}`,
}
: {
file: `${outputDir}/${format}/${outputFileName}${minify ? '.min' : ''}.js`,
}),
format,
sourcemap: true,
preserveModules,
},
})),
)
.flat();
const typesFileConfig = {
input: inputs[0],
output: [
{
file: `${outputDir}/${outputFileName}.d.ts`,
format: 'esm',
},
],
plugins: [
dts({
include: ['src'],
}),
],
};
export default [...configs, typesFileConfig];
@@ -0,0 +1,36 @@
/* eslint-disable import/no-extraneous-dependencies */
import base64SVG from '@lucide/build-icons/utils/base64SVG.mjs';
export default async ({
componentName,
iconName,
children,
getSvg,
deprecated,
deprecationReason,
}) => {
const svgContents = await getSvg();
const svgBase64 = base64SVG(svgContents);
return `
import type { LucideIconData } from '../types';
/**
* @name ${iconName}
* @description Lucide SVG icon node.
*
* @preview ![img](data:image/svg+xml;base64,${svgBase64}) - https://lucide.dev/icons/${iconName}
* @see https://lucide.dev/guide/packages/lucide - Documentation
*
* @returns {Array}
* ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/
const ${componentName}: LucideIconData = ${JSON.stringify({
name: iconName,
size: 24,
node: children,
})}
export default ${componentName};
`;
};
@@ -0,0 +1,3 @@
export * from './aliases';
export * from './prefixed';
export * from './suffixed';
@@ -0,0 +1,12 @@
import { SVGProps } from './types';
const defaultAttributes: SVGProps = {
xmlns: 'http://www.w3.org/2000/svg',
fill: 'none',
stroke: 'currentColor',
'stroke-width': 2,
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
};
export default defaultAttributes;
+1
View File
@@ -0,0 +1 @@
Folder for generated icons
@@ -0,0 +1,2 @@
export * from './icons';
export * from './aliases';
@@ -0,0 +1,3 @@
export * as icons from './icons';
export * from './aliases/prefixed';
export * from './types';
@@ -0,0 +1,3 @@
export * as icons from './icons';
export * from './aliases/suffixed';
export * from './types';
@@ -0,0 +1,4 @@
export * from './icons';
export * as icons from './icons';
export * from './aliases';
export * from './types';
+10
View File
@@ -0,0 +1,10 @@
export type SVGProps = Record<string, string | number>;
export type LucideIconNode = [tag: string, attrs: SVGProps][];
export type LucideIconData = {
name: string;
node: LucideIconNode;
} & ({ size: number } | { width: number; height: number });
export type Icons = { [key: string]: LucideIconData };
@@ -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,76 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`createIcons > should add custom attributes 1`] = `
<svg xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewbox="0 0 24 24"
fill="black"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
data-lucide="volume-2"
class="lucide lucide-volume-2 icon custom-class"
>
<path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z">
</path>
<path d="M16 9a5 5 0 0 1 0 6">
</path>
<path d="M19.364 18.364a9 9 0 0 0 0-12.728">
</path>
</svg>
`;
exports[`createIcons > should read elements from DOM and replace icon with alias name 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"
data-lucide="grid"
class="lucide lucide-grid"
>
<rect width="18"
height="18"
x="3"
y="3"
rx="2"
>
</rect>
<path d="M3 9h18">
</path>
<path d="M3 15h18">
</path>
<path d="M9 3v18">
</path>
<path d="M15 3v18">
</path>
</svg>
`;
exports[`createIcons > should read elements from DOM and replace it with icons 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"
data-lucide="volume-2"
class="lucide lucide-volume-2"
>
<path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z">
</path>
<path d="M16 9a5 5 0 0 1 0 6">
</path>
<path d="M19.364 18.364a9 9 0 0 0 0-12.728">
</path>
</svg>
`;
@@ -0,0 +1,3 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`combineClassNames > should returns a string of classNames 1`] = `"item item1 item2 item3 item4 item5 item6 item7 item8 item9"`;
@@ -0,0 +1,37 @@
import { describe, expect, it } from 'vitest';
import { createElement, House } from '../src/lucide-icons';
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 });
};
@@ -0,0 +1,91 @@
import { describe, expect, it } from 'vitest';
import { createIcons, icons } from '../src/lucide-icons';
import { getOriginalSvg } from './helpers';
describe('createIcons', () => {
it('should read elements from DOM and replace it with icons', () => {
document.body.innerHTML = `<i data-lucide="volume-2"></i>`;
createIcons({ icons });
const svg = getOriginalSvg('volume-2');
expect(document.body.innerHTML).toBe(svg);
expect(document.body.innerHTML).toMatchSnapshot();
});
it('should customize the name attribute', () => {
document.body.innerHTML = `<i data-custom-name="volume-2"></i>`;
createIcons({
icons,
nameAttr: 'data-custom-name',
});
const hasSvg = !!document.querySelector('svg');
expect(hasSvg).toBeTruthy();
});
it('should add custom attributes', () => {
document.body.innerHTML = `<i data-lucide="volume-2" class="lucide"></i>`;
const attrs = {
class: 'lucide lucide-volume-2 icon custom-class',
fill: 'black',
};
createIcons({ icons, attrs });
const element = document.querySelector('svg') as SVGSVGElement;
const attributes = element.getAttributeNames();
const attributesAndValues = attributes.reduce(
(acc, item) => {
acc[item] = element.getAttribute(item);
return acc;
},
{} as Record<string, string | null>,
);
expect(document.body.innerHTML).toMatchSnapshot();
expect(attributesAndValues).toEqual(expect.objectContaining(attrs));
});
it('should inherit elements attributes', () => {
document.body.innerHTML = `<i data-lucide="sun" data-theme-switcher="light"></i>`;
const attrs = {
'data-theme-switcher': 'light',
};
createIcons({ icons });
const element = document.querySelector('svg') as SVGSVGElement;
const attributes = element.getAttributeNames();
const attributesAndValues = attributes.reduce(
(acc, item) => {
acc[item] = element.getAttribute(item);
return acc;
},
{} as Record<string, string | null>,
);
expect(attributesAndValues).toEqual(expect.objectContaining(attrs));
});
it('should read elements from DOM and replace icon with alias name', () => {
document.body.innerHTML = `<i data-lucide="grid"></i>`;
createIcons({ icons });
const svg = getOriginalSvg('grid-3x3', 'grid');
expect(document.body.innerHTML).toBe(svg);
expect(document.body.innerHTML).toMatchSnapshot();
});
});
@@ -0,0 +1,64 @@
import { describe, it, expect } from 'vitest';
import { getAttrs, getClassNames, combineClassNames } from '../src/replaceElement';
describe('getAtts', () => {
it('should returns attrbrutes of an element', () => {
const element = {
attributes: [
{
name: 'class',
value: 'item1 item2 item4',
},
{
name: 'date-name',
value: 'volume',
},
],
};
const attrs = getAttrs(element as unknown as Element);
expect(attrs.class).toBe(element.attributes[0].value);
});
});
describe('getClassNames', () => {
it('should returns an array when giving class property of string', () => {
const elementAttrs = {
class: 'item1 item2 item3',
};
const attrs = getClassNames(elementAttrs);
expect(JSON.stringify(attrs)).toBe(JSON.stringify(['item1', 'item2', 'item3']));
});
it('should returns an array when givind class property with an array', () => {
const elementAttrs = {
class: ['item1', 'item2', 'item3'],
};
const attrs = getClassNames(elementAttrs);
expect(JSON.stringify(attrs)).toBe(JSON.stringify(['item1', 'item2', 'item3']));
});
});
describe('combineClassNames', () => {
it('should returns a string of classNames', () => {
const arrayOfClassnames: (string | Record<string, string[]>)[] = [
'item',
{
class: ['item1', 'item2', 'item3'],
},
{
class: ['item4', 'item5', 'item6'],
},
{
class: ['item7', 'item8', 'item9'],
},
];
const combinedClassNames = combineClassNames(arrayOfClassnames);
expect(combinedClassNames).toMatchSnapshot();
});
});
@@ -0,0 +1,5 @@
import { expect } from 'vitest';
import '@testing-library/jest-dom/vitest';
import htmlSerializer from 'jest-serializer-html';
expect.addSnapshotSerializer(htmlSerializer);
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"strict": true,
"declaration": true,
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"module": "ESNext",
"target": "ESNext",
"esModuleInterop": true,
"allowJs": true,
"lib": ["esnext", "dom"],
"resolveJsonModule": true,
"sourceMap": true,
"outDir": "./dist",
},
}
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: './tests/setupVitest.js',
}
});