Compare commits

..
1 Commits
Author SHA1 Message Date
Eric Fennis 63353ad207 Setup solid 2 2026-07-10 11:51:42 +02:00
65 changed files with 9343 additions and 5180 deletions
@@ -33,7 +33,7 @@ jobs:
Lucide **does not accept** brand logos, and we do not plan to add them in the future. This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**.
[Click here to read our official statement about brand logos in Lucide.](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md)
[Click here to read our official statement about brand logos in Lucide.](./BRAND_LOGOS_STATEMENT.md)
You can [search for similar issues.](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+${VALUE})
@@ -13,21 +13,23 @@ const { go } = useRouter()
<HomeSectionTitle>Available For:</HomeSectionTitle>
<div class="packages-list">
<a
v-for="{ name, label, path, logo, logoDark } in data.packages"
:href="path"
v-for="{ name, logo, logoDark } in data.packages"
:href="`/guide/packages/${name}`"
class="package-logo"
@click.prevent="go(path)"
:aria-label="`Read more about: ${name} package`"
@click.prevent="go(`/guide/packages/${name}`)"
>
<img
:src="logo"
:class="{ light: logoDark, 'image-logo': true }"
:alt="label"
:alt="`${name} logo`"
loading="lazy"
/>
<img
v-if="logoDark"
:src="logoDark"
:alt="label"
:alt="`${name} logo`"
class="image-logo dark"
loading="lazy"
/>
@@ -3,7 +3,6 @@ import type { IconEntity } from '../../types';
import { computed, ref } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { useIconStyleContext } from '../../composables/useIconStyle';
import { squarePen } from '../../../data/iconNodes';
const props = defineProps<{
name: IconEntity['name'];
@@ -20,31 +19,6 @@ const iconComponent = computed(() => {
if (!props.name || !props.iconNode) return null;
return createLucideIcon(props.name, props.iconNode);
});
const SquarePenIcon = createLucideIcon('SquarePen', squarePen);
const innerSvg = computed(() => {
if (!props.iconNode) return '';
const string = props.iconNode
.map(([tag, attrs]) => {
const attributes = Object.entries(attrs)
.map(([key, value]) => `${key}="${value}"`)
.join(' ');
return `<${tag} ${attributes}/>`;
})
.join('');
return string
.replace(/>[\r\n ]+</g, '><')
.replace(/(<.*?>)|\s+/g, (m, $1) => $1 || ' ')
.trim();
});
const base64InnerSvg = computed(() => {
if (typeof window === 'undefined') return '';
return btoa(innerSvg.value);
});
</script>
<template>
@@ -83,16 +57,6 @@ const base64InnerSvg = computed(() => {
/>
</g>
</svg>
<a
:href="`https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}`"
target="_blank"
rel="noopener noreferrer"
class="studio-button"
title="Open in Lucide Studio"
aria-label="Open in Lucide Studio"
>
<SquarePenIcon :size="14" />
</a>
</div>
</template>
@@ -111,8 +75,6 @@ const base64InnerSvg = computed(() => {
position: relative;
background: var(--vp-c-bg-alt);
border-radius: 8px;
overflow: hidden;
flex-shrink: 0;
}
.icon-container > :deep(svg:not(.icon-grid)) {
width: 100%;
@@ -123,34 +85,6 @@ const base64InnerSvg = computed(() => {
opacity: 0.8;
}
.studio-button {
position: absolute;
bottom: 8px;
right: 8px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
color: var(--vp-c-text-2);
opacity: 0;
transition: opacity 0.2s, color 0.2s, background-color 0.2s;
z-index: 2;
}
.icon-container:hover .studio-button,
.studio-button:focus-visible {
opacity: 1;
}
.studio-button:hover,
.studio-button:focus-visible {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
}
.icon-component.customizable {
will-change: width, height, stroke-width, stroke;
/* color: var(--customize-color, currentColor);
@@ -44,12 +44,8 @@ function setActiveIconName(name: string) {
activeIconName.value = name;
}
const { execute: fetchTags, data: tags, isFetching: isFetchingTags } = useFetchTags();
const {
execute: fetchCategories,
data: categoriesMap,
isFetching: isFetchingCategories,
} = useFetchCategories();
const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl);
@@ -121,9 +117,6 @@ const categoriesList = computed(() => {
}, []);
});
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
const isSearchMetadataLoading = computed(
() => searchQuery.value.length > 0 && (tags.value == null || categoriesMap.value == null),
);
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(categoriesList, {
itemHeight: ICON_SIZE + ICON_GRID_GAP,
@@ -142,21 +135,15 @@ onMounted(() => {
useEventListener(window, 'scroll', containerProps.onScroll);
});
function loadSearchMetadata() {
if (tags.value == null && !isFetchingTags.value) {
void fetchTags();
function onFocusSearchInput() {
if (tags.value == null) {
fetchTags();
}
if (categoriesMap.value == null && !isFetchingCategories.value) {
void fetchCategories();
if (categoriesMap.value == null) {
fetchCategories();
}
}
watch(searchQuery, (searchString) => {
if (searchString !== '') {
loadSearchMetadata();
}
});
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
@@ -191,11 +178,11 @@ function handleCloseDrawer() {
:shortcut="kbdSearchShortcut"
class="input-wrapper"
ref="searchInput"
@focus="loadSearchMetadata"
@focus="onFocusSearchInput"
/>
</StickyBar>
<NoResults
v-if="searchPlaceholder.isNoResults && !isSearchMetadataLoading"
v-if="searchPlaceholder.isNoResults"
:searchQuery="searchPlaceholder.query"
:isBrandSearch="searchPlaceholder.isBrand"
@clear="searchQuery = ''"
@@ -52,12 +52,8 @@ const props = defineProps<{
const activeIconName = ref(null);
const selectedSort = ref(SORTING[0])
const { execute: fetchTags, data: tags, isFetching: isFetchingTags } = useFetchTags();
const {
execute: fetchCategories,
data: categories,
isFetching: isFetchingCategories,
} = useFetchCategories();
const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categories } = useFetchCategories();
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl);
@@ -114,9 +110,6 @@ const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
]);
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
const isSearchMetadataLoading = computed(
() => searchQuery.value.length > 0 && (tags.value == null || categories.value == null),
);
const chunkedIcons = computed(() => {
return chunkArray(searchResults.value, columnSize.value);
@@ -142,21 +135,15 @@ function setActiveIconName(name: string) {
activeIconName.value = name;
}
function loadSearchMetadata() {
if (tags.value == null && !isFetchingTags.value) {
void fetchTags();
function onFocusSearchInput() {
if (tags.value == null) {
fetchTags();
}
if (categories.value == null && !isFetchingCategories.value) {
void fetchCategories();
if (categories.value == null) {
fetchCategories();
}
}
watch(searchQuery, (searchString) => {
if (searchString !== '') {
loadSearchMetadata();
}
});
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
@@ -192,7 +179,7 @@ function handleCloseDrawer() {
ref="searchInput"
:shortcut="kbdSearchShortcut"
class="input-wrapper"
@focus="loadSearchMetadata"
@focus="onFocusSearchInput"
/>
<Select
@@ -210,7 +197,7 @@ function handleCloseDrawer() {
</Select>
</StickyBar>
<NoResults
v-if="searchPlaceholder.isNoResults && !isSearchMetadataLoading"
v-if="searchPlaceholder.isNoResults"
:searchQuery="searchPlaceholder.query"
:isBrandSearch="searchPlaceholder.isBrand"
@clear="searchQuery = ''"
+18 -19
View File
@@ -32,32 +32,31 @@
"@lucide/shared": "workspace:*",
"@rollup/plugin-replace": "^6.0.3",
"@types/semver": "^7.7.1",
"h3": "^1.15.11",
"markdown-it": "^14.3.0",
"markdown-it-container": "^4.0.0",
"h3": "^1.15.9",
"nitropack": "^2.13.4",
"rollup-plugin-copy": "^3.5.0",
"semver": "^7.8.5",
"sharp": "^0.34.5",
"shiki": "^3.23.0",
"simple-git": "^3.36.0",
"sitemap": "^7.1.3",
"svg-path-commander": "^2.2.1",
"svg-pathdata": "^6.0.3",
"svgson": "^5.3.1",
"svg-path-commander": "^2.1.11",
"vitepress": "^1.6.4",
"vitepress-plugin-group-icons": "^1.7.5",
"vitepress-plugin-llms": "^1.13.3"
"vitepress-plugin-group-icons": "^1.6.5",
"vitepress-plugin-llms": "^1.11.1",
"markdown-it": "^14.2.0",
"markdown-it-container": "^4.0.0",
"semver": "^7.7.3",
"sharp": "^0.34.5",
"shiki": "^3.15.0",
"simple-git": "^3.36.0",
"sitemap": "^7.1.2",
"svg-pathdata": "^6.0.3",
"svgson": "^5.3.1"
},
"dependencies": {
"@floating-ui/vue": "^1.1.11",
"@floating-ui/vue": "^1.1.9",
"@headlessui/vue": "^1.7.23",
"@lucide/vue": "workspace:*",
"@vueuse/components": "^14.3.0",
"@vueuse/core": "^14.3.0",
"@vueuse/integrations": "^14.3.0",
"@vueuse/components": "^14.0.0",
"@vueuse/core": "^14.0.0",
"element-to-path": "^1.2.1",
"fuse.js": "^7.5.0",
"fuse.js": "^7.1.0",
"jszip": "^3.10.1",
"lodash-es": "^4.18.1",
"lucide": "workspace:*",
@@ -66,6 +65,6 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"sandpack-vue3": "3.1.12",
"vue": "^3.5.39"
"vue": "^3.5.24"
}
}
+1 -6
View File
@@ -21,15 +21,10 @@
"paid",
"partner",
"promo",
"sponsor",
"audio description",
"video description",
"described video",
"visual description"
"sponsor"
],
"categories": [
"multimedia",
"accessibility",
"notifications"
]
}
+2 -2
View File
@@ -9,7 +9,7 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 5v16" />
<path d="M12 21V7" />
<path d="m16 12 2 2 4-4" />
<path d="M22 6V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2h4.001A2 2 0 0022 17v-1.344" />
<path d="M22 6V4a1 1 0 0 0-1-1h-5a4 4 0 0 0-4 4 4 4 0 0 0-4-4H3a1 1 0 0 0-1 1v13a1 1 0 0 0 1 1h6a3 3 0 0 1 3 3 3 3 0 0 1 3-3h6a1 1 0 0 0 1-1v-1.3" />
</svg>

Before

Width:  |  Height:  |  Size: 424 B

After

Width:  |  Height:  |  Size: 414 B

+1 -2
View File
@@ -1,8 +1,7 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"danielbayley",
"karsa-mistmere"
"danielbayley"
],
"use-cases": [],
"tags": [
+6 -6
View File
@@ -9,10 +9,10 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 5v16" />
<path d="M16 13h2" />
<path d="M16 9h2" />
<path d="M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z" />
<path d="M6 13h2" />
<path d="M6 9h2" />
<path d="M12 7v14" />
<path d="M16 12h2" />
<path d="M16 8h2" />
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" />
<path d="M6 12h2" />
<path d="M6 8h2" />
</svg>

Before

Width:  |  Height:  |  Size: 479 B

After

Width:  |  Height:  |  Size: 470 B

+1 -2
View File
@@ -3,8 +3,7 @@
"contributors": [
"colebemis",
"csandman",
"ericfennis",
"karsa-mistmere"
"ericfennis"
],
"use-cases": [],
"tags": [
+2 -2
View File
@@ -9,6 +9,6 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 5v16" />
<path d="M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z" />
<path d="M12 7v14" />
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" />
</svg>

Before

Width:  |  Height:  |  Size: 387 B

After

Width:  |  Height:  |  Size: 378 B

+1 -2
View File
@@ -3,8 +3,7 @@
"contributors": [
"irvineacosta",
"danielbayley",
"karsa-mistmere",
"kamilasw"
"karsa-mistmere"
],
"use-cases": [],
"tags": [
+11 -11
View File
@@ -9,16 +9,16 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M10.6 21H5a2 2 0 01-2-2V5a2 2 0 012-2h14a2 2 0 012 2v5.6" />
<path d="m14.305 19.53.923-.382" />
<path d="M15 3v7.6" />
<path d="m15.229 16.852-.924-.383" />
<path d="m16.852 15.228-.383-.923" />
<path d="m16.852 20.772-.383.924" />
<path d="m19.148 15.228.383-.923" />
<path d="m19.53 21.696-.382-.924" />
<path d="m20.773 16.852.922-.383" />
<path d="m20.773 19.148.922.383" />
<path d="M10.5 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5.5" />
<path d="m14.3 19.6 1-.4" />
<path d="M15 3v7.5" />
<path d="m15.2 16.9-.9-.3" />
<path d="m16.6 21.7.3-.9" />
<path d="m16.8 15.3-.4-1" />
<path d="m19.1 15.2.3-.9" />
<path d="m19.6 21.7-.4-1" />
<path d="m20.7 16.8 1-.4" />
<path d="m21.7 19.4-.9-.3" />
<path d="M9 3v18" />
<circle cx="18" cy="18" r="3" />
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 617 B

+1 -2
View File
@@ -1,8 +1,7 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"ericfennis",
"karsa-mistmere"
"ericfennis"
],
"use-cases": [],
"tags": [
+2 -2
View File
@@ -9,9 +9,9 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m15 20 3-3h2a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h2l3 3z" />
<path d="M6 8v1" />
<path d="M10 8v1" />
<path d="M14 8v1" />
<path d="M18 8v1" />
<path d="M19 17a2 2 0 00-1.765 1.059l-.47.882A2 2 0 0115 20H9a2 2 0 01-1.765-1.059l-.47-.882A2 2 0 005 17H4a2 2 0 01-2-2V6a2 2 0 012-2h16a2 2 0 012 2v9a2 2 0 01-2 2z" />
<path d="M6 8v1" />
</svg>

Before

Width:  |  Height:  |  Size: 471 B

After

Width:  |  Height:  |  Size: 396 B

+3 -26
View File
@@ -4,36 +4,13 @@
"karsa-mistmere",
"danielbayley"
],
"use-cases": [
"representing 3D model files such as OBJ, STL, FBX, or GLTF",
"indicating CAD and engineering model documents",
"showing printable or manufacturable 3D assets",
"identifying files containing digital product models"
],
"use-cases": [],
"tags": [
"document",
"page",
"sheet",
"cube",
"box",
"3d",
"model",
"asset",
"object",
"geometry",
"mesh",
"ar",
"augmented reality",
"cad",
"design",
"blueprint",
"draft",
"package",
"scene"
"model"
],
"categories": [
"files",
"design",
"development"
"files"
]
}
+5 -5
View File
@@ -9,9 +9,9 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M14 2v5a1 1 0 001 1h5" />
<path d="M14.692 22H18a2 2 0 002-2V8a2.4 2.4 0 00-.706-1.706l-3.588-3.588A2.4 2.4 0 0014 2H6a2 2 0 00-2 2v3.804" />
<path d="M2.264 13.752 7 16.5l4.737-2.748" />
<path d="M2.995 13.014A2 2 0 002 14.744v3.516a2 2 0 00.996 1.73l3 1.74a2 2 0 002.008 0l3-1.74A2 2 0 0012 18.26v-3.517a2 2 0 00-.995-1.73l-3-1.742a2 2 0 00-1.892-.064z" />
<path d="M7 16.5V22" />
<path d="M14.5 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v3.8" />
<path d="M14 2v5a1 1 0 0 0 1 1h5" />
<path d="M11.7 14.2 7 17l-4.7-2.8" />
<path d="M3 13.1a2 2 0 0 0-.999 1.76v3.24a2 2 0 0 0 .969 1.78L6 21.7a2 2 0 0 0 2.03.01L11 19.9a2 2 0 0 0 1-1.76V14.9a2 2 0 0 0-.97-1.78L8 11.3a2 2 0 0 0-2.03-.01z" />
<path d="M7 17v5" />
</svg>

Before

Width:  |  Height:  |  Size: 610 B

After

Width:  |  Height:  |  Size: 599 B

+1 -2
View File
@@ -1,8 +1,7 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"danielbayley",
"karsa-mistmere"
"danielbayley"
],
"use-cases": [],
"tags": [
+2 -2
View File
@@ -9,6 +9,6 @@
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M22 9a1 1 0 00-1-1H3a1 1 0 00-1 1v4a1 1 0 001 1h.5a2 2 0 011.6.8l.3.4A2 2 0 007 16h10a2 2 0 001.6-.8l.3-.4a2 2 0 011.6-.8h.5a1 1 0 001-1z" />
<path d="M8 12h8" />
<path d="M22 9a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h1l2 2h12l2-2h1a1 1 0 0 0 1-1Z" />
<path d="M7.5 12h9" />
</svg>

Before

Width:  |  Height:  |  Size: 384 B

After

Width:  |  Height:  |  Size: 330 B

-46
View File
@@ -1,46 +0,0 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"wojtekmaj",
"ericfennis",
"karsa-mistmere"
],
"use-cases": [
"starting an augmented reality scan",
"placing virtual 3D objects in a real-world scene",
"indicating spatial mapping or room scanning",
"previewing products with virtual try-on",
"recognizing the surrounding environment for AR experiences",
"scanning an object to create a 3D model",
"measuring room dimensions for interior planning",
"calibrating depth sensors or LiDAR",
"placing furniture or decor in a room using AR"
],
"tags": [
"ar",
"augmented reality",
"3d",
"object detection",
"object recognition",
"tracking",
"spatial computing",
"capture",
"cube",
"bounding box",
"camera",
"frame",
"shape",
"boundary",
"lidar",
"depth",
"scanning",
"mapping",
"placement"
],
"categories": [
"design",
"devices",
"shopping",
"gaming"
]
}
-19
View File
@@ -1,19 +0,0 @@
<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 12v5.5" />
<path d="M17 3h2a2 2 0 012 2v2" />
<path d="M21 17v2a2 2 0 01-2 2h-2" />
<path d="M3 7V5a2 2 0 012-2h2" />
<path d="M7 21H5a2 2 0 01-2-2v-2" />
<path d="M7.264 9.252 12 12l4.737-2.748" />
<path d="M7.995 8.514A2 2 0 007 10.244v3.516a2 2 0 00.996 1.73l3 1.74a2 2 0 002.008 0l3-1.74A2 2 0 0017 13.76v-3.517a2 2 0 00-.995-1.73l-3-1.742a2 2 0 00-1.892-.064z" />
</svg>

Before

Width:  |  Height:  |  Size: 604 B

-54
View File
@@ -1,54 +0,0 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"karsa-mistmere"
],
"use-cases": [
"Indicating that a resource or area is protected and requires authorized access",
"Representing access control and permission management settings",
"Showing security options for accounts, applications, or protected systems",
"Indicating content or functionality restricted to authenticated users",
"Representing authentication and secure sign-in features",
"Showing protected credentials, secrets, or other sensitive information",
"Representing identity and account protection features",
"Indicating security policies or authorization requirements"
],
"tags": [
"cybersecurity",
"secure",
"safety",
"protection",
"defense",
"defence",
"defender",
"block",
"threat",
"prevention",
"antivirus",
"vigilance",
"vigilant",
"detection",
"scan",
"find",
"strength",
"strong",
"tough",
"invincible",
"invincibility",
"invulnerable",
"undamaged",
"audit",
"admin",
"verification",
"crest",
"bravery",
"trooper",
"pawn"
],
"categories": [
"account",
"security",
"development",
"files"
]
}
-15
View File
@@ -1,15 +0,0 @@
<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 13v3" />
<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 01-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 011-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 011.52 0C14.51 3.81 17 5 19 5a1 1 0 011 1z" />
<circle cx="12" cy="11" r="2" />
</svg>

Before

Width:  |  Height:  |  Size: 438 B

+9 -9
View File
@@ -42,16 +42,16 @@
"renamePattern": "node ./scripts/rename/renamePattern.mts"
},
"devDependencies": {
"@actions/core": "^3.0.1",
"@actions/github": "^9.1.1",
"@actions/core": "^1.11.1",
"@actions/github": "^6.0.1",
"@html-eslint/eslint-plugin": "^0.19.1",
"@html-eslint/parser": "^0.19.1",
"@octokit/rest": "^19.0.13",
"@types/yargs": "^17.0.35",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitest/coverage-v8": "4.1.10",
"@vitest/ui": "4.1.10",
"@vitest/coverage-v8": "4.0.12",
"@vitest/ui": "4.0.12",
"ajv-cli": "^5.0.0",
"dotenv": "^17.4.2",
"eslint": "^8.57.1",
@@ -64,18 +64,18 @@
"eslint-plugin-import": "^2.32.0",
"husky": "^8.0.3",
"jsdom": "^27.4.0",
"lint-staged": "^17.0.8",
"lint-staged": "^13.3.0",
"minimist": "^1.2.8",
"openai": "^5.23.2",
"p-memoize": "^7.1.1",
"prettier": "3.2.4",
"prettier-plugin-astro": "^0.14.1",
"semver": "^7.8.5",
"semver": "^7.8.0",
"simple-git": "^3.36.0",
"svgo": "^3.3.4",
"svgo": "^3.3.3",
"svgson": "^5.3.1",
"vitest": "4.1.10",
"yargs": "^17.7.3",
"vitest": "4.1.0",
"yargs": "^17.7.2",
"zod": "^3.25.76"
},
"engines": {
+15 -15
View File
@@ -46,24 +46,24 @@
"@angular-eslint/eslint-plugin-template": "~21.1.0",
"@angular-eslint/schematics": "~21.1.0",
"@angular-eslint/template-parser": "~21.1.0",
"@angular/build": "^21.2.19",
"@angular/cli": "^21.2.19",
"@angular/common": "^21.2.18",
"@angular/compiler": "^21.2.18",
"@angular/compiler-cli": "^21.2.18",
"@angular/core": "^21.2.18",
"@angular/forms": "^21.2.18",
"@angular/platform-browser": "^21.2.18",
"@angular/platform-server": "^21.2.18",
"@angular/router": "^21.2.18",
"@angular/build": "^21.0.3",
"@angular/cli": "^21.0.3",
"@angular/common": "^21.2.17",
"@angular/compiler": "^21.2.17",
"@angular/compiler-cli": "^21.0.0",
"@angular/core": "^21.2.17",
"@angular/forms": "^21.0.0",
"@angular/platform-browser": "^21.0.0",
"@angular/platform-server": "^21.2.17",
"@angular/router": "^21.0.0",
"@lucide/build-icons": "workspace:*",
"@lucide/helpers": "workspace:*",
"@vitest/browser-playwright": "^4.1.10",
"@vitest/browser-playwright": "^4.0.12",
"angular-eslint": "21.1.0",
"ng-packagr": "^21.2.5",
"rxjs": "~7.8.2",
"tslib": "^2.8.1",
"typescript": "~5.9.3"
"ng-packagr": "^21.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"typescript": "~5.9.2"
},
"peerDependencies": {
"@angular/common": ">=17.0.0",
+9 -9
View File
@@ -48,17 +48,17 @@
"test:watch": "pnpm build:icons && vitest run --watch"
},
"devDependencies": {
"@astrojs/ts-plugin": "^1.10.10",
"@astrojs/ts-plugin": "^1.10.4",
"@lucide/build-icons": "workspace:*",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"astro": "^6.4.8",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.8.0",
"jest-serializer-html": "^7.1.0",
"linkedom": "^0.18.13",
"prettier": "^3.9.5",
"typescript": "^5.9.3",
"vite": "^7.3.6",
"vitest": "4.1.10"
"linkedom": "^0.18.5",
"prettier": "^3.4.2",
"typescript": "^5.8.3",
"vite": "^7.3.2",
"vitest": "^4.1.0",
"astro": "^6.4.6"
},
"peerDependencies": {
"astro": "^4 || ^5 || ^6 || ^7"
+7 -7
View File
@@ -63,14 +63,14 @@
"@lucide/build-icons": "workspace:*",
"@lucide/helpers": "^1.0.0",
"@lucide/rollup-plugins": "workspace:*",
"@rollup/plugin-replace": "^6.0.3",
"@testing-library/jest-dom": "^6.9.1",
"@rollup/plugin-replace": "^6.0.2",
"@testing-library/jest-dom": "^6.6.3",
"jest-serializer-html": "^7.1.0",
"rollup": "^4.62.2",
"rollup-plugin-dts": "^6.4.1",
"rollup": "^4.53.3",
"rollup-plugin-dts": "^6.2.3",
"rollup-plugin-preserve-directives": "^0.4.0",
"typescript": "^5.9.3",
"vite": "^7.3.6",
"vitest": "4.1.10"
"typescript": "^5.8.3",
"vite": "^7.2.4",
"vitest": "^4.1.0"
}
}
+8 -8
View File
@@ -50,15 +50,15 @@
"@lucide/build-icons": "workspace:*",
"@lucide/rollup-plugins": "workspace:*",
"@lucide/shared": "workspace:*",
"@preact/preset-vite": "^2.10.5",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/preact": "^3.2.4",
"@preact/preset-vite": "^2.10.2",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/preact": "^3.2.3",
"jest-serializer-html": "^7.1.0",
"preact": "^10.29.7",
"rollup": "^4.62.2",
"rollup-plugin-dts": "^6.4.1",
"typescript": "^5.9.3",
"vite": "^7.3.6"
"preact": "^10.27.3",
"rollup": "^4.59.0",
"rollup-plugin-dts": "^6.2.3",
"typescript": "^5.8.3",
"vite": "^7.2.4"
},
"peerDependencies": {
"preact": "^10.27.2"
+13 -13
View File
@@ -68,20 +68,20 @@
"@lucide/helpers": "workspace:*",
"@lucide/rollup-plugins": "workspace:*",
"@lucide/shared": "workspace:*",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^14.3.1",
"@types/prop-types": "^15.7.15",
"@types/react": "^18.3.31",
"@vitejs/plugin-react": "^4.7.0",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^14.1.2",
"@types/prop-types": "^15.7.5",
"@types/react": "^18.0.21",
"@vitejs/plugin-react": "^4.2.1",
"jest-serializer-html": "^7.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-native": "^0.76.9",
"react-native-svg": "^15.15.5",
"rollup": "^4.62.2",
"rollup-plugin-dts": "^6.4.1",
"typescript": "^5.9.3",
"vite": "^7.3.6"
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-native": "^0.76.0",
"react-native-svg": "^15.8.0",
"rollup": "^4.59.0",
"rollup-plugin-dts": "^6.2.3",
"typescript": "^5.8.3",
"vite": "^7.2.4"
},
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+8 -8
View File
@@ -53,18 +53,18 @@
"@lucide/build-icons": "workspace:*",
"@lucide/rollup-plugins": "workspace:*",
"@lucide/shared": "workspace:*",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^14.3.1",
"@types/react": "^18.3.31",
"@vitejs/plugin-react": "^4.7.0",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^14.1.2",
"@types/react": "^18.2.37",
"@vitejs/plugin-react": "^4.6.0",
"jest-serializer-html": "^7.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"rollup": "^4.62.2",
"rollup-plugin-dts": "^6.4.1",
"rollup": "^4.59.0",
"rollup-plugin-dts": "^6.2.3",
"rollup-plugin-preserve-directives": "^0.4.0",
"typescript": "^5.9.3",
"vite": "^7.3.6"
"typescript": "^5.8.3",
"vite": "^7.2.4"
},
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+12 -12
View File
@@ -68,23 +68,23 @@
"test:watch": "vitest watch"
},
"devDependencies": {
"@babel/core": "^7.29.7",
"@babel/preset-env": "^7.29.7",
"@babel/preset-typescript": "^7.29.7",
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-typescript": "^7.23.3",
"@lucide/build-icons": "workspace:*",
"@lucide/rollup-plugins": "workspace:*",
"@lucide/shared": "workspace:*",
"@rollup/plugin-babel": "^6.1.0",
"@rollup/plugin-babel": "^6.0.4",
"@solidjs/testing-library": "^0.8.10",
"@testing-library/jest-dom": "^6.9.1",
"babel-preset-solid": "^1.9.12",
"esbuild": "^0.28.1",
"@testing-library/jest-dom": "^6.8.0",
"babel-preset-solid": "^1.8.12",
"jest-serializer-html": "^7.1.0",
"rollup": "^4.62.2",
"solid-js": "^1.9.14",
"typescript": "^5.9.3",
"vite": "^7.3.6",
"vite-plugin-solid": "^2.11.12"
"rollup": "^4.59.0",
"solid-js": "^1.9.4",
"typescript": "^5.8.3",
"vite": "^7.2.4",
"vite-plugin-solid": "^2.11.6",
"esbuild": "^0.28.1"
},
"peerDependencies": {
"solid-js": "^1.4.7"
+5 -5
View File
@@ -40,10 +40,10 @@
"@lucide/build-icons": "workspace:*",
"@lucide/helpers": "workspace:*",
"@lucide/rollup-plugins": "workspace:*",
"@types/node": "^22.20.1",
"prettier": "^2.8.8",
"rollup": "^4.62.2",
"rollup-plugin-dts": "^6.4.1",
"svgson": "^5.3.1"
"@types/node": "^22.15.30",
"prettier": "^2.3.2",
"svgson": "^5.2.1",
"rollup": "^4.59.0",
"rollup-plugin-dts": "^6.2.1"
}
}
+7 -7
View File
@@ -43,14 +43,14 @@
},
"devDependencies": {
"@lucide/build-icons": "workspace:*",
"@lucide/rollup-plugins": "workspace:*",
"@lucide/shared": "workspace:*",
"@rollup/plugin-replace": "^6.0.3",
"@testing-library/jest-dom": "^6.9.1",
"@lucide/rollup-plugins": "workspace:*",
"@rollup/plugin-replace": "^6.0.2",
"@testing-library/jest-dom": "^6.8.0",
"jest-serializer-html": "^7.1.0",
"rollup": "^4.62.2",
"rollup-plugin-dts": "^6.4.1",
"typescript": "^5.9.3",
"vite": "^7.3.6"
"rollup": "^4.59.0",
"rollup-plugin-dts": "^6.2.3",
"typescript": "^5.8.3",
"vite": "^7.2.4"
}
}
+1 -1
View File
@@ -13,6 +13,6 @@
"test:watch": "vitest watch"
},
"devDependencies": {
"vite": "^7.3.6"
"vite": "^7.2.4"
}
}
+87
View File
@@ -0,0 +1,87 @@
<p align="center">
<a href="https://github.com/lucide-icons/lucide">
<img src="https://lucide.dev/package-logos/lucide-solid.svg" alt="Lucide icon library for Solid applications." width="540">
</a>
</p>
<p align="center">
Lucide icon library for Solid v2 applications.
</p>
<div align="center">
[![npm](https://img.shields.io/npm/v/lucide-solid?color=blue)](https://www.npmjs.com/package/lucide-solid)
![NPM Downloads](https://img.shields.io/npm/dw/lucide-solid)
[![License](https://img.shields.io/badge/license-ISC-green)](https://lucide.dev/license)
</div>
<p align="center">
<a href="https://lucide.dev/guide/">About</a>
·
<a href="https://lucide.dev/icons/">Icons</a>
·
<a href="https://lucide.dev/guide/solid">Documentation</a>
·
<a href="https://lucide.dev/license">License</a>
</p>
# Lucide Solid
Implementation of the Lucide icon library for solid applications.
## Installation
```sh
pnpm add @lucide/solid
```
```sh
npm install @lucide/solid
```
```sh
yarn add @lucide/solid
```
```sh
bun add @lucide/solid
```
## Documentation
For full documentation, visit [lucide.dev](https://lucide.dev/guide/packages/lucide-solid)
## Community
Join the [Discord server](https://discord.gg/EH6nSts) to chat with the maintainers and other users.
## License
Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/license).
[//]: <> (Sponsors)
## Sponsors
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
<img src="https://lucide.dev/vercel.svg" alt="Powered by Vercel" width="200" />
</a>
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="https://lucide.dev/digitalocean.svg" width="200" alt="DigitalOcean Referral Badge" /></a>
### Hero backers 🦸
<a href="https://zephyr-cloud.io/"><img src="https://lucide.dev/sponsors/zephyr-cloud.svg" width="180" alt="Zephyr Cloud – From idea to prod: fast micro-frontend delivery!" /></a>
### Awesome backers 🍺
<a href="https://github.com/pdfme/pdfme"><img src="https://lucide.dev/sponsors/pdfme.svg" width="180" alt="pdfme – Open-source PDF generation library built with TypeScript and React." /></a>
<a href="https://www.paxhistoria.co/"><img src="https://lucide.dev/sponsors/paxhistoria.svg?" width="180" alt="Pax Historia – An alternate history sandbox game" /></a>
### Backers ☕
<a href="https://www.fina.money/"><img src="https://lucide.dev/sponsors/fina-money.png" width="180" alt="Fina Money – Modular Finance Tracker" /></a>
### Other contributors 💸
You can find all our past and non-recurring financial contributors at [our Open Collective page](https://opencollective.com/lucide-icons).
+94
View File
@@ -0,0 +1,94 @@
{
"name": "@lucide/solid",
"description": "A Lucide icon library package for Solid v2 applications.",
"version": "0.0.1",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",
"repository": {
"type": "git",
"url": "https://github.com/lucide-icons/lucide.git",
"directory": "packages/solid"
},
"keywords": [
"Lucide",
"Solid",
"SolidJS",
"Feather",
"Icons",
"Icon",
"SVG",
"Feather Icons",
"Fontawesome",
"Font Awesome"
],
"author": "Eric Fennis",
"source": "src/lucide-solid.ts",
"main": "dist/cjs/lucide-solid.js",
"module": "dist/esm/lucide-solid.mjs",
"types": "dist/types/lucide-solid.d.ts",
"files": [
"dist"
],
"exports": {
".": {
"types": "./dist/types/lucide-solid.d.ts",
"solid": "./dist/source/lucide-solid.jsx",
"import": "./dist/esm/lucide-solid.mjs",
"browser": "./dist/esm/lucide-solid.mjs",
"require": "./dist/cjs/lucide-solid.js"
},
"./icons": {
"types": "./dist/types/lucide-solid.d.ts",
"solid": "./dist/source/lucide-solid.jsx",
"import": "./dist/esm/lucide-solid.mjs",
"browser": "./dist/esm/lucide-solid.mjs",
"require": "./dist/cjs/lucide-solid.js"
},
"./icons/*": {
"types": "./dist/types/icons/*.d.ts",
"solid": "./dist/source/icons/*.jsx",
"import": "./dist/esm/icons/*.mjs",
"browser": "./dist/esm/icons/*.mjs",
"require": "./dist/cjs/icons/*.js"
}
},
"sideEffects": false,
"scripts": {
"build": "pnpm clean && pnpm copy:license && pnpm build:icons && pnpm build:bundle",
"copy:license": "cp ../../LICENSE ./LICENSE",
"clean": "rm -rf dist && rm -rf stats && rm -rf ./src/icons/*.ts",
"build:transpile": "tsc --jsx preserve -t es2020 --rootDir src --outDir dist --noEmit false",
"build:version": "node ./scripts/replaceVersion.mjs",
"build:bundle": "rollup -c rollup.config.mjs",
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --withAliases --separateAliasesFile --separateAliasesFileIgnore=fingerprint --aliasesFileExtension=.ts --iconFileExtension=.tsx --exportFileName=index.ts",
"typecheck": "tsc",
"typecheck:watch": "tsc -w",
"test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch"
},
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-typescript": "^7.23.3",
"@lucide/build-icons": "workspace:*",
"@lucide/rollup-plugins": "workspace:*",
"@lucide/shared": "workspace:*",
"@rollup/plugin-babel": "^6.0.4",
"@solidjs/testing-library": "1.0.0-beta.2",
"@solidjs/web": "2.0.0-beta.16",
"@testing-library/jest-dom": "^6.8.0",
"babel-preset-solid": "2.0.0-beta.16",
"esbuild": "^0.28.1",
"jest-serializer-html": "^7.1.0",
"rollup": "^4.59.0",
"solid-js": "2.0.0-beta.16",
"typescript": "^5.8.3",
"vite": "^7.2.4",
"vite-plugin-solid": "^2.11.6"
},
"peerDependencies": {
"solid-js": "2.0.0-beta.16",
"@solidjs/web": "2.0.0-beta.16"
}
}
+135
View File
@@ -0,0 +1,135 @@
import path from 'path';
import { babel } from '@rollup/plugin-babel';
import esbuild from 'esbuild';
import plugins from '@lucide/rollup-plugins';
import ts from 'typescript';
import pkg from './package.json' with { type: 'json' };
const packageName = 'LucideSolid';
const outputFileName = 'lucide-solid';
const outputDir = 'dist';
const inputs = ['src/lucide-solid.ts'];
const bundles = [
{
format: 'cjs',
inputs,
outputDir,
preserveModules: true,
},
{
format: 'esm',
inputs,
outputDir,
preserveModules: true,
extension: 'mjs',
},
];
const configs = bundles
.map(({ inputs, outputDir, format, preserveModules, extension = 'js' }) =>
inputs.map((input) => ({
input,
plugins: [
babel({
extensions: ['.ts', '.tsx', '.js', '.jsx'],
babelHelpers: 'bundled',
presets: [
'babel-preset-solid',
'@babel/preset-typescript',
['@babel/preset-env', { bugfixes: true, targets: 'last 2 years' }],
],
}),
...plugins({
pkg,
withEsbuild: false,
}),
format === 'esm'
? {
name: 'ts',
buildEnd() {
// Transpile typescript to './dist/source'
esbuild.build({
entryPoints: ['./src/**/*.tsx', './src/**/*.ts'],
outdir: './dist/source',
outExtension: {
'.js': '.jsx',
},
loader: {
'.js': 'jsx',
},
jsx: 'preserve',
jsxImportSource: 'solid-js',
bundle: true,
format: 'esm',
sourcemap: true,
target: ['esnext'],
banner: {
js: `/**
* @license ${pkg.name} v${pkg.version} - ${pkg.license}
*
* This source code is licensed under the ${pkg.license} license.
* See the LICENSE file in the root directory of this source tree.
*/`,
},
plugins: [
{
name: 'externalize-everything-except-own-dependencies',
setup(build) {
build.onResolve({ filter: /(.*)/ }, (args) => {
const modulePath = path.join(args.resolveDir, args.path);
if (
args.kind === 'import-statement' &&
args.path !== '@lucide/shared' &&
!modulePath.includes('packages/shared')
) {
return { path: args.path, external: true };
}
});
},
},
],
external: ['solid-js'],
});
// Generate types
const program = ts.createProgram([pkg.source], {
target: ts.ScriptTarget.ESNext,
module: ts.ModuleKind.ESNext,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
jsx: ts.JsxEmit.Preserve,
jsxImportSource: 'solid-js',
allowSyntheticDefaultImports: true,
esModuleInterop: true,
declarationDir: `dist/types`,
declaration: true,
emitDeclarationOnly: true,
});
program.emit();
},
}
: null,
],
external: ['solid-js', '@solidjs/web', 'solid-js/store'],
output: {
name: packageName,
...(preserveModules
? {
dir: `${outputDir}/${format}`,
exports: 'auto',
entryFileNames: `[name].${extension}`,
}
: {
file: `${outputDir}/${format}/${outputFileName}.${extension}`,
}),
format: format === 'source' ? 'esm' : format,
preserveModules,
preserveModulesRoot: 'src',
sourcemap: true,
},
})),
)
.flat();
export default configs;
+38
View File
@@ -0,0 +1,38 @@
import base64SVG from '@lucide/build-icons/utils/base64SVG';
import defineExportTemplate from '@lucide/build-icons/utils/defineExportTemplate';
export default defineExportTemplate(async ({
componentName,
iconName,
children,
getSvg,
deprecated,
deprecationReason,
}) => {
const svgContents = await getSvg();
const svgBase64 = base64SVG(svgContents);
return `
import Icon from '../Icon';
import type { IconNode, LucideProps } from '../types';
const iconNode: IconNode = ${JSON.stringify(children)};
/**
* @component @name ${componentName}
* @description Lucide SVG icon component, renders SVG Element with children.
*
* @preview ![img](data:image/svg+xml;base64,${svgBase64}) - https://lucide.dev/icons/${iconName}
* @see https://lucide.dev/guide/packages/lucide-solid - Documentation
*
* @param {Object} props - Lucide icons props and any valid SVG attribute
* @returns {JSX.Element} JSX Element
* ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/
const ${componentName} = (props: LucideProps) => (
<Icon {...props} iconNode={iconNode} name="${iconName}" />
)
export default ${componentName};
`;
});
+78
View File
@@ -0,0 +1,78 @@
import { For, omit, useContext } from 'solid-js';
import { Dynamic } from '@solidjs/web';
import defaultAttributes from './defaultAttributes';
import { IconNode, LucideProps } from './types';
import { LucideContext } from './context';
import { hasA11yProp, mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared';
import { JSX } from '@solidjs/web';
interface IconProps {
name?: string;
iconNode: IconNode;
}
const Icon = (props: LucideProps & IconProps) => {
const [localProps, rest] = omit(props, [
'color',
'size',
'strokeWidth',
'children',
'class',
'name',
'iconNode',
'absoluteStrokeWidth',
]);
const globalProps = useContext(LucideContext);
return (
<svg
{...defaultAttributes}
width={localProps.size ?? globalProps.size ?? defaultAttributes.width}
height={localProps.size ?? globalProps.size ?? defaultAttributes.height}
stroke={localProps.color ?? globalProps.color ?? defaultAttributes.stroke}
stroke-width={
(localProps.absoluteStrokeWidth ?? globalProps.absoluteStrokeWidth) === true
? (Number(
localProps.strokeWidth ??
globalProps.strokeWidth ??
defaultAttributes['stroke-width'],
) *
24) /
Number(localProps.size ?? globalProps.size)
: Number(
localProps.strokeWidth ??
globalProps.strokeWidth ??
defaultAttributes['stroke-width'],
)
}
class={mergeClasses(
'lucide',
'lucide-icon',
globalProps.class,
...(localProps.name != null
? [
`lucide-${toKebabCase(toPascalCase(localProps.name))}`,
`lucide-${toKebabCase(localProps.name)}`,
]
: []),
localProps.class,
)}
aria-hidden={!localProps.children && !hasA11yProp(rest) ? 'true' : undefined}
{...rest}
>
<For each={localProps.iconNode}>
{([elementName, attrs]) => {
return (
<Dynamic
component={elementName}
{...attrs}
/>
);
}}
</For>
</svg>
);
};
export default Icon;
+3
View File
@@ -0,0 +1,3 @@
export * from './aliases';
export * from './prefixed';
export * from './suffixed';
+37
View File
@@ -0,0 +1,37 @@
import { createContext, omit } from 'solid-js';
import { type JSX } from '@solidjs/web';
export const LucideContext = createContext<{
size?: number;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
class?: string;
}>({
size: 24,
color: 'currentColor',
strokeWidth: 2,
absoluteStrokeWidth: false,
class: '',
});
interface LucideProviderProps {
children: JSX.Element;
size?: number;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
class?: string;
}
export function LucideProvider(props: LucideProviderProps) {
const [value, rest] = omit(props, [
'size',
'color',
'strokeWidth',
'absoluteStrokeWidth',
'class',
]);
return <LucideContext.Provider value={value}>{rest.children}</LucideContext.Provider>;
}
+15
View File
@@ -0,0 +1,15 @@
import { type SVGAttributes } from './types';
const defaultAttributes: SVGAttributes = {
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',
};
export default defaultAttributes;
+1
View File
@@ -0,0 +1 @@
Folder for generated icons
+7
View File
@@ -0,0 +1,7 @@
export * from './icons';
export * as icons from './icons';
export * from './aliases';
export * from './types';
export * from './context';
export { default as Icon } from './Icon';
+15
View File
@@ -0,0 +1,15 @@
import { type JSX } from '@solidjs/web';
export type IconNode = [elementName: keyof JSX.IntrinsicElements, attrs: Record<string, string>][];
export type SVGAttributes = Partial<JSX.SvgSVGAttributes<SVGSVGElement>>;
export interface LucideProps extends SVGAttributes {
key?: string | number;
class?: string;
size?: string | number;
color?: string;
strokeWidth?: string | number;
absoluteStrokeWidth?: boolean;
}
export type LucideIcon = (props: LucideProps) => JSX.Element;
+106
View File
@@ -0,0 +1,106 @@
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();
});
});
describe('Icon Component Accessibility', () => {
it('should have aria-hidden prop when no aria prop is present', async () => {
const { container } = render(() => (
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
/>
));
expect(container.firstChild).toHaveAttribute('aria-hidden', 'true');
});
it('should not have aria-hidden prop when aria prop is present', async () => {
const { container } = render(() => (
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
aria-label="Air conditioning"
/>
));
expect(container.firstChild).not.toHaveAttribute('aria-hidden');
});
it('should not have aria-hidden prop when title prop is present', async () => {
const { container } = render(() => (
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
// @ts-expect-error
title="Air conditioning"
/>
));
expect(container.firstChild).not.toHaveAttribute('aria-hidden');
});
it('should not have aria-hidden prop when there are children that could be a <title> element', async () => {
const { container } = render(() => (
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
>
<title>Some title</title>
</Icon>
));
expect(container.firstChild).not.toHaveAttribute('aria-hidden');
});
it('should never override aria-hidden prop', async () => {
const { container } = render(() => (
<Icon
iconNode={airVent}
size={48}
stroke="red"
absoluteStrokeWidth
aria-hidden={false}
/>
));
expect(container.firstChild).toHaveAttribute('aria-hidden', 'false');
});
});
@@ -0,0 +1,34 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
aria-hidden="true"
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>
`;
@@ -0,0 +1,26 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using LucideProvider > should render the icon with LucideProvider 1`] = `
<svg
aria-hidden="true"
class="lucide lucide-icon lucide-house"
fill="none"
height="48"
stroke="red"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"
key="5wwlr5"
/>
<path
d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
key="r6nss1"
/>
</svg>
`;
@@ -0,0 +1,123 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using lucide icon components > should adjust the size, stroke color and stroke width 1`] = `
<svg xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
width="48"
height="48"
stroke="red"
stroke-width="4"
class="lucide lucide-icon lucide-grid3x3 lucide-grid-3x3"
aria-hidden="true"
data-testid="grid-icon"
>
<rect width="18"
height="18"
x="3"
y="3"
rx="2"
key="afitv7"
>
</rect>
<path d="M3 9h18"
key="1pudct"
>
</path>
<path d="M3 15h18"
key="5xshup"
>
</path>
<path d="M9 3v18"
key="fh3hqa"
>
</path>
<path d="M15 3v18"
key="14nvp0"
>
</path>
</svg>
`;
exports[`Using lucide icon components > should not scale the strokeWidth when absoluteStrokeWidth is set 1`] = `
<svg xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
width="48"
height="48"
stroke="red"
stroke-width="1"
class="lucide lucide-icon lucide-grid3x3 lucide-grid-3x3"
aria-hidden="true"
data-testid="grid-icon"
>
<rect width="18"
height="18"
x="3"
y="3"
rx="2"
key="afitv7"
>
</rect>
<path d="M3 9h18"
key="1pudct"
>
</path>
<path d="M3 15h18"
key="5xshup"
>
</path>
<path d="M9 3v18"
key="fh3hqa"
>
</path>
<path d="M15 3v18"
key="14nvp0"
>
</path>
</svg>
`;
exports[`Using lucide icon components > should render a component 1`] = `
<svg xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 24 24"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
width="24"
height="24"
stroke="currentColor"
stroke-width="2"
class="lucide lucide-icon lucide-grid3x3 lucide-grid-3x3"
aria-hidden="true"
>
<rect width="18"
height="18"
x="3"
y="3"
rx="2"
key="afitv7"
>
</rect>
<path d="M3 9h18"
key="1pudct"
>
</path>
<path d="M3 15h18"
key="5xshup"
>
</path>
<path d="M9 3v18"
key="fh3hqa"
>
</path>
<path d="M15 3v18"
key="14nvp0"
>
</path>
</svg>
`;
+102
View File
@@ -0,0 +1,102 @@
import { render } from '@solidjs/testing-library';
import { describe, expect, it } from 'vitest';
import { House, LucideProvider } from '../src/lucide-solid';
describe('Using LucideProvider', () => {
it('should render the icon with LucideProvider', () => {
const { container } = render(() => (
<LucideProvider
size={48}
color="red"
>
<House />
</LucideProvider>
));
expect(container.firstChild).toMatchSnapshot();
});
it('should render the icon with default props when no provider is used', () => {
const { container } = render(() => <House />);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '24');
expect(IconComponent).toHaveAttribute('height', '24');
expect(IconComponent).toHaveAttribute('stroke', 'currentColor');
expect(IconComponent).toHaveAttribute('stroke-width', '2');
});
it('should render the icon with LucideProvider and custom strokeWidth', () => {
const { container } = render(() => (
<LucideProvider
size={48}
color="red"
strokeWidth={4}
>
<House />
</LucideProvider>
));
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '48');
expect(IconComponent).toHaveAttribute('height', '48');
expect(IconComponent).toHaveAttribute('stroke', 'red');
expect(IconComponent).toHaveAttribute('stroke-width', '4');
});
it('should render the icon with LucideProvider and custom absoluteStrokeWidth', () => {
const { container } = render(() => (
<LucideProvider
size={48}
color="red"
absoluteStrokeWidth
>
<House />
</LucideProvider>
));
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('stroke-width', '1');
});
it("should override the provider's global props when passing props to the icon", () => {
const { container } = render(() => (
<LucideProvider
size={48}
color="red"
strokeWidth={4}
>
<House
size={24}
color="blue"
strokeWidth={2}
/>
</LucideProvider>
));
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('width', '24');
expect(IconComponent).toHaveAttribute('height', '24');
expect(IconComponent).toHaveAttribute('stroke', 'blue');
expect(IconComponent).toHaveAttribute('stroke-width', '2');
});
it('should merge className from provider and icon', () => {
const { container } = render(() => (
<LucideProvider class="provider-class">
<House class="icon-class" />
</LucideProvider>
));
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute(
'class',
'lucide lucide-icon provider-class lucide-house icon-class',
);
});
});
@@ -0,0 +1,90 @@
import { describe, it, expect } from 'vitest';
import { render, cleanup } from '@solidjs/testing-library';
import { Edit2, Grid, Pen, Droplet } from '../src/lucide-solid';
describe('Using lucide icon components', () => {
it('should render a component', () => {
const { container } = render(() => <Grid />);
expect(container.innerHTML).toMatchSnapshot();
});
it('should adjust the size, stroke color and stroke width', async () => {
const testId = 'grid-icon';
const { container, getByTestId } = render(() => (
<Grid
data-testid={testId}
size={48}
color="red"
strokeWidth={4}
/>
));
const { attributes } = (await 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');
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}
/>
));
const PenIconRenderedHTML = container.innerHTML;
cleanup();
const { container: Edit2Container } = render(() => (
<Edit2
data-testid={testId}
size={48}
stroke="red"
strokeWidth={4}
/>
));
expect(PenIconRenderedHTML).toBe(Edit2Container.innerHTML);
});
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
const testId = 'grid-icon';
const { container, getByTestId } = render(() => (
<Grid
data-testid={testId}
size={48}
color="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');
expect(container.innerHTML).toMatchSnapshot();
});
it('should add all classes to the element', () => {
const testClass = 'my-class';
const { container } = render(() => <Droplet class={testClass} />);
expect(container.firstChild).toHaveClass(testClass);
expect(container.firstChild).toHaveClass('lucide');
expect(container.firstChild).toHaveClass('lucide-droplet');
});
});
+10
View File
@@ -0,0 +1,10 @@
import { expect, afterEach } from 'vitest';
import { cleanup } from '@solidjs/testing-library';
import '@testing-library/jest-dom/vitest';
import htmlSerializer from 'jest-serializer-html';
expect.addSnapshotSerializer(htmlSerializer);
afterEach(() => {
cleanup();
});
+22
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' }],
];
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "@solidjs/web",
"types": ["vite/client", "@testing-library/jest-dom"],
"noEmit": true,
"isolatedModules": true,
"outDir": "./dist",
"declarationDir": "./dist",
"declaration": true,
},
}
+13
View File
@@ -0,0 +1,13 @@
import { defineConfig } from 'vitest/config'
import solidPlugin from 'vite-plugin-solid';
export default defineConfig({
// TODO: Remove this when Solid testing library has support for Vitest 1.0, see: https://github.com/solidjs/solid-testing-library/issues/52
// @ts-ignore
plugins: [solidPlugin()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './tests/setupVitest.js',
},
});
+11 -11
View File
@@ -60,18 +60,18 @@
"devDependencies": {
"@lucide/build-icons": "workspace:*",
"@lucide/helpers": "workspace:*",
"@sveltejs/package": "^2.5.8",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/svelte": "^5.4.2",
"@tsconfig/svelte": "^5.0.8",
"@sveltejs/package": "^2.5.0",
"@sveltejs/vite-plugin-svelte": "^6.1.3",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/svelte": "^5.2.8",
"@tsconfig/svelte": "^5.0.4",
"jest-serializer-html": "^7.1.0",
"jsdom": "^27.4.0",
"svelte": "^5.56.5",
"svelte-check": "^4.7.2",
"svelte-preprocess": "^6.0.5",
"typescript": "^5.9.3",
"vite": "^7.3.6"
"jsdom": "^20.0.3",
"svelte": "^5.38.6",
"svelte-check": "^4.3.4",
"svelte-preprocess": "^6.0.3",
"typescript": "^5.8.3",
"vite": "^6.3.6"
},
"peerDependencies": {
"svelte": "^5"
@@ -110,6 +110,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
<!---->
</svg>
</div>
`;
@@ -203,6 +204,7 @@ exports[`Using lucide icon components > should render an component 1`] = `
<!---->
</svg>
</div>
`;
@@ -263,5 +265,6 @@ exports[`Using lucide icon components > should render an icon slot 1`] = `
<!---->
</svg>
</div>
`;
+7 -6
View File
@@ -44,15 +44,16 @@
"@lucide/build-icons": "workspace:*",
"@lucide/rollup-plugins": "workspace:*",
"@lucide/shared": "workspace:*",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/vue": "^8.1.0",
"@vitejs/plugin-vue": "^4.6.2",
"@vue/test-utils": "2.4.5",
"rollup": "^4.62.2",
"rollup-plugin-dts": "^6.4.1",
"typescript": "^5.9.3",
"vite": "^7.3.6",
"vue": "^3.5.39"
"@vitejs/plugin-vue2": "2.2.0",
"rollup-plugin-dts": "^6.1.0",
"rollup": "^4.59.0",
"typescript": "^5.8.3",
"vite": "^7.2.4",
"vue": "^3.4.21"
},
"peerDependencies": {
"vue": ">=3.0.1"
+8078 -4712
View File
File diff suppressed because it is too large Load Diff
+1 -4
View File
@@ -4,16 +4,13 @@ packages:
- 'docs'
overrides:
axios: ^1.12.0
cross-spawn: 7.0.5
fast-json-patch: ^3.1.1
form-data: ^4.0.4
semver: ^7.7.3
vite-prerender-plugin: 0.5.12
webpack-dev-middleware: ^5.3.4
vitest: '4.1.10'
vite: '^7.3.6'
minimatch@>=9.0.0 <9.0.6: ^9.0.6
minimatch@>=9.0.0 <9.0.7: ^9.0.7
allowBuilds:
'@parcel/watcher': false
+4 -4
View File
@@ -13,16 +13,16 @@
"author": "",
"license": "ISC",
"dependencies": {
"@vercel/blob": "^2.6.1",
"fs-extra": "^11.3.6",
"@vercel/blob": "^2.3.0",
"fs-extra": "^11.2.0",
"minimist": "^1.2.8",
"oslllo-svg-fixer": "^5.0.0",
"svgtofont": "^6.5.3"
"svgtofont": "^6.5.2"
},
"devDependencies": {
"@lucide/helpers": "workspace:*",
"@types/fs-extra": "^11.0.4",
"@types/minimist": "^1.2.5",
"@types/node": "^22.20.1"
"@types/node": "^22"
}
}
+1 -1
View File
@@ -9,6 +9,6 @@
"license": "ISC",
"type": "module",
"devDependencies": {
"@types/node": "^22.20.1"
"@types/node": "^22"
}
}
+4 -4
View File
@@ -37,13 +37,13 @@
"devDependencies": {
"@lucide/helpers": "workspace:*",
"@types/minimist": "^1.2.5",
"@types/node": "^22.20.1"
"@types/node": "^22"
},
"dependencies": {
"minimist": "^1.2.8",
"minimist": "^1.2.7",
"prettier": "2.7.1",
"svgo": "^3.3.4",
"svgson": "^5.3.1"
"svgo": "^3.3.3",
"svgson": "^5.2.1"
},
"peerDependencies": {
"@lucide/helpers": "*"
+3 -3
View File
@@ -11,10 +11,10 @@
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-replace": "^6.0.3",
"esbuild": "^0.28.1",
"rollup": "^4.62.2",
"rollup": "^4.59.0",
"rollup-plugin-esbuild": "^6.2.1",
"rollup-plugin-license": "^3.7.1",
"rollup-plugin-license": "^3.6.0",
"rollup-plugin-summary": "^3.0.1",
"rollup-plugin-visualizer": "^6.0.11"
"rollup-plugin-visualizer": "^6.0.5"
}
}