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
52 changed files with 10970 additions and 7021 deletions

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

@@ -1,5 +1,5 @@
import { createLucideIcon } from 'lucide-react/src/lucide-react'; 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 { IconEntity } from '../theme/types';
import { renderToStaticMarkup } from 'react-dom/server'; import { renderToStaticMarkup } from 'react-dom/server';
import { IconContent } from './generateZip'; import { IconContent } from './generateZip';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,16 +22,16 @@ export default {
logo: '/framework-logos/svelte.svg', logo: '/framework-logos/svelte.svg',
label: 'Lucide documentation for Svelte', label: 'Lucide documentation for Svelte',
}, },
{
name: 'lucide-solid',
logo: '/framework-logos/solid.svg',
label: 'Lucide documentation for Solid',
},
{ {
name: 'lucide-preact', name: 'lucide-preact',
logo: '/framework-logos/preact.svg', logo: '/framework-logos/preact.svg',
label: 'Lucide documentation for Preact', label: 'Lucide documentation for Preact',
}, },
{
name: 'lucide-solid',
logo: '/framework-logos/solid.svg',
label: 'Lucide documentation for Solid',
},
{ {
name: 'lucide-angular', name: 'lucide-angular',
logo: '/framework-logos/angular.svg', logo: '/framework-logos/angular.svg',
@@ -48,6 +48,11 @@ export default {
logo: '/framework-logos/react-native.svg', logo: '/framework-logos/react-native.svg',
label: 'Lucide documentation for React Native', 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 { useData } from 'vitepress';
import { useSessionStorage } from '@vueuse/core'; import { useSessionStorage } from '@vueuse/core';
import IconButton from '../base/IconButton.vue'; import IconButton from '../base/IconButton.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'; // import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
import { x } from '../../../data/iconNodes'; import { x } from '../../../data/iconNodes'
import Icon from 'lucide-vue-next/src/Icon'; import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
const { theme } = useData(); const { theme } = useData()
const showAd = useSessionStorage('show-carbon-ads', true); const showAd = useSessionStorage('show-carbon-ads', true)
const carbonLoaded = ref(true); const carbonLoaded = ref(true)
defineProps<{ defineProps<{
drawerOpen: boolean; drawerOpen: boolean
}>(); }>()
const CloseIcon = createLucideIcon('Close', x)
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(() => {
if (window?._carbonads == null) { if (window?._carbonads == null) {
carbonLoaded.value = false; carbonLoaded.value = false
} }
}, 5000); }, 5000)
}); })
</script> </script>
<template> <template>
<div <div
:class="{ :class="{
'drawer-open': drawerOpen, 'drawer-open': drawerOpen,
'hide-ad': !(showAd && carbonLoaded), 'hide-ad': !(showAd && carbonLoaded)
}" }"
class="floating-ad" class="floating-ad"
v-if="theme.carbonAds" v-if="theme.carbonAds"
> >
<IconButton <IconButton @click="showAd = false" class="hide-button">
@click="showAd = false" <component :is="CloseIcon" :size="20" absoluteStrokeWidth />
class="hide-button"
>
<Icon
:iconNode="x"
:size="20"
absoluteStrokeWidth
/>
</IconButton> </IconButton>
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" /> <VPDocAsideCarbonAds
:carbon-ads="theme.carbonAds"
/>
</div> </div>
</template> </template>
@@ -54,9 +51,7 @@ onMounted(() => {
bottom: 32px; bottom: 32px;
width: 224px; width: 224px;
right: 32px; right: 32px;
transition: transition: opacity 0.5s, transform 0.25s ease;
opacity 0.5s,
transform 0.25s ease;
} }
.floating-ad.drawer-open { .floating-ad.drawer-open {
@@ -72,11 +67,8 @@ onMounted(() => {
transform: translateY(-288px) translateX(224px); transform: translateY(-288px) translateX(224px);
} }
.floating-ad.drawer-open, .floating-ad.drawer-open, .floating-ad.hide-ad {
.floating-ad.hide-ad { transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
transition:
opacity 0.25s,
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
} }
@media (min-width: 1280px) { @media (min-width: 1280px) {

View File

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

View File

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

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

View File

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

View File

@@ -14,6 +14,7 @@
margin-top: -32px; margin-top: -32px;
width: 100%; width: 100%;
display: flex; display: flex;
gap: 16px;
margin-bottom: 32px; margin-bottom: 32px;
background: var(--vp-c-bg); background: var(--vp-c-bg);
box-shadow: 0 16px 24px 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'; import Vue from 'vue';
declare module '*.vue' { declare module '*.vue' {
@@ -20,6 +20,5 @@ declare module 'node:module' {
} }
declare module '*.node.json' { declare module '*.node.json' {
const value: IconNode; export default IconNode;
export default value;
} }

View File

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

View File

@@ -42,7 +42,7 @@ const getRelatedIcons = (currentIcon, icons) => {
}; };
const iconsMetaDataPromises = svgFiles.map(async (iconName) => { 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 metaData = JSON.parse(metaDataFileContent);
const name = iconName.replace('.json', ''); const name = iconName.replace('.json', '');

View File

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

View File

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

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

View File

@@ -9,8 +9,8 @@
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
> >
<path d="M11 7 6 2" /> <path d="M19 12H2" />
<path d="M18.992 12H2.041" />
<path d="M21.145 18.38A3.34 3.34 0 0 1 20 16.5a3.3 3.3 0 0 1-1.145 1.88c-.575.46-.855 1.02-.855 1.595A2 2 0 0 0 20 22a2 2 0 0 0 2-2.025c0-.58-.285-1.13-.855-1.595" /> <path d="M21.145 18.38A3.34 3.34 0 0 1 20 16.5a3.3 3.3 0 0 1-1.145 1.88c-.575.46-.855 1.02-.855 1.595A2 2 0 0 0 20 22a2 2 0 0 0 2-2.025c0-.58-.285-1.13-.855-1.595" />
<path d="m6 2 5 5" />
<path d="m8.5 4.5 2.148-2.148a1.205 1.205 0 0 1 1.704 0l7.296 7.296a1.205 1.205 0 0 1 0 1.704l-7.592 7.592a3.615 3.615 0 0 1-5.112 0l-3.888-3.888a3.615 3.615 0 0 1 0-5.112L5.67 7.33" /> <path d="m8.5 4.5 2.148-2.148a1.205 1.205 0 0 1 1.704 0l7.296 7.296a1.205 1.205 0 0 1 0 1.704l-7.592 7.592a3.615 3.615 0 0 1-5.112 0l-3.888-3.888a3.615 3.615 0 0 1 0-5.112L5.67 7.33" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 622 B

After

Width:  |  Height:  |  Size: 613 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

@@ -1,6 +1,6 @@
{ {
"name": "@lucide/angular", "name": "lucide-angular",
"description": "A Lucide icon library package for Angular applications", "description": "A Lucide icon library package for Angular applications.",
"version": "0.0.1", "version": "0.0.1",
"author": "SMAH1", "author": "SMAH1",
"license": "ISC", "license": "ISC",
@@ -38,19 +38,19 @@
"version": "pnpm version --git-tag-version=false" "version": "pnpm version --git-tag-version=false"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~17.3.14", "@angular-devkit/build-angular": "~13.3.11",
"@angular-eslint/builder": "~17.5.3", "@angular-eslint/builder": "~13.0.0",
"@angular-eslint/eslint-plugin": "~17.5.3", "@angular-eslint/eslint-plugin": "~13.0.0",
"@angular-eslint/eslint-plugin-template": "~17.5.3", "@angular-eslint/eslint-plugin-template": "~13.0.0",
"@angular-eslint/schematics": "~17.5.3", "@angular-eslint/schematics": "~13.0.0",
"@angular-eslint/template-parser": "~17.5.3", "@angular-eslint/template-parser": "~13.0.0",
"@angular/cli": "~17.3.14", "@angular/cli": "~13.3.11",
"@angular/common": "~17.3.12", "@angular/common": "~13.3.0",
"@angular/compiler": "~17.3.12", "@angular/compiler": "~13.3.0",
"@angular/compiler-cli": "~17.3.12", "@angular/compiler-cli": "~13.3.0",
"@angular/core": "~17.3.12", "@angular/core": "~13.3.0",
"@angular/platform-browser": "~17.3.12", "@angular/platform-browser": "~13.3.0",
"@angular/platform-browser-dynamic": "~17.3.12", "@angular/platform-browser-dynamic": "~13.3.0",
"@lucide/build-icons": "workspace:*", "@lucide/build-icons": "workspace:*",
"@types/jasmine": "~3.10.0", "@types/jasmine": "~3.10.0",
"@types/node": "^12.11.1", "@types/node": "^12.11.1",
@@ -65,12 +65,12 @@
"karma-coverage": "~2.1.0", "karma-coverage": "~2.1.0",
"karma-jasmine": "~4.0.0", "karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0", "karma-jasmine-html-reporter": "~1.7.0",
"ng-packagr": "^17.3.0", "ng-packagr": "^13.3.0",
"prettier": "^2.8.4", "prettier": "^2.8.4",
"rxjs": "~6.5.3", "rxjs": "~7.5.0",
"ts-node": "~10.9.1", "ts-node": "~10.9.1",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"typescript": "~5.4.5", "typescript": "~4.6.2",
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -8,65 +8,26 @@ export default defineExportTemplate(async ({
getSvg, getSvg,
deprecated, deprecated,
deprecationReason, deprecationReason,
aliases = [],
toPascalCase,
}) => { }) => {
const svgContents = await getSvg(); const svgContents = await getSvg();
const svgBase64 = base64SVG(svgContents); const svgBase64 = base64SVG(svgContents);
const angularComponentName = `Lucide${componentName}`;
const selectors = [`svg[lucide${toPascalCase(iconName)}]`];
const aliasComponentNames: string[] = [];
for (const alias of aliases) {
const aliasName = typeof alias === 'string' ? alias : alias.name;
const aliasComponentName = `Lucide${toPascalCase(aliasName)}`;
const aliasSelector = `svg[lucide${toPascalCase(aliasName)}]`;
if (!selectors.includes(aliasSelector)) {
selectors.push(aliasSelector);
}
if (aliasComponentName !== angularComponentName && !aliasComponentNames.includes(aliasComponentName)) {
aliasComponentNames.push(aliasComponentName);
}
}
return `\ return `\
import { LucideIconData } from './types'; import { LucideIconData } from './types';
import { LucideIcon } from '../lib/lucide-icon.component';
import { Component } from '@angular/core';
/** /**
* @component @name ${componentName} * @component @name ${componentName}
* @description Lucide SVG icon component, renders SVG Element with children. * @description Lucide SVG icon component, renders SVG Element with children.
* *
* @preview ![img](data:image/svg+xml;base64,${svgBase64}) - https://lucide.dev/icons/${iconName} * @preview ![img](data:image/svg+xml;base64,${svgBase64}) - https://lucide.dev/icons/${iconName}
* @see https://lucide.dev/guide/packages/lucide-angular - Documentation * @see https://lucide.dev/guide/packages/lucide-vue-next - Documentation
* *
* @param {Object} props - Lucide icons props and any valid SVG attribute * @param {Object} props - Lucide icons props and any valid SVG attribute
* @returns {FunctionalComponent} Vue component
* ${deprecated ? `@deprecated ${deprecationReason}` : ''} * ${deprecated ? `@deprecated ${deprecationReason}` : ''}
*/ */
@Component({ const ${componentName}: LucideIconData = ${JSON.stringify(children)}; //eslint-disable-line no-shadow-restricted-names
selector: '${selectors.join(', ')}',
template: '',
standalone: true,
})
export class ${angularComponentName} extends LucideIcon {
static iconData: LucideIconData = ${JSON.stringify(children)};
static iconName = '${iconName}';
override get icon() {
return ${angularComponentName}.iconData;
}
override get name() {
return ${angularComponentName}.iconName;
}
}
${aliasComponentNames.map(([aliasComponentName]) => { export default ${componentName};
return `
/**
* @deprecated
* @see ${angularComponentName}
*/
export const ${aliasComponentName} = ${angularComponentName};
`;
}).join(`\n\n`)}
`; `;
}); });

View File

@@ -0,0 +1,3 @@
export * from './aliases';
export * from './prefixed';
export * from './suffixed';

View File

@@ -0,0 +1,8 @@
{
"ngPackage": {
"dest": "dist",
"lib": {
"entryFile": "../public-api.ts"
}
}
}

View File

@@ -0,0 +1,4 @@
/** @deprecated Use the injection token LUCIDE_ICONS instead. Will be removed in v1.0. */
export class Icons {
constructor(private icons: object) {}
}

View File

@@ -1,19 +1,23 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { formatFixed, LucideIcon } from './lucide-icon.component'; import { LucideAngularModule } from './lucide-angular.module';
import { formatFixed, LucideAngularComponent } from './lucide-angular.component';
import defaultAttributes from '../icons/constants/default-attributes'; import defaultAttributes from '../icons/constants/default-attributes';
import { LucideIconData } from '../icons/types'; import { LucideIcons } from '../icons/types';
describe('LucideAngularComponent', () => { describe('LucideAngularComponent', () => {
let testHostComponent: TestHostComponent; let testHostComponent: TestHostComponent;
let testHostFixture: ComponentFixture<TestHostComponent>; let testHostFixture: ComponentFixture<TestHostComponent>;
const getSvgAttribute = (attr: string) => const getSvgAttribute = (attr: string) =>
testHostFixture.nativeElement.querySelector('svg').getAttribute(attr); testHostFixture.nativeElement.querySelector('svg').getAttribute(attr);
const testIcon: LucideIconData = [['polyline', { points: '1 1 22 22' }]]; const testIcons: LucideIcons = {
Demo: [['polyline', { points: '1 1 22 22' }]],
};
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [LucideIcon, TestHostComponent], declarations: [LucideAngularComponent, TestHostComponent],
imports: [], imports: [LucideAngularModule.pick(testIcons)],
}).compileComponents(); }).compileComponents();
testHostFixture = TestBed.createComponent(TestHostComponent); testHostFixture = TestBed.createComponent(TestHostComponent);
testHostComponent = testHostFixture.componentInstance; testHostComponent = testHostFixture.componentInstance;
@@ -59,7 +63,7 @@ describe('LucideAngularComponent', () => {
testHostComponent.setAbsoluteStrokeWidth(true); testHostComponent.setAbsoluteStrokeWidth(true);
testHostFixture.detectChanges(); testHostFixture.detectChanges();
expect(getSvgAttribute('stroke-width')).toBe( expect(getSvgAttribute('stroke-width')).toBe(
formatFixed(strokeWidth / (size / defaultAttributes.height)) formatFixed(strokeWidth / (size / defaultAttributes.height)),
); );
}); });
@@ -67,7 +71,6 @@ describe('LucideAngularComponent', () => {
selector: 'lucide-spec-host-component', selector: 'lucide-spec-host-component',
template: ` <i-lucide template: ` <i-lucide
name="demo" name="demo"
[img]="testIcon"
class="my-icon" class="my-icon"
[color]="color" [color]="color"
[size]="size" [size]="size"
@@ -80,7 +83,6 @@ describe('LucideAngularComponent', () => {
size?: number; size?: number;
strokeWidth?: number; strokeWidth?: number;
absoluteStrokeWidth = true; absoluteStrokeWidth = true;
readonly testIcon = testIcon;
setColor(color: string): void { setColor(color: string): void {
this.color = color; this.color = color;

View File

@@ -5,13 +5,12 @@ import {
Inject, Inject,
Input, Input,
OnChanges, OnChanges,
OnInit,
Renderer2, Renderer2,
SimpleChange, SimpleChange,
Type,
} from '@angular/core'; } from '@angular/core';
import { LucideIconData } from '../icons/types'; import { LucideIconData } from '../icons/types';
import defaultAttributes from '../icons/constants/default-attributes'; import defaultAttributes from '../icons/constants/default-attributes';
import { LUCIDE_ICONS, LucideIconProviderInterface } from './lucide-icon.provider';
import { LucideIconConfig } from './lucide-icon.config'; import { LucideIconConfig } from './lucide-icon.config';
interface TypedChange<T> extends SimpleChange { interface TypedChange<T> extends SimpleChange {
@@ -23,7 +22,7 @@ type SvgAttributes = { [key: string]: string | number };
type LucideAngularComponentChanges = { type LucideAngularComponentChanges = {
name?: TypedChange<string | LucideIconData>; name?: TypedChange<string | LucideIconData>;
icon?: TypedChange<LucideIconData | undefined>; img?: TypedChange<LucideIconData | undefined>;
color?: TypedChange<string>; color?: TypedChange<string>;
size?: TypedChange<number>; size?: TypedChange<number>;
strokeWidth?: TypedChange<number>; strokeWidth?: TypedChange<number>;
@@ -35,50 +34,24 @@ export function formatFixed(number: number, decimals = 3): string {
return parseFloat(number.toFixed(decimals)).toString(10); return parseFloat(number.toFixed(decimals)).toString(10);
} }
export type LucideIconComponentType = Type<LucideIcon> & { iconData: LucideIconData; name: string };
function isLucideIconComponent(icon: unknown): icon is LucideIconComponentType {
return (
icon instanceof Type &&
'iconData' in icon &&
Array.isArray(icon.iconData) &&
'iconName' in icon &&
typeof icon.iconName === 'string'
);
}
@Component({ @Component({
// eslint-disable-next-line @angular-eslint/component-selector selector: 'lucide-angular, lucide-icon, i-lucide, span-lucide',
selector: 'svg[lucideIcon]',
template: '<ng-content></ng-content>', template: '<ng-content></ng-content>',
standalone: true,
}) })
// eslint-disable-next-line @angular-eslint/component-class-suffix export class LucideAngularComponent implements OnChanges {
export class LucideIcon implements OnInit, OnChanges {
@Input() class?: string; @Input() class?: string;
_name?: string; @Input() name?: string | LucideIconData;
@Input() set name(name: string | undefined) { @Input() img?: LucideIconData;
this._name = name;
}
get name() {
return this._name;
}
_icon?: LucideIconData | LucideIconComponentType | null;
@Input('lucideIcon') set icon(icon: LucideIconData | LucideIconComponentType | null | undefined) {
this._icon = icon;
}
get icon() {
return this._icon;
}
@Input() color?: string; @Input() color?: string;
@Input() absoluteStrokeWidth = false; @Input() absoluteStrokeWidth = false;
defaultSize: number; defaultSize: number;
constructor( constructor(
@Inject(ElementRef) protected elem: ElementRef, @Inject(ElementRef) private elem: ElementRef,
@Inject(Renderer2) protected renderer: Renderer2, @Inject(Renderer2) private renderer: Renderer2,
@Inject(ChangeDetectorRef) protected changeDetector: ChangeDetectorRef, @Inject(ChangeDetectorRef) private changeDetector: ChangeDetectorRef,
@Inject(LucideIconConfig) protected iconConfig: LucideIconConfig @Inject(LUCIDE_ICONS) private iconProviders: LucideIconProviderInterface[],
@Inject(LucideIconConfig) private iconConfig: LucideIconConfig,
) { ) {
this.defaultSize = defaultAttributes.height; this.defaultSize = defaultAttributes.height;
} }
@@ -111,37 +84,40 @@ export class LucideIcon implements OnInit, OnChanges {
} }
} }
ngOnInit() {
this.buildIcon();
}
ngOnChanges(changes: LucideAngularComponentChanges): void { ngOnChanges(changes: LucideAngularComponentChanges): void {
if ( if (
changes.name || changes.name ||
changes.icon || changes.img ||
changes.color || changes.color ||
changes.size || changes.size ||
changes.absoluteStrokeWidth || changes.absoluteStrokeWidth ||
changes.strokeWidth || changes.strokeWidth ||
changes.class changes.class
) { ) {
this.buildIcon(); this.color = this.color ?? this.iconConfig.color;
this.size = this.parseNumber(this.size ?? this.iconConfig.size);
this.strokeWidth = this.parseNumber(this.strokeWidth ?? this.iconConfig.strokeWidth);
this.absoluteStrokeWidth = this.absoluteStrokeWidth ?? this.iconConfig.absoluteStrokeWidth;
const nameOrIcon = this.img ?? this.name;
if (typeof nameOrIcon === 'string') {
const icoOfName = this.getIcon(this.toPascalCase(nameOrIcon));
if (icoOfName) {
this.replaceElement(icoOfName);
} else {
throw new Error(
`The "${nameOrIcon}" icon has not been provided by any available icon providers.`,
);
}
} else if (Array.isArray(nameOrIcon)) {
this.replaceElement(nameOrIcon);
} else {
throw new Error(`No icon name or image has been provided.`);
}
} }
this.changeDetector.markForCheck(); this.changeDetector.markForCheck();
} }
buildIcon(): void {
this.color = this.color ?? this.iconConfig.color;
this.size = this.parseNumber(this.size ?? this.iconConfig.size);
this.strokeWidth = this.parseNumber(this.strokeWidth ?? this.iconConfig.strokeWidth);
this.absoluteStrokeWidth = this.absoluteStrokeWidth ?? this.iconConfig.absoluteStrokeWidth;
console.log('Hello, my name is ', this.name, ' my icon is ', this.icon);
if (this.icon) {
this.replaceElement(isLucideIconComponent(this.icon) ? this.icon.iconData : this.icon);
}
}
replaceElement(img: LucideIconData): void { replaceElement(img: LucideIconData): void {
const attributes = { const attributes = {
...defaultAttributes, ...defaultAttributes,
@@ -152,10 +128,7 @@ export class LucideIcon implements OnInit, OnChanges {
? formatFixed(this.strokeWidth / (this.size / this.defaultSize)) ? formatFixed(this.strokeWidth / (this.size / this.defaultSize))
: this.strokeWidth.toString(10), : this.strokeWidth.toString(10),
}; };
const icoElement = this.elem.nativeElement; const icoElement = this.createElement(['svg', attributes, img]);
for (const [name, value] of Object.entries(attributes)) {
icoElement.setAttribute(name, value);
}
icoElement.classList.add('lucide'); icoElement.classList.add('lucide');
if (typeof this.name === 'string') { if (typeof this.name === 'string') {
icoElement.classList.add(`lucide-${this.name.replace('_', '-')}`); icoElement.classList.add(`lucide-${this.name.replace('_', '-')}`);
@@ -165,19 +138,24 @@ export class LucideIcon implements OnInit, OnChanges {
...this.class ...this.class
.split(/ /) .split(/ /)
.map((a) => a.trim()) .map((a) => a.trim())
.filter((a) => a.length > 0) .filter((a) => a.length > 0),
); );
} }
for (const child of icoElement.children) { const childElements = this.elem.nativeElement.childNodes;
for (const child of childElements) {
this.renderer.removeChild(this.elem.nativeElement, child); this.renderer.removeChild(this.elem.nativeElement, child);
} }
for (const node of img) { this.renderer.appendChild(this.elem.nativeElement, icoElement);
const childElement = this.createElement(node);
this.renderer.appendChild(icoElement, childElement);
}
} }
protected parseNumber(value: string | number): number { toPascalCase(str: string): string {
return str.replace(
/(\w)([a-z0-9]*)(_|-|\s*)/g,
(g0, g1, g2) => g1.toUpperCase() + g2.toLowerCase(),
);
}
private parseNumber(value: string | number): number {
if (typeof value === 'string') { if (typeof value === 'string') {
const parsedValue = parseInt(value, 10); const parsedValue = parseInt(value, 10);
if (isNaN(parsedValue)) { if (isNaN(parsedValue)) {
@@ -188,10 +166,21 @@ export class LucideIcon implements OnInit, OnChanges {
return value; return value;
} }
protected createElement([tag, attrs, children = []]: readonly [ private getIcon(name: string): LucideIconData | null {
for (const iconProvider of Array.isArray(this.iconProviders)
? this.iconProviders
: [this.iconProviders]) {
if (iconProvider.hasIcon(name)) {
return iconProvider.getIcon(name);
}
}
return null;
}
private createElement([tag, attrs, children = []]: readonly [
string, string,
SvgAttributes, SvgAttributes,
LucideIconData? LucideIconData?,
]) { ]) {
const element = this.renderer.createElement(tag, 'http://www.w3.org/2000/svg'); const element = this.renderer.createElement(tag, 'http://www.w3.org/2000/svg');

View File

@@ -0,0 +1,31 @@
import { ModuleWithProviders, NgModule, Optional } from '@angular/core';
import { LucideAngularComponent } from './lucide-angular.component';
import { LucideIcons } from '../icons/types';
import { LUCIDE_ICONS, LucideIconProvider } from './lucide-icon.provider';
import { Icons } from './icons.provider';
const legacyIconProviderFactory = (icons?: LucideIcons) => {
return new LucideIconProvider(icons ?? {});
};
@NgModule({
declarations: [LucideAngularComponent],
imports: [],
exports: [LucideAngularComponent],
})
export class LucideAngularModule {
static pick(icons: LucideIcons): ModuleWithProviders<LucideAngularModule> {
return {
ngModule: LucideAngularModule,
providers: [
{ provide: LUCIDE_ICONS, multi: true, useValue: new LucideIconProvider(icons) },
{
provide: LUCIDE_ICONS,
multi: true,
useFactory: legacyIconProviderFactory,
deps: [[new Optional(), Icons]],
},
],
};
}
}

View File

@@ -0,0 +1,24 @@
import { LucideIconData, LucideIcons } from '../icons/types';
import { InjectionToken } from '@angular/core';
export interface LucideIconProviderInterface {
hasIcon(name: string): boolean;
getIcon(name: string): LucideIconData | null;
}
export const LUCIDE_ICONS = new InjectionToken<LucideIconProviderInterface>('LucideIcons', {
factory: () => new LucideIconProvider({}),
});
export class LucideIconProvider implements LucideIconProviderInterface {
constructor(private icons: LucideIcons) {}
getIcon(name: string): LucideIconData | null {
return this.hasIcon(name) ? this.icons[name] : null;
}
hasIcon(name: string): boolean {
return typeof this.icons === 'object' && name in this.icons;
}
}

View File

@@ -1,7 +1,10 @@
import * as icons from './icons/lucide-icons'; import * as icons from './icons/lucide-icons';
export * from './lib/lucide-icon.component'; export * from './lib/lucide-angular.component';
export * from './lib/lucide-angular.module';
export * from './lib/lucide-icon.config'; export * from './lib/lucide-icon.config';
export * from './lib/lucide-icon.provider';
export * from './icons/lucide-icons'; export * from './icons/lucide-icons';
export * from './icons/types'; export * from './icons/types';
export * from './aliases';
export { icons }; export { icons };

16456
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@ export default async function generateExportFile(
} else if (exportModuleNameCasing === 'pascal') { } else if (exportModuleNameCasing === 'pascal') {
componentName = toPascalCase(iconName); componentName = toPascalCase(iconName);
} }
const importString = `export * from './${iconName}${iconFileExtension}';\n`; const importString = `export { default as ${componentName} } from './${iconName}${iconFileExtension}';\n`;
return appendFile(importString, fileName, outputDirectory); return appendFile(importString, fileName, outputDirectory);
}); });

View File

@@ -48,11 +48,7 @@ function generateIconFiles({
]); ]);
const getSvg = () => readSvg(`${iconName}.svg`, iconsDir); const getSvg = () => readSvg(`${iconName}.svg`, iconsDir);
const { const { deprecated = false, toBeRemovedInVersion = undefined } = iconMetaData[iconName];
deprecated = false,
toBeRemovedInVersion = undefined,
aliases,
} = iconMetaData[iconName];
const deprecationReason = deprecated const deprecationReason = deprecated
? deprecationReasonTemplate(iconMetaData[iconName]?.deprecationReason ?? '', { ? deprecationReasonTemplate(iconMetaData[iconName]?.deprecationReason ?? '', {
componentName, componentName,
@@ -68,8 +64,6 @@ function generateIconFiles({
getSvg, getSvg,
deprecated, deprecated,
deprecationReason, deprecationReason,
aliases,
toPascalCase,
}); });
const output = pretty const output = pretty
@@ -77,7 +71,7 @@ function generateIconFiles({
singleQuote: true, singleQuote: true,
trailingComma: 'all', trailingComma: 'all',
printWidth: 100, printWidth: 100,
parser: iconFileExtension.endsWith('.ts') ? 'babel-ts' : 'babel', parser: 'babel',
}) })
: elementTemplate; : elementTemplate;
@@ -87,7 +81,7 @@ function generateIconFiles({
const output = `export { default } from "./${iconName}${iconFileExtension}";\n`; const output = `export { default } from "./${iconName}${iconFileExtension}";\n`;
const location = path.join( const location = path.join(
iconsDistDirectory, iconsDistDirectory,
`${iconName}${separateIconFileExportExtension ?? iconFileExtension}` `${iconName}${separateIconFileExportExtension ?? iconFileExtension}`,
); );
await fs.promises.writeFile(location, output, 'utf-8'); await fs.promises.writeFile(location, output, 'utf-8');

View File

@@ -13,8 +13,6 @@ export type TemplateFunction = (params: {
getSvg: () => Promise<string>; getSvg: () => Promise<string>;
deprecated?: boolean; deprecated?: boolean;
deprecationReason?: string; deprecationReason?: string;
aliases?: (string | AliasDeprecation)[];
toPascalCase: (value: string) => string;
}) => Promise<string>; }) => Promise<string>;
export type Path = string; export type Path = string;

View File

@@ -7,8 +7,6 @@ export interface ExportTemplate {
getSvg: () => Promise<string>; getSvg: () => Promise<string>;
deprecated: boolean; deprecated: boolean;
deprecationReason: string; deprecationReason: string;
aliases: Array<string | { name: string }>;
toPascalCase: (value: string) => string;
} }
export type TemplateFunction = (params: ExportTemplate) => Promise<string>; export type TemplateFunction = (params: ExportTemplate) => Promise<string>;