Compare commits
24 Commits
export-typ
...
0.379.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b3173b17b | ||
|
|
d5f4275055 | ||
|
|
6abae7cc14 | ||
|
|
f32ffcd2a2 | ||
|
|
824bb897cf | ||
|
|
2843a76e28 | ||
|
|
155ff3319a | ||
|
|
34dddb811b | ||
|
|
5fead67bf3 | ||
|
|
48dc9372db | ||
|
|
747446fc76 | ||
|
|
5862ea735e | ||
|
|
3a8a349771 | ||
|
|
70bc2245c7 | ||
|
|
89f6b6357d | ||
|
|
354af456d3 | ||
|
|
e50582e93e | ||
|
|
65deefa53c | ||
|
|
54ef137b49 | ||
|
|
d4df542117 | ||
|
|
8c1e56a7bf | ||
|
|
dff2172173 | ||
|
|
e8ccd3df7e | ||
|
|
b593355537 |
3
.github/ISSUE_TEMPLATE/02_bug_report.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
6
.github/workflows/pull-request.yml
vendored
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3';
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
|
||||
40
docs/.vitepress/api/figma/data.ts
Normal 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;
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3';
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 { }
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
36
docs/scripts/writeCategoriesMetadata.mjs
Normal 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}`);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"colebemis"
|
||||
"colebemis",
|
||||
"jguddas"
|
||||
],
|
||||
"tags": [
|
||||
"pulse",
|
||||
|
||||
@@ -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 |
@@ -2,7 +2,8 @@
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"danielbayley",
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"tags": [
|
||||
"fire",
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -30,7 +30,7 @@
|
||||
],
|
||||
"categories": [
|
||||
"multimedia",
|
||||
"shape",
|
||||
"shapes",
|
||||
"photography",
|
||||
"tools",
|
||||
"devices"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
],
|
||||
"categories": [
|
||||
"multimedia",
|
||||
"shape",
|
||||
"shapes",
|
||||
"photography",
|
||||
"tools",
|
||||
"devices"
|
||||
|
||||
32
icons/grid-2x2-check.json
Normal 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
@@ -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
@@ -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
@@ -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 |
@@ -4,8 +4,7 @@
|
||||
"ashygee",
|
||||
"csandman",
|
||||
"mittalyashu",
|
||||
"ericfennis",
|
||||
"jguddas"
|
||||
"ericfennis"
|
||||
],
|
||||
"tags": [
|
||||
"password",
|
||||
|
||||
@@ -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 |
@@ -7,9 +7,17 @@
|
||||
"ericfennis"
|
||||
],
|
||||
"tags": [
|
||||
"load"
|
||||
"loading",
|
||||
"wait",
|
||||
"busy",
|
||||
"progress",
|
||||
"spinner",
|
||||
"spinning",
|
||||
"throbber",
|
||||
"circle"
|
||||
],
|
||||
"categories": [
|
||||
"cursors",
|
||||
"multimedia",
|
||||
"layout"
|
||||
],
|
||||
|
||||
22
icons/loader-pinwheel.json
Normal 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
@@ -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 |
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -5,9 +5,8 @@
|
||||
"csandman"
|
||||
],
|
||||
"tags": [
|
||||
"arrow",
|
||||
"cursor",
|
||||
"click"
|
||||
"click",
|
||||
"select"
|
||||
],
|
||||
"categories": [
|
||||
"arrows",
|
||||
|
||||
18
icons/mouse-pointer-ban.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"danielbayley"
|
||||
],
|
||||
"tags": [
|
||||
"wait",
|
||||
"busy",
|
||||
"loading",
|
||||
"blocked",
|
||||
"frozen",
|
||||
"freeze"
|
||||
],
|
||||
"categories": [
|
||||
"arrows",
|
||||
"cursors"
|
||||
]
|
||||
}
|
||||
15
icons/mouse-pointer-ban.svg
Normal 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 |
@@ -6,9 +6,8 @@
|
||||
"jguddas"
|
||||
],
|
||||
"tags": [
|
||||
"arrow",
|
||||
"cursor",
|
||||
"click"
|
||||
"click",
|
||||
"select"
|
||||
],
|
||||
"categories": [
|
||||
"arrows",
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
"ericfennis"
|
||||
],
|
||||
"tags": [
|
||||
"arrow",
|
||||
"cursor",
|
||||
"click"
|
||||
"click",
|
||||
"select"
|
||||
],
|
||||
"categories": [
|
||||
"arrows",
|
||||
|
||||
@@ -19,5 +19,8 @@
|
||||
"photography",
|
||||
"home",
|
||||
"tools"
|
||||
],
|
||||
"aliases": [
|
||||
"paintbrush-2"
|
||||
]
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
50
packages/lucide-preact/src/Icon.ts
Normal 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;
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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';
|
||||
|
||||
12
packages/lucide-preact/src/types.ts
Normal 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>;
|
||||
33
packages/lucide-preact/tests/Icon.spec.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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"
|
||||
|
||||
15
packages/lucide-preact/tests/createLucideIcon.spec.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
22
packages/lucide-preact/tests/testIconNodes.ts
Normal 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' }],
|
||||
];
|
||||
@@ -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",
|
||||
|
||||
69
packages/lucide-react-native/src/Icon.ts
Normal 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;
|
||||
@@ -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(
|
||||
|
||||
@@ -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';
|
||||
|
||||
12
packages/lucide-react-native/src/types.ts
Normal 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>;
|
||||
35
packages/lucide-react-native/tests/Icon.spec.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
22
packages/lucide-react-native/tests/testIconNodes.ts
Normal 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' }],
|
||||
];
|
||||
59
packages/lucide-react/src/Icon.ts
Normal 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;
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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';
|
||||
|
||||
15
packages/lucide-react/src/types.ts
Normal 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>
|
||||
>;
|
||||
33
packages/lucide-react/tests/Icon.spec.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
29
packages/lucide-react/tests/__snapshots__/Icon.spec.tsx.snap
Normal 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>
|
||||
`;
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
15
packages/lucide-react/tests/createLucideIcon.spec.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
38
packages/lucide-react/tests/dynamicImports.spec.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
22
packages/lucide-react/tests/testIconNodes.ts
Normal 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' }],
|
||||
];
|
||||
@@ -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}>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export * from './icons';
|
||||
export * as icons from './icons';
|
||||
export * from './aliases';
|
||||
export * from './types';
|
||||
|
||||
export { default as Icon } from './Icon';
|
||||
|
||||
@@ -11,3 +11,5 @@ export interface LucideProps extends SVGAttributes {
|
||||
class?: string;
|
||||
absoluteStrokeWidth?: boolean;
|
||||
}
|
||||
|
||||
export type LucideIcon = (props: LucideProps) => JSX.Element;
|
||||
|
||||
33
packages/lucide-solid/tests/Icon.spec.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
33
packages/lucide-solid/tests/__snapshots__/Icon.spec.tsx.snap
Normal 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>
|
||||
`;
|
||||
@@ -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"
|
||||
|
||||
22
packages/lucide-solid/tests/testIconNodes.ts
Normal 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' }],
|
||||
];
|
||||
@@ -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": {
|
||||
|
||||
@@ -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}/>
|
||||
|
||||
@@ -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';
|
||||
|
||||
33
packages/lucide-svelte/tests/Icon.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
36
packages/lucide-svelte/tests/__snapshots__/Icon.spec.ts.snap
Normal 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>
|
||||
`;
|
||||