Compare commits

...

11 Commits

39 changed files with 983 additions and 45 deletions

View File

@@ -23,6 +23,7 @@ body:
- label: lucide-vue
- label: lucide-vue-next
- label: lucide-astro
- label: '@lucide/icons'
- label: Figma plugin
- label: source/main
- label: other/not relevant

View File

@@ -23,6 +23,7 @@ body:
- label: lucide-vue
- label: lucide-vue-next
- label: lucide-astro
- label: '@lucide/icons'
- label: Figma plugin
- label: all JS packages
- label: site

6
.github/labeler.yml vendored
View File

@@ -85,6 +85,12 @@
- any-glob-to-any-file:
- 'packages/astro/*'
# For changes in the @lucide/icons package
❇️ lucide-icons:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-icons/*'
# For changes in the lucide static package
🪨 static package:
- changed-files:

43
.github/workflows/icons.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Lucide Icons Checks
on:
pull_request:
paths:
- packages/icons/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter @lucide/icons build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter @lucide/icons test

View File

@@ -60,6 +60,7 @@ jobs:
'lucide-svelte',
'@lucide/astro',
'@lucide/svelte',
'@lucide/icons',
]
steps:
- uses: actions/checkout@v6

View File

@@ -31,8 +31,26 @@
}
]
},
"lucide-vue-next": {
"@lucide/icons": {
"docsAlias": "lucide-icons",
"packageDirname": "lucide-icons",
"order": 2,
"icon": "ts",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/@lucide/icons",
"href": "https://www.npmjs.com/package/@lucide/icons"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/@lucide/icons",
"href": "https://www.npmjs.com/package/@lucide/icons"
}
]
},
"lucide-vue-next": {
"order": 3,
"icon": "vue-next",
"shields": [
{
@@ -48,7 +66,7 @@
]
},
"lucide-svelte": {
"order": 3,
"order": 4,
"icon": "svelte",
"shields": [
{
@@ -64,7 +82,7 @@
]
},
"lucide-solid": {
"order": 4,
"order": 5,
"icon": "solid",
"shields": [
{
@@ -80,7 +98,7 @@
]
},
"lucide-react-native": {
"order": 5,
"order": 6,
"icon": "react-native",
"shields": [
{
@@ -96,7 +114,7 @@
]
},
"lucide-angular": {
"order": 6,
"order": 7,
"icon": "angular",
"shields": [
{
@@ -112,7 +130,7 @@
]
},
"lucide-preact": {
"order": 7,
"order": 8,
"icon": "preact",
"shields": [
{
@@ -130,7 +148,7 @@
"@lucide/astro": {
"docsAlias": "lucide-astro",
"packageDirname": "astro",
"order": 8,
"order": 9,
"icon": "astro",
"iconDark": "astro-dark",
"shields": [
@@ -147,7 +165,7 @@
]
},
"lucide-static": {
"order": 9,
"order": 10,
"icon": "svg",
"shields": [
{

View File

@@ -79,6 +79,16 @@ declare module "lucide-react-native" {
}
```
```ts [@lucide-icons]
declare module "@lucide/icons" {
// Prefixed import names
export * from "@lucide/icons/dist/lucide-icons.prefixed";
// or
// Suffixed import names
export * from "@lucide/icons/dist/lucide-icons.suffixed";
}
```
:::
Place this in your project root or in a folder where your tsconfig.json is located, or locate it in your defined type directory.

View File

@@ -0,0 +1,83 @@
# Lucide Icons
This is a helper library that exports icon data in a tree-shakable, dynamically importable way, but provides no real rendering logic on its own.
Some of our packages, notably [
`@lucide/angular`](http://npmjs.com/package/@lucide/angular) can leverage this functionality to allow dynamically importing icons, and utilising tree-shaking when using static imports. You can also use this package to implement a third party package for a framework we do not yet support.
## Installation
::: code-group
```sh [pnpm]
pnpm install @lucide/icons
```
```sh [yarn]
yarn add @lucide/icons
```
```sh [npm]
npm install @lucide/icons
```
```sh [bun]
bun add @lucide/icons
```
:::
## Icon data format
Each icon is described by the following interface:
```typescript
export type LucideIconData = {
name: string;
node: LucideIconNode[];
} & ({ size: number } | { width: number; height: number; })
```
| name | type | description |
|------------------------------|--------------------|------------------------------------------------------------------------------|
| `name` | `string` | The name of the icon. |
| `node` | `LucideIconNode[]` | A list of SVG paths, each path described as a `[tagName, attributes]` tuple. |
| `size` or `width` & `height` | `number` | The dimensions of the icon. |
## How to use
Lucide is built with ES Modules, so it's completely tree-shakable.
Each icon data descriptor can be imported separately. This way, only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away.
## Building icons
`@lucide/icons` provides four ways to build icons for later rendering, these are:
### `buildLucideIconData`
Returns the icon as a `LucideIconNode` tuple: `['svg', attributes, childNodes]`.
### Building SVG strings
`buildLucideSvg(icon: LucideIcon)`
Returns the icon
## Dynamic imports
It is possible to create one generic icon component to load icons, but it is not recommended.
Since it is importing all icons during build. This increases build time and the different modules it will create.
`DynamicIcon` is useful for applications that want to show icons dynamically by icon name. For example, when using a content management system with where icon names are stored in a database.
For static use cases, it is recommended to import the icons directly.
The same props can be passed to adjust the icon appearance. The
`name` prop is required to load the correct icon.
```js
import {lucideDynamicIconImports} from '@lucide/icons/dynamic';
const icon: LucideIconData = await lucideDynamicIconImports['house']().then();
```

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path fill="#3178C6" d="M24 0H0v24h24V0Z"/>
<path fill="#fff" fill-rule="evenodd" d="M14.11 18.797v2.588c.421.215.918.375 1.49.487a9.45 9.45 0 0 0 1.819.16 8.35 8.35 0 0 0 1.772-.179 4.407 4.407 0 0 0 1.472-.59c.421-.272.75-.628 1.003-1.07.243-.44.365-.983.365-1.63 0-.47-.065-.882-.206-1.238a2.98 2.98 0 0 0-.61-.947 4.48 4.48 0 0 0-.946-.74 11.05 11.05 0 0 0-1.247-.62c-.338-.14-.647-.27-.919-.412a4.124 4.124 0 0 1-.684-.403 1.784 1.784 0 0 1-.44-.44.928.928 0 0 1-.15-.525c0-.178.046-.338.14-.478.093-.141.225-.263.384-.366.169-.103.375-.178.619-.234.244-.057.515-.085.806-.085.216 0 .44.019.684.047.244.028.479.084.722.15.244.066.479.15.713.253.225.103.44.225.637.356v-2.418a6.524 6.524 0 0 0-1.293-.338 10.228 10.228 0 0 0-1.604-.112c-.618 0-1.2.065-1.753.197a4.42 4.42 0 0 0-1.453.618c-.422.282-.75.638-.993 1.07-.244.43-.366.955-.366 1.555 0 .77.225 1.425.666 1.978.45.544 1.125 1.004 2.024 1.388.357.15.685.29.994.431.31.14.572.281.797.44.225.16.403.32.534.498.132.178.197.384.197.61a.921.921 0 0 1-.121.468c-.085.14-.207.262-.366.375-.16.112-.366.187-.619.244a3.957 3.957 0 0 1-.862.084 4.938 4.938 0 0 1-1.67-.3 4.752 4.752 0 0 1-1.537-.872Zm-4.313-6.45h3.328V10.22H3.844v2.128h3.31v9.497h2.633v-9.497h.01Z" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

10
packages/icons/.npmignore Normal file
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
packages/icons/README.md Normal file
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>

View File

@@ -0,0 +1,77 @@
{
"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"
],
"amdName": "lucide-icons",
"source": "src/lucide-icons.ts",
"main": "dist/cjs/lucide-icons.cjs",
"module": "dist/esm/lucide-icons.mjs",
"types": "dist/esm/lucide-icons.d.ts",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/esm/lucide-icons.d.ts",
"import": "./dist/esm/lucide-icons.mjs",
"require": "./dist/cjs/lucide-icons.cjs",
"default": "./dist/esm/lucide-icons.mjs"
},
"./icons/*": {
"import": "./dist/esm/icons/*.mjs",
"default": "./dist/esm/icons/*.mjs"
},
"./dynamic": {
"types": "./dist/esm/dynamic.d.ts",
"import": "./dist/esm/dynamic.mjs",
"require": "./dist/cjs/dynamic.cjs",
"default": "./dist/esm/dynamic.mjs"
},
"./build": {
"types": "./dist/esm/build.d.ts",
"import": "./dist/esm/build.mjs",
"require": "./dist/cjs/build.cjs",
"default": "./dist/esm/build.mjs"
}
},
"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",
"test:update": "vitest -u",
"version": "pnpm version --git-tag-version=false"
},
"devDependencies": {
"@lucide/build-icons": "workspace:*",
"@lucide/helpers": "^1.0.0",
"@lucide/rollup-plugins": "workspace:*",
"@rollup/plugin-replace": "^6.0.2",
"@testing-library/jest-dom": "^6.6.3",
"jest-serializer-html": "^7.1.0",
"rollup": "^4.53.3",
"rollup-plugin-dts": "^6.2.3",
"rollup-plugin-preserve-directives": "^0.4.0",
"typescript": "^5.8.3",
"vite": "^7.2.4",
"vitest": "^4.0.12"
}
}

View File

@@ -0,0 +1,87 @@
import plugins from '@lucide/rollup-plugins';
import pkg from './package.json' with { type: 'json' };
import dts from 'rollup-plugin-dts';
const packageName = '@lucide/icons';
const outputFileName = 'lucide-icons';
const inputs = [`src/lucide-icons.ts`];
const bundles = [
{
format: 'cjs',
inputs,
extension: 'cjs',
},
{
format: 'esm',
inputs: [...inputs, './src/dynamic.ts', './src/build.ts'],
preserveModules: true,
extension: 'mjs',
},
];
const configs = bundles
.map(
({
inputs,
outputDir = 'dist',
outputFile,
format,
minify,
preserveModules,
entryFileNames,
external = [],
paths,
extension = 'js',
}) =>
inputs.map((input) => ({
input,
plugins: [...plugins({ pkg, minify })],
external,
output: {
name: packageName,
entryFileNames,
...(preserveModules
? {
dir: `${outputDir}/${format}`,
entryFileNames: `[name].${extension}`,
}
: {
file:
outputFile ??
`${outputDir}/${format}/${outputFileName}${minify ? '.min' : ''}.${extension}`,
}),
paths,
format,
sourcemap: true,
preserveModules,
exports: 'named',
globals: {},
preserveModulesRoot: 'src',
},
})),
)
.flat();
export default [
...[
outputFileName,
`${outputFileName}.prefixed`,
`${outputFileName}.suffixed`,
'dynamic',
'build',
].map((filename) => ({
input: `./src/${filename}.ts`,
output: [
{
file: `dist/esm/${filename}.d.ts`,
format: 'esm',
},
{
file: `dist/cjs/${filename}.d.cts`,
format: 'cjs',
},
],
plugins: [dts()],
})),
...configs,
];

View File

@@ -0,0 +1,36 @@
/* eslint-disable import/no-extraneous-dependencies */
import base64SVG from '@lucide/build-icons/utils/base64SVG';
export default async ({
componentName,
iconName,
children,
getSvg,
deprecated,
deprecationReason,
}) => {
const svgContents = await getSvg();
const svgBase64 = base64SVG(svgContents);
return `
import type { LucideIcon } 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}: LucideIcon = ${JSON.stringify({
name: iconName,
size: 24,
node: children,
})}
export default ${componentName};
`;
};

