Compare commits

..

3 Commits

Author SHA1 Message Date
Eric Fennis
985fa35a0b Add call for iconNodes 2025-12-10 17:29:31 +01:00
Eric Fennis
8c0e5cf302 Merge branch 'main' of https://github.com/lucide-icons/lucide into version-search 2025-12-10 16:15:19 +01:00
Eric Fennis
4acd574de2 Add version filter 2025-04-11 17:01:58 +02:00
172 changed files with 8067 additions and 6643 deletions

View File

@@ -9,3 +9,9 @@ strikethrough
touchpad
ungroup
toc
# Brands
codepen
codesandbox
dribbble
x.com

View File

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

View File

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

5
categories/brands.json Normal file
View File

@@ -0,0 +1,5 @@
{
"$schema": "../category.schema.json",
"title": "Brands",
"icon": "facebook"
}

View File

@@ -0,0 +1,13 @@
import { eventHandler, setResponseHeader } from 'h3';
import releaseMeta from '../../data/releaseMetaData.json';
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return Object.fromEntries(
Object.entries(releaseMeta).map(
([name, { createdRelease }]) => [name, createdRelease.version]
)
);
});

View File

@@ -1,3 +0,0 @@
export default eventHandler(() => {
return { nitro: 'Is Awesome! asda' };
});

View File

