mirror of
https://github.com/lucide-icons/lucide.git
synced 2025-12-17 21:47:40 +01:00
Compare commits
13 Commits
version-se
...
package/an
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae1ca07e36 | ||
|
|
818d99f41e | ||
|
|
a3e7e75b90 | ||
|
|
3edcd9e0c3 | ||
|
|
e851a03672 | ||
|
|
0abfa2f0d5 | ||
|
|
0b8f99326c | ||
|
|
7abb61630e | ||
|
|
3b0d158ea1 | ||
|
|
124572c83b | ||
|
|
6c1e34df19 | ||
|
|
669f62bb64 | ||
|
|
708d5114d6 |
@@ -1,13 +0,0 @@
|
|||||||
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]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
3
docs/.vitepress/api/test.ts
Normal file
3
docs/.vitepress/api/test.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default eventHandler(() => {
|
||||||
|
return { nitro: 'Is Awesome! asda' };
|
||||||
|
});
|
||||||
@@ -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/createLucideIcon';
|
import { type LucideProps, type IconNode } from 'lucide-react/src/types';
|
||||||
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';
|
||||||
|
|||||||
@@ -1,23 +1,31 @@
|
|||||||
<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 emit = defineEmits(['update:modelValue'])
|
const { isDark } = useData();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
const value = computed({
|
const value = computed({
|
||||||
get: () => props.modelValue,
|
get: () => {
|
||||||
set: (val) => emit('update:modelValue', val)
|
if (props.modelValue == null || props.modelValue === 'currentColor') {
|
||||||
})
|
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"
|
||||||
@@ -33,6 +41,7 @@ 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>
|
||||||
@@ -45,27 +54,33 @@ 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: 12px;
|
border-radius: 4px;
|
||||||
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: 4px 8px;
|
padding: 3px 8px 3px 3px;
|
||||||
height: auto;
|
height: auto;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
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 {
|
||||||
@@ -75,19 +90,22 @@ const value = computed({
|
|||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--vp-c-text-1);
|
color: var(--vp-c-text-1);
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
transition: border-color 0.25s, background 0.4s ease;
|
transition:
|
||||||
|
border-color 0.25s,
|
||||||
|
background 0.4s ease;
|
||||||
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker:hover, .color-picker:focus {
|
.color-picker:hover,
|
||||||
|
.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>
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
import Icon from 'lucide-vue-next/src/Icon';
|
||||||
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">
|
||||||
<component :is="SearchIcon" class="search-icon"/>
|
<Icon
|
||||||
<slot/>
|
:iconNode="search"
|
||||||
<kbd v-if="shortcut" class="shortcut">{{ shortcut }}</kbd>
|
class="search-icon"
|
||||||
|
/>
|
||||||
|
<slot />
|
||||||
|
<kbd
|
||||||
|
v-if="shortcut"
|
||||||
|
class="shortcut"
|
||||||
|
>{{ shortcut }}</kbd
|
||||||
|
>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -34,10 +39,14 @@ defineProps({
|
|||||||
cursor: text;
|
cursor: text;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
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);
|
border-color: var(--vp-c-brand);
|
||||||
background: var(--vp-c-bg-alt);
|
background: var(--vp-c-bg-alt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.icon-button {
|
.icon-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
@@ -30,9 +29,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 {
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ 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',
|
||||||
@@ -21,7 +24,7 @@ const input = ref();
|
|||||||
const wrapperEl = ref();
|
const wrapperEl = ref();
|
||||||
const shortcutEl = ref();
|
const shortcutEl = ref();
|
||||||
|
|
||||||
defineEmits(['change', 'input', 'update:modelValue']);
|
const emit = defineEmits(['change', 'input', 'update:modelValue']);
|
||||||
|
|
||||||
const updateShortcutSpacing = () => {
|
const updateShortcutSpacing = () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -35,6 +38,11 @@ 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();
|
||||||
@@ -60,6 +68,17 @@ 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"
|
||||||
@@ -73,6 +92,7 @@ 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;
|
||||||
@@ -82,7 +102,10 @@ 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: border-color 0.2s ease-in-out;
|
transition:
|
||||||
|
color 0.25s,
|
||||||
|
border-color 0.25s,
|
||||||
|
background-color 0.25s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input.has-shortcut {
|
.input.has-shortcut {
|
||||||
@@ -99,6 +122,14 @@ 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;
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ 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 createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
import Icon from 'lucide-vue-next/src/Icon';
|
||||||
import { search, x } from '../../../data/iconNodes';
|
import { search } from '../../../data/iconNodes';
|
||||||
|
|
||||||
const SearchIcon = createLucideIcon('search', search);
|
|
||||||
const XIcon = createLucideIcon('close', x);
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: string;
|
modelValue: string;
|
||||||
@@ -34,10 +31,6 @@ 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>
|
||||||
@@ -50,26 +43,12 @@ const clear = () => {
|
|||||||
v-model="value"
|
v-model="value"
|
||||||
class="input-wrapper"
|
class="input-wrapper"
|
||||||
>
|
>
|
||||||
<template #startIcon>
|
<template #icon>
|
||||||
<component
|
<Icon
|
||||||
:is="SearchIcon"
|
:iconNode="search"
|
||||||
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>
|
||||||
|
|
||||||
@@ -81,9 +60,7 @@ const clear = () => {
|
|||||||
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,
|
||||||
@@ -93,31 +70,9 @@ const clear = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.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>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { rotateCw } from '../../../data/iconNodes'
|
import { rotateCw } from '../../../data/iconNodes';
|
||||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
import Icon from 'lucide-vue-next/src/Icon';
|
||||||
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">
|
||||||
<RotateIcon :size="20"/>
|
<Icon
|
||||||
|
:size="20"
|
||||||
|
:iconNode="rotateCw"
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
|
|||||||
0% {
|
0% {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
transform: rotate(359deg);
|
transform: rotate(359deg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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-preact',
|
|
||||||
logo: '/framework-logos/preact.svg',
|
|
||||||
label: 'Lucide documentation for Preact',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'lucide-solid',
|
name: 'lucide-solid',
|
||||||
logo: '/framework-logos/solid.svg',
|
logo: '/framework-logos/solid.svg',
|
||||||
label: 'Lucide documentation for Solid',
|
label: 'Lucide documentation for Solid',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-preact',
|
||||||
|
logo: '/framework-logos/preact.svg',
|
||||||
|
label: 'Lucide documentation for Preact',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'lucide-angular',
|
name: 'lucide-angular',
|
||||||
logo: '/framework-logos/angular.svg',
|
logo: '/framework-logos/angular.svg',
|
||||||
@@ -48,11 +48,6 @@ 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',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,45 +2,48 @@
|
|||||||
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 createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
import Icon from 'lucide-vue-next/src/Icon';
|
||||||
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 @click="showAd = false" class="hide-button">
|
<IconButton
|
||||||
<component :is="CloseIcon" :size="20" absoluteStrokeWidth />
|
@click="showAd = false"
|
||||||
|
class="hide-button"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
:iconNode="x"
|
||||||
|
:size="20"
|
||||||
|
absoluteStrokeWidth
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<VPDocAsideCarbonAds
|
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" />
|
||||||
:carbon-ads="theme.carbonAds"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -51,7 +54,9 @@ onMounted(() => {
|
|||||||
bottom: 32px;
|
bottom: 32px;
|
||||||
width: 224px;
|
width: 224px;
|
||||||
right: 32px;
|
right: 32px;
|
||||||
transition: opacity 0.5s, transform 0.25s ease;
|
transition:
|
||||||
|
opacity 0.5s,
|
||||||
|
transform 0.25s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-ad.drawer-open {
|
.floating-ad.drawer-open {
|
||||||
@@ -67,8 +72,11 @@ onMounted(() => {
|
|||||||
transform: translateY(-288px) translateX(224px);
|
transform: translateY(-288px) translateX(224px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-ad.drawer-open, .floating-ad.hide-ad {
|
.floating-ad.drawer-open,
|
||||||
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
.floating-ad.hide-ad {
|
||||||
|
transition:
|
||||||
|
opacity 0.25s,
|
||||||
|
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
@media (min-width: 1280px) {
|
||||||
|
|||||||
@@ -1,70 +1,68 @@
|
|||||||
<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>
|
||||||
@@ -75,10 +73,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>
|
||||||
|
|||||||
@@ -1,41 +1,44 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, useSlots } from 'vue';
|
import { computed, useSlots } from 'vue';
|
||||||
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
import { copy } from '../../../data/iconNodes';
|
||||||
import { copy } from '../../../data/iconNodes'
|
|
||||||
import useConfetti from '../../composables/useConfetti';
|
import useConfetti from '../../composables/useConfetti';
|
||||||
const { animate, confetti } = useConfetti()
|
import Icon from 'lucide-vue-next/src/Icon';
|
||||||
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() {
|
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 />
|
||||||
<Copy :size="20" class="copy-icon"/>
|
<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 .15s;;
|
transition: background ease-in 0.15s;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: auto;
|
width: auto;
|
||||||
@@ -48,7 +51,7 @@ const Copy = createLucideIcon('ChevronUp', copy)
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-name:hover .copy-icon {
|
.icon-name:hover .copy-icon {
|
||||||
opacity: .9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-name:before,
|
.icon-name:before,
|
||||||
@@ -65,10 +68,10 @@ const Copy = createLucideIcon('ChevronUp', copy)
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
transition:ease .3s opacity;
|
transition: ease 0.3s opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-name:hover .copy-icon:hover {
|
.icon-name:hover .copy-icon:hover {
|
||||||
opacity: .6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
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
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, defineAsyncComponent, onMounted, watch, watchEffect } from 'vue';
|
import { ref, computed, defineAsyncComponent, onMounted, watch } 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,13 +11,9 @@ 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;
|
||||||
@@ -36,15 +32,9 @@ 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);
|
||||||
@@ -54,31 +44,20 @@ const columnSize = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mappedIcons = computed(() => {
|
const mappedIcons = computed(() => {
|
||||||
let icons = props.icons;
|
if (tags.value == null) {
|
||||||
|
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,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedVersion.value == null || versionIcons.value == null) {
|
return props.icons.map((icon) => {
|
||||||
console.log('no release info');
|
const iconTags = tags.value[icon.name];
|
||||||
|
const iconCategories = categories.value?.[icon.name] ?? [];
|
||||||
|
|
||||||
return icons;
|
return {
|
||||||
}
|
...icon,
|
||||||
|
tags: iconTags,
|
||||||
return Object.values(versionIcons.value).filter(([name, iconNode]) => ({
|
categories: iconCategories,
|
||||||
name,
|
};
|
||||||
iconNode,
|
});
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||||
@@ -128,12 +107,6 @@ 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'));
|
||||||
@@ -156,17 +129,13 @@ function handleCloseDrawer() {
|
|||||||
>
|
>
|
||||||
<StickyBar>
|
<StickyBar>
|
||||||
<InputSearch
|
<InputSearch
|
||||||
:placeholder="`Search ${mappedIcons.length} icons ...`"
|
:placeholder="`Search ${icons.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"
|
||||||
|
|||||||
@@ -1,75 +1,72 @@
|
|||||||
<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>(typeof document !== 'undefined' ? document?.documentElement : undefined)
|
const documentRef = shallowRef<HTMLElement | undefined>(
|
||||||
|
typeof document !== 'undefined' ? document?.documentElement : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
const colorCssVar = useCssVar(
|
const colorCssVar = useCssVar('--customize-color', props.rootEl?.value ?? documentRef.value, {
|
||||||
'--customize-color',
|
initialValue: `${STYLE_DEFAULTS.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(
|
const sizeCssVar = useCssVar('--customize-size', props.rootEl?.value ?? documentRef.value, {
|
||||||
'--customize-size',
|
initialValue: `${STYLE_DEFAULTS.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 color.value !== STYLE_DEFAULTS.color
|
return (
|
||||||
|| strokeWidth.value !== STYLE_DEFAULTS.strokeWidth
|
color.value !== STYLE_DEFAULTS.color ||
|
||||||
|| size.value !== STYLE_DEFAULTS.size
|
strokeWidth.value !== STYLE_DEFAULTS.strokeWidth ||
|
||||||
|| absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
|
size.value !== STYLE_DEFAULTS.size ||
|
||||||
})
|
absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
|
||||||
|
);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="customizer-card" :class="{ customized: customizingActive }">
|
<div
|
||||||
|
class="customizer-card"
|
||||||
|
:class="{ customized: customizingActive }"
|
||||||
|
>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-title">
|
<h2 class="card-title">Customizer</h2>
|
||||||
Customizer
|
|
||||||
</h2>
|
|
||||||
<ResetButton @click="resetStyle"></ResetButton>
|
<ResetButton @click="resetStyle"></ResetButton>
|
||||||
</div>
|
</div>
|
||||||
<InputField
|
<InputField
|
||||||
@@ -77,7 +74,11 @@ const customizingActive = computed(() => {
|
|||||||
label="Color"
|
label="Color"
|
||||||
>
|
>
|
||||||
<template #display>
|
<template #display>
|
||||||
<ColorPicker v-model="color" id="icon-color" class="color-picker"/>
|
<ColorPicker
|
||||||
|
v-model="color"
|
||||||
|
id="icon-color"
|
||||||
|
class="color-picker"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
@@ -117,7 +118,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"
|
||||||
@@ -143,6 +144,7 @@ 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;
|
||||||
@@ -151,7 +153,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 .4s ease-in-out;
|
transition: border-color 0.4s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customizer-card.customized {
|
.customizer-card.customized {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
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);
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IconNode } from 'lucide-vue-next/src/createLucideIcon';
|
import { type IconNode } from 'lucide-vue-next/src/types';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
declare module '*.vue' {
|
declare module '*.vue' {
|
||||||
@@ -20,5 +20,6 @@ declare module 'node:module' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare module '*.node.json' {
|
declare module '*.node.json' {
|
||||||
export default IconNode;
|
const value: IconNode;
|
||||||
|
export default value;
|
||||||
}
|
}
|
||||||
@@ -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.mts",
|
"prebuild:iconNodes": "node ./scripts/writeIconNodes.mjs",
|
||||||
"prebuild:metaJson": "node ./scripts/writeIconMetaIndex.mts",
|
"prebuild:metaJson": "node ./scripts/writeIconMetaIndex.mjs",
|
||||||
"prebuild:releaseJson": "node ./scripts/writeReleaseMetadata.mts",
|
"prebuild:releaseJson": "node ./scripts/writeReleaseMetadata.mjs",
|
||||||
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mts",
|
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mjs",
|
||||||
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mts",
|
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mjs",
|
||||||
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mts",
|
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mjs",
|
||||||
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mts",
|
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mjs",
|
||||||
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mts",
|
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mjs",
|
||||||
"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",
|
||||||
|
|||||||
@@ -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}`, 'utf-8');
|
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`);
|
||||||
const metaData = JSON.parse(metaDataFileContent);
|
const metaData = JSON.parse(metaDataFileContent);
|
||||||
|
|
||||||
const name = iconName.replace('.json', '');
|
const name = iconName.replace('.json', '');
|
||||||
@@ -29,7 +29,7 @@ if (!fs.existsSync(releaseMetaDataDirectory)) {
|
|||||||
fs.mkdirSync(releaseMetaDataDirectory);
|
fs.mkdirSync(releaseMetaDataDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchAllReleases = async () => {
|
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 } = {} as typeof comparisons[number]) => {
|
comparisons.forEach(({ tag, iconFiles, date } = {}) => {
|
||||||
if (tag == null) return;
|
if (tag == null) return;
|
||||||
|
|
||||||
iconFiles.forEach(({ status, file, renamedFile }) => {
|
iconFiles.forEach(({ status, file, renamedFile }) => {
|
||||||
@@ -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) => typeof alias === 'string' ? alias : alias.name).join('|');
|
const aliasRouteMatches = aliases.map((alias) => alias.name).join('|');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
src: `/icons/${aliasRouteMatches}`,
|
src: `/icons/${aliasRouteMatches}`,
|
||||||
14
icons/cannabis-off.json
Normal file
14
icons/cannabis-off.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../icon.schema.json",
|
||||||
|
"contributors": [
|
||||||
|
"nickveles"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"cannabis",
|
||||||
|
"weed",
|
||||||
|
"leaf"
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
"nature"
|
||||||
|
]
|
||||||
|
}
|
||||||
18
icons/cannabis-off.svg
Normal file
18
icons/cannabis-off.svg
Normal 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="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>
|
||||||
|
After Width: | Height: | Size: 681 B |
@@ -9,8 +9,8 @@
|
|||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path d="M19 12H2" />
|
<path d="M11 7 6 2" />
|
||||||
|
<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: 613 B After Width: | Height: | Size: 622 B |
24
icons/stone.json
Normal file
24
icons/stone.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../icon.schema.json",
|
||||||
|
"contributors": [
|
||||||
|
"Alportan",
|
||||||
|
"karsa-mistmere"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"mineral",
|
||||||
|
"geology",
|
||||||
|
"nature",
|
||||||
|
"solid",
|
||||||
|
"pebble",
|
||||||
|
"crystal",
|
||||||
|
"ore",
|
||||||
|
"hard",
|
||||||
|
"coal",
|
||||||
|
"stone",
|
||||||
|
"rock",
|
||||||
|
"boulder"
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
"nature"
|
||||||
|
]
|
||||||
|
}
|
||||||
15
icons/stone.svg
Normal file
15
icons/stone.svg
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="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>
|
||||||
|
After Width: | Height: | Size: 435 B |
73
packages/lucide-angular-next/README.md
Normal file
73
packages/lucide-angular-next/README.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/lucide-icons/lucide">
|
||||||
|
<img src="https://lucide.dev/package-logos/lucide-angular.svg" alt="Lucide icon library for Angular applications." width="540">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Lucide icon library for Angular applications.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/lucide-angular)
|
||||||
|

|
||||||
|
[](https://lucide.dev/license)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://lucide.dev/guide/">About</a>
|
||||||
|
·
|
||||||
|
<a href="https://lucide.dev/icons/">Icons</a>
|
||||||
|
·
|
||||||
|
<a href="https://lucide.dev/guide/packages/lucide-angular">Documentation</a>
|
||||||
|
·
|
||||||
|
<a href="https://lucide.dev/license">License</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# Lucide Angular
|
||||||
|
|
||||||
|
Implementation of the lucide icon library for angular applications.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm add lucide-angular
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install lucide-angular
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add lucide-angular
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun add lucide-angular
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
For full documentation, visit [lucide.dev](https://lucide.dev/guide/packages/lucide-angular)
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
|
Join the [Discord server](https://discord.gg/EH6nSts) to chat with the maintainers and other users.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/license).
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
|
||||||
|
<img src="https://lucide.dev/vercel.svg" alt="Powered by Vercel" width="200" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="https://lucide.dev/digitalocean.svg" width="200" alt="DigitalOcean Referral Badge" /></a>
|
||||||
|
|
||||||
|
### Awesome backers 🍺
|
||||||
|
|
||||||
|
<a href="https://www.scipress.io?utm_source=lucide"><img src="https://lucide.dev/sponsors/scipress.svg" width="180" alt="Scipress sponsor badge" /></a>
|
||||||
|
<a href="https://github.com/pdfme/pdfme"><img src="https://lucide.dev/sponsors/pdfme.svg" width="180" alt="pdfme sponsor badge" /></a>
|
||||||
45
packages/lucide-angular-next/angular.json
Normal file
45
packages/lucide-angular-next/angular.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"cli": {
|
||||||
|
"packageManager": "npm"
|
||||||
|
},
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"@lucide/angular": {
|
||||||
|
"projectType": "library",
|
||||||
|
"root": ".",
|
||||||
|
"sourceRoot": "./src",
|
||||||
|
"prefix": "lib",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular/build:ng-packagr",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"tsConfig": "./tsconfig.lib.prod.json"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"tsConfig": "./tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular/build:unit-test",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": "./tsconfig.spec.json",
|
||||||
|
"coverage": true,
|
||||||
|
"coverageReporters": ["html", "lcov"],
|
||||||
|
"coverageExclude": ["src/icons/*"],
|
||||||
|
"coverageThresholds": {
|
||||||
|
"statements": 80,
|
||||||
|
"branches": 80,
|
||||||
|
"functions": 80,
|
||||||
|
"lines": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/lucide-angular-next/ng-package.json
Normal file
7
packages/lucide-angular-next/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/ng-packagr/ng-package.schema.json",
|
||||||
|
"dest": "./dist",
|
||||||
|
"lib": {
|
||||||
|
"entryFile": "./src/public-api.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
56
packages/lucide-angular-next/package.json
Normal file
56
packages/lucide-angular-next/package.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"name": "@lucide/angular",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"prebuild": "pnpm clean && pnpm copy:license && pnpm build:icons",
|
||||||
|
"build": "pnpm build:ng",
|
||||||
|
"copy:license": "cp ../../LICENSE ./LICENSE",
|
||||||
|
"clean": "rm -rf dist && rm -rf ./src/icons/*.ts",
|
||||||
|
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --iconFileExtension=.ts --exportFileName=lucide-angular.ts",
|
||||||
|
"build:ng": "ng build --configuration production",
|
||||||
|
"test": "ng test --no-watch",
|
||||||
|
"test:watch": "ng test",
|
||||||
|
"lint": "npx eslint 'src/**/*.{js,jsx,ts,tsx,html,css,scss}' --quiet --fix",
|
||||||
|
"e2e": "ng e2e",
|
||||||
|
"version": "pnpm version --git-tag-version=false"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"printWidth": 100,
|
||||||
|
"singleQuote": true,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.html",
|
||||||
|
"options": {
|
||||||
|
"parser": "angular"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular/build": "^21.0.3",
|
||||||
|
"@angular/cli": "^21.0.3",
|
||||||
|
"@angular/common": "^21.0.0",
|
||||||
|
"@angular/compiler": "^21.0.0",
|
||||||
|
"@angular/compiler-cli": "^21.0.0",
|
||||||
|
"@angular/core": "^21.0.0",
|
||||||
|
"@angular/forms": "^21.0.0",
|
||||||
|
"@angular/platform-browser": "^21.0.0",
|
||||||
|
"@angular/router": "^21.0.0",
|
||||||
|
"@lucide/build-icons": "workspace:*",
|
||||||
|
"@vitest/browser-playwright": "^4.0.16",
|
||||||
|
"@vitest/coverage-v8": "^4.0.16",
|
||||||
|
"jsdom": "^27.1.0",
|
||||||
|
"ng-packagr": "^21.0.0",
|
||||||
|
"rxjs": "~7.8.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"typescript": "~5.9.2",
|
||||||
|
"vitest": "^4.0.16"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": "13.x - 21.x",
|
||||||
|
"@angular/core": "13.x - 21.x"
|
||||||
|
}
|
||||||
|
}
|
||||||
68
packages/lucide-angular-next/scripts/exportTemplate.mts
Normal file
68
packages/lucide-angular-next/scripts/exportTemplate.mts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import base64SVG from '@lucide/build-icons/utils/base64SVG';
|
||||||
|
import defineExportTemplate from '@lucide/build-icons/utils/defineExportTemplate';
|
||||||
|
|
||||||
|
export default defineExportTemplate(async ({
|
||||||
|
componentName,
|
||||||
|
iconName,
|
||||||
|
children,
|
||||||
|
getSvg,
|
||||||
|
deprecated,
|
||||||
|
deprecationReason,
|
||||||
|
aliases = [],
|
||||||
|
toPascalCase,
|
||||||
|
}) => {
|
||||||
|
const svgContents = await getSvg();
|
||||||
|
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 `\
|
||||||
|
import { LucideIconData } from '../types';
|
||||||
|
import { LucideIconBase } from '../lucide-icon-base';
|
||||||
|
import { Component, signal } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @component @name ${componentName}
|
||||||
|
* @description Lucide SVG icon component, renders SVG Element with children.
|
||||||
|
*
|
||||||
|
* @preview  - https://lucide.dev/icons/${iconName}
|
||||||
|
* @see https://lucide.dev/guide/packages/lucide-angular - Documentation
|
||||||
|
*
|
||||||
|
* @param {Object} props - Lucide icons props and any valid SVG attribute
|
||||||
|
* ${deprecated ? `@deprecated ${deprecationReason}` : ''}
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: '${selectors.join(', ')}',
|
||||||
|
templateUrl: '../lucide-icon.html',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
export class ${angularComponentName} extends LucideIconBase {
|
||||||
|
static iconData: LucideIconData = ${JSON.stringify(children)};
|
||||||
|
static iconName = '${iconName}';
|
||||||
|
override readonly icon = signal(${angularComponentName}.iconData);
|
||||||
|
override readonly name = signal(${angularComponentName}.iconName);
|
||||||
|
}
|
||||||
|
|
||||||
|
${aliasComponentNames.map((aliasComponentName) => {
|
||||||
|
return `
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* @see ${angularComponentName}
|
||||||
|
*/
|
||||||
|
export const ${aliasComponentName} = ${angularComponentName};
|
||||||
|
`;
|
||||||
|
}).join(`\n\n`)}
|
||||||
|
`;
|
||||||
|
});
|
||||||
11
packages/lucide-angular-next/src/default-attributes.ts
Normal file
11
packages/lucide-angular-next/src/default-attributes.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export default {
|
||||||
|
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',
|
||||||
|
};
|
||||||
25
packages/lucide-angular-next/src/lucide-config.spec.ts
Normal file
25
packages/lucide-angular-next/src/lucide-config.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { LUCIDE_CONFIG, lucideDefaultConfig, provideLucideConfig } from './lucide-config';
|
||||||
|
|
||||||
|
describe('Lucide config', () => {
|
||||||
|
describe('LUCIDE_CONFIG', () => {
|
||||||
|
it('should use default', () => {
|
||||||
|
expect(TestBed.inject(LUCIDE_CONFIG)).toBe(lucideDefaultConfig);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('provideLucideConfig', () => {
|
||||||
|
it('should use defaults', () => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
provideLucideConfig({
|
||||||
|
size: 18,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(TestBed.inject(LUCIDE_CONFIG)).toEqual({
|
||||||
|
...lucideDefaultConfig,
|
||||||
|
size: 18,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
38
packages/lucide-angular-next/src/lucide-config.ts
Normal file
38
packages/lucide-angular-next/src/lucide-config.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { InjectionToken, Provider } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A configuration service for Lucide icon components.
|
||||||
|
*
|
||||||
|
* You can inject this service, typically in AppComponent, and customize its property values in
|
||||||
|
* order to provide default values for all the icons used in the application.
|
||||||
|
*/
|
||||||
|
export interface LucideConfig {
|
||||||
|
color: string;
|
||||||
|
size: number;
|
||||||
|
strokeWidth: number;
|
||||||
|
absoluteStrokeWidth: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lucideDefaultConfig: LucideConfig = {
|
||||||
|
color: 'currentColor',
|
||||||
|
size: 24,
|
||||||
|
strokeWidth: 2,
|
||||||
|
absoluteStrokeWidth: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LUCIDE_CONFIG = new InjectionToken<LucideConfig>(
|
||||||
|
'Lucide icon config',
|
||||||
|
{
|
||||||
|
factory: () => lucideDefaultConfig,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export function provideLucideConfig(config: Partial<LucideConfig>): Provider {
|
||||||
|
return {
|
||||||
|
provide: LUCIDE_CONFIG,
|
||||||
|
useValue: {
|
||||||
|
...lucideDefaultConfig,
|
||||||
|
...config,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
114
packages/lucide-angular-next/src/lucide-icon-base.ts
Normal file
114
packages/lucide-angular-next/src/lucide-icon-base.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
computed,
|
||||||
|
effect,
|
||||||
|
ElementRef,
|
||||||
|
inject,
|
||||||
|
input,
|
||||||
|
Renderer2,
|
||||||
|
Signal,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { LUCIDE_CONFIG } from './lucide-config';
|
||||||
|
import { LucideIconData, Nullable } from './types';
|
||||||
|
import defaultAttributes from './default-attributes';
|
||||||
|
import { formatFixed } from './utils/format-fixed';
|
||||||
|
import { toKebabCase } from './utils/to-kebab-case';
|
||||||
|
|
||||||
|
function transformNumericStringInput(
|
||||||
|
value: Nullable<string | number>,
|
||||||
|
defaultValue: number,
|
||||||
|
): number {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const parsedValue = parseInt(value, 10);
|
||||||
|
if (isNaN(parsedValue)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return parsedValue;
|
||||||
|
}
|
||||||
|
return value ?? defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
// eslint-disable-next-line @angular-eslint/component-selector
|
||||||
|
selector: 'svg[lucideIcon]',
|
||||||
|
templateUrl: './lucide-icon.html',
|
||||||
|
host: {
|
||||||
|
...defaultAttributes,
|
||||||
|
class: 'lucide',
|
||||||
|
'[attr.width]': 'size().toString(10)',
|
||||||
|
'[attr.height]': 'size().toString(10)',
|
||||||
|
'[attr.stroke]': 'color()',
|
||||||
|
'[attr.stroke-width]': 'computedStrokeWidth()',
|
||||||
|
'[attr.aria-hidden]': 'ariaHidden()',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export abstract class LucideIconBase {
|
||||||
|
abstract icon: Signal<Nullable<LucideIconData>>;
|
||||||
|
abstract name: Signal<Nullable<string>>;
|
||||||
|
protected readonly iconConfig = inject(LUCIDE_CONFIG);
|
||||||
|
protected readonly elRef = inject(ElementRef);
|
||||||
|
protected readonly renderer = inject(Renderer2);
|
||||||
|
readonly title = input<Nullable<string>>();
|
||||||
|
readonly ariaHidden = computed(() => {
|
||||||
|
return !this.title();
|
||||||
|
});
|
||||||
|
readonly size = input(this.iconConfig.size, {
|
||||||
|
transform: (value: Nullable<string | number>) =>
|
||||||
|
transformNumericStringInput(value, this.iconConfig.size),
|
||||||
|
});
|
||||||
|
readonly color = input(this.iconConfig.color, {
|
||||||
|
transform: (value: Nullable<string>) => value ?? this.iconConfig.color,
|
||||||
|
});
|
||||||
|
readonly strokeWidth = input(this.iconConfig.strokeWidth, {
|
||||||
|
transform: (value: Nullable<string | number>) =>
|
||||||
|
transformNumericStringInput(value, this.iconConfig.strokeWidth),
|
||||||
|
});
|
||||||
|
readonly absoluteStrokeWidth = input(this.iconConfig.absoluteStrokeWidth, {
|
||||||
|
transform: (value: Nullable<boolean>) => value ?? this.iconConfig.absoluteStrokeWidth,
|
||||||
|
});
|
||||||
|
protected readonly computedStrokeWidth = computed(() => {
|
||||||
|
const strokeWidth = this.strokeWidth();
|
||||||
|
const size = this.size();
|
||||||
|
return this.absoluteStrokeWidth()
|
||||||
|
? formatFixed(strokeWidth / (size / 24))
|
||||||
|
: strokeWidth.toString(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
effect((onCleanup) => {
|
||||||
|
const icon = this.icon();
|
||||||
|
if (icon) {
|
||||||
|
const elements = icon.map(([name, attrs]) => {
|
||||||
|
const element = this.renderer.createElement(name, 'http://www.w3.org/2000/svg');
|
||||||
|
for (const [name, value] of Object.entries(attrs)) {
|
||||||
|
this.renderer.setAttribute(
|
||||||
|
element,
|
||||||
|
name,
|
||||||
|
typeof value === 'number' ? value.toString(10) : value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.renderer.appendChild(this.elRef.nativeElement, element);
|
||||||
|
return element;
|
||||||
|
});
|
||||||
|
onCleanup(() => {
|
||||||
|
for (const element of elements) {
|
||||||
|
this.renderer.removeChild(this.elRef.nativeElement, element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
effect((onCleanup) => {
|
||||||
|
const name = this.name();
|
||||||
|
if (name) {
|
||||||
|
const cssClass = `lucide-${toKebabCase(name)}`;
|
||||||
|
this.renderer.addClass(this.elRef.nativeElement, cssClass);
|
||||||
|
onCleanup(() => {
|
||||||
|
this.renderer.removeClass(this.elRef.nativeElement, cssClass);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/lucide-angular-next/src/lucide-icon.html
Normal file
4
packages/lucide-angular-next/src/lucide-icon.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@if (title(); as titleValue) {
|
||||||
|
<title>{{ titleValue }}</title>
|
||||||
|
}
|
||||||
|
<ng-content />
|
||||||
243
packages/lucide-angular-next/src/lucide-icon.spec.ts
Normal file
243
packages/lucide-angular-next/src/lucide-icon.spec.ts
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
import { Component, input, inputBinding, signal, WritableSignal } from '@angular/core';
|
||||||
|
import { LucideIcon } from './lucide-icon';
|
||||||
|
import { LucideIconData, LucideIconInput } from './types';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { provideLucideIcons } from './lucide-icons';
|
||||||
|
import { LucideActivity } from './icons/activity';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `@if (icon(); as iconData) {
|
||||||
|
<svg [lucideIcon]="iconData">
|
||||||
|
<rect x="1" y="1" width="22" height="22" />
|
||||||
|
</svg>
|
||||||
|
}`,
|
||||||
|
imports: [LucideIcon],
|
||||||
|
})
|
||||||
|
class TestHostComponent {
|
||||||
|
readonly icon = input<LucideIconData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('LucideIcon', () => {
|
||||||
|
let component: LucideIcon;
|
||||||
|
let fixture: ComponentFixture<LucideIcon>;
|
||||||
|
let icon: WritableSignal<LucideIconInput | null | undefined>;
|
||||||
|
let name: WritableSignal<string | undefined>;
|
||||||
|
let title: WritableSignal<string | undefined>;
|
||||||
|
let color: WritableSignal<string | undefined>;
|
||||||
|
let size: WritableSignal<string | number | undefined>;
|
||||||
|
let strokeWidth: WritableSignal<string | number | undefined>;
|
||||||
|
let absoluteStrokeWidth: WritableSignal<boolean | undefined>;
|
||||||
|
const getSvgAttribute = (attr: string) => fixture.nativeElement.getAttribute(attr);
|
||||||
|
const testIcon: LucideIconData = [['polyline', { points: '1 1 22 22' }]];
|
||||||
|
const testIcon2: LucideIconData = [
|
||||||
|
['circle', { cx: 12, cy: 12, r: 8 }],
|
||||||
|
['polyline', { points: '1 1 22 22' }],
|
||||||
|
];
|
||||||
|
beforeEach(async () => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [provideLucideIcons({ demo: testIcon })],
|
||||||
|
});
|
||||||
|
icon = signal('demo');
|
||||||
|
name = signal(undefined);
|
||||||
|
title = signal(undefined);
|
||||||
|
color = signal(undefined);
|
||||||
|
size = signal(undefined);
|
||||||
|
strokeWidth = signal(undefined);
|
||||||
|
absoluteStrokeWidth = signal(undefined);
|
||||||
|
fixture = TestBed.createComponent(LucideIcon, {
|
||||||
|
inferTagName: true,
|
||||||
|
bindings: [
|
||||||
|
inputBinding('lucideIcon', icon),
|
||||||
|
inputBinding('name', name),
|
||||||
|
inputBinding('title', title),
|
||||||
|
inputBinding('color', color),
|
||||||
|
inputBinding('size', size),
|
||||||
|
inputBinding('strokeWidth', strokeWidth),
|
||||||
|
inputBinding('absoluteStrokeWidth', absoluteStrokeWidth),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render children', () => {
|
||||||
|
icon.set(testIcon2);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement.innerHTML).toBe(
|
||||||
|
'<!--container--><circle cx="12" cy="12" r="8"></circle><polyline points="1 1 22 22"></polyline>',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove children on change', () => {
|
||||||
|
icon.set(null);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement.innerHTML).toBe('<!--container-->');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('iconInput', () => {
|
||||||
|
it('should support LucideIconData input', () => {
|
||||||
|
icon.set(testIcon);
|
||||||
|
name.set('custom-name');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.icon()).toBe(testIcon);
|
||||||
|
expect(component.name()).toBe('custom-name');
|
||||||
|
expect(fixture.nativeElement.innerHTML).toBe(
|
||||||
|
'<!--container--><polyline points="1 1 22 22"></polyline>',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should support LucideIconComponentType input', () => {
|
||||||
|
icon.set(LucideActivity);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.icon()).toBe(LucideActivity.iconData);
|
||||||
|
expect(component.name()).toBe(LucideActivity.iconName);
|
||||||
|
});
|
||||||
|
it('should support string icon name', () => {
|
||||||
|
icon.set('demo');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.icon()).toBe(testIcon);
|
||||||
|
expect(component.name()).toBe('demo');
|
||||||
|
});
|
||||||
|
it('should throw error if no icon founds', () => {
|
||||||
|
icon.set('invalid');
|
||||||
|
expect(() => fixture.detectChanges()).toThrowError(`Unable to resolve icon 'invalid'`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('class', () => {
|
||||||
|
it('should add all classes', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('class')).toBe('lucide lucide-demo');
|
||||||
|
});
|
||||||
|
it('should add class from name, even if icon has name', () => {
|
||||||
|
icon.set(LucideActivity);
|
||||||
|
name.set('custom-name');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(getSvgAttribute('class')).toBe('lucide lucide-custom-name');
|
||||||
|
});
|
||||||
|
it('should add class icon if available', () => {
|
||||||
|
icon.set(LucideActivity);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(getSvgAttribute('class')).toBe('lucide lucide-activity');
|
||||||
|
});
|
||||||
|
it('should remove class on change', () => {
|
||||||
|
icon.set(null);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('class')).toBe('lucide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('color', () => {
|
||||||
|
it('should default to currentColor', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('stroke')).toBe('currentColor');
|
||||||
|
});
|
||||||
|
it('should set color', () => {
|
||||||
|
color.set('red');
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('stroke')).toBe('red');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('size', () => {
|
||||||
|
it('should default to 24', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('width')).toBe('24');
|
||||||
|
expect(getSvgAttribute('height')).toBe('24');
|
||||||
|
});
|
||||||
|
it('should set size', () => {
|
||||||
|
size.set(12);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('width')).toBe('12');
|
||||||
|
expect(getSvgAttribute('height')).toBe('12');
|
||||||
|
});
|
||||||
|
it('should allow string size', () => {
|
||||||
|
size.set('18');
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('width')).toBe('18');
|
||||||
|
expect(getSvgAttribute('height')).toBe('18');
|
||||||
|
});
|
||||||
|
it('should use default on invalid string', () => {
|
||||||
|
size.set('large');
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('width')).toBe('24');
|
||||||
|
expect(getSvgAttribute('height')).toBe('24');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('strokeWidth', () => {
|
||||||
|
it('should default to 2', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('stroke-width')).toBe('2');
|
||||||
|
});
|
||||||
|
it('should set stroke width', () => {
|
||||||
|
strokeWidth.set(1.41);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('stroke-width')).toBe('1.41');
|
||||||
|
});
|
||||||
|
it('should allow string stroke width', () => {
|
||||||
|
strokeWidth.set('1px');
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('stroke-width')).toBe('1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('absoluteStrokeWidth', () => {
|
||||||
|
it('should not adjust stroke width', () => {
|
||||||
|
strokeWidth.set(2);
|
||||||
|
size.set(12);
|
||||||
|
absoluteStrokeWidth.set(false);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('stroke-width')).toBe('2');
|
||||||
|
});
|
||||||
|
it('should adjust stroke width', () => {
|
||||||
|
strokeWidth.set(2);
|
||||||
|
size.set(12);
|
||||||
|
absoluteStrokeWidth.set(true);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('stroke-width')).toBe('4');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('title', () => {
|
||||||
|
it('should set title if provided', () => {
|
||||||
|
title.set('Foobar');
|
||||||
|
fixture.detectChanges();
|
||||||
|
const titleEl = fixture.debugElement.query(By.css('title')).nativeElement;
|
||||||
|
expect(titleEl).toBeDefined();
|
||||||
|
expect(titleEl.textContent).toBe('Foobar');
|
||||||
|
});
|
||||||
|
it('should not set aria-hidden when title is set', () => {
|
||||||
|
title.set('Foobar');
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('aria-hidden')).toBeUndefined;
|
||||||
|
});
|
||||||
|
it('should set aria-hidden if no title is provided', () => {
|
||||||
|
title.set(undefined);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSvgAttribute('aria-hidden')).toBeUndefined;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('content projection', () => {
|
||||||
|
it('should project content', () => {
|
||||||
|
const hostFixture = TestBed.createComponent(TestHostComponent);
|
||||||
|
hostFixture.componentRef.setInput('icon', testIcon);
|
||||||
|
hostFixture.detectChanges();
|
||||||
|
hostFixture.componentRef.setInput('icon', testIcon2);
|
||||||
|
hostFixture.detectChanges();
|
||||||
|
const rect = hostFixture.debugElement.query(By.css('rect')).nativeElement;
|
||||||
|
expect(rect).toBeInstanceOf(SVGElement);
|
||||||
|
expect(rect.outerHTML).toBe('<rect x="1" y="1" width="22" height="22"></rect>');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
62
packages/lucide-angular-next/src/lucide-icon.ts
Normal file
62
packages/lucide-angular-next/src/lucide-icon.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Component, computed, inject, input } from '@angular/core';
|
||||||
|
import { isLucideIconComponent, isLucideIconData, LucideIconInput } from './types';
|
||||||
|
import { LucideIconBase } from './lucide-icon-base';
|
||||||
|
import { LUCIDE_ICONS } from './lucide-icons';
|
||||||
|
import { LucideIconData } from './types';
|
||||||
|
import { toKebabCase } from './utils/to-kebab-case';
|
||||||
|
|
||||||
|
interface LucideResolvedIcon {
|
||||||
|
name?: string | null;
|
||||||
|
data: LucideIconData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'svg[lucideIcon]',
|
||||||
|
templateUrl: './lucide-icon.html',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
export class LucideIcon extends LucideIconBase {
|
||||||
|
protected readonly icons = inject(LUCIDE_ICONS);
|
||||||
|
readonly nameInput = input<string | null>(null, { alias: 'name' });
|
||||||
|
readonly iconInput = input.required<LucideIconInput | null>({
|
||||||
|
alias: 'lucideIcon',
|
||||||
|
});
|
||||||
|
readonly resolvedIcon = computed<LucideResolvedIcon | null>(() => {
|
||||||
|
return this.resolveIcon(this.nameInput(), this.iconInput());
|
||||||
|
});
|
||||||
|
override readonly name = computed<string | null>(() => {
|
||||||
|
return this.resolvedIcon()?.name ?? null;
|
||||||
|
});
|
||||||
|
override readonly icon = computed<LucideIconData | null>(() => {
|
||||||
|
return this.resolvedIcon()?.data ?? null;
|
||||||
|
});
|
||||||
|
|
||||||
|
protected resolveIcon(
|
||||||
|
name: string | null | undefined,
|
||||||
|
icon: LucideIconInput | null | undefined,
|
||||||
|
): LucideResolvedIcon | null {
|
||||||
|
if (isLucideIconData(icon)) {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
data: icon,
|
||||||
|
};
|
||||||
|
} else if (isLucideIconComponent(icon)) {
|
||||||
|
return {
|
||||||
|
name: name ?? icon.iconName,
|
||||||
|
data: icon.iconData,
|
||||||
|
};
|
||||||
|
} else if (typeof icon === 'string') {
|
||||||
|
const name = toKebabCase(icon);
|
||||||
|
if (name in this.icons) {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
data: this.icons[name],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unable to resolve icon '${icon}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
packages/lucide-angular-next/src/lucide-icons.spec.ts
Normal file
44
packages/lucide-angular-next/src/lucide-icons.spec.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { LUCIDE_ICONS, provideLucideIcons } from './lucide-icons';
|
||||||
|
import { LucideIconData } from './types';
|
||||||
|
import { LucideActivity } from './icons/activity';
|
||||||
|
import { LucideCircle } from './icons/circle';
|
||||||
|
import { LucideSquareX } from './icons/square-x';
|
||||||
|
|
||||||
|
describe('Lucide icons', () => {
|
||||||
|
describe('LUCIDE_ICONS', () => {
|
||||||
|
it('should default to empty map', () => {
|
||||||
|
expect(TestBed.inject(LUCIDE_ICONS)).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('provideLucideIcons', () => {
|
||||||
|
const mockIcon: LucideIconData = [['polyline', { points: '1 1 22 22' }]];
|
||||||
|
const mockIcon2: LucideIconData = [['circle', { cx: 12, cy: 12, r: 8 }]];
|
||||||
|
it('should accept dictionary of icons', () => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
provideLucideIcons({
|
||||||
|
DemoIcon: mockIcon,
|
||||||
|
MockIcon: mockIcon2,
|
||||||
|
TestIcon: LucideActivity,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(TestBed.inject(LUCIDE_ICONS)).toEqual({
|
||||||
|
'demo-icon': mockIcon,
|
||||||
|
'mock-icon': mockIcon2,
|
||||||
|
[LucideActivity.iconName]: LucideActivity.iconData,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should accept list of icon components', () => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [provideLucideIcons([LucideActivity, LucideSquareX, LucideCircle])],
|
||||||
|
});
|
||||||
|
expect(TestBed.inject(LUCIDE_ICONS)).toEqual({
|
||||||
|
[LucideActivity.iconName]: LucideActivity.iconData,
|
||||||
|
[LucideSquareX.iconName]: LucideSquareX.iconData,
|
||||||
|
[LucideCircle.iconName]: LucideCircle.iconData,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
34
packages/lucide-angular-next/src/lucide-icons.ts
Normal file
34
packages/lucide-angular-next/src/lucide-icons.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { InjectionToken, Provider } from '@angular/core';
|
||||||
|
import { LucideIconData, LucideIcons } from './types';
|
||||||
|
import { isLucideIconComponent, LucideIconComponentType } from './types';
|
||||||
|
import { toKebabCase } from './utils/to-kebab-case';
|
||||||
|
|
||||||
|
export const LUCIDE_ICONS = new InjectionToken<LucideIcons>('Lucide icons', {
|
||||||
|
factory: () => ({}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function provideLucideIcons(
|
||||||
|
icons: Record<string, LucideIconData | LucideIconComponentType> | Array<LucideIconComponentType>,
|
||||||
|
): Provider {
|
||||||
|
if (Array.isArray(icons)) {
|
||||||
|
return {
|
||||||
|
provide: LUCIDE_ICONS,
|
||||||
|
useValue: icons.reduce((acc, icon) => {
|
||||||
|
acc[toKebabCase(icon.iconName)] = icon.iconData;
|
||||||
|
return acc;
|
||||||
|
}, {} as LucideIcons),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
provide: LUCIDE_ICONS,
|
||||||
|
useValue: Object.entries(icons).reduce((acc, [name, icon]) => {
|
||||||
|
if (isLucideIconComponent(icon)) {
|
||||||
|
acc[icon.iconName] = icon.iconData;
|
||||||
|
} else {
|
||||||
|
acc[toKebabCase(name)] = icon;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {} as LucideIcons),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/lucide-angular-next/src/public-api.ts
Normal file
7
packages/lucide-angular-next/src/public-api.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import * as icons from './icons/lucide-angular';
|
||||||
|
|
||||||
|
export * from './lucide-config';
|
||||||
|
export * from './lucide-icon';
|
||||||
|
export * from './lucide-icons';
|
||||||
|
export * from './types';
|
||||||
|
export { icons };
|
||||||
34
packages/lucide-angular-next/src/types.ts
Normal file
34
packages/lucide-angular-next/src/types.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { InputSignal, Signal, Type } from '@angular/core';
|
||||||
|
|
||||||
|
type HtmlAttributes = { [key: string]: string | number };
|
||||||
|
export type LucideIconNode = readonly [string, HtmlAttributes];
|
||||||
|
export type LucideIconData = readonly LucideIconNode[];
|
||||||
|
export type LucideIcons = { [key: string]: LucideIconData };
|
||||||
|
|
||||||
|
export interface LucideIconComponent {
|
||||||
|
name: Signal<Nullable<string>>;
|
||||||
|
icon: Signal<Nullable<LucideIconData>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LucideIconComponentType = Type<LucideIconComponent> & {
|
||||||
|
iconName: string;
|
||||||
|
iconData: LucideIconData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isLucideIconData(icon: unknown): icon is LucideIconData {
|
||||||
|
return Array.isArray(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LucideIconInput = LucideIconComponentType | LucideIconData | string;
|
||||||
|
|
||||||
|
export type Nullable<T> = T | null | undefined;
|
||||||
3
packages/lucide-angular-next/src/utils/format-fixed.ts
Normal file
3
packages/lucide-angular-next/src/utils/format-fixed.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function formatFixed(number: number, decimals = 3): string {
|
||||||
|
return parseFloat(number.toFixed(decimals)).toString(10);
|
||||||
|
}
|
||||||
2
packages/lucide-angular-next/src/utils/to-kebab-case.ts
Normal file
2
packages/lucide-angular-next/src/utils/to-kebab-case.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const toKebabCase = (name: string) =>
|
||||||
|
name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
||||||
38
packages/lucide-angular-next/tsconfig.json
Normal file
38
packages/lucide-angular-next/tsconfig.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||||
|
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@lucide/angular": [
|
||||||
|
"./dist"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "preserve"
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
packages/lucide-angular-next/tsconfig.lib.json
Normal file
18
packages/lucide-angular-next/tsconfig.lib.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||||
|
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/lib",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
11
packages/lucide-angular-next/tsconfig.lib.prod.json
Normal file
11
packages/lucide-angular-next/tsconfig.lib.prod.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||||
|
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.lib.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"declarationMap": false
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"compilationMode": "partial"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
packages/lucide-angular-next/tsconfig.spec.json
Normal file
15
packages/lucide-angular-next/tsconfig.spec.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||||
|
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"vitest/globals"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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": "~13.3.11",
|
"@angular-devkit/build-angular": "~17.3.14",
|
||||||
"@angular-eslint/builder": "~13.0.0",
|
"@angular-eslint/builder": "~17.5.3",
|
||||||
"@angular-eslint/eslint-plugin": "~13.0.0",
|
"@angular-eslint/eslint-plugin": "~17.5.3",
|
||||||
"@angular-eslint/eslint-plugin-template": "~13.0.0",
|
"@angular-eslint/eslint-plugin-template": "~17.5.3",
|
||||||
"@angular-eslint/schematics": "~13.0.0",
|
"@angular-eslint/schematics": "~17.5.3",
|
||||||
"@angular-eslint/template-parser": "~13.0.0",
|
"@angular-eslint/template-parser": "~17.5.3",
|
||||||
"@angular/cli": "~13.3.11",
|
"@angular/cli": "~17.3.14",
|
||||||
"@angular/common": "~13.3.0",
|
"@angular/common": "~17.3.12",
|
||||||
"@angular/compiler": "~13.3.0",
|
"@angular/compiler": "~17.3.12",
|
||||||
"@angular/compiler-cli": "~13.3.0",
|
"@angular/compiler-cli": "~17.3.12",
|
||||||
"@angular/core": "~13.3.0",
|
"@angular/core": "~17.3.12",
|
||||||
"@angular/platform-browser": "~13.3.0",
|
"@angular/platform-browser": "~17.3.12",
|
||||||
"@angular/platform-browser-dynamic": "~13.3.0",
|
"@angular/platform-browser-dynamic": "~17.3.12",
|
||||||
"@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": "^13.3.0",
|
"ng-packagr": "^17.3.0",
|
||||||
"prettier": "^2.8.4",
|
"prettier": "^2.8.4",
|
||||||
"rxjs": "~7.5.0",
|
"rxjs": "~6.5.3",
|
||||||
"ts-node": "~10.9.1",
|
"ts-node": "~10.9.1",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"typescript": "~4.6.2",
|
"typescript": "~5.4.5",
|
||||||
"zone.js": "~0.11.4"
|
"zone.js": "~0.11.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -8,26 +8,65 @@ 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  - https://lucide.dev/icons/${iconName}
|
* @preview  - https://lucide.dev/icons/${iconName}
|
||||||
* @see https://lucide.dev/guide/packages/lucide-vue-next - Documentation
|
* @see https://lucide.dev/guide/packages/lucide-angular - 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}` : ''}
|
||||||
*/
|
*/
|
||||||
const ${componentName}: LucideIconData = ${JSON.stringify(children)}; //eslint-disable-line no-shadow-restricted-names
|
@Component({
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default ${componentName};
|
${aliasComponentNames.map(([aliasComponentName]) => {
|
||||||
|
return `
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* @see ${angularComponentName}
|
||||||
|
*/
|
||||||
|
export const ${aliasComponentName} = ${angularComponentName};
|
||||||
|
`;
|
||||||
|
}).join(`\n\n`)}
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './aliases';
|
|
||||||
export * from './prefixed';
|
|
||||||
export * from './suffixed';
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"ngPackage": {
|
|
||||||
"dest": "dist",
|
|
||||||
"lib": {
|
|
||||||
"entryFile": "../public-api.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
/** @deprecated Use the injection token LUCIDE_ICONS instead. Will be removed in v1.0. */
|
|
||||||
export class Icons {
|
|
||||||
constructor(private icons: object) {}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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]],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,19 @@
|
|||||||
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 { LucideAngularModule } from './lucide-angular.module';
|
import { formatFixed, LucideIcon } from './lucide-icon.component';
|
||||||
import { formatFixed, LucideAngularComponent } from './lucide-angular.component';
|
|
||||||
import defaultAttributes from '../icons/constants/default-attributes';
|
import defaultAttributes from '../icons/constants/default-attributes';
|
||||||
import { LucideIcons } from '../icons/types';
|
import { LucideIconData } 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 testIcons: LucideIcons = {
|
const testIcon: LucideIconData = [['polyline', { points: '1 1 22 22' }]];
|
||||||
Demo: [['polyline', { points: '1 1 22 22' }]],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [LucideAngularComponent, TestHostComponent],
|
declarations: [LucideIcon, TestHostComponent],
|
||||||
imports: [LucideAngularModule.pick(testIcons)],
|
imports: [],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
testHostFixture = TestBed.createComponent(TestHostComponent);
|
testHostFixture = TestBed.createComponent(TestHostComponent);
|
||||||
testHostComponent = testHostFixture.componentInstance;
|
testHostComponent = testHostFixture.componentInstance;
|
||||||
@@ -63,7 +59,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))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,6 +67,7 @@ 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"
|
||||||
@@ -83,6 +80,7 @@ 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;
|
||||||
@@ -5,12 +5,13 @@ 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 {
|
||||||
@@ -22,7 +23,7 @@ type SvgAttributes = { [key: string]: string | number };
|
|||||||
|
|
||||||
type LucideAngularComponentChanges = {
|
type LucideAngularComponentChanges = {
|
||||||
name?: TypedChange<string | LucideIconData>;
|
name?: TypedChange<string | LucideIconData>;
|
||||||
img?: TypedChange<LucideIconData | undefined>;
|
icon?: TypedChange<LucideIconData | undefined>;
|
||||||
color?: TypedChange<string>;
|
color?: TypedChange<string>;
|
||||||
size?: TypedChange<number>;
|
size?: TypedChange<number>;
|
||||||
strokeWidth?: TypedChange<number>;
|
strokeWidth?: TypedChange<number>;
|
||||||
@@ -34,24 +35,50 @@ 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({
|
||||||
selector: 'lucide-angular, lucide-icon, i-lucide, span-lucide',
|
// eslint-disable-next-line @angular-eslint/component-selector
|
||||||
|
selector: 'svg[lucideIcon]',
|
||||||
template: '<ng-content></ng-content>',
|
template: '<ng-content></ng-content>',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class LucideAngularComponent implements OnChanges {
|
// eslint-disable-next-line @angular-eslint/component-class-suffix
|
||||||
|
export class LucideIcon implements OnInit, OnChanges {
|
||||||
@Input() class?: string;
|
@Input() class?: string;
|
||||||
@Input() name?: string | LucideIconData;
|
_name?: string;
|
||||||
@Input() img?: LucideIconData;
|
@Input() set name(name: string | undefined) {
|
||||||
|
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) private elem: ElementRef,
|
@Inject(ElementRef) protected elem: ElementRef,
|
||||||
@Inject(Renderer2) private renderer: Renderer2,
|
@Inject(Renderer2) protected renderer: Renderer2,
|
||||||
@Inject(ChangeDetectorRef) private changeDetector: ChangeDetectorRef,
|
@Inject(ChangeDetectorRef) protected changeDetector: ChangeDetectorRef,
|
||||||
@Inject(LUCIDE_ICONS) private iconProviders: LucideIconProviderInterface[],
|
@Inject(LucideIconConfig) protected iconConfig: LucideIconConfig
|
||||||
@Inject(LucideIconConfig) private iconConfig: LucideIconConfig,
|
|
||||||
) {
|
) {
|
||||||
this.defaultSize = defaultAttributes.height;
|
this.defaultSize = defaultAttributes.height;
|
||||||
}
|
}
|
||||||
@@ -84,40 +111,37 @@ export class LucideAngularComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.buildIcon();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: LucideAngularComponentChanges): void {
|
ngOnChanges(changes: LucideAngularComponentChanges): void {
|
||||||
if (
|
if (
|
||||||
changes.name ||
|
changes.name ||
|
||||||
changes.img ||
|
changes.icon ||
|
||||||
changes.color ||
|
changes.color ||
|
||||||
changes.size ||
|
changes.size ||
|
||||||
changes.absoluteStrokeWidth ||
|
changes.absoluteStrokeWidth ||
|
||||||
changes.strokeWidth ||
|
changes.strokeWidth ||
|
||||||
changes.class
|
changes.class
|
||||||
) {
|
) {
|
||||||
this.color = this.color ?? this.iconConfig.color;
|
this.buildIcon();
|
||||||
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,
|
||||||
@@ -128,7 +152,10 @@ export class LucideAngularComponent implements 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.createElement(['svg', attributes, img]);
|
const icoElement = this.elem.nativeElement;
|
||||||
|
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('_', '-')}`);
|
||||||
@@ -138,24 +165,19 @@ export class LucideAngularComponent implements 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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const childElements = this.elem.nativeElement.childNodes;
|
for (const child of icoElement.children) {
|
||||||
for (const child of childElements) {
|
|
||||||
this.renderer.removeChild(this.elem.nativeElement, child);
|
this.renderer.removeChild(this.elem.nativeElement, child);
|
||||||
}
|
}
|
||||||
this.renderer.appendChild(this.elem.nativeElement, icoElement);
|
for (const node of img) {
|
||||||
|
const childElement = this.createElement(node);
|
||||||
|
this.renderer.appendChild(icoElement, childElement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toPascalCase(str: string): string {
|
protected parseNumber(value: string | number): number {
|
||||||
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)) {
|
||||||
@@ -166,21 +188,10 @@ export class LucideAngularComponent implements OnChanges {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getIcon(name: string): LucideIconData | null {
|
protected createElement([tag, attrs, children = []]: readonly [
|
||||||
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');
|
||||||
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import * as icons from './icons/lucide-icons';
|
import * as icons from './icons/lucide-icons';
|
||||||
|
|
||||||
export * from './lib/lucide-angular.component';
|
export * from './lib/lucide-icon.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 };
|
||||||
|
|||||||
19889
pnpm-lock.yaml
generated
19889
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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 { default as ${componentName} } from './${iconName}${iconFileExtension}';\n`;
|
const importString = `export * from './${iconName}${iconFileExtension}';\n`;
|
||||||
return appendFile(importString, fileName, outputDirectory);
|
return appendFile(importString, fileName, outputDirectory);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,11 @@ function generateIconFiles({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const getSvg = () => readSvg(`${iconName}.svg`, iconsDir);
|
const getSvg = () => readSvg(`${iconName}.svg`, iconsDir);
|
||||||
const { deprecated = false, toBeRemovedInVersion = undefined } = iconMetaData[iconName];
|
const {
|
||||||
|
deprecated = false,
|
||||||
|
toBeRemovedInVersion = undefined,
|
||||||
|
aliases,
|
||||||
|
} = iconMetaData[iconName];
|
||||||
const deprecationReason = deprecated
|
const deprecationReason = deprecated
|
||||||
? deprecationReasonTemplate(iconMetaData[iconName]?.deprecationReason ?? '', {
|
? deprecationReasonTemplate(iconMetaData[iconName]?.deprecationReason ?? '', {
|
||||||
componentName,
|
componentName,
|
||||||
@@ -64,6 +68,8 @@ function generateIconFiles({
|
|||||||
getSvg,
|
getSvg,
|
||||||
deprecated,
|
deprecated,
|
||||||
deprecationReason,
|
deprecationReason,
|
||||||
|
aliases,
|
||||||
|
toPascalCase,
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = pretty
|
const output = pretty
|
||||||
@@ -71,7 +77,7 @@ function generateIconFiles({
|
|||||||
singleQuote: true,
|
singleQuote: true,
|
||||||
trailingComma: 'all',
|
trailingComma: 'all',
|
||||||
printWidth: 100,
|
printWidth: 100,
|
||||||
parser: 'babel',
|
parser: iconFileExtension.endsWith('.ts') ? 'babel-ts' : 'babel',
|
||||||
})
|
})
|
||||||
: elementTemplate;
|
: elementTemplate;
|
||||||
|
|
||||||
@@ -81,7 +87,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');
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ 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;
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ 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>;
|
||||||
|
|||||||
Reference in New Issue
Block a user