Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
603 changes: 79 additions & 524 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,24 @@
"author": "Dazl",
"license": "MIT",
"devDependencies": {
"@file-services/node": "^10.0.1",
"@playwright/browser-chromium": "^1.54.1",
"@playwright/test": "^1.54.1",
"@types/chai": "^5.2.2",
"@types/node": "^24.0.15",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"chai": "^5.2.1",
"eslint": "^9.31.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-no-only-tests": "^3.3.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"lucide-react": "^0.525.0",
"prettier": "^3.6.2",
"promise-assist": "^2.0.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"rimraf": "^6.0.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.38.0"
Expand Down
21 changes: 21 additions & 0 deletions packages/lucide-react/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License Copyright (c) 2025 Dazl

Permission is hereby granted, free of
charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice
(including the next paragraph) shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
31 changes: 31 additions & 0 deletions packages/lucide-react/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# @dazl-libs/lucide-react

**dazl-lucide-react** is a utility library for working with the [Lucide](https://lucide.dev/) icon set in React projects. It provides utilities to list all available icons and access prerendered SVGs programmatically, making it easy to integrate Lucide icons into your workflows or build tools.
Comment thread
AviVahl marked this conversation as resolved.

## Features

- Retrieve a full list of Lucide icon names
- Access prerendered SVGs as data or files
- Utility functions for searching and filtering icons
- Works with the official Lucide icon set

## Installation

```bash
npm install @dazl-libs/lucide-react
```

## Usage

```js
import iconList from '@dazl-libs/lucide-react';
import PrerenderdCameraSVG from '@dazl-libs/lucide-react/camera.svg';

## Prerendered SVGs

All icons are available as prerendered SVG strings or files, suitable for direct embedding or further processing.

## License

[MIT](./LICENSE)
Comment thread
AviVahl marked this conversation as resolved.
```
110 changes: 110 additions & 0 deletions packages/lucide-react/build.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React from 'react';
Comment thread
AviVahl marked this conversation as resolved.
import { renderToStaticMarkup } from 'react-dom/server';
import { nodeFs } from '@file-services/node';
import * as lucideReact from 'lucide-react';

interface IconData {
name: string;
aliases: string[];
svgString: string;
}

interface IconTypeAugmentation {
Comment thread
AviVahl marked this conversation as resolved.
name: string;
aliases: string[];
file: string;
}

interface TypeAugmentationFile {
icons: IconTypeAugmentation[];
}

function buildIcons(): void {
const distDir = nodeFs.join(import.meta.dirname, 'dist');

Check failure on line 23 in packages/lucide-react/build.tsx

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Unsafe argument of type error typed assigned to a parameter of type `string`

Check failure on line 23 in packages/lucide-react/build.tsx

View workflow job for this annotation

GitHub Actions / windows-latest

Unsafe argument of type error typed assigned to a parameter of type `string`

Check failure on line 23 in packages/lucide-react/build.tsx

View workflow job for this annotation

GitHub Actions / macOS-latest

Unsafe argument of type error typed assigned to a parameter of type `string`

Check failure on line 23 in packages/lucide-react/build.tsx

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Unsafe argument of type error typed assigned to a parameter of type `string`

Check failure on line 23 in packages/lucide-react/build.tsx

View workflow job for this annotation

GitHub Actions / windows-latest

Unsafe argument of type error typed assigned to a parameter of type `string`

Check failure on line 23 in packages/lucide-react/build.tsx

View workflow job for this annotation

GitHub Actions / macOS-latest

Unsafe argument of type error typed assigned to a parameter of type `string`
const svgDir = nodeFs.join(distDir, 'svg');

nodeFs.ensureDirectorySync(distDir);
nodeFs.ensureDirectorySync(svgDir);

const svgsByContent = new Map<string, IconData>();

for (const [exportName, IconComponent] of Object.entries(lucideReact)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lucide-react exports the same icon 3 times:

  • Speaker
  • LucideSpeaker
  • SpeakerIcon

So while we could list all 3, we could just list "Speaker", and use the "SpeakerIcon" export (for less code confusion)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could iterate on lucideReact.icons instead to avoid the duplicates

// Skip non-component exports
if (typeof IconComponent !== 'function' && typeof IconComponent !== 'object') {
continue;
}

if (!IconComponent) {
continue;
}

// Skip known utility functions
if (exportName === 'createLucideIcon' || exportName === 'Icon') {
continue;
}

// Icon components have uppercase first letter
if (exportName[0] !== exportName[0]?.toUpperCase()) {
continue;
}

try {
const svgString = renderToStaticMarkup(
React.createElement(IconComponent as React.ComponentType<{ size: number; strokeWidth: number }>, {
size: 24,
strokeWidth: 2,
}),
);

// Check if we've seen this SVG content before
if (svgsByContent.has(svgString)) {
const existing = svgsByContent.get(svgString)!;
// Keep the shortest name
if (exportName.length < existing.name.length) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while this works for the Abc/LucideAbc/AbcIcon case, this makes no sense for icons that are being renamed (with the old one kept around)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you suggest?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iterate on lucideReact.icons and avoid checking for same-content and aliases.

existing.aliases.push(existing.name);
existing.name = exportName;
} else {
existing.aliases.push(exportName);
}
} else {
svgsByContent.set(svgString, {
name: exportName,
aliases: [exportName],
svgString,
});
}
} catch {
// Skip icons that fail to render
continue;
}
}

const iconsTypeAugmentation: IconTypeAugmentation[] = [];

// Save unique SVGs and build icons list
for (const { name, aliases, svgString } of svgsByContent.values()) {
const filename = `${name.toLowerCase()}.svg`;
const filepath = nodeFs.join(svgDir, filename);

// Save SVG file
nodeFs.writeFileSync(filepath, svgString);

// Add to icons list (use relative path from dist folder)
const iconEntry = {
name: name,
aliases: aliases,
file: `svg/${filename}`,
};

iconsTypeAugmentation.push(iconEntry);
Comment thread
AviVahl marked this conversation as resolved.
}

// Save type augmentation file
const typeAugmentationFile: TypeAugmentationFile = {
icons: iconsTypeAugmentation,
};
const typeAugmentationPath = nodeFs.join(distDir, 'types-augmentation.json');
nodeFs.writeFileSync(typeAugmentationPath, JSON.stringify(typeAugmentationFile, null, 2));
}

export default buildIcons;
3 changes: 3 additions & 0 deletions packages/lucide-react/do-build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import buildIcons from './build.tsx';

buildIcons();
21 changes: 21 additions & 0 deletions packages/lucide-react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@dazl-libs/lucide-react",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scoped packages must have publishConfig

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, use "type": "module", like all our other packages

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still missing publishConfig key. see dev package for example. otherwise, it will fail during first publish

"version": "1.0.0",
"type": "module",
"license": "MIT",
"exports": {
".": {
"import": "./dist/types-augmentation.json",
"require": "./dist/types-augmentation.json"
},
"./*.svg": {
"import": "./dist/svg/*.svg",
"require": "./dist/svg/*.svg"
},
"./package.json": "./package.json"
},
"scripts": {
"build": "node do-build.ts",
"prepack": "npm run build"
}
}
7 changes: 7 additions & 0 deletions packages/lucide-react/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure where you came up with this config. this tsconfig does not extend the root base one.
see the dev package for a proper example.
this repo uses tsconfig workspaces. connect it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add noEmit: true to the package own tsconfig, and add a reference to it from root tsconfig.json, so it will be type-checked when package is built

"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"jsx": "react-jsx"
}
}
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
/* Language and Environment */
"target": "es2023", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"lib": ["es2023","dom"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react-jsx",

// "jsx": "preserve", /* Specify what JSX code is generated. */
// "libReplacement": true, /* Enable lib replacement. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
Expand Down
Loading