View File

@@ -0,0 +1,3 @@
export * from './aliases';
export * from './prefixed';
export * from './suffixed';

View File

@@ -0,0 +1,4 @@
export { default as buildLucideIconNode } from './buildLucideIconNode';
export { default as buildLucideDataUri } from './buildLucideDataUri';
export { default as buildLucideIconElement } from './buildLucideIconElement';
export { default as buildLucideSvg } from './buildLucideSvg';

View File

@@ -0,0 +1,14 @@
import { LucideBuildParams, LucideIcon } from './types';
import buildLucideSvg from './buildLucideSvg';
/**
* Creates a base64 encoded data URI from a Lucide icon object.
*
* @param icon The icon to build.
* @param params Additional build parameters.
*/
function buildLucideDataUri(icon: LucideIcon, params: LucideBuildParams = {}) {
return `data:image/svg+xml;base64,${btoa(buildLucideSvg(icon, params))}`;
}
export default buildLucideDataUri;

View File

@@ -0,0 +1,34 @@
import { LucideBuildParams, LucideIcon, LucideIconNode } from './types';
import buildLucideIconNode from './buildLucideIconNode';
const buildDomElement = (
document: Document,
[tagName, attributes, children = []]: LucideIconNode,
): Element => {
const element = document.createElementNS('http://www.w3.org/2000/svg', tagName);
for (const [attrName, value] of Object.entries(attributes)) {
element.setAttribute(attrName, value);
}
for (const node of children) {
const childNode = buildDomElement(document, node);
element.appendChild(childNode);
}
return element;
};
/**
* Creates an SvgElement from a Lucide icon object.
*
* @param document The document to create the Element in.
* @param icon The icon to build.
* @param params Additional build parameters.
*/
function buildLucideIconElement(
document: Document,
icon: LucideIcon,
params: LucideBuildParams = {},
) {
return buildDomElement(document, buildLucideIconNode(icon, params));
}
export default buildLucideIconElement;