@@ -15,6 +15,10 @@
"name": "arrows",
"title": "Arrows"
},
{
"name": "brands",
"title": "Brands"
},
{
"name": "buildings",
"title": "Buildings"

View File

@@ -31,12 +31,20 @@
}
]
},
"@lucide/vue": {
"lucide-vue-next": {
"order": 2,
"icon": "vue",
"docsAlias": "lucide-vue",
"packageDirname": "vue",
"icon": "vue-next",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
}
]
},
"lucide-svelte": {

View File

@@ -43,7 +43,7 @@ export default App;
language: 'vue',
title: 'Vue',
code: `<script setup>
import { $PascalCase } from '@lucide/vue';
import { $PascalCase } from 'lucide-vue-next';
</script>
<template>

View File

@@ -1,5 +1,5 @@
import { createLucideIcon } from 'lucide-react/src/lucide-react';
import { type LucideProps, type IconNode } from 'lucide-react/src/types';
import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon';
import { IconEntity } from '../theme/types';
import { renderToStaticMarkup } from 'react-dom/server';
import { IconContent } from './generateZip';

View File

@@ -74,7 +74,7 @@ const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
},
{
text: 'Lucide Vue',
link: '/guide/packages/lucide-vue',
link: '/guide/packages/lucide-vue-next',
},
{
text: 'Lucide Svelte',

View File

@@ -1,31 +1,23 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { computed } from 'vue';
const props = defineProps<{
modelValue: string;
id: string;
}>();
modelValue: string
id: string
}>()
const { isDark } = useData();
const emit = defineEmits(['update:modelValue']);
const emit = defineEmits(['update:modelValue'])
const value = computed({
get: () => {
if (props.modelValue == null || props.modelValue === 'currentColor') {
return isDark.value ? '#ffffff' : '#000000';
}
return props.modelValue;
},
set: (val) => emit('update:modelValue', val),
});
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
</script>
<template>
<div class="color-picker">
<div class="color-input-wrapper">
<!-- TODO: Add currentColor div if value is currentColor -->
<input
type="color"
:id="id"
@@ -41,7 +33,6 @@ const value = computed({
class="color-input-text"
aria-label="Color picker input"
v-model="value"
placeholder="[default]"
/>
</div>
</template>
@@ -54,21 +45,19 @@ const value = computed({
top: -5px;
left: -5px;
}
.color-input-wrapper {
height: 24px;
width: 24px;
overflow: hidden;
position: relative;
border-radius: 4px;
border-radius: 12px;
flex-shrink: 0;
}
.color-picker {
background: var(--color-picker-bg, var(--vp-c-bg-alt));
border-radius: 8px;
color: var(--vp-c-text-2);
padding: 3px 8px 3px 3px;
padding: 4px 8px;
height: auto;
font-size: 14px;
text-align: left;
@@ -77,10 +66,6 @@ const value = computed({
display: flex;
align-items: center;
gap: 2px;
transition:
color 0.25s,
border-color 0.25s,
background-color 0.25s;
}
.color-input-text {
@@ -94,18 +79,15 @@ const value = computed({
text-align: left;
border-radius: 8px;
cursor: text;
transition:
border-color 0.25s,
background 0.4s ease;
letter-spacing: 1px;
transition: border-color 0.25s, background 0.4s ease;
}
.color-picker:hover,
.color-picker:focus {
.color-picker:hover, .color-picker:focus {
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt);
}
.color-input[value='currentColor'] {
.color-input[value="currentColor"] {
}
</style>

View File

@@ -1,27 +1,22 @@
<script setup>
import Icon from 'lucide-vue-next/src/Icon';
import { search } from '../../../data/iconNodes';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import { search } from '../../../data/iconNodes'
const SearchIcon = createLucideIcon('search', search)
defineProps({
shortcut: {
type: String,
required: false,
},
});
required: false
}
})
</script>
<template>
<button class="fake-input">
<Icon
:iconNode="search"
class="search-icon"
/>
<slot />
<kbd
v-if="shortcut"
class="shortcut"
>{{ shortcut }}</kbd
>
<component :is="SearchIcon" class="search-icon"/>
<slot/>
<kbd v-if="shortcut" class="shortcut">{{ shortcut }}</kbd>
</button>
</template>
@@ -39,14 +34,10 @@ defineProps({
cursor: text;
display: flex;
gap: 12px;
transition:
color 0.25s,
border-color 0.25s,
background-color 0.25s;
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
}
.fake-input:hover,
.fake-input:focus {
.fake-input:hover, .fake-input:focus {
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt);
}

View File

@@ -5,6 +5,7 @@
</template>
<style scoped>
.icon-button {
display: inline-flex;
border: 1px solid transparent;
@@ -29,9 +30,9 @@
}
.icon-button:active {
border-color: var(--vp-button-alt-active-border);
color: var(--vp-button-alt-active-text);
background-color: var(--vp-button-alt-active-bg);
border-color: var(--vp-button-alt-active-border);
color: var(--vp-button-alt-active-text);
background-color: var(--vp-button-alt-active-bg);
}
.icon-button.active {

View File

@@ -12,9 +12,6 @@ export interface InputProps {
<script setup lang="ts">
import { ref, onMounted, nextTick, watch } from 'vue';
import Icon from 'lucide-vue-next/src/Icon';
import { x } from '../../../data/iconNodes';
import IconButton from './IconButton.vue';
const props = withDefaults(defineProps<InputProps>(), {
type: 'text',
@@ -24,7 +21,7 @@ const input = ref();
const wrapperEl = ref();
const shortcutEl = ref();
const emit = defineEmits(['change', 'input', 'update:modelValue']);
defineEmits(['change', 'input', 'update:modelValue']);
const updateShortcutSpacing = () => {
nextTick(() => {
@@ -38,11 +35,6 @@ const updateShortcutSpacing = () => {
onMounted(updateShortcutSpacing);
watch(() => props.shortcut, updateShortcutSpacing);
function onClear() {
emit('update:modelValue', '');
input.value.focus();
}
defineExpose({
focus: () => {
input.value.focus();
@@ -68,17 +60,6 @@ defineExpose({
v-bind="$attrs"
@input="$emit('update:modelValue', $event.target.value)"
/>
<IconButton
@click="onClear"
v-if="type === 'search' && modelValue"
class="clear-button"
aria-label="Clear input"
>
<Icon
:iconNode="x"
:size="20"
/>
</IconButton>
<kbd
v-if="shortcut"
class="shortcut"
@@ -92,7 +73,6 @@ defineExpose({
.input-wrapper {
position: relative;
}
.input {
justify-content: flex-start;
border: 1px solid transparent;
@@ -102,10 +82,7 @@ defineExpose({
height: 40px;
background-color: var(--vp-c-bg-alt);
font-size: 14px;
transition:
color 0.25s,
border-color 0.25s,
background-color 0.25s;
transition: border-color 0.2s ease-in-out;
}
.input.has-shortcut {
@@ -122,14 +99,6 @@ defineExpose({
padding-left: 52px;
}
.clear-button {
position: absolute;
right: 56px;
top: 9px;
padding: 4px;
transition: background-color .25s;
}
.shortcut {
position: absolute;
right: 12px;

View File

@@ -7,8 +7,11 @@ export default {
<script setup lang="ts">
import { computed, ref } from 'vue';
import Input from './Input.vue';
import Icon from 'lucide-vue-next/src/Icon';
import { search } from '../../../data/iconNodes';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import { search, x } from '../../../data/iconNodes';
const SearchIcon = createLucideIcon('search', search);
const XIcon = createLucideIcon('close', x);
interface Props {
modelValue: string;
@@ -31,6 +34,10 @@ const value = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
});
const clear = () => {
value.value = '';
};
</script>
<template>
@@ -43,12 +50,26 @@ const value = computed({
v-model="value"
class="input-wrapper"
>
<template #icon>
<Icon
:iconNode="search"
<template #startIcon>
<component
:is="SearchIcon"
class="search-icon"
/>
</template>
<template #endIcon>
<button
class="clear-button"
type="button"
v-if="value"
>
<component
:is="XIcon"
class="x-icon"
@click="clear"
/>
</button>
</template>
</Input>
</template>
@@ -60,7 +81,9 @@ const value = computed({
padding: 0 10px 0 12px;
width: 100%;
height: 40px;
color: var(--vp-c-text-1);
background-color: var(--vp-c-bg-alt);
transition: border-color 0.15s ease-in-out;
}
.input:hover,
@@ -70,9 +93,31 @@ const value = computed({
}
.input-wrapper:deep(.input) {
/* padding: 12px 24px; */
padding-block: 12px;
font-size: 14px;
height: 48px;
}
.clear-button {
position: absolute;
padding: 4px;
right: 8px;
top: 8px;
width: 32px;
height: 32px;
background: transparent;
border-radius: 6px;
transition: background 0.2s ease;
}
.clear-button:hover {
background: var(--vp-button-alt-bg);
}
.input-wrapper:deep(.input)::-webkit-search-decoration,
.input-wrapper:deep(.input)::-webkit-search-cancel-button,
.input-wrapper:deep(.input)::-webkit-search-results-button,
.input-wrapper:deep(.input)::-webkit-search-results-decoration {
display: none;
}
</style>

View File

@@ -1,15 +1,14 @@
<script setup lang="ts">
import { rotateCw } from '../../../data/iconNodes';
import Icon from 'lucide-vue-next/src/Icon';
import IconButton from './IconButton.vue';
import { rotateCw } from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import IconButton from "./IconButton.vue";
const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
</script>
<template>
<IconButton class="reset-button">
<Icon
:size="20"
:iconNode="rotateCw"
/>
<RotateIcon :size="20"/>
</IconButton>
</template>
@@ -33,7 +32,6 @@ import IconButton from './IconButton.vue';
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}

View File

@@ -52,7 +52,6 @@
width: 20px;
height: 20px;
border-radius: 50%;
/* background-color: var(--vp-c-neutral); */
box-shadow: var(--vp-shadow-1);
}

View File

@@ -13,25 +13,25 @@ export default {
label: 'Lucide documentation for React',
},
{
name: 'lucide-vue',
name: 'lucide-vue-next',
logo: '/framework-logos/vue.svg',
label: 'Lucide documentation for Vue',
label: 'Lucide documentation for Vue 3',
},
{
name: 'lucide-svelte',
logo: '/framework-logos/svelte.svg',
label: 'Lucide documentation for Svelte',
},
{
name: 'lucide-solid',
logo: '/framework-logos/solid.svg',
label: 'Lucide documentation for Solid',
},
{
name: 'lucide-preact',
logo: '/framework-logos/preact.svg',
label: 'Lucide documentation for Preact',
},
{
name: 'lucide-solid',
logo: '/framework-logos/solid.svg',
label: 'Lucide documentation for Solid',
},
{
name: 'lucide-angular',
logo: '/framework-logos/angular.svg',
@@ -48,6 +48,11 @@ export default {
logo: '/framework-logos/react-native.svg',
label: 'Lucide documentation for React Native',
},
{
name: 'lucide-flutter',
logo: '/framework-logos/flutter.svg',
label: 'Lucide documentation for Flutter',
},
],
};
},

View File

@@ -2,48 +2,45 @@
import { useData } from 'vitepress';
import { useSessionStorage } from '@vueuse/core';
import IconButton from '../base/IconButton.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue';
import { x } from '../../../data/iconNodes';
import Icon from 'lucide-vue-next/src/Icon';
// import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
import { x } from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import { onMounted, ref } from 'vue';
const { theme } = useData();
const showAd = useSessionStorage('show-carbon-ads', true);
const carbonLoaded = ref(true);
const { theme } = useData()
const showAd = useSessionStorage('show-carbon-ads', true)
const carbonLoaded = ref(true)
defineProps<{
drawerOpen: boolean;
}>();
drawerOpen: boolean
}>()
const CloseIcon = createLucideIcon('Close', x)
onMounted(() => {
setTimeout(() => {
if (window?._carbonads == null) {
carbonLoaded.value = false;
carbonLoaded.value = false
}
}, 5000);
});
}, 5000)
})
</script>
<template>
<div
:class="{
'drawer-open': drawerOpen,
'hide-ad': !(showAd && carbonLoaded),
'hide-ad': !(showAd && carbonLoaded)
}"
class="floating-ad"
v-if="theme.carbonAds"
>
<IconButton
@click="showAd = false"
class="hide-button"
>
<Icon
:iconNode="x"
:size="20"
absoluteStrokeWidth
/>
<IconButton @click="showAd = false" class="hide-button">
<component :is="CloseIcon" :size="20" absoluteStrokeWidth />
</IconButton>
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" />
<VPDocAsideCarbonAds
:carbon-ads="theme.carbonAds"
/>
</div>
</template>
@@ -54,9 +51,7 @@ onMounted(() => {
bottom: 32px;
width: 224px;
right: 32px;
transition:
opacity 0.5s,
transform 0.25s ease;
transition: opacity 0.5s, transform 0.25s ease;
}
.floating-ad.drawer-open {
@@ -72,11 +67,8 @@ onMounted(() => {
transform: translateY(-288px) translateX(224px);
}
.floating-ad.drawer-open,
.floating-ad.hide-ad {
transition:
opacity 0.25s,
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
.floating-ad.drawer-open, .floating-ad.hide-ad {
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
}
@media (min-width: 1280px) {

View File

@@ -1,68 +1,70 @@
<script setup lang="ts">
import { ref } from 'vue';
import ButtonMenu from '../base/ButtonMenu.vue';
import ButtonMenu from '../base/ButtonMenu.vue'
import { useIconStyleContext } from '../../composables/useIconStyle';
import useConfetti from '../../composables/useConfetti';
import getSVGIcon from '../../utils/getSVGIcon';
import downloadData from '../../utils/downloadData';
const downloadText = 'Download!';
const copiedText = 'Copied!';
const confettiText = ref(copiedText);
const downloadText = 'Download!'
const copiedText = 'Copied!'
const confettiText = ref(copiedText)
const props = defineProps<{
name: string;
popoverPosition?: 'top' | 'bottom';
}>();
name: string
popoverPosition?: 'top' | 'bottom'
}>()
const { size } = useIconStyleContext();
const { size } = useIconStyleContext()
const { animate, confetti } = useConfetti();
const { animate, confetti } = useConfetti()
function copySVG() {
confettiText.value = copiedText;
const svgString = getSVGIcon();
confettiText.value = copiedText
const svgString = getSVGIcon()
navigator.clipboard.writeText(svgString);
navigator.clipboard.writeText(svgString)
confetti();
confetti()
}
function copyDataUrl() {
confettiText.value = copiedText;
const svgString = getSVGIcon();
confettiText.value = copiedText
const svgString = getSVGIcon()
// Create SVG data url
const dataUrl = `data:image/svg+xml;base64,${btoa(svgString)}`;
navigator.clipboard.writeText(dataUrl);
const dataUrl = `data:image/svg+xml;base64,${btoa(svgString)}`
navigator.clipboard.writeText(dataUrl)
confetti();
confetti()
}
function downloadSVG() {
confettiText.value = downloadText;
const svgString = getSVGIcon();
confettiText.value = downloadText
const svgString = getSVGIcon()
downloadData(`${props.name}.svg`, `data:image/svg+xml;base64,${btoa(svgString)}`);
confetti();
downloadData(`${props.name}.svg`, `data:image/svg+xml;base64,${btoa(svgString)}`)
confetti()
}
function downloadPNG() {
confettiText.value = downloadText;
const svgString = getSVGIcon();
confettiText.value = downloadText
const svgString = getSVGIcon()
const canvas = document.createElement('canvas');
canvas.width = size.value;
canvas.height = size.value;
const ctx = canvas.getContext('2d');
const ctx = canvas.getContext("2d");
const image = new Image();
image.src = `data:image/svg+xml;base64,${btoa(svgString)}`;
image.onload = function () {
image.onload = function() {
ctx.drawImage(image, 0, 0);
downloadData(`${props.name}.png`, canvas.toDataURL('image/png'));
confetti();
};
downloadData(`${props.name}.png`, canvas.toDataURL('image/png'))
confetti()
}
}
</script>
<template>
@@ -73,10 +75,10 @@ function downloadPNG() {
:data-confetti-text="confettiText"
:popoverPosition="popoverPosition"
:options="[
{ text: 'Copy SVG', onClick: copySVG },
{ text: 'Copy Data URL', onClick: copyDataUrl },
{ text: 'Download SVG', onClick: downloadSVG },
{ text: 'Download PNG', onClick: downloadPNG },
{ text: 'Copy SVG' , onClick: copySVG },
{ text: 'Copy Data URL' , onClick: copyDataUrl },
{ text: 'Download SVG' , onClick: downloadSVG },
{ text: 'Download PNG' , onClick: downloadPNG },
]"
/>
</template>

View File

@@ -1,44 +1,41 @@
<script setup lang="ts">
import { computed, useSlots } from 'vue';
import { copy } from '../../../data/iconNodes';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import { copy } from '../../../data/iconNodes'
import useConfetti from '../../composables/useConfetti';
import Icon from 'lucide-vue-next/src/Icon';
const { animate, confetti } = useConfetti();
const slots = useSlots();
const { animate, confetti } = useConfetti()
const slots = useSlots()
const copiedText = computed(() => slots.default?.()[0].children);
const copiedText = computed(() => slots.default?.()[0].children)
function copyText() {
navigator.clipboard.writeText(copiedText.value);
navigator.clipboard.writeText(copiedText.value)
confetti();
confetti()
}
const Copy = createLucideIcon('ChevronUp', copy)
</script>
<template>
<h1
class="icon-name confetti-button"
:class="{ animate }"
:class="{animate}"
data-confetti-text="Copied!"
@click="copyText"
>
<slot />
<Icon
:iconNode="copy"
:size="20"
class="copy-icon"
/>
<Copy :size="20" class="copy-icon"/>
</h1>
</template>
<style scoped>
@import './confetti.css';
.icon-name {
font-size: 24px;
font-weight: 500;
line-height: 32px;
transition: background ease-in 0.15s;
transition: background ease-in .15s;;
padding: 2px 8px;
border-radius: 8px;
width: auto;
@@ -51,7 +48,7 @@ function copyText() {
}
.icon-name:hover .copy-icon {
opacity: 0.9;
opacity: .9;
}
.icon-name:before,
@@ -68,10 +65,10 @@ function copyText() {
opacity: 0;
margin-left: 12px;
margin-top: 6px;
transition: ease 0.3s opacity;
transition:ease .3s opacity;
}
.icon-name:hover .copy-icon:hover {
opacity: 0.6;
opacity: .6;
}
</style>

View File

@@ -29,12 +29,7 @@ const props = defineProps<{
const iconComponent = computed(() => {
if (!props.name || !props.iconNode) return null;
try {
return createLucideIcon(props.name, props.iconNode);
} catch (error) {
console.warn(`Icon ${props.name} not found, using fallback`);
return null;
}
return createLucideIcon(props.name, props.iconNode);
});
const CalendarIcon = createLucideIcon('calendar', Calendar.iconNode);
@@ -66,7 +61,7 @@ const prettyName = props.name
</script>
<template>
<section class="showcase" v-if="iconComponent">
<section class="showcase">
<h2 class="title">See this icon in action</h2>
<div class="showcase-grid">
<div class="showcase-item column">

View File

@@ -0,0 +1,21 @@
import { getAllData } from '../../../lib/icons';
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
import iconsMetaData from '../../../data/iconMetaData';
import { fetchAllReleases } from '../../../../scripts/writeReleaseMetadata.mts';
import { satisfies } from 'semver';
export default {
async load() {
const versions = await fetchAllReleases();
const mappedVersions = versions
.map((tag) => tag.replace('v', ''))
.reverse()
mappedVersions.length = 100
return {
versions: mappedVersions,
};
},
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue';
import { ref, computed, defineAsyncComponent, onMounted, watch, watchEffect } from 'vue';
import type { IconEntity } from '../../types';
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
import { useRoute } from 'vitepress';
@@ -11,9 +11,13 @@ import useSearchShortcut from '../../utils/useSearchShortcut';
import StickyBar from './StickyBar.vue';
import useFetchTags from '../../composables/useFetchTags';
import useFetchCategories from '../../composables/useFetchCategories';
import useFetchVersionIcons from '../../composables/useFetchVersionIcons';
import chunkArray from '../../utils/chunkArray';
import CarbonAdOverlay from './CarbonAdOverlay.vue';
import VersionSelect from './VersionSelect.vue';
import { sort, satisfies } from 'semver';
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
import { data as versionData } from './IconsOverview.data';
const ICON_SIZE = 56;
const ICON_GRID_GAP = 8;
@@ -32,9 +36,15 @@ const props = defineProps<{
}>();
const activeIconName = ref(null);
const selectedVersion = ref();
const { execute: fetchTags, data: tags } = useFetchTags();
const { execute: fetchCategories, data: categories } = useFetchCategories();
const { execute: fetchVersionIcons, data: versionIcons } = useFetchVersionIcons(selectedVersion);
watch(selectedVersion, () => {
fetchVersionIcons();
});
const overviewEl = ref<HTMLElement | null>(null);
const { width: containerWidth } = useElementSize(overviewEl);
@@ -44,20 +54,31 @@ const columnSize = computed(() => {
});
const mappedIcons = computed(() => {
if (tags.value == null) {
return props.icons;
let icons = props.icons;
if (tags.value != null && categories.value != null) {
icons = props.icons.map((icon) => {
const iconTags = tags.value[icon.name];
const iconCategories = categories.value?.[icon.name] ?? [];
return {
...icon,
tags: iconTags,
categories: iconCategories,
};
});
}
return props.icons.map((icon) => {
const iconTags = tags.value[icon.name];
const iconCategories = categories.value?.[icon.name] ?? [];
if (selectedVersion.value == null || versionIcons.value == null) {
console.log('no release info');
return {
...icon,
tags: iconTags,
categories: iconCategories,
};
});
return icons;
}
return Object.values(versionIcons.value).filter(([name, iconNode]) => ({
name,
iconNode,
}));
});
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
@@ -107,6 +128,12 @@ function onFocusSearchInput() {
}
}
// function onFocusVersionSelect() {
// if (releaseInfo.value == null) {
// fetchReleaseInfo();
// }
// }
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
@@ -129,13 +156,17 @@ function handleCloseDrawer() {
>
<StickyBar>
<InputSearch
:placeholder="`Search ${icons.length} icons ...`"
:placeholder="`Search ${mappedIcons.length} icons ...`"
v-model="searchQuery"
ref="searchInput"
:shortcut="kbdSearchShortcut"
class="input-wrapper"
@focus="onFocusSearchInput"
/>
<VersionSelect
v-model="selectedVersion"
:versions="versionData.versions"
/>
</StickyBar>
<NoResults
v-if="searchPlaceholder.isNoResults"

View File

@@ -1,72 +1,75 @@
<script setup lang="ts">
import { shallowRef, type Ref, watch, computed } from 'vue';
import { useCssVar, syncRef } from '@vueuse/core';
import { STYLE_DEFAULTS, useIconStyleContext } from '../../composables/useIconStyle';
import RangeSlider from '../base/RangeSlider.vue';
import InputField from '../base/InputField.vue';
import ColorPicker from '../base/ColorPicker.vue';
import ResetButton from '../base/ResetButton.vue';
import Switch from '../base/Switch.vue';
import { shallowRef, type Ref, watch, computed } from 'vue'
import { useCssVar, syncRef } from '@vueuse/core'
import { STYLE_DEFAULTS, useIconStyleContext } from '../../composables/useIconStyle'
import RangeSlider from '../base/RangeSlider.vue'
import InputField from '../base/InputField.vue'
import ColorPicker from '../base/ColorPicker.vue'
import ResetButton from '../base/ResetButton.vue'
import Switch from '../base/Switch.vue'
const props = defineProps<{
rootEl?: Ref<HTMLElement>;
}>();
rootEl?: Ref<HTMLElement>
}>()
const { color, strokeWidth, size, absoluteStrokeWidth } = useIconStyleContext();
const documentRef = shallowRef<HTMLElement | undefined>(
typeof document !== 'undefined' ? document?.documentElement : undefined,
);
const { color, strokeWidth, size, absoluteStrokeWidth } = useIconStyleContext()
const documentRef = shallowRef<HTMLElement | undefined>(typeof document !== 'undefined' ? document?.documentElement : undefined)
const colorCssVar = useCssVar('--customize-color', props.rootEl?.value ?? documentRef.value, {
initialValue: `${STYLE_DEFAULTS.color}`,
});
const colorCssVar = useCssVar(
'--customize-color',
props.rootEl?.value ?? documentRef.value,
{
initialValue: `${STYLE_DEFAULTS.color}`
}
)
const strokeWidthCssVar = useCssVar(
'--customize-strokeWidth',
props.rootEl?.value ?? documentRef.value,
{
initialValue: `${STYLE_DEFAULTS.strokeWidth}`,
},
);
initialValue: `${STYLE_DEFAULTS.strokeWidth}`
}
)
const sizeCssVar = useCssVar('--customize-size', props.rootEl?.value ?? documentRef.value, {
initialValue: `${STYLE_DEFAULTS.size}`,
});
const sizeCssVar = useCssVar(
'--customize-size',
props.rootEl?.value ?? documentRef.value,
{
initialValue: `${STYLE_DEFAULTS.size}`
}
)
syncRef(color, colorCssVar, { direction: 'ltr' });
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' });
syncRef(size, sizeCssVar, { direction: 'ltr' });
syncRef(color, colorCssVar, { direction: 'ltr' })
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' })
syncRef(size, sizeCssVar, { direction: 'ltr' })
function resetStyle() {
color.value = STYLE_DEFAULTS.color;
strokeWidth.value = STYLE_DEFAULTS.strokeWidth;
size.value = STYLE_DEFAULTS.size;
absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth;
function resetStyle () {
color.value = STYLE_DEFAULTS.color
strokeWidth.value = STYLE_DEFAULTS.strokeWidth
size.value = STYLE_DEFAULTS.size
absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth
}
watch(absoluteStrokeWidth, (enabled) => {
const htmlEl = document.documentElement;
const htmlEl = document.documentElement
htmlEl.classList.toggle('absolute-stroke-width', enabled);
});
htmlEl.classList.toggle('absolute-stroke-width', enabled)
})
const customizingActive = computed(() => {
return (
color.value !== STYLE_DEFAULTS.color ||
strokeWidth.value !== STYLE_DEFAULTS.strokeWidth ||
size.value !== STYLE_DEFAULTS.size ||
absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
);
});
return color.value !== STYLE_DEFAULTS.color
|| strokeWidth.value !== STYLE_DEFAULTS.strokeWidth
|| size.value !== STYLE_DEFAULTS.size
|| absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
})
</script>
<template>
<div
class="customizer-card"
:class="{ customized: customizingActive }"
>
<div class="customizer-card" :class="{ customized: customizingActive }">
<div class="card-header">
<h2 class="card-title">Customizer</h2>
<h2 class="card-title">
Customizer
</h2>
<ResetButton @click="resetStyle"></ResetButton>
</div>
<InputField
@@ -74,11 +77,7 @@ const customizingActive = computed(() => {
label="Color"
>
<template #display>
<ColorPicker
v-model="color"
id="icon-color"
class="color-picker"
/>
<ColorPicker v-model="color" id="icon-color" class="color-picker"/>
</template>
</InputField>
@@ -118,7 +117,7 @@ const customizingActive = computed(() => {
<InputField
id="absolute-stroke-width"
label="Absolute stroke width"
label="Absolute Stroke width"
>
<Switch
id="absolute-stroke-width"
@@ -144,7 +143,6 @@ const customizingActive = computed(() => {
font-size: 16px;
/* margin-bottom: 12px; */
}
.customizer-card {
background: var(--vp-c-bg);
padding: 12px 24px 24px;
@@ -153,7 +151,7 @@ const customizingActive = computed(() => {
position: relative;
z-index: 0;
border: 1px solid transparent;
transition: border-color 0.4s ease-in-out;
transition: border-color .4s ease-in-out;
}
.customizer-card.customized {

View File

@@ -14,6 +14,7 @@
margin-top: -32px;
width: 100%;
display: flex;
gap: 16px;
margin-bottom: 32px;
background: var(--vp-c-bg);
box-shadow: 0 16px 24px var(--vp-c-bg);

View File

@@ -0,0 +1,221 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import {
Combobox,
ComboboxInput,
ComboboxButton,
ComboboxOptions,
ComboboxOption,
} from '@headlessui/vue';
import { chevronsUpDown, check } from '../../../data/iconNodes';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
const model = defineModel<string | undefined>();
const props = defineProps<{
versions: string[];
}>();
let query = ref('');
let filteredVersions = computed(() =>
query.value === ''
? [...props.versions]
: props.versions.filter((version) =>
version
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.value.toLowerCase().replace(/\s+/g, '')),
),
);
const emit = defineEmits(['focus']);
const ChevronUpDown = createLucideIcon('ChevronUpDown', chevronsUpDown);
const Check = createLucideIcon('Check', check);
</script>
<template>
<Combobox v-model="model">
<div class="combobox-container">
<div class="combobox-input-container">
<ComboboxInput
class="combobox-input"
placeholder="Latest version"
@change="query = $event.target.value"
@focus="emit('focus')"
/>
<ComboboxButton class="combobox-button">
<ChevronUpDown
class="icon-chevron"
aria-hidden="true"
/>
</ComboboxButton>
</div>
<ComboboxOptions class="combobox-options">
<div
v-if="versions.length === 0 && query !== ''"
class="combobox-no-results"
>
No match!
</div>
<ComboboxOption
v-for="version in filteredVersions"
as="template"
:key="version"
:value="version"
v-slot="{ selected, active }"
>
<li
class="combobox-option"
:class="{ active: active }"
>
<span
class="combobox-option-text"
:class="{ selected: selected }"
>
{{ version }}
</span>
<span
v-if="selected"
class="combobox-option-icon"
:class="{ active: active }"
>
<Check
class="icon-check"
aria-hidden="true"
/>
</span>
</li>
</ComboboxOption>
</ComboboxOptions>
</div>
</Combobox>
</template>
<style scoped>
.combobox-input-container {
position: relative;
width: 100%;
height: 48px;
min-width: 160px;
cursor: default;
overflow: hidden;
background-color: var(--vp-c-bg-alt);
text-align: left;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
outline: none;
}
.combobox-input {
width: 100%;
border: none;
padding: 12px;
border-radius: 8px;
font-size: 0.875rem;
line-height: 1.25rem;
color: var(--vp-c-text-1);
border: 1px solid transparent;
transition: border-color 0.15s ease-in-out;
}
.combobox-input:hover,
.combobox-input:focus {
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt);
}
.combobox-button {
position: absolute;
top: 4px;
padding: 9px 8px;
right: 0;
display: flex;
align-items: center;
padding-right: 0.5rem;
z-index: 10;
}
.icon-chevron {
height: 20px;
width: 20px;
color: #9ca3af;
}
.combobox-options {
position: absolute;
margin-top: 0.25rem;
width: 100%;
max-width: 160px;
padding: 12px;
min-width: 128px;
overflow-y: auto;
border-radius: 12px;
border: 1px solid var(--vp-c-divider);
background-color: var(--vp-c-bg-elv);
box-shadow: var(--vp-shadow-3);
transition: background-color 0.5s;
max-height: 240px;
outline: none;
right: 0;
}
.combobox-no-results {
position: relative;
cursor: default;
padding: 0.5rem 1rem;
color: #4b5563;
}
.combobox-option {
position: relative;
cursor: default;
padding: 2px 8px;
border-radius: 6px;
color: var(--vp-c-text-1);
line-height: 32px;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
transition:
background-color 0.25s,
color 0.25s;
}
.combobox-option.active {
color: var(--vp-c-brand);
background-color: var(--vp-c-default-soft);
}
.combobox-option-text {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.combobox-option-text.selected {
font-weight: 500;
}
.combobox-option-icon {
position: absolute;
right: 8px;
top: 8px;
display: flex;
align-items: center;
padding-left: 0.75rem;
color: var(--vp-c-brand) !important;
}
.combobox-option-icon.active {
color: var(--vp-c-brand) !important;
}
.icon-check {
height: 20px;
width: 20px;
}
</style>

View File

@@ -0,0 +1,17 @@
import { useFetch } from '@vueuse/core';
import { computed, Ref } from 'vue';
const useFetchVersionIcons = (version: Ref<string | undefined>) =>{
const fetchUrl = computed(() => {
if(version.value == null ) return ''
return `https://unpkg.com/lucide-static@${version.value}/icon-nodes.json`
})
return useFetch<Record<string, string>>(
fetchUrl,
{
refetch: true
}
).json();
}
export default useFetchVersionIcons;

