Compare commits

...

24 Commits

Author SHA1 Message Date
Karsa
1b3173b17b fix(icons): arcified and renamed paintbrush-2 (#2146)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-05-21 15:34:42 +02:00
Eric Fennis
d5f4275055 fix(lucide-svelte): Remove export mergeClasses in svelte Icon (#2119)
* Remove export svelte icon

* formatting
2024-05-21 15:31:05 +02:00
Jakob Guddas
6abae7cc14 fix(icons): arcified key icon (#2067)
* Updated icons/key.svg

* Updated icons/key.json

* Update icons/key.svg

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>

* Add space before close key.svg

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-05-21 15:29:20 +02:00
Eric Fennis
f32ffcd2a2 fix(docs): Fix data api 2024-05-14 23:31:11 +02:00
Karsa
824bb897cf feat(figma): add data API endpoint for new plugin (#2018)
* feat(figma): add data API endpoint with every metadata needed by new Figma plugin

* chore(docs/api): extract data as static const

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-05-14 22:44:52 +02:00
Jakob Guddas
2843a76e28 docs(site): changed my name in the team section (#2142) 2024-05-13 08:23:53 +02:00
Jakob Guddas
155ff3319a fix: resolved broken icons as code details section issue (#2144) 2024-05-13 08:23:23 +02:00
Karsa
34dddb811b fix(icons): fix incorrect category name for diamond-plus/minus (#2125)
Co-authored-by: Karsa <karsa@sztaki.hu>
2024-05-03 17:15:48 +02:00
Daniel Bayley
5fead67bf3 Sort out loader/cursor icons (#1331)
* Add `loader-pinwheel` icon

* Optimise `loader` icon

* Improve `loader` metadata

* Add `loader-circle-big` variant

* Rename `loader-2` to `loader-circle`

* Improve `more-horizontal` metadata

* Add `mouse-pointer-ban` icon

* Improve `mouse-pointer` icons metadata

* Improve metadata

* Revert rename of `loader-2` to `loader-circle`

* Revert "Improve `more-horizontal` metadata"

This reverts commit 41fa676b15.

* Formatting

* Format pinwheel and remove loader-circle-big

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-05-03 10:31:21 +02:00
Karsa
48dc9372db fix(icons): add more Lucide-ish rounding to sparkle icons (#1937)
* fix(icons): add more Lucide-ish rounding to sparkle icons

* chore(icons): sharpen points ever so slightly for better visual weight

* chore(icons): linting fix

* chore(icons): update sparkles & moon-star icon as per Discord

---------

Co-authored-by: Karsa <karsa@sztaki.hu>
2024-05-03 10:27:46 +02:00
Karsa
747446fc76 fix(docs): fix uppercase import in lucide-static docs (#2118) 2024-05-03 08:34:04 +02:00
Jakob Guddas
5862ea735e fix(icons): arcified award icon (#2113)
* Updated icons/award.svg

* Update icons/award.svg

Co-authored-by: Karsa <contact@karsa.org>

---------

Co-authored-by: Karsa <contact@karsa.org>
2024-05-02 11:44:15 +02:00
Rob van Bakel
3a8a349771 fix: Prevent internal router from handling external links (#2107) 2024-04-28 11:22:07 +02:00
Eric Fennis
70bc2245c7 fix(lucide-svelte): Revert shared package for lucide-svelte (#2109)
* fix(lucide-svelte): Revert shared package for `lucide-svelte`

* Update lockfile
2024-04-28 11:05:58 +02:00
Eric Fennis
89f6b6357d fix: Revert moving createIcons to separate file 2024-04-26 19:39:48 +02:00
Jakob Guddas
354af456d3 fix(icons): added rounding to alarm-smoke icon (#2059)
* Updated icons/alarm-smoke.svg

* Updated icons/alarm-smoke.svg

* Updated icons/alarm-smoke.json

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
2024-04-26 18:45:50 +02:00
Eric Fennis
e50582e93e feat(icon-component): Creating icons with iconNodes (#1997)
* Add useIconComponent, lucide-react

* Add concept useIconComponent

* add useIconComponents to packages

* Add icon component

* Add icon component

* Add tests for react packages

* Reset changes in icons

* Add types

* Add support for Icon components in Lucide Vue Next

* update tests

* Update tests

* Enable Svelte component

* Fix lucide-react-native tests

* Update Solid package

* update snapshots

* Add docs

* add docs

* Update tests

* Formatting

* Formatting

* Update package lock

* Remove `useIconComponent`

* Update guides

* Update exports preact and solid package

* Formatting

* Format createIcons.ts

* Add lucide lab repo link in docs
2024-04-26 17:59:04 +02:00
Eric Fennis
65deefa53c ci: Revert pnpm in pull-request.yml 2024-04-26 16:25:44 +02:00
Kyle Angelo Galendez
54ef137b49 feat(icons): added grid-2x2-x icon (#2085)
* Added icons/grid-2x2-x.svg

* Added icons/grid-2x2-x.json

* Archify grid-2x2-x.svg
2024-04-26 15:41:08 +02:00
Kyle Angelo Galendez
d4df542117 feat(icons): added grid-2x2-check icon (#2084)
* Added icons/grid-2x2-check.svg

* Added icons/grid-2x2-check.json

* Updated icons/grid-2x2-check.json

* Arcify grid-2x2-check.svg

* Unarcify the check
2024-04-26 15:40:50 +02:00
Jakob Guddas
8c1e56a7bf fix(icons): arcify activity icon (#2058)
* Updated icons/activity.svg

* Updated icons/activity.svg

* Updated icons/activity.json

* Updated icons/activity.svg

* Updated icons/activity.svg

* Update icons/activity.svg

Co-authored-by: Karsa <contact@karsa.org>

---------

Co-authored-by: Karsa <contact@karsa.org>
2024-04-26 10:59:23 +02:00
Jakob Guddas
dff2172173 Updated icons/printer.svg (#2066) 2024-04-26 10:42:33 +02:00
Kyle Angelo Galendez
e8ccd3df7e Add more OS (#2101)
This includes ChromeOS, iOS, and Android to ensure the report is concise as possible.
2024-04-26 10:40:29 +02:00
Kyle Angelo Galendez
b593355537 Add more OS (#2100)
This includes ChromeOS, iOS, and Android to ensure the report is concise as possible.
2024-04-26 10:40:08 +02:00
120 changed files with 2000 additions and 460 deletions

View File

@@ -69,6 +69,9 @@ body:
- label: Windows
- label: Linux
- label: macOS
- label: ChromeOS
- label: iOS
- label: Android
- label: Other/not relevant
- type: textarea
id: description

View File

@@ -30,6 +30,9 @@ body:
- label: Windows
- label: Linux
- label: macOS
- label: ChromeOS
- label: iOS
- label: Android
- label: Other/not relevant
- type: textarea
id: description

View File

@@ -45,9 +45,8 @@ jobs:
with:
files: icons/*
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@v2
- name: Install simple-git (safer and faster than installing all deps)
run: pnpm install simple-git
run: npm install simple-git
- name: Generate annotations
run: node ./scripts/updateContributors.mjs
env:
@@ -96,9 +95,8 @@ jobs:
body-includes: Added or changed icons
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@v2
- name: Install svgson for code preview (safer and faster than installing all deps)
run: pnpm install svgson
run: npm install svgson
- name: Generate comment markup
run: node ./scripts/generateChangedIconsCommentMarkup.mjs >> comment-markup.md

1
.gitignore vendored
View File

@@ -34,6 +34,7 @@ docs/.vitepress/data/iconNodes
docs/.vitepress/data/iconMetaData.ts
docs/.vitepress/data/releaseMetaData.json
docs/.vitepress/data/releaseMetaData
docs/.vitepress/data/categoriesData.json
docs/.vitepress/data/iconDetails
docs/.vitepress/data/relatedIcons.json
docs/.vercel

View File

@@ -2,6 +2,10 @@ pnpm-lock.yaml
# docs examples
docs/**/examples/
docs/.vitepress/.temp
docs/.vitepress/cache
docs/.vitepress/data
docs/.nitro
# lucide-angular
packages/lucide-angular/.angular/cache

View File

@@ -1,4 +1,3 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData';
export default eventHandler((event) => {

View File

@@ -0,0 +1,40 @@
import iconNodes from '../../data/iconNodes/index.ts';
import { IconNodeWithKeys } from '../../theme/types';
import iconMetaData from '../../data/iconMetaData';
import releaseMeta from '../../data/releaseMetaData.json';
import categories from '../../data/categoriesData.json';
const dataResponse = {
icons: Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
return [name, attrs];
});
acc[name] = {
iconNode: newIconNode,
aliases: (iconMetaData[name]?.aliases ?? []).map((alias) =>
typeof alias === 'string' ? alias : alias.name,
),
tags: iconMetaData[name].tags ?? [],
categories: iconMetaData[name].categories ?? [],
...releaseMeta[name],
};
return acc;
}, {}),
aliases: Object.entries(iconNodes).reduce((acc, [name]) => {
for (const alias of iconMetaData[name]?.aliases ?? []) {
acc[typeof alias === 'string' ? alias : alias.name] = name;
}
return acc;
}, {}),
categories,
};
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return dataResponse;
});

View File

@@ -1,4 +1,3 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../data/iconMetaData';
export default eventHandler((event) => {

View File

@@ -31,8 +31,8 @@
]
},
{
"name": "jguddas",
"title": "Maintainer of Lucide & Software engineer @lego",
"name": "Jakob Guddas",
"title": "Maintainer of Lucide & Software engineer @LEGO",
"image": "https://github.com/jguddas.png?size=192",
"socialLinks": [
{

View File

@@ -7,7 +7,16 @@ const props = defineProps<{
href?: string
}>()
const isExternal = computed(() => props.href?.startsWith('http') ?? false)
const component = computed(() => props.href ? 'a' : 'div')
const target = computed(() => isExternal.value ? '_blank' : undefined)
const rel = computed(() => isExternal.value ? 'noreferrer noopener' : undefined)
const onClick = computed(() => {
if(!props.href || isExternal) return
return go(props.href)
})
</script>
<template>
@@ -15,7 +24,9 @@ const component = computed(() => props.href ? 'a' : 'div')
:is="component"
:href="href"
class="badge"
@click="props?.href ? go(href) : undefined"
:target="target"
:rel="rel"
@click="onClick"
>
<slot/>
</component>

View File

@@ -8,8 +8,6 @@ import { data } from './HomeHeroBefore.data'
<HomeContainer class="container">
<Badge
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
target="_blank"
rel="noreferrer noopener"
>v{{ data.version }}</Badge>
</HomeContainer>
</template>

View File

@@ -55,8 +55,6 @@ const Expand = createLucideIcon('Expand', expand)
v-if="icon.createdRelease"
class="version"
:href="releaseTagLink(icon.createdRelease.version)"
target="_blank"
rel="noreferrer noopener"
>v{{ icon.createdRelease.version }}</Badge>
<IconButton @click="go(`/icons/${icon.name}`)">
<component :is="Expand" />

View File

@@ -115,3 +115,20 @@ import { icons } from 'lucide-angular';
LucideAngularModule.pick(icons)
```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used in the same way as the official icons.
```js
import { LucideAngularModule } from 'lucide-angular';
import { burger } from '@lucide/lab';
@NgModule({
imports: [
LucideAngularModule.pick({ burger })
]
})
export class AppModule { }
```

View File

@@ -67,6 +67,26 @@ const App = () => {
> SVG attributes in Preact aren't transformed, so if you want to change for example the `stroke-linejoin` you need to pass it in kebabcase. Basically how the SVG spec want you to write it. See this topic in the [Preact documentation](https://preactjs.com/guide/v10/differences-to-react/#svg-inside-jsx).
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```jsx
import { Icon } from 'lucide-preact';
import { burger } from '@lucide/lab';
const App = () => (
<Icon iconNode={burger} />
);
```
## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended.

View File

@@ -61,6 +61,26 @@ const App = () => {
};
```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```jsx
import { Icon } from 'lucide-react-native';
import { burger } from '@lucide/lab';
const App = () => (
<Icon iconNode={burger} />
);
```
## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended.

View File

@@ -61,6 +61,26 @@ const App = () => {
};
```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```jsx
import { Icon } from 'lucide-react';
import { burger } from '@lucide/lab';
const App = () => (
<Icon iconNode={burger} />
);
```
## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended.

View File

@@ -61,6 +61,26 @@ const App = () => {
};
```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like the regular Lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```jsx
import { Icon } from 'lucide-solid';
import { burger, sausage } from '@lucide/lab';
const App = () => (
<Icon iconNode={sausage} color="red"/>
);
```
## One generic icon component
It is possible to create one generic icon component to load icons. It's not recommended.

View File

@@ -143,7 +143,7 @@ and update the SVG as follows
### Icon Font
```css
@import ('~lucide-static/font/Lucide.css');
@import ('~lucide-static/font/lucide.css');
```
```html

View File

@@ -166,6 +166,27 @@ The package includes type definitions for all icons. This is useful if you want
For more details about typing the `svelte:component` directive, see the [Svelte documentation](https://svelte.dev/docs/typescript#types-componenttype).
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like the regular Lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```svelte
<script>
import { Icon } from 'lucide-svelte';
import { burger, sausage } from '@lucide/lab';
</script>
<Icon iconNode={burger} />
<Icon iconNode={sausage} color="red"/>
```
## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended.

View File

@@ -37,16 +37,16 @@ Each icon can be imported as a Vue component, which renders an inline SVG Elemen
You can pass additional props to adjust the icon.
```vue
<script setup>
import { Camera } from 'lucide-vue-next';
</script>
<template>
<Camera
color="red"
:size="32"
/>
</template>
<script setup>
import { Camera } from 'lucide-vue-next';
</script>
```
## Props
@@ -69,6 +69,28 @@ To customize the appearance of an icon, you can pass custom properties as props
</template>
```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```vue
<script setup>
import { Icon } from 'lucide-vue-next';
import { burger } from '@lucide/lab';
</script>
<template>
<Icon :iconNode={burger} />
</template>
```
## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended.

View File

@@ -130,3 +130,18 @@ menuIcon.classList.add('my-icon-class');
const myApp = document.getElementById('app');
myApp.appendChild(menuIcon);
```
### With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used in the same way as the official icons.
```js
import { burger } from '@lucide/lab';
createIcons({
icons: {
burger
}
});
```

View File

@@ -69,8 +69,6 @@ function releaseTagLink(version) {
<Label>Created:</Label>
<Badge
:href="releaseTagLink(params.createdRelease.version)"
target="_blank"
rel="noreferrer noopener"
>
v{{params.createdRelease.version}}
</Badge>
@@ -82,8 +80,6 @@ function releaseTagLink(version) {
<Label>Last changed:</Label>
<Badge
:href="releaseTagLink(params.changedRelease.version)"
target="_blank"
rel="noreferrer noopener"
>
v{{params.changedRelease.version}}
</Badge>

View File

@@ -12,6 +12,7 @@
"prebuild:iconNodes": "node ../scripts/writeIconNodes.mjs",
"prebuild:metaJson": "node ../scripts/writeIconMetaIndex.mjs",
"prebuild:releaseJson": "node ../scripts/writeReleaseMetadata.mjs",
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mjs",
"prebuild:relatedIcons": "node ../scripts/writeIconRelatedIcons.mjs",
"prebuild:iconDetails": "node ../scripts/writeIconDetails.mjs",
"postbuild:vercelJson": "node ../scripts/writeVercelOutput.mjs",

View File

@@ -0,0 +1,36 @@
import fs from 'fs';
import path from 'path';
const currentDir = process.cwd();
const dataDirectory = path.resolve(currentDir, '.vitepress/data');
const directory = path.join(process.cwd(), '../categories');
function getAllCategoryFiles() {
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
return fileNames.map((fileName) => {
const name = path.basename(fileName, '.json');
const fileContent = fs.readFileSync(path.join(directory, fileName), 'utf8');
const parsedFileContent = JSON.parse(fileContent);
return {
name,
title: parsedFileContent.title,
};
});
}
const categoriesFile = path.resolve(dataDirectory, `categoriesData.json`);
const categoriesData = getAllCategoryFiles()
fs.promises
.writeFile(categoriesFile, JSON.stringify(categoriesData, null, 2), 'utf-8')
.then(() => {
console.log('Successfully written categoriesData.json file');
})
.catch((error) => {
throw new Error(`Something went wrong generating the categoriesData.json file,\n ${error}`);
});

View File

@@ -1,7 +1,8 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"colebemis"
"colebemis",
"jguddas"
],
"tags": [
"pulse",

View File

@@ -9,5 +9,5 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2" />
</svg>

Before

Width:  |  Height:  |  Size: 249 B

After

Width:  |  Height:  |  Size: 346 B

View File

@@ -2,7 +2,8 @@
"$schema": "../icon.schema.json",
"contributors": [
"danielbayley",
"karsa-mistmere"
"karsa-mistmere",
"jguddas"
],
"tags": [
"fire",

View File

@@ -9,9 +9,9 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M4 8a2 2 0 0 1-2-2V3h20v3a2 2 0 0 1-2 2Z" />
<path d="m19 8-.8 3c-.1.6-.6 1-1.2 1H7c-.6 0-1.1-.4-1.2-1L5 8" />
<path d="M16 21c0-2.5 2-2.5 2-5" />
<path d="M11 21c0-2.5 2-2.5 2-5" />
<path d="M16 21c0-2.5 2-2.5 2-5" />
<path d="m19 8-.8 3a1.25 1.25 0 0 1-1.2 1H7a1.25 1.25 0 0 1-1.2-1L5 8" />
<path d="M21 3a1 1 0 0 1 1 1v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a1 1 0 0 1 1-1z" />
<path d="M6 21c0-2.5 2-2.5 2-5" />
</svg>

Before

Width:  |  Height:  |  Size: 445 B

After

Width:  |  Height:  |  Size: 481 B

View File

@@ -9,6 +9,6 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526" />
<circle cx="12" cy="8" r="6" />
<path d="M15.477 12.89 17 22l-5-3-5 3 1.523-9.11" />
</svg>

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 376 B

View File

@@ -30,7 +30,7 @@
],
"categories": [
"multimedia",
"shape",
"shapes",
"photography",
"tools",
"devices"

View File

@@ -25,7 +25,7 @@
],
"categories": [
"multimedia",
"shape",
"shapes",
"photography",
"tools",
"devices"

32
icons/grid-2x2-check.json Normal file
View File

@@ -0,0 +1,32 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"danielbayley",
"chessurisme"
],
"tags": [
"table",
"rows",
"columns",
"blocks",
"plot",
"land",
"geometry",
"measure",
"data",
"size",
"width",
"height",
"distance",
"surface area",
"square meter",
"acre"
],
"categories": [
"text",
"layout",
"design",
"shapes",
"maths"
]
}

14
icons/grid-2x2-check.svg Normal file
View File

@@ -0,0 +1,14 @@
<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="M12 3v17a1 1 0 0 1-1 1H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v6a1 1 0 0 1-1 1H3" />
<path d="m16 19 2 2 4-4" />
</svg>

After

Width:  |  Height:  |  Size: 343 B

32
icons/grid-2x2-x.json Normal file
View File

@@ -0,0 +1,32 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"danielbayley",
"chessurisme"
],
"tags": [
"table",
"rows",
"columns",
"data",
"blocks",
"plot",
"land",
"geometry",
"measure",
"size",
"width",
"height",
"distance",
"surface area",
"square meter",
"acre"
],
"categories": [
"text",
"layout",
"design",
"shapes",
"maths"
]
}

15
icons/grid-2x2-x.svg Normal file
View File

@@ -0,0 +1,15 @@
<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="M12 3v17a1 1 0 0 1-1 1H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v6a1 1 0 0 1-1 1H3" />
<path d="m16 16 5 5" />
<path d="m16 21 5-5" />
</svg>

After

Width:  |  Height:  |  Size: 365 B

View File

@@ -4,8 +4,7 @@
"ashygee",
"csandman",
"mittalyashu",
"ericfennis",
"jguddas"
"ericfennis"
],
"tags": [
"password",

View File

@@ -9,7 +9,7 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="7.5" cy="15.5" r="5.5" />
<path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4" />
<path d="m21 2-9.6 9.6" />
<path d="m15.5 7.5 3 3L22 7l-3-3" />
<circle cx="7.5" cy="15.5" r="5.5" />
</svg>

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 355 B

View File

@@ -7,9 +7,17 @@
"ericfennis"
],
"tags": [
"load"
"loading",
"wait",
"busy",
"progress",
"spinner",
"spinning",
"throbber",
"circle"
],
"categories": [
"cursors",
"multimedia",
"layout"
],

View File

@@ -0,0 +1,22 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"danielbayley"
],
"tags": [
"loading",
"wait",
"busy",
"progress",
"throbber",
"spinner",
"spinning",
"beach ball",
"frozen",
"freeze"
],
"categories": [
"cursors",
"design"
]
}

16
icons/loader-pinwheel.svg Normal file
View File

@@ -0,0 +1,16 @@
<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="M2 12c0-2.8 2.2-5 5-5s5 2.2 5 5 2.2 5 5 5 5-2.2 5-5" />
<path d="M7 20.7a1 1 0 1 1 5-8.7 1 1 0 1 0 5-8.6" />
<path d="M7 3.3a1 1 0 1 1 5 8.6 1 1 0 1 0 5 8.6" />
<circle cx="12" cy="12" r="10" />
</svg>

After

Width:  |  Height:  |  Size: 420 B

View File

@@ -2,14 +2,22 @@
"$schema": "../icon.schema.json",
"contributors": [
"colebemis",
"ericfennis"
"ericfennis",
"danielbayley"
],
"tags": [
"load",
"wait"
"loading",
"wait",
"busy",
"progress",
"spinner",
"spinning",
"throbber"
],
"categories": [
"cursors",
"multimedia",
"layout"
"layout",
"design"
]
}

View File

@@ -9,12 +9,12 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="12" x2="12" y1="2" y2="6" />
<line x1="12" x2="12" y1="18" y2="22" />
<line x1="4.93" x2="7.76" y1="4.93" y2="7.76" />
<line x1="16.24" x2="19.07" y1="16.24" y2="19.07" />
<line x1="2" x2="6" y1="12" y2="12" />
<line x1="18" x2="22" y1="12" y2="12" />
<line x1="4.93" x2="7.76" y1="19.07" y2="16.24" />
<line x1="16.24" x2="19.07" y1="7.76" y2="4.93" />
<path d="M12 2v4" />
<path d="m16.2 7.8 2.9-2.9" />
<path d="M18 12h4" />
<path d="m16.2 16.2 2.9 2.9" />
<path d="M12 18v4" />
<path d="m4.9 19.1 2.9-2.9" />
<path d="M2 12h4" />
<path d="m4.9 4.9 2.9 2.9" />
</svg>

Before

Width:  |  Height:  |  Size: 588 B

After

Width:  |  Height:  |  Size: 434 B

View File

@@ -9,7 +9,7 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
<path d="M19 3v4" />
<path d="M21 5h-4" />
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9" />
<path d="M20 3v4" />
<path d="M22 5h-4" />
</svg>

Before

Width:  |  Height:  |  Size: 305 B

After

Width:  |  Height:  |  Size: 304 B

View File

@@ -5,9 +5,8 @@
"csandman"
],
"tags": [
"arrow",
"cursor",
"click"
"click",
"select"
],
"categories": [
"arrows",

View File

@@ -0,0 +1,18 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"danielbayley"
],
"tags": [
"wait",
"busy",
"loading",
"blocked",
"frozen",
"freeze"
],
"categories": [
"arrows",
"cursors"
]
}

View File

@@ -0,0 +1,15 @@
<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="m2 2 4 11 2-5 5-2Z" />
<circle cx="16" cy="16" r="6" />
<path d="m11.8 11.8 8.4 8.4" />
</svg>

After

Width:  |  Height:  |  Size: 311 B

View File

@@ -6,9 +6,8 @@
"jguddas"
],
"tags": [
"arrow",
"cursor",
"click"
"click",
"select"
],
"categories": [
"arrows",

View File

@@ -5,9 +5,8 @@
"ericfennis"
],
"tags": [
"arrow",
"cursor",
"click"
"click",
"select"
],
"categories": [
"arrows",

View File

@@ -19,5 +19,8 @@
"photography",
"home",
"tools"
],
"aliases": [
"paintbrush-2"
]
}

View File

@@ -9,8 +9,8 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M14 19.9V16h3a2 2 0 0 0 2-2v-2H5v2c0 1.1.9 2 2 2h3v3.9a2 2 0 1 0 4 0Z" />
<path d="M6 12V2h12v10" />
<path d="M14 2v4" />
<path d="M10 2v2" />
<path d="M14 2v4" />
<path d="M17 2a1 1 0 0 1 1 1v9H6V3a1 1 0 0 1 1-1z" />
<path d="M6 12a1 1 0 0 0-1 1v1a2 2 0 0 0 2 2h2a1 1 0 0 1 1 1v2.9a2 2 0 1 0 4 0V17a1 1 0 0 1 1-1h2a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1" />
</svg>

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 445 B

View File

@@ -9,7 +9,7 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="6 9 6 2 18 2 18 9" />
<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" />
<rect width="12" height="8" x="6" y="14" />
<path d="M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6" />
<rect x="6" y="14" width="12" height="8" rx="1" />
</svg>

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 406 B

View File

@@ -9,5 +9,5 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m12 3-1.9 5.8a2 2 0 0 1-1.287 1.288L3 12l5.8 1.9a2 2 0 0 1 1.288 1.287L12 21l1.9-5.8a2 2 0 0 1 1.287-1.288L21 12l-5.8-1.9a2 2 0 0 1-1.288-1.287Z" />
</svg>
<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" />
</svg>

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 475 B

View File

@@ -9,9 +9,9 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" />
<path d="M5 3v4" />
<path d="M19 17v4" />
<path d="M3 5h4" />
<path d="M17 19h4" />
<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" />
<path d="M20 3v4" />
<path d="M22 5h-4" />
<path d="M4 17v2" />
<path d="M5 18H3" />
</svg>

Before

Width:  |  Height:  |  Size: 481 B

After

Width:  |  Height:  |  Size: 568 B

View File

@@ -0,0 +1,50 @@
import { h, toChildArray } from 'preact';
import defaultAttributes from './defaultAttributes';
import type { IconNode, LucideProps } from './types';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
}
/**
* Lucide icon component
*
* @component Icon
* @param {object} props
* @param {string} props.color - The color of the icon
* @param {number} props.size - The size of the icon
* @param {number} props.strokeWidth - The stroke width of the icon
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
* @param {string} props.class - The class name of the icon
* @param {IconNode} props.children - The children of the icon
* @param {IconNode} props.iconNode - The icon node of the icon
*
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const Icon = ({
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
children,
iconNode,
class: classes = '',
...rest
}: IconComponentProps) =>
h(
'svg',
{
...defaultAttributes,
width: String(size),
height: size,
stroke: color,
['stroke-width' as 'strokeWidth']: absoluteStrokeWidth
? (Number(strokeWidth) * 24) / Number(size)
: strokeWidth,
class: ['lucide', classes].join(' '),
...rest,
},
[...iconNode.map(([tag, attrs]) => h(tag, attrs)), ...toChildArray(children)],
);
export default Icon;

View File

@@ -1,17 +1,7 @@
import { type FunctionComponent, h, type JSX, toChildArray } from 'preact';
import defaultAttributes from './defaultAttributes';
import { toKebabCase } from '@lucide/shared';
export type IconNode = [elementName: keyof JSX.IntrinsicElements, attrs: Record<string, string>][];
export interface LucideProps extends Partial<Omit<JSX.SVGAttributes, 'ref' | 'size'>> {
color?: string;
size?: string | number;
strokeWidth?: string | number;
absoluteStrokeWidth?: boolean;
}
export type LucideIcon = FunctionComponent<LucideProps>;
import { h, type JSX } from 'preact';
import { mergeClasses, toKebabCase } from '@lucide/shared';
import Icon from './Icon';
import type { IconNode, LucideIcon, LucideProps } from './types';
/**
* Create a Lucide icon component
@@ -20,29 +10,18 @@ export type LucideIcon = FunctionComponent<LucideProps>;
* @returns {FunctionComponent} LucideIcon
*/
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
const Component = ({
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
children,
class: classes = '',
...rest
}: LucideProps) =>
const Component = ({ class: classes = '', children, ...props }: LucideProps) =>
h(
'svg',
Icon,
{
...defaultAttributes,
width: String(size),
height: size,
stroke: color,
['stroke-width' as 'strokeWidth']: absoluteStrokeWidth
? (Number(strokeWidth) * 24) / Number(size)
: strokeWidth,
class: ['lucide', `lucide-${toKebabCase(iconName)}`, classes].join(' '),
...rest,
...props,
iconNode,
class: mergeClasses<string | JSX.SignalLike<string | undefined>>(
`lucide-${toKebabCase(iconName)}`,
classes,
),
},
[...iconNode.map(([tag, attrs]) => h(tag, attrs)), ...toChildArray(children)],
children,
);
Component.displayName = `${iconName}`;

View File

@@ -1,4 +1,7 @@
export * from './icons';
export * as icons from './icons';
export * from './aliases';
export * from './types';
export { default as createLucideIcon } from './createLucideIcon';
export { default as Icon } from './Icon';

View File

@@ -0,0 +1,12 @@
import { type FunctionComponent, type JSX } from 'preact';
export type IconNode = [elementName: keyof JSX.IntrinsicElements, attrs: Record<string, string>][];
export interface LucideProps extends Partial<Omit<JSX.SVGAttributes, 'ref' | 'size'>> {
color?: string;
size?: string | number;
strokeWidth?: string | number;
absoluteStrokeWidth?: boolean;
}
export type LucideIcon = FunctionComponent<LucideProps>;

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/preact';
import { airVent } from './testIconNodes';
import { Icon } from '../src/lucide-preact';
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
class="lucide "
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
`;

View File

@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using createLucideIcon > should create a component from an iconNode 1`] = `
<svg
class="lucide lucide-air-vent"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
`;

View File

@@ -10,8 +10,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-grid3x3 "
data-testid="grid-icon"
class="lucide lucide-grid3x3"
>
<rect width="18"
height="18"
@@ -41,8 +40,7 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-grid3x3 "
data-testid="grid-icon"
class="lucide lucide-grid3x3"
>
<rect width="18"
height="18"
@@ -72,7 +70,7 @@ exports[`Using lucide icon components > should render an component 1`] = `
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-grid3x3 "
class="lucide lucide-grid3x3"
>
<rect width="18"
height="18"

View File

@@ -0,0 +1,15 @@
import { describe, it, expect } from 'vitest';
import { createLucideIcon } from '../src/lucide-preact';
import { airVent } from './testIconNodes';
import { render } from '@testing-library/preact';
describe('Using createLucideIcon', () => {
it('should create a component from an iconNode', () => {
const AirVent = createLucideIcon('AirVent', airVent);
const { container } = render(<AirVent />);
expect(container.firstChild).toMatchSnapshot();
expect(container.firstChild).toBeDefined();
});
});

View File

@@ -1,6 +1,7 @@
import { describe, it, expect } from 'vitest';
import { render, cleanup } from '@testing-library/preact';
import { Pen, Edit2, Grid, Droplet } from '../src/lucide-preact';
import defaultAttributes from '../src/defaultAttributes';
type AttributesAssertion = { attributes: Record<string, { value: string }> };
@@ -11,30 +12,43 @@ describe('Using lucide icon components', () => {
expect(container.innerHTML).toMatchSnapshot();
});
it('should render the icon with the default attributes', () => {
const { container } = render(<Grid />);
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('xmlns', defaultAttributes.xmlns);
expect(SVGElement).toHaveAttribute('width', String(defaultAttributes.width));
expect(SVGElement).toHaveAttribute('height', String(defaultAttributes.height));
expect(SVGElement).toHaveAttribute('viewBox', defaultAttributes.viewBox);
expect(SVGElement).toHaveAttribute('fill', defaultAttributes.fill);
expect(SVGElement).toHaveAttribute('stroke', defaultAttributes.stroke);
expect(SVGElement).toHaveAttribute('stroke-width', String(defaultAttributes['stroke-width']));
expect(SVGElement).toHaveAttribute('stroke-linecap', defaultAttributes['stroke-linecap']);
expect(SVGElement).toHaveAttribute('stroke-linejoin', defaultAttributes['stroke-linejoin']);
});
it('should adjust the size, stroke color and stroke width', () => {
const testId = 'grid-icon';
const { container, getByTestId } = render(
const { container } = render(
<Grid
data-testid={testId}
size={48}
stroke="red"
strokeWidth={4}
/>,
);
const { attributes } = getByTestId(testId) as unknown as AttributesAssertion;
expect(attributes.stroke.value).toBe('red');
expect(attributes.width.value).toBe('48');
expect(attributes.height.value).toBe('48');
expect(attributes['stroke-width'].value).toBe('4');
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(SVGElement).toHaveAttribute('width', '48');
expect(SVGElement).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '4');
expect(container.innerHTML).toMatchSnapshot();
});
it('should render the alias icon', () => {
const testId = 'pen-icon';
const { container } = render(
<Pen
data-testid={testId}
size={48}
stroke="red"
strokeWidth={4}
@@ -47,7 +61,6 @@ describe('Using lucide icon components', () => {
const { container: Edit2Container } = render(
<Edit2
data-testid={testId}
size={48}
stroke="red"
strokeWidth={4}
@@ -58,22 +71,21 @@ describe('Using lucide icon components', () => {
});
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'grid-icon';
const { container, getByTestId } = render(
const { container } = render(
<Grid
data-testid={testId}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
const { attributes } = getByTestId(testId) as unknown as AttributesAssertion;
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(SVGElement).toHaveAttribute('width', '48');
expect(SVGElement).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '1');
expect(attributes.stroke.value).toBe('red');
expect(attributes.width.value).toBe('48');
expect(attributes.height.value).toBe('48');
expect(attributes['stroke-width'].value).toBe('1');
expect(container.innerHTML).toMatchSnapshot();
});

View File

@@ -1,5 +1,10 @@
import { expect } from 'vitest';
import '@testing-library/jest-dom';
import { expect, afterEach } from 'vitest';
import { cleanup } from '@testing-library/preact';
import '@testing-library/jest-dom/vitest';
import htmlSerializer from 'jest-serializer-html';
expect.addSnapshotSerializer(htmlSerializer);
afterEach(() => {
cleanup();
});

View File

@@ -0,0 +1,22 @@
import { IconNode } from '../src/createLucideIcon';
export const airVent: IconNode = [
[
'path',
{
d: 'M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2',
key: 'larmp2',
},
],
['path', { d: 'M6 8h12', key: '6g4wlu' }],
['path', { d: 'M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12', key: '1bo8pg' }],
['path', { d: 'M6.6 15.6A2 2 0 1 0 10 17v-5', key: 't9h90c' }],
];
export const coffee: IconNode = [
['path', { d: 'M17 8h1a4 4 0 1 1 0 8h-1', key: 'jx4kbh' }],
['path', { d: 'M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z', key: '1bxrl0' }],
['line', { x1: '6', x2: '6', y1: '2', y2: '4', key: '1cr9l3' }],
['line', { x1: '10', x2: '10', y1: '2', y2: '4', key: '170wym' }],
['line', { x1: '14', x2: '14', y1: '2', y2: '4', key: '1c5f70' }],
];

View File

@@ -45,6 +45,7 @@
"devDependencies": {
"@lucide/rollup-plugins": "workspace:*",
"@lucide/build-icons": "workspace:*",
"@lucide/shared": "workspace:*",
"@testing-library/jest-dom": "^6.1.6",
"@testing-library/react": "^14.1.2",
"@types/prop-types": "^15.7.5",

View File

@@ -0,0 +1,69 @@
import { createElement, forwardRef, type FunctionComponent } from 'react';
import * as NativeSvg from 'react-native-svg';
import defaultAttributes, { childDefaultAttributes } from './defaultAttributes';
import { IconNode, LucideProps } from './types';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
}
/**
* Lucide icon component
*
* @component Icon
* @param {object} props
* @param {string} props.color - The color of the icon
* @param {number} props.size - The size of the icon
* @param {number} props.strokeWidth - The stroke width of the icon
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
* @param {string} props.className - The class name of the icon
* @param {IconNode} props.children - The children of the icon
* @param {IconNode} props.iconNode - The icon node of the icon
*
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
(
{
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
children,
iconNode,
...rest
},
ref,
) => {
const customAttrs = {
stroke: color,
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
...rest,
};
return createElement(
NativeSvg.Svg as unknown as string,
{
ref,
...defaultAttributes,
width: size,
height: size,
...customAttrs,
},
[
...iconNode.map(([tag, attrs]) => {
const upperCasedTag = (tag.charAt(0).toUpperCase() +
tag.slice(1)) as keyof typeof NativeSvg;
// duplicating the attributes here because generating the OTA update bundles don't inherit the SVG properties from parent (codepush, expo-updates)
return createElement(
NativeSvg[upperCasedTag] as FunctionComponent<LucideProps>,
{ ...childDefaultAttributes, ...customAttrs, ...attrs } as LucideProps,
);
}),
...((Array.isArray(children) ? children : [children]) || []),
],
);
},
);
export default Icon;

View File

@@ -7,17 +7,7 @@ import {
} from 'react';
import * as NativeSvg from 'react-native-svg';
import defaultAttributes, { childDefaultAttributes } from './defaultAttributes';
import type { SvgProps } from 'react-native-svg';
export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][];
export interface LucideProps extends SvgProps {
size?: string | number;
absoluteStrokeWidth?: boolean;
'data-testid'?: string;
}
export type LucideIcon = ForwardRefExoticComponent<LucideProps>;
import { IconNode, LucideIcon, LucideProps } from './types';
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
const Component = forwardRef(

View File

@@ -1,9 +1,7 @@
export * from './icons';
export * as icons from './icons';
export * from './aliases';
export {
default as createLucideIcon,
type IconNode,
type LucideProps,
type LucideIcon,
} from './createLucideIcon';
export * from './types';
export { default as createLucideIcon } from './createLucideIcon';
export { default as Icon } from './Icon';

View File

@@ -0,0 +1,12 @@
import type { ForwardRefExoticComponent, ReactSVG } from 'react';
import type { SvgProps } from 'react-native-svg';
export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][];
export interface LucideProps extends SvgProps {
size?: string | number;
absoluteStrokeWidth?: boolean;
'data-testid'?: string;
}
export type LucideIcon = ForwardRefExoticComponent<LucideProps>;

View File

@@ -0,0 +1,35 @@
import { describe, it, expect, vi } from 'vitest';
import { render } from '@testing-library/react';
import { airVent } from './testIconNodes';
import { Icon } from '../src/lucide-react-native';
vi.mock('react-native-svg');
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,48 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
/>
<path
d="M6 8h12"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
fill="none"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
/>
</svg>
`;

View File

@@ -10,7 +10,6 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
data-testid="grid-icon"
>
<rect fill="none"
stroke="red"
@@ -128,7 +127,6 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
data-testid="grid-icon"
>
<rect fill="none"
stroke="red"

View File

@@ -14,21 +14,20 @@ describe('Using lucide icon components', () => {
});
it('should adjust the size, stroke color and stroke width', () => {
const testId = 'grid-icon';
const { container, getByTestId } = render(
const { container } = render(
<Grid
data-testid={testId}
size={48}
stroke="red"
strokeWidth={4}
/>,
);
const { attributes } = getByTestId(testId);
expect((attributes as unknown as Attributes).stroke.value).toBe('red');
expect((attributes as unknown as Attributes).width.value).toBe('48');
expect((attributes as unknown as Attributes).height.value).toBe('48');
expect((attributes as unknown as Attributes)['stroke-width'].value).toBe('4');
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(SVGElement).toHaveAttribute('width', '48');
expect(SVGElement).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '4');
expect(container.innerHTML).toMatchSnapshot();
});
@@ -61,23 +60,20 @@ describe('Using lucide icon components', () => {
});
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'grid-icon';
const { container, getByTestId } = render(
const { container } = render(
<Grid
data-testid={testId}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
const { attributes } = getByTestId(testId) as unknown as {
attributes: Record<string, { value: string }>;
};
expect(attributes.stroke.value).toBe('red');
expect(attributes.width.value).toBe('48');
expect(attributes.height.value).toBe('48');
expect(attributes['stroke-width'].value).toBe('1');
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(SVGElement).toHaveAttribute('width', '48');
expect(SVGElement).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '1');
expect(container.innerHTML).toMatchSnapshot();
});
@@ -91,8 +87,8 @@ describe('Using lucide icon components', () => {
<Grid data-testid={childId} />
</Grid>,
);
const { children } = getByTestId(testId) as unknown as { children: HTMLCollection };
const lastChild = children[children.length - 1];
const { children } = container.firstElementChild ?? {};
const lastChild = children?.[children.length - 1];
expect(lastChild).toEqual(getByTestId(childId));
expect(container.innerHTML).toMatchSnapshot();

View File

@@ -0,0 +1,22 @@
import { IconNode } from '../src/types';
export const airVent: IconNode = [
[
'path',
{
d: 'M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2',
key: 'larmp2',
},
],
['path', { d: 'M6 8h12', key: '6g4wlu' }],
['path', { d: 'M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12', key: '1bo8pg' }],
['path', { d: 'M6.6 15.6A2 2 0 1 0 10 17v-5', key: 't9h90c' }],
];
export const coffee: IconNode = [
['path', { d: 'M17 8h1a4 4 0 1 1 0 8h-1', key: 'jx4kbh' }],
['path', { d: 'M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z', key: '1bxrl0' }],
['line', { x1: '6', x2: '6', y1: '2', y2: '4', key: '1cr9l3' }],
['line', { x1: '10', x2: '10', y1: '2', y2: '4', key: '170wym' }],
['line', { x1: '14', x2: '14', y1: '2', y2: '4', key: '1c5f70' }],
];

View File

@@ -0,0 +1,59 @@
import { createElement, forwardRef } from 'react';
import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types';
import { mergeClasses } from '@lucide/shared';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
}
/**
* Lucide icon component
*
* @component Icon
* @param {object} props
* @param {string} props.color - The color of the icon
* @param {number} props.size - The size of the icon
* @param {number} props.strokeWidth - The stroke width of the icon
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
* @param {string} props.className - The class name of the icon
* @param {IconNode} props.children - The children of the icon
* @param {IconNode} props.iconNode - The icon node of the icon
*
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
(
{
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
className = '',
children,
iconNode,
...rest
},
ref,
) => {
return createElement(
'svg',
{
ref,
...defaultAttributes,
width: size,
height: size,
stroke: color,
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
className: mergeClasses('lucide', className),
...rest,
},
[
...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
...(Array.isArray(children) ? children : [children]),
],
);
},
);
export default Icon;

View File

@@ -1,60 +1,22 @@
import {
forwardRef,
createElement,
ReactSVG,
SVGProps,
ForwardRefExoticComponent,
RefAttributes,
} from 'react';
import defaultAttributes from './defaultAttributes';
import { toKebabCase } from '@lucide/shared';
import { createElement, forwardRef } from 'react';
import { mergeClasses, toKebabCase } from '@lucide/shared';
import { IconNode, LucideProps } from './types';
import Icon from './Icon';
export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][];
export type SVGAttributes = Partial<SVGProps<SVGSVGElement>>;
type ComponentAttributes = RefAttributes<SVGSVGElement> & SVGAttributes;
export interface LucideProps extends ComponentAttributes {
size?: string | number;
absoluteStrokeWidth?: boolean;
}
export type LucideIcon = ForwardRefExoticComponent<LucideProps>;
const createLucideIcon = (iconName: string, iconNode: IconNode): LucideIcon => {
const Component = forwardRef<SVGSVGElement, LucideProps>(
(
{
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
className = '',
children,
...rest
},
/**
* Create a Lucide icon component
* @param {string} iconName
* @param {array} iconNode
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const createLucideIcon = (iconName: string, iconNode: IconNode) => {
const Component = forwardRef<SVGSVGElement, LucideProps>(({ className, ...props }, ref) =>
createElement(Icon, {
ref,
) => {
return createElement(
'svg',
{
ref,
...defaultAttributes,
width: size,
height: size,
stroke: color,
strokeWidth: absoluteStrokeWidth
? (Number(strokeWidth) * 24) / Number(size)
: strokeWidth,
className: ['lucide', `lucide-${toKebabCase(iconName)}`, className].join(' '),
...rest,
},
[
...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
...(Array.isArray(children) ? children : [children]),
],
);
},
iconNode,
className: mergeClasses(`lucide-${toKebabCase(iconName)}`, className),
...props,
}),
);
Component.displayName = `${iconName}`;

View File

@@ -1,9 +1,7 @@
export * from './icons';
export * as icons from './icons';
export * from './aliases';
export {
default as createLucideIcon,
type IconNode,
type LucideProps,
type LucideIcon,
} from './createLucideIcon';
export * from './types';
export { default as createLucideIcon } from './createLucideIcon';
export { default as Icon } from './Icon';

View File

@@ -0,0 +1,15 @@
import { ReactSVG, SVGProps, ForwardRefExoticComponent, RefAttributes } from 'react';
export type IconNode = [elementName: keyof ReactSVG, attrs: Record<string, string>][];
export type SVGAttributes = Partial<SVGProps<SVGSVGElement>>;
type ElementAttributes = RefAttributes<SVGSVGElement> & SVGAttributes;
export interface LucideProps extends ElementAttributes {
size?: string | number;
absoluteStrokeWidth?: boolean;
}
export type LucideIcon = ForwardRefExoticComponent<
Omit<LucideProps, 'ref'> & RefAttributes<SVGSVGElement>
>;

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/react';
import { airVent } from './testIconNodes';
import { Icon } from '../src/lucide-react';
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
class="lucide"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
`;

View File

@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using createLucideIcon > should create a component from an iconNode 1`] = `
<svg
class="lucide lucide-air-vent"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
`;

View File

@@ -0,0 +1,36 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using dynamicImports > should render icons dynamically by using the dynamicIconImports module 1`] = `
<svg xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
viewbox="0 0 24 24"
fill="none"
stroke="red"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-smile"
aria-label="smile"
>
<circle cx="12"
cy="12"
r="10"
>
</circle>
<path d="M8 14s1.5 2 4 2 4-2 4-2">
</path>
<line x1="9"
x2="9.01"
y1="9"
y2="9"
>
</line>
<line x1="15"
x2="15.01"
y1="9"
y2="9"
>
</line>
</svg>
`;

View File

@@ -10,8 +10,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-grid3x3 "
data-testid="grid-icon"
class="lucide lucide-grid3x3"
>
<rect width="18"
height="18"
@@ -41,8 +40,7 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-grid3x3 "
data-testid="grid-icon"
class="lucide lucide-grid3x3"
>
<rect width="18"
height="18"
@@ -72,7 +70,7 @@ exports[`Using lucide icon components > should render an component 1`] = `
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-grid3x3 "
class="lucide lucide-grid3x3"
>
<rect width="18"
height="18"
@@ -91,38 +89,3 @@ exports[`Using lucide icon components > should render an component 1`] = `
</path>
</svg>
`;
exports[`Using lucide icon components > should render icons dynamically by using the dynamicIconImports module 1`] = `
<svg xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
viewbox="0 0 24 24"
fill="none"
stroke="red"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-smile "
aria-label="smile"
>
<circle cx="12"
cy="12"
r="10"
>
</circle>
<path d="M8 14s1.5 2 4 2 4-2 4-2">
</path>
<line x1="9"
x2="9.01"
y1="9"
y2="9"
>
</line>
<line x1="15"
x2="15.01"
y1="9"
y2="9"
>
</line>
</svg>
`;

View File

@@ -0,0 +1,15 @@
import { describe, it, expect } from 'vitest';
import { createLucideIcon } from '../src/lucide-react';
import { airVent } from './testIconNodes';
import { render } from '@testing-library/react';
describe('Using createLucideIcon', () => {
it('should create a component from an iconNode', () => {
const AirVent = createLucideIcon('AirVent', airVent);
const { container } = render(<AirVent />);
expect(container.firstChild).toMatchSnapshot();
expect(container.firstChild).toBeDefined();
});
});

View File

@@ -0,0 +1,38 @@
import { describe, it, expect } from 'vitest';
import { Suspense, lazy } from 'react';
import { render, waitFor } from '@testing-library/react';
import dynamicIconImports from '../src/dynamicIconImports';
import { LucideProps } from '../src/types';
describe('Using dynamicImports', () => {
it('should render icons dynamically by using the dynamicIconImports module', async () => {
interface IconProps extends Omit<LucideProps, 'ref'> {
name: keyof typeof dynamicIconImports;
}
const Icon = ({ name, ...props }: IconProps) => {
const LucideIcon = lazy(dynamicIconImports[name]);
return (
<Suspense fallback={null}>
<LucideIcon {...props} />
</Suspense>
);
};
const { container, getByLabelText } = render(
<Icon
aria-label="smile"
name="smile"
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
await waitFor(() => getByLabelText('smile'));
expect(container.innerHTML).toMatchSnapshot();
});
});

View File

@@ -1,8 +1,7 @@
import { describe, it, expect } from 'vitest';
import { render, cleanup, waitFor } from '@testing-library/react';
import { Pen, Edit2, Grid, LucideProps, Droplet } from '../src/lucide-react';
import { Suspense, lazy } from 'react';
import dynamicIconImports from '../src/dynamicIconImports';
import { render, cleanup } from '@testing-library/react';
import { Pen, Edit2, Grid, Droplet } from '../src/lucide-react';
import defaultAttributes from '../src/defaultAttributes';
describe('Using lucide icon components', () => {
it('should render an component', () => {
@@ -11,24 +10,37 @@ describe('Using lucide icon components', () => {
expect(container.innerHTML).toMatchSnapshot();
});
it('should render the icon with default attributes', () => {
const { container } = render(<Grid />);
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('xmlns', defaultAttributes.xmlns);
expect(SVGElement).toHaveAttribute('width', String(defaultAttributes.width));
expect(SVGElement).toHaveAttribute('height', String(defaultAttributes.height));
expect(SVGElement).toHaveAttribute('viewBox', defaultAttributes.viewBox);
expect(SVGElement).toHaveAttribute('fill', defaultAttributes.fill);
expect(SVGElement).toHaveAttribute('stroke', defaultAttributes.stroke);
expect(SVGElement).toHaveAttribute('stroke-width', String(defaultAttributes.strokeWidth));
expect(SVGElement).toHaveAttribute('stroke-linecap', defaultAttributes.strokeLinecap);
expect(SVGElement).toHaveAttribute('stroke-linejoin', defaultAttributes.strokeLinejoin);
});
it('should adjust the size, stroke color and stroke width', () => {
const testId = 'grid-icon';
const { container, getByTestId } = render(
const { container } = render(
<Grid
data-testid={testId}
size={48}
stroke="red"
strokeWidth={4}
/>,
);
const { attributes } = getByTestId(testId) as unknown as {
attributes: Record<string, { value: string }>;
};
expect(attributes.stroke.value).toBe('red');
expect(attributes.width.value).toBe('48');
expect(attributes.height.value).toBe('48');
expect(attributes['stroke-width'].value).toBe('4');
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(SVGElement).toHaveAttribute('width', '48');
expect(SVGElement).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '4');
expect(container.innerHTML).toMatchSnapshot();
});
@@ -58,23 +70,20 @@ describe('Using lucide icon components', () => {
});
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'grid-icon';
const { container, getByTestId } = render(
<Grid
data-testid={testId}
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
const { attributes } = getByTestId(testId) as unknown as {
attributes: Record<string, { value: string }>;
};
expect(attributes.stroke.value).toBe('red');
expect(attributes.width.value).toBe('48');
expect(attributes.height.value).toBe('48');
expect(attributes['stroke-width'].value).toBe('1');
const SVGElement = container.firstElementChild;
expect(SVGElement).toHaveAttribute('stroke', 'red');
expect(SVGElement).toHaveAttribute('width', '48');
expect(SVGElement).toHaveAttribute('height', '48');
expect(SVGElement).toHaveAttribute('stroke-width', '1');
expect(container.innerHTML).toMatchSnapshot();
});
@@ -87,34 +96,4 @@ describe('Using lucide icon components', () => {
expect(container.firstChild).toHaveClass('lucide');
expect(container.firstChild).toHaveClass('lucide-droplet');
});
it('should render icons dynamically by using the dynamicIconImports module', async () => {
interface IconProps extends Omit<LucideProps, 'ref'> {
name: keyof typeof dynamicIconImports;
}
const Icon = ({ name, ...props }: IconProps) => {
const LucideIcon = lazy(dynamicIconImports[name]);
return (
<Suspense fallback={null}>
<LucideIcon {...props} />
</Suspense>
);
};
const { container, getByLabelText } = render(
<Icon
aria-label="smile"
name="smile"
size={48}
stroke="red"
absoluteStrokeWidth
/>,
);
await waitFor(() => getByLabelText('smile'));
expect(container.innerHTML).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,22 @@
import { IconNode } from '../src/types';
export const airVent: IconNode = [
[
'path',
{
d: 'M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2',
key: 'larmp2',
},
],
['path', { d: 'M6 8h12', key: '6g4wlu' }],
['path', { d: 'M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12', key: '1bo8pg' }],
['path', { d: 'M6.6 15.6A2 2 0 1 0 10 17v-5', key: 't9h90c' }],
];
export const coffee: IconNode = [
['path', { d: 'M17 8h1a4 4 0 1 1 0 8h-1', key: 'jx4kbh' }],
['path', { d: 'M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z', key: '1bxrl0' }],
['line', { x1: '6', x2: '6', y1: '2', y2: '4', key: '1cr9l3' }],
['line', { x1: '10', x2: '10', y1: '2', y2: '4', key: '170wym' }],
['line', { x1: '14', x2: '14', y1: '2', y2: '4', key: '1c5f70' }],
];

View File

@@ -2,10 +2,10 @@ import { For, splitProps } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types';
import { toKebabCase } from '@lucide/shared';
import { mergeClasses, toKebabCase } from '@lucide/shared';
interface IconProps {
name: string;
name?: string;
iconNode: IconNode;
}
@@ -33,9 +33,12 @@ const Icon = (props: LucideProps & IconProps) => {
Number(localProps.size)
: Number(localProps.strokeWidth ?? defaultAttributes['stroke-width'])
}
class={`lucide lucide-${toKebabCase(localProps?.name ?? 'icon')} ${
localProps.class != null ? localProps.class : ''
}`}
class={mergeClasses(
'lucide',
'lucide-icon',
localProps.name != null ? `lucide-${toKebabCase(localProps?.name)}` : undefined,
localProps.class != null ? localProps.class : '',
)}
{...rest}
>
<For each={localProps.iconNode}>

View File

@@ -1,3 +1,6 @@
export * from './icons';
export * as icons from './icons';
export * from './aliases';
export * from './types';
export { default as Icon } from './Icon';

View File

@@ -11,3 +11,5 @@ export interface LucideProps extends SVGAttributes {
class?: string;
absoluteStrokeWidth?: boolean;
}
export type LucideIcon = (props: LucideProps) => JSX.Element;

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { render } from '@solidjs/testing-library';
import { airVent } from './testIconNodes';
import { Icon } from '../src/lucide-solid';
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(() => (
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>
));
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(() => (
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>
));
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,33 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
class="lucide lucide-icon"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
key="larmp2"
/>
<path
d="M6 8h12"
key="6g4wlu"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
key="1bo8pg"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
key="t9h90c"
/>
</svg>
`;

View File

@@ -10,7 +10,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
height="48"
stroke="red"
stroke-width="4"
class="lucide lucide-grid3x3 "
class="lucide lucide-icon lucide-grid3x3"
data-testid="grid-icon"
>
<rect width="18"
@@ -50,7 +50,7 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
height="48"
stroke="red"
stroke-width="1"
class="lucide lucide-grid3x3 "
class="lucide lucide-icon lucide-grid3x3"
data-testid="grid-icon"
>
<rect width="18"
@@ -90,7 +90,7 @@ exports[`Using lucide icon components > should render a component 1`] = `
height="24"
stroke="currentColor"
stroke-width="2"
class="lucide lucide-grid3x3 "
class="lucide lucide-icon lucide-grid3x3"
>
<rect width="18"
height="18"

View File

@@ -0,0 +1,22 @@
import { IconNode } from '../src/types';
export const airVent: IconNode = [
[
'path',
{
d: 'M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2',
key: 'larmp2',
},
],
['path', { d: 'M6 8h12', key: '6g4wlu' }],
['path', { d: 'M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12', key: '1bo8pg' }],
['path', { d: 'M6.6 15.6A2 2 0 1 0 10 17v-5', key: 't9h90c' }],
];
export const coffee: IconNode = [
['path', { d: 'M17 8h1a4 4 0 1 1 0 8h-1', key: 'jx4kbh' }],
['path', { d: 'M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z', key: '1bxrl0' }],
['line', { x1: '6', x2: '6', y1: '2', y2: '4', key: '1cr9l3' }],
['line', { x1: '10', x2: '10', y1: '2', y2: '4', key: '170wym' }],
['line', { x1: '14', x2: '14', y1: '2', y2: '4', key: '1c5f70' }],
];

View File

@@ -52,6 +52,7 @@
"build:package": "svelte-package --input ./src",
"build:license": "node ./scripts/appendBlockComments.mjs",
"test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false"
},
"devDependencies": {

View File

@@ -2,12 +2,20 @@
import defaultAttributes from './defaultAttributes'
import type { IconNode } from './types';
export let name: string
export let name: string | undefined = undefined
export let color = 'currentColor'
export let size: number | string = 24
export let strokeWidth: number | string = 2
export let absoluteStrokeWidth: boolean = false
export let iconNode: IconNode
const mergeClasses = <ClassType = string | undefined | null>(
...classes: ClassType[]
) => classes.filter((className, index, array) => {
return Boolean(className) && array.indexOf(className) === index;
})
.join(' ');
</script>
<svg
@@ -21,7 +29,14 @@
? Number(strokeWidth) * 24 / Number(size)
: strokeWidth
}
class={`lucide-icon lucide lucide-${name} ${$$props.class ?? ''}`}
class={
mergeClasses(
'lucide-icon',
'lucide',
name ? `lucide-${name}`: '',
$$props.class
)
}
>
{#each iconNode as [tag, attrs]}
<svelte:element this={tag} {...attrs}/>

View File

@@ -3,3 +3,4 @@ export * as icons from './icons/index.js';
export * from './aliases.js';
export { default as defaultAttributes } from './defaultAttributes.js';
export * from './types.js';
export { default as Icon } from './Icon.svelte';

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/svelte';
import { Icon } from '../src/lucide-svelte';
import { airVent } from './testIconNodes';
describe('Using Icon Component', () => {
it('should render icon based on a iconNode', async () => {
const { container } = render(Icon, {
props: {
iconNode: airVent,
size: 48,
color: 'red',
absoluteStrokeWidth: true,
},
});
expect(container.firstChild).toBeDefined();
});
it('should render icon and match snapshot', async () => {
const { container } = render(Icon, {
props: {
iconNode: airVent,
size: 48,
color: 'red',
absoluteStrokeWidth: true,
},
});
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,36 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<div>
<svg
class="lucide-icon lucide"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/>
<path
d="M6 8h12"
/>
<path
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
/>
<path
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
/>
</svg>
</div>
`;

Some files were not shown because too many files have changed in this diff Show More