View File

@@ -0,0 +1,45 @@
import { LucideBuildParams, LucideIcon, LucideIconNode } from './types';
const defaultAttributes = {
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',
};
/**
* Creates a Lucide icon node (an svgson-like format) from a Lucide icon object.
*
* @param icon The icon to build.
* @param params Additional build parameters.
*/
function buildLucideIconNode(icon: LucideIcon, params: LucideBuildParams = {}): LucideIconNode {
const viewBoxWidth = ('size' in icon ? icon.size : icon.width) ?? defaultAttributes.width;
const viewBoxHeight = ('size' in icon ? icon.size : icon.height) ?? defaultAttributes.height;
const attributes = {
...defaultAttributes,
...('color' in params && { stroke: params['color'] }),
...('size' in params &&
params['size'] && {
width: params['size'].toString(10),
height: params['size'].toString(10),
}),
...('width' in params && params['width'] && { width: params['width'].toString(10) }),
...('height' in params && params['height'] && { height: params['height'].toString(10) }),
...('strokeWidth' in params &&
params['strokeWidth'] && { 'stroke-width': params['strokeWidth'].toString(10) }),
...('absoluteStrokeWidth' in params &&
params['absoluteStrokeWidth'] && { 'vector-effect': 'non-scaling-stroke' }),
class: `lucide lucide-${icon.name} ${params['className'] ?? ''}`.trim(),
viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
...('attributes' in params && params.attributes),
};
return ['svg', attributes, icon.node];
}
export default buildLucideIconNode;