View File

@@ -1,4 +1,4 @@
import { type IconNode } from 'lucide-vue-next/src/types';
import { IconNode } from 'lucide-vue-next/src/createLucideIcon';
import Vue from 'vue';
declare module '*.vue' {
@@ -20,6 +20,5 @@ declare module 'node:module' {
}
declare module '*.node.json' {
const value: IconNode;
export default value;
export default IconNode;
}

View File

@@ -64,24 +64,25 @@ Implementation of the lucide icon library for Vue applications.
::: code-group
```sh [pnpm]
pnpm add @lucide/vue
pnpm add lucide-vue-next
```
```sh [yarn]
yarn add @lucide/vue
yarn add lucide-vue-next
```
```sh [npm]
npm install @lucide/vue
npm install lucide-vue-next
```
```sh [bun]
bun add @lucide/vue
bun add lucide-vue-next
```
:::
For more details, see the [documentation](packages/lucide-vue.md).
For more details, see the [documentation](packages/lucide-vue-next.md).
For Vue 2 use the `lucide-vue` package.
## Svelte
@@ -90,22 +91,22 @@ Implementation of the lucide icon library for Svelte applications.
::: code-group
```sh [pnpm]
pnpm add @lucide/svelte
pnpm add lucide-svelte
```
```sh [yarn]
yarn add @lucide/svelte
yarn add lucide-svelte
```
```sh [npm]
npm install @lucide/svelte
npm install lucide-svelte
```
```sh [bun]
bun add @lucide/svelte
bun add lucide-svelte
```
:::
> `@lucide/svelte` is only for Svelte 5, for Svelte 4 use the `lucide-svelte` package.
For more details, see the [documentation](packages/lucide-svelte.md).

View File

@@ -30,7 +30,14 @@ This package includes the following implementations of Lucide icons:
SVG sprites and icon fonts include **all icons**, which can significantly increase your app's bundle size and load time.
For production environments, we recommend using a bundler with tree-shaking support to include only the icons you actually use. Consider using one of the framework-specific [packages](../../packages).
For production environments, we recommend using a bundler with tree-shaking support to include only the icons you actually use. Consider using:
- [lucide](lucide)
- [lucide-react](lucide-react)
- [lucide-vue](lucide-vue)
- [lucide-vue-next](lucide-vue-next)
- [lucide-angular](lucide-angular)
- [lucide-preact](lucide-preact)
:::
## Installation

View File

@@ -0,0 +1,148 @@
# Lucide Vue Next
Vue 3 components for Lucide icons that leverage the Composition API and modern Vue features. Each icon is a reactive Vue component that renders as an inline SVG, providing excellent performance and developer experience in Vue 3 applications.
**What you can accomplish:**
- Use icons as Vue 3 components with full reactivity and TypeScript support
- Bind icon properties to reactive data and computed values
- Customize icons with props, slots, and Vue's powerful templating system
- Integrate seamlessly with Vue 3's Composition API and script setup syntax
- Build dynamic interfaces where icons respond to application state changes
## Installation
::: code-group
```sh [pnpm]
pnpm add lucide-vue-next
```
```sh [yarn]
yarn add lucide-vue-next
```
```sh [npm]
npm install lucide-vue-next
```
```sh [bun]
bun add lucide-vue-next
```
:::
## How to use
Lucide is built with ES Modules, so it's completely tree-shakable.
Each icon can be imported as a Vue component, which renders an inline SVG Element. This way only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away.
### Example
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>
```
## Props
| name | type | default |
| ----------------------- | --------- | ------------ |
| `size` | *number* | 24 |
| `color` | *string* | currentColor |
| `stroke-width` | *number* | 2 |
| `absoluteStrokeWidth` | *boolean* | false |
| `default-class` | *string* | lucide-icon |
### Applying props
To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation).
```vue
<template>
<Camera fill="red" />
</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 { baseball } from '@lucide/lab';
</script>
<template>
<Icon :iconNode="baseball" />
</template>
```
## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended.
::: danger
The example below imports all ES Modules, so exercise caution when using it. Importing all icons will significantly increase the build size of the application, negatively affecting its performance. This is especially important when using bundlers like `Webpack`, `Rollup`, or `Vite`.
:::
### Icon Component Example
```vue
<script setup>
import { computed } from 'vue';
import * as icons from "lucide-vue-next";
const props = defineProps({
name: {
type: String,
required: true
},
size: Number,
color: String,
strokeWidth: Number,
defaultClass: String
})
const icon = computed(() => icons[props.name]);
</script>
<template>
<component
:is="icon"
:size="size"
:color="color"
:stroke-width="strokeWidth" :default-class="defaultClass"
/>
</template>
```
### Using the Icon Component
All other props listed above also work on the `Icon` Component.
```vue
<template>
<div id="app">
<Icon name="Airplay" />
</div>
</template>
```

View File

@@ -9,24 +9,28 @@ Vue 2 components for Lucide icons that integrate with Vue's Options API and temp
- Build applications using Vue 2's familiar syntax and patterns
- Bridge the gap while planning migration to Vue 3
::: danger
This package is deprecated. Vue 2 is EOF See [Announcement](https://v2.vuejs.org/eol/). Migrate to Vue 3.
:::
## Installation
::: code-group
```sh [pnpm]
pnpm add @lucide/vue
pnpm add lucide-vue
```
```sh [yarn]
yarn add @lucide/vue
yarn add lucide-vue
```
```sh [npm]
npm install @lucide/vue
npm install lucide-vue
```
```sh [bun]
bun add @lucide/vue
bun add lucide-vue
```
:::
@@ -39,19 +43,21 @@ Each icon can be imported as a Vue component, which renders an inline SVG Elemen
### Example
You can pass additional props to adjust the icon.
Additional props can be passed to adjust the icon:
```vue
<script setup>
import { Camera } from '@lucide/vue';
</script>
<template>
<Camera
color="red"
:size="32"
/>
<Camera color="red" :size="32" />
</template>
<script>
import { Camera } from 'lucide-vue';
export default {
name: 'My Component',
components: { Camera }
};
</script>
```
## Props
@@ -74,28 +80,6 @@ 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';
import { baseball } from '@lucide/lab';
</script>
<template>
<Icon :iconNode="baseball" />
</template>
```
## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended.
@@ -107,37 +91,30 @@ The example below imports all ES Modules, so exercise caution when using it. Imp
### Icon Component Example
```vue
<script setup>
import { computed } from 'vue';
import * as icons from "@lucide/vue";
const props = defineProps({
name: {
type: String,
required: true
},
size: Number,
color: String,
strokeWidth: Number,
defaultClass: String
})
const icon = computed(() => icons[props.name]);
</script>
<template>
<component
:is="icon"
:size="size"
:color="color"
:stroke-width="strokeWidth" :default-class="defaultClass"
/>
<component :is="icon" />
</template>
<script>
import * as icons from 'lucide-vue';
export default {
props: {
name: {
type: String,
required: true
}
},
computed: {
icon() {
return icons[this.name];
}
}
};
</script>
```
### Using the Icon Component
All other props listed above also work on the `Icon` Component.
#### Using the Icon Component
```vue
<template>

View File

@@ -9,14 +9,14 @@
"docs:build": "pnpm run /^prebuild:.*/ && vitepress build",
"docs:preview": "vitepress preview",
"build:docs": "vitepress build",
"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",
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mjs",
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mjs",
"prebuild:iconNodes": "node ./scripts/writeIconNodes.mts",
"prebuild:metaJson": "node ./scripts/writeIconMetaIndex.mts",
"prebuild:releaseJson": "node ./scripts/writeReleaseMetadata.mts",
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mts",
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mts",
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mts",
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mts",
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mts",
"dev": "npx nitropack dev",
"prebuild:api": "npx nitropack prepare",
"build:api": "npx nitropack build",

View File

@@ -42,7 +42,7 @@ const getRelatedIcons = (currentIcon, icons) => {
};
const iconsMetaDataPromises = svgFiles.map(async (iconName) => {
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`);
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`, 'utf-8');
const metaData = JSON.parse(metaDataFileContent);
const name = iconName.replace('.json', '');

View File

@@ -29,7 +29,7 @@ if (!fs.existsSync(releaseMetaDataDirectory)) {
fs.mkdirSync(releaseMetaDataDirectory);
}
const fetchAllReleases = async () => {
export const fetchAllReleases = async () => {
await git.fetch('https://github.com/lucide-icons/lucide.git', '--tags');
return Promise.all(
@@ -73,7 +73,7 @@ const comparisonsPromises = tags.map(async (tag, index) => {
const comparisons = await Promise.all(comparisonsPromises);
const newReleaseMetaData = {};
comparisons.forEach(({ tag, iconFiles, date } = {}) => {
comparisons.forEach(({ tag, iconFiles, date } = {} as typeof comparisons[number]) => {
if (tag == null) return;
iconFiles.forEach(({ status, file, renamedFile }) => {

View File

@@ -11,7 +11,7 @@ const iconMetaData = await getIconMetaData(path.resolve(scriptDir, '../../icons'
const iconAliasesRedirectRoutes = Object.entries(iconMetaData)
.filter(([, { aliases }]) => aliases?.length)
.map(([iconName, { aliases }]) => {
const aliasRouteMatches = aliases.map((alias) => alias.name).join('|');
const aliasRouteMatches = aliases.map((alias) => typeof alias === 'string' ? alias : alias.name).join('|');
return {
src: `/icons/${aliasRouteMatches}`,

View File

@@ -34,11 +34,6 @@
],
"destination": "/icons",
"permanent": false
},
{
"source": "/guide/packages/lucide-vue-next",
"destination": "/guide/packages/lucide-vue",
"permanent": false
}
],
"headers": [

View File

@@ -56,6 +56,7 @@
"account",
"animals",
"arrows",
"brands",
"buildings",
"charts",
"communication",
@@ -133,7 +134,7 @@
"$defs": {
"iconDeprecationReasons": {
"type": "string",
"enum": ["icon.renamed"]
"enum": ["icon.brand"]
},
"aliasDeprecationReasons": {
"type": "string",

View File

@@ -16,6 +16,8 @@
],
"categories": [
"multimedia",
"connectivity"
"connectivity",
"devices",
"brands"
]
}

View File

@@ -10,6 +10,7 @@
"payment"
],
"categories": [
"brands",
"development",
"finance"
]

View File

@@ -1,14 +0,0 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"nickveles"
],
"tags": [
"cannabis",
"weed",
"leaf"
],
"categories": [
"nature"
]
}

View File

@@ -1,18 +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 22v-4c1.5 1.5 3.5 3 6 3 0-1.5-.5-3.5-2-5" />
<path d="M13.988 8.327C13.902 6.054 13.365 3.82 12 2a9.3 9.3 0 0 0-1.445 2.9" />
<path d="M17.375 11.725C18.882 10.53 21 7.841 21 6c-2.324 0-5.08 1.296-6.662 2.684" />
<path d="m2 2 20 20" />
<path d="M21.024 15.378A15 15 0 0 0 22 15c-.426-1.279-2.67-2.557-4.25-2.907" />
<path d="M6.995 6.992C5.714 6.4 4.29 6 3 6c0 2 2.5 5 4 6-1.5 0-4.5 1.5-5 3 3.5 1.5 6 1 6 1-1.5 1.5-2 3.5-2 5 2.5 0 4.5-1.5 6-3" />
</svg>

Before

Width:  |  Height:  |  Size: 681 B

25
icons/chromium.json Normal file
View File

@@ -0,0 +1,25 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"ericfennis"
],
"tags": [
"browser",
"logo"
],
"categories": [
"brands"
],
"aliases": [
{
"name": "chrome",
"deprecated": true,
"deprecationReason": "alias.name",
"toBeRemovedInVersion": "v1.0"
}
]
}

17
icons/chromium.svg Normal file
View File

@@ -0,0 +1,17 @@
<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="M10.88 21.94 15.46 14" />
<path d="M21.17 8H12" />
<path d="M3.95 6.06 8.54 14" />
<circle cx="12" cy="12" r="10" />
<circle cx="12" cy="12" r="4" />
</svg>

After

Width:  |  Height:  |  Size: 377 B

17
icons/codepen.json Normal file
View File

@@ -0,0 +1,17 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"ericfennis"
],
"tags": [
"logo"
],
"categories": [
"brands",
"development"
]
}

17
icons/codepen.svg Normal file
View File

@@ -0,0 +1,17 @@
<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"
>
<polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2" />
<line x1="12" x2="12" y1="22" y2="15.5" />
<polyline points="22 8.5 12 15.5 2 8.5" />
<polyline points="2 15.5 12 8.5 22 15.5" />
<line x1="12" x2="12" y1="2" y2="8.5" />
</svg>

After

Width:  |  Height:  |  Size: 454 B

18
icons/codesandbox.json Normal file
View File

@@ -0,0 +1,18 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"csandman",
"ericfennis"
],
"tags": [
"logo"
],
"categories": [
"brands",
"development"
]
}

18
icons/codesandbox.svg Normal file
View File

@@ -0,0 +1,18 @@
<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="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<polyline points="7.5 4.21 12 6.81 16.5 4.21" />
<polyline points="7.5 19.79 7.5 14.6 3 12" />
<polyline points="21 12 16.5 14.6 16.5 19.79" />
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
<line x1="12" x2="12" y1="22.08" y2="12" />
</svg>

After

Width:  |  Height:  |  Size: 595 B

18
icons/dribbble.json Normal file
View File

@@ -0,0 +1,18 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"ahtohbi4"
],
"tags": [
"design",
"social"
],
"categories": [
"brands",
"social",
"design"
]
}

16
icons/dribbble.svg Normal file
View File

@@ -0,0 +1,16 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<path d="M19.13 5.09C15.22 9.14 10 10.44 2.25 10.94" />
<path d="M21.75 12.84c-6.62-1.41-12.14 1-16.38 6.32" />
<path d="M8.56 2.75c4.37 6 6 9.42 8 17.72" />
</svg>

After

Width:  |  Height:  |  Size: 408 B

19
icons/facebook.json Normal file
View File

@@ -0,0 +1,19 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"csandman",
"ericfennis"
],
"tags": [
"logo",
"social"
],
"categories": [
"social",
"brands"
]
}

13
icons/facebook.svg Normal file
View File

@@ -0,0 +1,13 @@
<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="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" />
</svg>

After

Width:  |  Height:  |  Size: 289 B

21
icons/figma.json Normal file
View File

@@ -0,0 +1,21 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"csandman",
"mittalyashu",
"ericfennis"
],
"tags": [
"logo",
"design",
"tool"
],
"categories": [
"brands",
"design"
]
}

17
icons/figma.svg Normal file
View File

@@ -0,0 +1,17 @@
<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="M5 5.5A3.5 3.5 0 0 1 8.5 2H12v7H8.5A3.5 3.5 0 0 1 5 5.5z" />
<path d="M12 2h3.5a3.5 3.5 0 1 1 0 7H12V2z" />
<path d="M12 12.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 1 1-7 0z" />
<path d="M5 19.5A3.5 3.5 0 0 1 8.5 16H12v3.5a3.5 3.5 0 1 1-7 0z" />
<path d="M5 12.5A3.5 3.5 0 0 1 8.5 9H12v7H8.5A3.5 3.5 0 0 1 5 12.5z" />
</svg>

After

Width:  |  Height:  |  Size: 534 B

21
icons/framer.json Normal file
View File

@@ -0,0 +1,21 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"csandman",
"mittalyashu",
"ericfennis"
],
"tags": [
"logo",
"design",
"tool"
],
"categories": [
"brands",
"design"
]
}

13
icons/framer.svg Normal file
View File

@@ -0,0 +1,13 @@
<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="M5 16V9h14V2H5l14 14h-7m-7 0 7 7v-7m-7 0h7" />
</svg>

After

Width:  |  Height:  |  Size: 266 B

20
icons/github.json Normal file
View File

@@ -0,0 +1,20 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"csandman",
"ericfennis",
"karsa-mistmere"
],
"tags": [
"logo",
"version control"
],
"categories": [
"brands",
"development"
]
}

14
icons/github.svg Normal file
View File

@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
<path d="M9 18c-4.51 2-5-2-7-2" />
</svg>

After

Width:  |  Height:  |  Size: 509 B

20
icons/gitlab.json Normal file
View File

@@ -0,0 +1,20 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"csandman",
"ericfennis",
"karsa-mistmere"
],
"tags": [
"logo",
"version control"
],
"categories": [
"brands",
"development"
]
}

13
icons/gitlab.svg Normal file
View File

@@ -0,0 +1,13 @@
<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="m22 13.29-3.33-10a.42.42 0 0 0-.14-.18.38.38 0 0 0-.22-.11.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18l-2.26 6.67H8.32L6.1 3.26a.42.42 0 0 0-.1-.18.38.38 0 0 0-.26-.08.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18L2 13.29a.74.74 0 0 0 .27.83L12 21l9.69-6.88a.71.71 0 0 0 .31-.83Z" />
</svg>

After

Width:  |  Height:  |  Size: 489 B

View File

@@ -12,6 +12,7 @@
],
"categories": [
"shapes",
"brands",
"development"
]
}

21
icons/instagram.json Normal file
View File

@@ -0,0 +1,21 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"csandman",
"ericfennis"
],
"tags": [
"logo",
"camera",
"social"
],
"categories": [
"brands",
"social",
"photography"
]
}

15
icons/instagram.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="20" height="20" x="2" y="2" rx="5" ry="5" />
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" />
<line x1="17.5" x2="17.51" y1="6.5" y2="6.5" />
</svg>

After

Width:  |  Height:  |  Size: 381 B

20
icons/linkedin.json Normal file
View File

@@ -0,0 +1,20 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"okcoker",
"csandman",
"ericfennis"
],
"tags": [
"logo",
"social media",
"social"
],
"categories": [
"social",
"brands"
]
}

15
icons/linkedin.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" />
<rect width="4" height="12" x="2" y="9" />
<circle cx="4" cy="4" r="2" />
</svg>

After

Width:  |  Height:  |  Size: 380 B

18
icons/pocket.json Normal file
View File

@@ -0,0 +1,18 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"csandman",
"ericfennis"
],
"tags": [
"logo",
"save"
],
"categories": [
"brands"
]
}

14
icons/pocket.svg Normal file
View File

@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M20 3a2 2 0 0 1 2 2v6a1 1 0 0 1-20 0V5a2 2 0 0 1 2-2z" />
<path d="m8 10 4 4 4-4" />
</svg>

After

Width:  |  Height:  |  Size: 306 B

19
icons/rail-symbol.json Normal file
View File

@@ -0,0 +1,19 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"danielbayley"
],
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"tags": [
"railway",
"train",
"track",
"line"
],
"categories": [
"transportation",
"navigation"
]
}

15
icons/rail-symbol.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 15h14" />
<path d="M5 9h14" />
<path d="m14 20-5-5 6-6-5-5" />
</svg>

After

Width:  |  Height:  |  Size: 289 B

22
icons/slack.json Normal file
View File

@@ -0,0 +1,22 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"ashygee",
"wojtekmaj",
"mittalyashu",
"ericfennis"
],
"tags": [
"logo"
],
"categories": [
"account",
"social",
"brands",
"development"
]
}

20
icons/slack.svg Normal file
View File

@@ -0,0 +1,20 @@
<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"
>
<rect width="3" height="8" x="13" y="2" rx="1.5" />
<path d="M19 8.5V10h1.5A1.5 1.5 0 1 0 19 8.5" />
<rect width="3" height="8" x="8" y="14" rx="1.5" />
<path d="M5 15.5V14H3.5A1.5 1.5 0 1 0 5 15.5" />
<rect width="8" height="3" x="14" y="13" rx="1.5" />
<path d="M15.5 19H14v1.5a1.5 1.5 0 1 0 1.5-1.5" />
<rect width="8" height="3" x="2" y="8" rx="1.5" />
<path d="M8.5 5H10V3.5A1.5 1.5 0 1 0 8.5 5" />
</svg>

After

Width:  |  Height:  |  Size: 628 B

View File

@@ -1,24 +0,0 @@
{
"$schema": "../icon.schema.json",
"contributors": [
"Alportan",
"karsa-mistmere"
],
"tags": [
"mineral",
"geology",
"nature",
"solid",
"pebble",
"crystal",
"ore",
"hard",
"coal",
"stone",
"rock",
"boulder"
],
"categories": [
"nature"
]
}

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="M11.264 2.205A4 4 0 0 0 6.42 4.211l-4 8a4 4 0 0 0 1.359 5.117l6 4a4 4 0 0 0 4.438 0l6-4a4 4 0 0 0 1.576-4.592l-2-6a4 4 0 0 0-2.53-2.53z" />
<path d="M11.99 22 14 12l7.822 3.184" />
<path d="M14 12 8.47 2.302" />
</svg>

Before

Width:  |  Height:  |  Size: 435 B

View File

@@ -13,6 +13,7 @@
"productivity"
],
"categories": [
"brands",
"gaming"
]
}

21
icons/trello.json Normal file
View File

@@ -0,0 +1,21 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"bdbch",
"csandman",
"mittalyashu",
"ericfennis"
],
"tags": [
"logo",
"brand"
],
"categories": [
"account",
"brands",
"development"
]
}

15
icons/trello.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
<rect width="3" height="9" x="7" y="7" />
<rect width="3" height="5" x="14" y="7" />
</svg>

After

Width:  |  Height:  |  Size: 357 B

20
icons/twitch.json Normal file
View File

@@ -0,0 +1,20 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"ahtohbi4",
"johnletey"
],
"tags": [
"logo",
"social"
],
"categories": [
"brands",
"social",
"account",
"gaming"
]
}

13
icons/twitch.svg Normal file
View File

@@ -0,0 +1,13 @@
<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="M21 2H3v16h5v4l4-4h5l4-4V2zm-10 9V7m5 4V7" />
</svg>

After

Width:  |  Height:  |  Size: 265 B

21
icons/twitter.json Normal file
View File

@@ -0,0 +1,21 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"csandman",
"ericfennis",
"karsa-mistmere"
],
"tags": [
"logo",
"social"
],
"categories": [
"brands",
"social",
"account"
]
}

13
icons/twitter.svg Normal file
View File

@@ -0,0 +1,13 @@
<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="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z" />
</svg>

After

Width:  |  Height:  |  Size: 359 B

24
icons/youtube.json Normal file
View File

@@ -0,0 +1,24 @@
{
"$schema": "../icon.schema.json",
"deprecated": true,
"deprecationReason": "icon.brand",
"toBeRemovedInVersion": "v1.0",
"contributors": [
"colebemis",
"csandman",
"ericfennis",
"karsa-mistmere",
"jguddas"
],
"tags": [
"logo",
"social",
"video",
"play"
],
"categories": [
"multimedia",
"social",
"brands"
]
}

14
icons/youtube.svg Normal file
View File

@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M2.5 17a24.12 24.12 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.56 49.56 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24.12 24.12 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.55 49.55 0 0 1-16.2 0A2 2 0 0 1 2.5 17" />
<path d="m10 15 5-3-5-3z" />
</svg>

After

Width:  |  Height:  |  Size: 428 B

View File

@@ -24,7 +24,9 @@
"author": "Eric Fennis",
"amdName": "lucide-preact",
"main": "dist/cjs/lucide-preact.js",
"main:umd": "dist/umd/lucide-preact.js",
"module": "dist/esm/lucide-preact.js",
"unpkg": "dist/umd/lucide-preact.min.js",
"typings": "dist/lucide-preact.d.ts",
"files": [
"dist"
@@ -37,7 +39,6 @@
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --withAliases --aliasesFileExtension=.ts --iconFileExtension=.ts --exportFileName=index.ts",
"build:bundles": "rollup -c ./rollup.config.mjs",
"test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false"
},
"devDependencies": {
@@ -45,10 +46,10 @@
"@lucide/rollup-plugins": "workspace:*",
"@lucide/shared": "workspace:*",
"@preact/preset-vite": "^2.10.2",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/preact": "^3.2.3",
"jest-serializer-html": "^7.1.0",
"preact": "^10.26.9",
"preact": "^10.19.2",
"rollup": "^4.53.3",
"rollup-plugin-dts": "^6.2.3",
"typescript": "^5.8.3",

View File

@@ -7,6 +7,17 @@ const outputFileName = 'lucide-preact';
const outputDir = 'dist';
const inputs = [`src/lucide-preact.ts`];
const bundles = [
{
format: 'umd',
inputs,
outputDir,
minify: true,
},
{
format: 'umd',
inputs,
outputDir,
},
{
format: 'cjs',
inputs,
@@ -21,7 +32,7 @@ const bundles = [
];
const configs = bundles
.map(({ inputs, outputDir, format, preserveModules }) =>
.map(({ inputs, outputDir, format, minify, preserveModules }) =>
inputs.map((input) => ({
input,
plugins: plugins({ pkg, minify }),
@@ -33,7 +44,7 @@ const configs = bundles
dir: `${outputDir}/${format}`,
}
: {
file: `${outputDir}/${format}/${outputFileName}.js`,
file: `${outputDir}/${format}/${outputFileName}${minify ? '.min' : ''}.js`,
}),
preserveModules,
format,

View File

@@ -1,8 +1,6 @@
import { h, toChildArray } from 'preact';
import defaultAttributes from './defaultAttributes';
import type { IconNode, LucideProps } from './types';
import { useLucideContext } from './context';
import { mergeClasses } from '@lucide/shared';
interface IconComponentProps extends LucideProps {
iconNode: IconNode;
@@ -24,41 +22,29 @@ interface IconComponentProps extends LucideProps {
* @returns {ForwardRefExoticComponent} LucideIcon
*/
const Icon = ({
color,
size,
strokeWidth,
color = 'currentColor',
size = 24,
strokeWidth = 2,
absoluteStrokeWidth,
children,
iconNode,
class: classes = '',
...rest
}: IconComponentProps) => {
const {
size: contextSize = 24,
strokeWidth: contextStrokeWidth = 2,
absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,
color: contextColor = 'currentColor',
class: contextClass = '',
} = useLucideContext() ?? {};
const calculatedStrokeWidth =
absoluteStrokeWidth ?? contextAbsoluteStrokeWidth
? (Number(strokeWidth ?? contextStrokeWidth) * 24) / Number(size ?? contextSize)
: strokeWidth ?? contextStrokeWidth;
return h(
}: IconComponentProps) =>
h(
'svg',
{
...defaultAttributes,
width: size ?? contextSize ?? 24,
height: size ?? contextSize ?? 24,
stroke: color ?? contextColor,
['stroke-width' as 'strokeWidth']: calculatedStrokeWidth,
class: mergeClasses('lucide', contextClass, classes),
width: String(size),
height: size,
stroke: color,
['stroke-width' as 'strokeWidth']: absoluteStrokeWidth
? (Number(strokeWidth) * 24) / Number(size)
: strokeWidth,
class: ['lucide', classes].join(' '),
...rest,
},
[...iconNode.map(([tag, attrs]) => h(tag, attrs)), ...toChildArray(children)],
);
};
export default Icon;

View File

@@ -1,49 +0,0 @@
import { createContext, type ComponentChildren } from 'preact';
import { useContext, useMemo } from 'preact/hooks';
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: ComponentChildren;
size?: number;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
class?: string;
}
export function LucideProvider({
children,
size,
color,
strokeWidth,
absoluteStrokeWidth,
class: className,
}: LucideProviderProps) {
const value = useMemo(
() => ({
size,
color,
strokeWidth,
absoluteStrokeWidth,
class: className,
}),
[size, color, strokeWidth, absoluteStrokeWidth, className],
);
return <LucideContext.Provider value={value}>{children}</LucideContext.Provider>;
}
export const useLucideContext = () => useContext(LucideContext);

View File

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

View File

@@ -2,7 +2,7 @@
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
<svg
class="lucide"
class="lucide "
fill="none"
height="48"
stroke="red"

View File

@@ -1,23 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Using LucideProvider > should render the icon with LucideProvider 1`] = `
<svg
class="lucide 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"
/>
<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"
/>
</svg>
`;