View File

@@ -0,0 +1,19 @@
import { LucideBuildParams, LucideIcon, LucideIconNode } from './types';
import buildLucideIconNode from './buildLucideIconNode';
const buildDomNode = ([tagName, attributes, children = []]: LucideIconNode): string =>
`<${tagName} ${Object.entries(attributes)
.map(([attrName, value]) => `${attrName}="${value}"`)
.join(' ')}>${children?.map((child) => buildDomNode(child)).join('')}</${tagName}>`;
/**
* Creates an SVG string from a Lucide icon object.
*
* @param icon The icon to build.
* @param params Additional build parameters.
*/
function buildLucideSvg(icon: LucideIcon, params: LucideBuildParams = {}) {
return buildDomNode(buildLucideIconNode(icon, params));
}
export default buildLucideSvg;

View File

@@ -0,0 +1,2 @@
export { lucideIconNames, type LucideIconName } from './dynamicIcon';
export { default as lucideDynamicIconImports } from './dynamicIconImports';

View File

@@ -0,0 +1,8 @@
import dynamicIconImports from './dynamicIconImports';
export type LucideIconName = keyof typeof dynamicIconImports;
/**
* The list of available Lucide icon names.
*/
export const lucideIconNames = Object.keys(dynamicIconImports) as Array<LucideIconName>;

View File

@@ -0,0 +1 @@
Folder for generated icons

View File

@@ -0,0 +1,5 @@
export * as icons from './icons';
export * from './aliases/prefixed';
export * from './types';
export * from './build';

View File

@@ -0,0 +1,3 @@
export * as icons from './icons';
export * from './aliases/suffixed';
export * from './types';

View File

@@ -0,0 +1,4 @@
export * as icons from './icons';
export * from './icons';
export * from './aliases/index';
export * from './types';

View File

@@ -0,0 +1,42 @@
export type SVGProps = Record<string, string>;
/**
* A Lucide icon node (an svgson-like internal format)
*/
export type LucideIconNode =
| [attrName: string, attributes: SVGProps]
| [attrName: string, attributes: SVGProps, children: LucideIconNode[]];
/**
* A Lucide icon object that fully describes an icon to be displayed.
*/
export type LucideIcon = {
name: string;
node: LucideIconNode[];
} & ({ size: number } | { width: number; height: number });
/**
* Build parameters for creating a Lucide icon instance for display.
*/
export type LucideBuildParams = {
/**
* The color of the icon.
*/
color?: string;
/**
* The stroke width.
*/
strokeWidth?: number;
/**
* @deprecated Use vector-effect: non-scaling-stroke instead.
*/
absoluteStrokeWidth?: boolean;
/**
* Extra CSS class names to pass to the SVG element.
*/
className?: string;
/**
* Any extra attributes to pass to the SVG element.
*/
attributes?: SVGProps;
} & ({ size: number } | { width: number; height: number });

View File

@@ -0,0 +1,24 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`buildLucideIconElement > 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"
class="lucide lucide-house"
>
<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"
key="5wwlr5"
>
</path>
<path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
key="r6nss1"
>
</path>
</svg>
`;

View File

@@ -0,0 +1,3 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`buildLucideIconNode > should match the snapshot 1`] = `"{"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","class":"lucide lucide-house"}"`;

View File

@@ -0,0 +1,26 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`buildLucideSvg > 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"
class="lucide lucide-house"
>
<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"
key="5wwlr5"
>
</path>
<path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
key="r6nss1"
>
</path>
</svg>
`;
exports[`buildLucideSvgDataUri > should match the snapshot 1`] = `"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWhvdXNlIj48cGF0aCBkPSJNMTUgMjF2LThhMSAxIDAgMCAwLTEtMWgtNGExIDEgMCAwIDAtMSAxdjgiIGtleT0iNXd3bHI1Ij48L3BhdGg+PHBhdGggZD0iTTMgMTBhMiAyIDAgMCAxIC43MDktMS41MjhsNy02YTIgMiAwIDAgMSAyLjU4MiAwbDcgNkEyIDIgMCAwIDEgMjEgMTB2OWEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMnoiIGtleT0icjZuc3MxIj48L3BhdGg+PC9zdmc+"`;

View File