View File

@@ -1,88 +0,0 @@
import { render } from '@testing-library/preact';
import { describe, expect, it } from 'vitest';
import { House, LucideProvider } from '../src/lucide-preact';
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 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 class names from LucideProvider and icon props', () => {
const { container } = render(
<LucideProvider class="provider-class">
<House class="icon-class" />
</LucideProvider>,
);
const IconComponent = container.firstElementChild;
expect(IconComponent).toHaveAttribute('class', 'lucide provider-class lucide-house icon-class');
});
});

View File

@@ -24,7 +24,9 @@
"author": "Eric Fennis",
"amdName": "lucide-react-native",
"main": "dist/cjs/lucide-react-native.js",
"main:umd": "dist/umd/lucide-react-native.js",
"module": "dist/esm/lucide-react-native.js",
"unpkg": "dist/umd/lucide-react-native.min.js",
"typings": "dist/lucide-react-native.d.ts",
"react-native": "dist/esm/lucide-react-native.js",
"sideEffects": false,
@@ -38,14 +40,13 @@
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --iconFileExtension=.ts --exportFileName=index.ts --withAliases --aliasesFileExtension=.ts",
"build:bundles": "rollup -c ./rollup.config.mjs",
"test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false"
},
"devDependencies": {
"@lucide/rollup-plugins": "workspace:*",
"@lucide/build-icons": "workspace:*",
"@lucide/shared": "workspace:*",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/jest-dom": "^6.1.6",
"@testing-library/react": "^14.1.2",
"@types/prop-types": "^15.7.5",
"@types/react": "^18.0.21",

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