@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest';
import { House } from '../src/lucide-icons';
import { getOriginalSvg, removeKeys } from './helpers';
import buildLucideIconElement from '../src/buildLucideIconElement';
describe('buildLucideIconElement', () => {
it('should create SVG Element', () => {
const HomeSVG = buildLucideIconElement(document, House);
expect(HomeSVG.tagName).toBe('svg');
});
it('should match the snapshot', () => {
const HomeSVG = buildLucideIconElement(document, House);
expect(HomeSVG.outerHTML).toMatchSnapshot();
});
it('should create SVG Element with attributes', () => {
const HomeSVG = buildLucideIconElement(document, House, { attributes: { fill: 'red' } });
expect(HomeSVG.getAttribute('fill')).toBe('red');
});
it('should create SVG Element with class name', () => {
const HomeSVG = buildLucideIconElement(document, House, { attributes: { class: 'icon' } });
expect(HomeSVG.getAttribute('class')).toBe('icon');
});
it('should merge classes', () => {
const HomeSVG = buildLucideIconElement(document, House, { className: 'icon' });
expect(HomeSVG.getAttribute('class')).toBe('lucide lucide-house icon');
});
it('should create the correct svg element', () => {
const HomeSVG = buildLucideIconElement(document, House);
const svg = getOriginalSvg('house', undefined, true);
expect(removeKeys(HomeSVG.outerHTML)).toBe(svg);
});
});

View File

@@ -0,0 +1,84 @@
import { describe, expect, it } from 'vitest';
import { House } from '../src/lucide-icons';
import buildLucideIconNode from '../src/buildLucideIconNode';
describe('buildLucideIconNode', () => {
it('should create icon node', () => {
const HouseSVG = buildLucideIconNode(House);
expect(HouseSVG.at(0)).toBe('svg');
});
it('should match the snapshot', () => {
const HouseSVG = buildLucideIconNode(House);
expect(JSON.stringify(HouseSVG.at(1))).toMatchSnapshot();
});
it('should override dimensions, but not viewBox', () => {
const HouseSVG = buildLucideIconNode(House, { size: 12 });
expect(HouseSVG[1]['width']).toBe('12');
expect(HouseSVG[1]['height']).toBe('12');
expect(HouseSVG[1]['viewBox']).toBe('0 0 24 24');
});
it('should override width, but not height', () => {
const HouseSVG = buildLucideIconNode(House, { width: 12 });
expect(HouseSVG[1]['width']).toBe('12');
expect(HouseSVG[1]['height']).toBe('24');
expect(HouseSVG[1]['viewBox']).toBe('0 0 24 24');
});
it('should override height, but not width', () => {
const HouseSVG = buildLucideIconNode(House, { height: 12 });
expect(HouseSVG[1]['width']).toBe('24');
expect(HouseSVG[1]['height']).toBe('12');
expect(HouseSVG[1]['viewBox']).toBe('0 0 24 24');
});
it('should override color', () => {
const HouseSVG = buildLucideIconNode(House, { color: 'pink' });
expect(HouseSVG[1]['stroke']).toBe('pink');
expect(HouseSVG[1]['fill']).toBe('none');
});
it('should override stroke width', () => {
const HouseSVG = buildLucideIconNode(House, { strokeWidth: 12 });
expect(HouseSVG[1]['stroke-width']).toBe('12');
});
it('should set non-scaling-stroke', () => {
const HouseSVG = buildLucideIconNode(House, { absoluteStrokeWidth: true });
expect(HouseSVG[1]['vector-effect']).toBe('non-scaling-stroke');
});
it('should not set non-scaling-stroke', () => {
const HouseSVG = buildLucideIconNode(House, { absoluteStrokeWidth: false });
expect(HouseSVG[1]['vector-effect']).toBeUndefined();
});
it('should create icon node with attributes', () => {
const HouseSVG = buildLucideIconNode(House, { attributes: { fill: 'red' } });
expect(HouseSVG[1]['fill']).toBe('red');
});
it('should create icon node with class name', () => {
const HouseSVG = buildLucideIconNode(House, { attributes: { class: 'icon' } });
expect(HouseSVG[1]['class']).toBe('icon');
});
it('should merge classes', () => {
const HouseSVG = buildLucideIconNode(House, { className: 'icon' });
expect(HouseSVG[1]['class']).toBe('lucide lucide-house icon');
});
});

View File

@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest';
import { House } from '../src/lucide-icons';
import { getOriginalSvg, removeKeys } from './helpers';
import buildLucideSvg from '../src/buildLucideSvg';
import buildLucideDataUri from '../src/buildLucideDataUri';
describe('buildLucideSvg', () => {
it('should match the snapshot', () => {
const HouseSVG = buildLucideSvg(House);
expect(HouseSVG).toMatchSnapshot();
});
it('should create the correct svg element', () => {
const HouseSVG = buildLucideSvg(House);
const svg = getOriginalSvg('house', undefined, true);
expect(removeKeys(HouseSVG)).toBe(svg);
});
});
describe('buildLucideSvgDataUri', () => {
it('should match the snapshot', () => {
const HouseDataUri = buildLucideDataUri(House);
expect(HouseDataUri).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,18 @@
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['class'] = `lucide lucide-${aliasName ?? iconName}`;
}
return stringify(svgParsed, { selfClose: false });
};
export const removeKeys = (svg: string) => svg.replaceAll(/ key="[^"]+"/g, '');

View File

@@ -0,0 +1,10 @@
import { describe, expect, it } from 'vitest';
import { House } from '../src/lucide-icons';
describe('lucide-icons', () => {
it('should init', () => {
const HouseSVG = House;
expect(House).toMatchObject(House);
});
});

View File

@@ -0,0 +1,5 @@
import { expect } from 'vitest';
import '@testing-library/jest-dom/vitest';
import htmlSerializer from 'jest-serializer-html';
expect.addSnapshotSerializer(htmlSerializer);

View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"strict": true,
"declaration": false,
"noEmitOnError": true,
"noEmit": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"module": "ESNext",
"target": "ESNext",
"esModuleInterop": true,
"isolatedModules": true,
"lib": ["esnext", "dom"],
"skipLibCheck": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"downlevelIteration": true,
"sourceMap": true,
"outDir": "./dist",
},
"exclude": ["**/node_modules"],
}

View File

@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: './tests/setupVitest.js',
}
});

94
pnpm-lock.yaml generated
View File

@@ -246,6 +246,45 @@ importers:
specifier: ^4.0.12
version: 4.0.12(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(jsdom@20.0.3)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
packages/icons:
devDependencies:
'@lucide/build-icons':
specifier: workspace:*
version: link:../../tools/build-icons
'@lucide/helpers':
specifier: ^1.0.0
version: 1.0.0
'@lucide/rollup-plugins':
specifier: workspace:*
version: link:../../tools/rollup-plugins
'@rollup/plugin-replace':
specifier: ^6.0.2
version: 6.0.3(rollup@4.53.3)
'@testing-library/jest-dom':
specifier: ^6.6.3
version: 6.9.1
jest-serializer-html:
specifier: ^7.1.0
version: 7.1.0
rollup:
specifier: ^4.53.3
version: 4.53.3
rollup-plugin-dts:
specifier: ^6.2.3
version: 6.2.3(rollup@4.53.3)(typescript@5.9.3)
rollup-plugin-preserve-directives:
specifier: ^0.4.0
version: 0.4.0(rollup@4.53.3)
typescript:
specifier: ^5.8.3
version: 5.9.3
vite:
specifier: ^7.2.4
version: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
vitest:
specifier: ^4.0.12
version: 4.0.12(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(jsdom@20.0.3)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
packages/lucide:
devDependencies:
'@lucide/build-icons':
@@ -3850,6 +3889,9 @@ packages:
'@lezer/lr@1.4.3':
resolution: {integrity: sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA==}
'@lucide/helpers@1.0.0':
resolution: {integrity: sha512-9dfxrgLLaoCfr3R/eh6wwlUcY+ZPdEv6SDwFMUhYoO6HhGL8yN8hb5ZwI/OfzbK9mdJpa+jYfwP4nF4ZlZwZLA==}
'@mapbox/node-pre-gyp@1.0.11':
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
hasBin: true
@@ -4581,15 +4623,6 @@ packages:
rollup:
optional: true
'@rollup/pluginutils@5.1.0':
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/pluginutils@5.3.0':
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
engines: {node: '>=14.0.0'}
@@ -9608,9 +9641,6 @@ packages:
resolution: {integrity: sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==}
engines: {node: '>=12'}
magic-string@0.30.11:
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -15834,7 +15864,7 @@ snapshots:
'@babel/parser': 7.28.5
'@babel/template': 7.27.2
'@babel/types': 7.28.5
debug: 4.3.5
debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -17066,6 +17096,8 @@ snapshots:
dependencies:
'@lezer/common': 1.3.0
'@lucide/helpers@1.0.0': {}
'@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)':
dependencies:
detect-libc: 2.1.2
@@ -17878,14 +17910,6 @@ snapshots:
optionalDependencies:
rollup: 4.53.3
'@rollup/pluginutils@5.1.0(rollup@4.53.3)':
dependencies:
'@types/estree': 1.0.6
estree-walker: 2.0.2
picomatch: 2.3.1
optionalDependencies:
rollup: 4.53.3
'@rollup/pluginutils@5.3.0(rollup@4.53.3)':
dependencies:
'@types/estree': 1.0.8
@@ -18175,7 +18199,7 @@ snapshots:
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.43.14)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)))(svelte@5.43.14)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0))':
dependencies:
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.43.14)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0))
debug: 4.4.1
debug: 4.4.3
svelte: 5.43.14
vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(less@4.2.0)(sass@1.77.8)(stylus@0.56.0)(terser@5.44.1)(yaml@2.8.0)
transitivePeerDependencies:
@@ -18486,11 +18510,11 @@ snapshots:
'@types/eslint-scope@3.7.7':
dependencies:
'@types/eslint': 9.6.0
'@types/estree': 0.0.51
'@types/estree': 1.0.8
'@types/eslint@9.6.0':
dependencies:
'@types/estree': 0.0.51
'@types/estree': 1.0.8
'@types/json-schema': 7.0.15
'@types/estree@0.0.39': {}
@@ -19311,7 +19335,7 @@ snapshots:
acorn-walk@8.3.3:
dependencies:
acorn: 8.14.1
acorn: 8.15.0
acorn@8.12.1: {}
@@ -20365,7 +20389,7 @@ snapshots:
code-red@1.0.4:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
'@types/estree': 1.0.6
'@types/estree': 1.0.8
acorn: 8.14.0
estree-walker: 3.0.3
periscopic: 3.1.0
@@ -22628,7 +22652,7 @@ snapshots:
dependencies:
'@tootallnate/once': 1.1.2
agent-base: 6.0.2
debug: 4.3.3
debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -22997,7 +23021,7 @@ snapshots:
is-reference@3.0.3:
dependencies:
'@types/estree': 1.0.6
'@types/estree': 1.0.8
is-regex@1.2.1:
dependencies:
@@ -23730,10 +23754,6 @@ snapshots:
dependencies:
sourcemap-codec: 1.4.8
magic-string@0.30.11:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -25392,7 +25412,7 @@ snapshots:
periscopic@3.1.0:
dependencies:
'@types/estree': 1.0.6
'@types/estree': 1.0.8
estree-walker: 3.0.3
is-reference: 3.0.3
@@ -26463,8 +26483,8 @@ snapshots:
rollup-plugin-preserve-directives@0.4.0(rollup@4.53.3):
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.53.3)
magic-string: 0.30.11
'@rollup/pluginutils': 5.3.0(rollup@4.53.3)
magic-string: 0.30.21
rollup: 4.53.3
rollup-plugin-sourcemaps@0.6.3(@types/node@12.20.55)(rollup@2.79.2):
@@ -26991,7 +27011,7 @@ snapshots:
socks-proxy-agent@6.2.1:
dependencies:
agent-base: 6.0.2
debug: 4.3.3
debug: 4.4.3
socks: 2.8.3
transitivePeerDependencies:
- supports-color
@@ -26999,7 +27019,7 @@ snapshots:
socks-proxy-agent@7.0.0:
dependencies:
agent-base: 6.0.2
debug: 4.3.3
debug: 4.4.3
socks: 2.8.3
transitivePeerDependencies:
- supports-color