mirror of
https://github.com/lucide-icons/lucide.git
synced 2025-12-19 14:09:22 +01:00
Compare commits
3 Commits
fix-stable
...
version-se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
985fa35a0b | ||
|
|
8c0e5cf302 | ||
|
|
4acd574de2 |
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -11,9 +11,6 @@ permissions:
|
|||||||
id-token: write # Required for OIDC
|
id-token: write # Required for OIDC
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-release:
|
create-release:
|
||||||
if: github.repository == 'lucide-icons/lucide' && startsWith(github.event.head_commit.message, 'feat(icons)')
|
if: github.repository == 'lucide-icons/lucide' && startsWith(github.event.head_commit.message, 'feat(icons)')
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -22,9 +22,6 @@ permissions:
|
|||||||
id-token: write # Required for OIDC
|
id-token: write # Required for OIDC
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-release:
|
pre-release:
|
||||||
if: github.repository == 'lucide-icons/lucide' && contains('["ericfennis", "karsa-mistmere", "jguddas"]', github.actor)
|
if: github.repository == 'lucide-icons/lucide' && contains('["ericfennis", "karsa-mistmere", "jguddas"]', github.actor)
|
||||||
@@ -138,8 +135,11 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Outline svg Icons
|
||||||
|
run: pnpm build:outline-icons
|
||||||
|
|
||||||
- name: Create font in ./lucide-font
|
- name: Create font in ./lucide-font
|
||||||
run: pnpm build:font --saveCodePoints
|
run: pnpm build:font
|
||||||
|
|
||||||
- name: 'Upload to Artifacts'
|
- name: 'Upload to Artifacts'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,7 +14,6 @@ coverage
|
|||||||
stats
|
stats
|
||||||
*.log
|
*.log
|
||||||
outlined
|
outlined
|
||||||
lucide-font
|
|
||||||
packages/**/src/icons/*.js
|
packages/**/src/icons/*.js
|
||||||
packages/**/src/icons/*.ts
|
packages/**/src/icons/*.ts
|
||||||
packages/**/src/icons/*.tsx
|
packages/**/src/icons/*.tsx
|
||||||
|
|||||||
13
docs/.vitepress/api/release-info/index.get.ts
Normal file
13
docs/.vitepress/api/release-info/index.get.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { eventHandler, setResponseHeader } from 'h3';
|
||||||
|
import releaseMeta from '../../data/releaseMetaData.json';
|
||||||
|
|
||||||
|
export default eventHandler((event) => {
|
||||||
|
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||||
|
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(releaseMeta).map(
|
||||||
|
([name, { createdRelease }]) => [name, createdRelease.version]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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/types';
|
import { type LucideProps, type IconNode } from 'lucide-react/src/createLucideIcon';
|
||||||
import { IconEntity } from '../theme/types';
|
import { IconEntity } from '../theme/types';
|
||||||
import { renderToStaticMarkup } from 'react-dom/server';
|
import { renderToStaticMarkup } from 'react-dom/server';
|
||||||
import { IconContent } from './generateZip';
|
import { IconContent } from './generateZip';
|
||||||
|
|||||||
@@ -1,31 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useData } from 'vitepress';
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string;
|
modelValue: string
|
||||||
id: string;
|
id: string
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const { isDark } = useData();
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
|
||||||
|
|
||||||
const value = computed({
|
const value = computed({
|
||||||
get: () => {
|
get: () => props.modelValue,
|
||||||
if (props.modelValue == null || props.modelValue === 'currentColor') {
|
set: (val) => emit('update:modelValue', val)
|
||||||
return isDark.value ? '#ffffff' : '#000000';
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
set: (val) => emit('update:modelValue', val),
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="color-picker">
|
<div class="color-picker">
|
||||||
<div class="color-input-wrapper">
|
<div class="color-input-wrapper">
|
||||||
|
<!-- TODO: Add currentColor div if value is currentColor -->
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
:id="id"
|
:id="id"
|
||||||
@@ -41,7 +33,6 @@ const value = computed({
|
|||||||
class="color-input-text"
|
class="color-input-text"
|
||||||
aria-label="Color picker input"
|
aria-label="Color picker input"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
placeholder="[default]"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -54,33 +45,27 @@ const value = computed({
|
|||||||
top: -5px;
|
top: -5px;
|
||||||
left: -5px;
|
left: -5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-input-wrapper {
|
.color-input-wrapper {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 4px;
|
border-radius: 12px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker {
|
.color-picker {
|
||||||
background: var(--color-picker-bg, var(--vp-c-bg-alt));
|
background: var(--color-picker-bg, var(--vp-c-bg-alt));
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: var(--vp-c-text-2);
|
color: var(--vp-c-text-2);
|
||||||
padding: 3px 8px 3px 3px;
|
padding: 4px 8px;
|
||||||
height: auto;
|
height: auto;
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
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 {
|
||||||
@@ -90,22 +75,19 @@ 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: 13px;
|
font-size: 14px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
transition:
|
transition: border-color 0.25s, background 0.4s ease;
|
||||||
border-color 0.25s,
|
|
||||||
background 0.4s ease;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker:hover,
|
.color-picker:hover, .color-picker:focus {
|
||||||
.color-picker:focus {
|
|
||||||
border-color: var(--vp-c-brand);
|
border-color: var(--vp-c-brand);
|
||||||
background: var(--vp-c-bg-alt);
|
background: var(--vp-c-bg-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-input[value='currentColor'] {
|
.color-input[value="currentColor"] {
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,27 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Icon from 'lucide-vue-next/src/Icon';
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
import { search } from '../../../data/iconNodes';
|
import { search } from '../../../data/iconNodes'
|
||||||
|
|
||||||
|
const SearchIcon = createLucideIcon('search', search)
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
shortcut: {
|
shortcut: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button class="fake-input">
|
<button class="fake-input">
|
||||||
<Icon
|
<component :is="SearchIcon" class="search-icon"/>
|
||||||
:iconNode="search"
|
<slot/>
|
||||||
class="search-icon"
|
<kbd v-if="shortcut" class="shortcut">{{ shortcut }}</kbd>
|
||||||
/>
|
|
||||||
<slot />
|
|
||||||
<kbd
|
|
||||||
v-if="shortcut"
|
|
||||||
class="shortcut"
|
|
||||||
>{{ shortcut }}</kbd
|
|
||||||
>
|
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -39,14 +34,10 @@ defineProps({
|
|||||||
cursor: text;
|
cursor: text;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
transition:
|
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||||
color 0.25s,
|
|
||||||
border-color 0.25s,
|
|
||||||
background-color 0.25s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fake-input:hover,
|
.fake-input:hover, .fake-input:focus {
|
||||||
.fake-input:focus {
|
|
||||||
border-color: var(--vp-c-brand);
|
border-color: var(--vp-c-brand);
|
||||||
background: var(--vp-c-bg-alt);
|
background: var(--vp-c-bg-alt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.icon-button {
|
.icon-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
@@ -29,9 +30,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-button:active {
|
.icon-button:active {
|
||||||
border-color: var(--vp-button-alt-active-border);
|
border-color: var(--vp-button-alt-active-border);
|
||||||
color: var(--vp-button-alt-active-text);
|
color: var(--vp-button-alt-active-text);
|
||||||
background-color: var(--vp-button-alt-active-bg);
|
background-color: var(--vp-button-alt-active-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-button.active {
|
.icon-button.active {
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ export interface InputProps {
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, nextTick, watch } from 'vue';
|
import { ref, onMounted, nextTick, watch } from 'vue';
|
||||||
import Icon from 'lucide-vue-next/src/Icon';
|
|
||||||
import { x } from '../../../data/iconNodes';
|
|
||||||
import IconButton from './IconButton.vue';
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<InputProps>(), {
|
const props = withDefaults(defineProps<InputProps>(), {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -24,7 +21,7 @@ const input = ref();
|
|||||||
const wrapperEl = ref();
|
const wrapperEl = ref();
|
||||||
const shortcutEl = ref();
|
const shortcutEl = ref();
|
||||||
|
|
||||||
const emit = defineEmits(['change', 'input', 'update:modelValue']);
|
defineEmits(['change', 'input', 'update:modelValue']);
|
||||||
|
|
||||||
const updateShortcutSpacing = () => {
|
const updateShortcutSpacing = () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -38,11 +35,6 @@ const updateShortcutSpacing = () => {
|
|||||||
onMounted(updateShortcutSpacing);
|
onMounted(updateShortcutSpacing);
|
||||||
watch(() => props.shortcut, updateShortcutSpacing);
|
watch(() => props.shortcut, updateShortcutSpacing);
|
||||||
|
|
||||||
function onClear() {
|
|
||||||
emit('update:modelValue', '');
|
|
||||||
input.value.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
focus: () => {
|
focus: () => {
|
||||||
input.value.focus();
|
input.value.focus();
|
||||||
@@ -68,17 +60,6 @@ defineExpose({
|
|||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@input="$emit('update:modelValue', $event.target.value)"
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
/>
|
/>
|
||||||
<IconButton
|
|
||||||
@click="onClear"
|
|
||||||
v-if="type === 'search' && modelValue"
|
|
||||||
class="clear-button"
|
|
||||||
aria-label="Clear input"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
:iconNode="x"
|
|
||||||
:size="20"
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
<kbd
|
<kbd
|
||||||
v-if="shortcut"
|
v-if="shortcut"
|
||||||
class="shortcut"
|
class="shortcut"
|
||||||
@@ -92,7 +73,6 @@ defineExpose({
|
|||||||
.input-wrapper {
|
.input-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
@@ -102,10 +82,7 @@ defineExpose({
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
background-color: var(--vp-c-bg-alt);
|
background-color: var(--vp-c-bg-alt);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
transition:
|
transition: border-color 0.2s ease-in-out;
|
||||||
color 0.25s,
|
|
||||||
border-color 0.25s,
|
|
||||||
background-color 0.25s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input.has-shortcut {
|
.input.has-shortcut {
|
||||||
@@ -122,14 +99,6 @@ defineExpose({
|
|||||||
padding-left: 52px;
|
padding-left: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 56px;
|
|
||||||
top: 9px;
|
|
||||||
padding: 4px;
|
|
||||||
transition: background-color .25s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut {
|
.shortcut {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ export default {
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import Input from './Input.vue';
|
import Input from './Input.vue';
|
||||||
import Icon from 'lucide-vue-next/src/Icon';
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||||
import { search } from '../../../data/iconNodes';
|
import { search, x } from '../../../data/iconNodes';
|
||||||
|
|
||||||
|
const SearchIcon = createLucideIcon('search', search);
|
||||||
|
const XIcon = createLucideIcon('close', x);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: string;
|
modelValue: string;
|
||||||
@@ -31,6 +34,10 @@ const value = computed({
|
|||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
set: (val) => emit('update:modelValue', val),
|
set: (val) => emit('update:modelValue', val),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
value.value = '';
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -43,12 +50,26 @@ const value = computed({
|
|||||||
v-model="value"
|
v-model="value"
|
||||||
class="input-wrapper"
|
class="input-wrapper"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #startIcon>
|
||||||
<Icon
|
<component
|
||||||
:iconNode="search"
|
:is="SearchIcon"
|
||||||
class="search-icon"
|
class="search-icon"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #endIcon>
|
||||||
|
<button
|
||||||
|
class="clear-button"
|
||||||
|
type="button"
|
||||||
|
v-if="value"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="XIcon"
|
||||||
|
class="x-icon"
|
||||||
|
@click="clear"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
</Input>
|
</Input>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -60,7 +81,9 @@ const value = computed({
|
|||||||
padding: 0 10px 0 12px;
|
padding: 0 10px 0 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
background-color: var(--vp-c-bg-alt);
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
transition: border-color 0.15s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input:hover,
|
.input:hover,
|
||||||
@@ -70,9 +93,31 @@ const value = computed({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-wrapper:deep(.input) {
|
.input-wrapper:deep(.input) {
|
||||||
/* padding: 12px 24px; */
|
|
||||||
padding-block: 12px;
|
padding-block: 12px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clear-button {
|
||||||
|
position: absolute;
|
||||||
|
padding: 4px;
|
||||||
|
right: 8px;
|
||||||
|
top: 8px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-button:hover {
|
||||||
|
background: var(--vp-button-alt-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper:deep(.input)::-webkit-search-decoration,
|
||||||
|
.input-wrapper:deep(.input)::-webkit-search-cancel-button,
|
||||||
|
.input-wrapper:deep(.input)::-webkit-search-results-button,
|
||||||
|
.input-wrapper:deep(.input)::-webkit-search-results-decoration {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { rotateCw } from '../../../data/iconNodes';
|
import { rotateCw } from '../../../data/iconNodes'
|
||||||
import Icon from 'lucide-vue-next/src/Icon';
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
import IconButton from './IconButton.vue';
|
import IconButton from "./IconButton.vue";
|
||||||
|
|
||||||
|
const RotateIcon = createLucideIcon('RotateIcon', rotateCw)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<IconButton class="reset-button">
|
<IconButton class="reset-button">
|
||||||
<Icon
|
<RotateIcon :size="20"/>
|
||||||
:size="20"
|
|
||||||
:iconNode="rotateCw"
|
|
||||||
/>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -33,7 +32,6 @@ import IconButton from './IconButton.vue';
|
|||||||
0% {
|
0% {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
transform: rotate(359deg);
|
transform: rotate(359deg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,6 @@
|
|||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
/* background-color: var(--vp-c-neutral); */
|
|
||||||
box-shadow: var(--vp-shadow-1);
|
box-shadow: var(--vp-shadow-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ export default {
|
|||||||
logo: '/framework-logos/svelte.svg',
|
logo: '/framework-logos/svelte.svg',
|
||||||
label: 'Lucide documentation for Svelte',
|
label: 'Lucide documentation for Svelte',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'lucide-solid',
|
|
||||||
logo: '/framework-logos/solid.svg',
|
|
||||||
label: 'Lucide documentation for Solid',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'lucide-preact',
|
name: 'lucide-preact',
|
||||||
logo: '/framework-logos/preact.svg',
|
logo: '/framework-logos/preact.svg',
|
||||||
label: 'Lucide documentation for Preact',
|
label: 'Lucide documentation for Preact',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-solid',
|
||||||
|
logo: '/framework-logos/solid.svg',
|
||||||
|
label: 'Lucide documentation for Solid',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'lucide-angular',
|
name: 'lucide-angular',
|
||||||
logo: '/framework-logos/angular.svg',
|
logo: '/framework-logos/angular.svg',
|
||||||
@@ -48,6 +48,11 @@ export default {
|
|||||||
logo: '/framework-logos/react-native.svg',
|
logo: '/framework-logos/react-native.svg',
|
||||||
label: 'Lucide documentation for React Native',
|
label: 'Lucide documentation for React Native',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'lucide-flutter',
|
||||||
|
logo: '/framework-logos/flutter.svg',
|
||||||
|
label: 'Lucide documentation for Flutter',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,48 +2,45 @@
|
|||||||
import { useData } from 'vitepress';
|
import { useData } from 'vitepress';
|
||||||
import { useSessionStorage } from '@vueuse/core';
|
import { useSessionStorage } from '@vueuse/core';
|
||||||
import IconButton from '../base/IconButton.vue';
|
import IconButton from '../base/IconButton.vue';
|
||||||
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue';
|
// import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue'
|
||||||
import { x } from '../../../data/iconNodes';
|
import { x } from '../../../data/iconNodes'
|
||||||
import Icon from 'lucide-vue-next/src/Icon';
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
const { theme } = useData();
|
const { theme } = useData()
|
||||||
const showAd = useSessionStorage('show-carbon-ads', true);
|
const showAd = useSessionStorage('show-carbon-ads', true)
|
||||||
const carbonLoaded = ref(true);
|
const carbonLoaded = ref(true)
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
drawerOpen: boolean;
|
drawerOpen: boolean
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
|
const CloseIcon = createLucideIcon('Close', x)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (window?._carbonads == null) {
|
if (window?._carbonads == null) {
|
||||||
carbonLoaded.value = false;
|
carbonLoaded.value = false
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000)
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="{
|
:class="{
|
||||||
'drawer-open': drawerOpen,
|
'drawer-open': drawerOpen,
|
||||||
'hide-ad': !(showAd && carbonLoaded),
|
'hide-ad': !(showAd && carbonLoaded)
|
||||||
}"
|
}"
|
||||||
class="floating-ad"
|
class="floating-ad"
|
||||||
v-if="theme.carbonAds"
|
v-if="theme.carbonAds"
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton @click="showAd = false" class="hide-button">
|
||||||
@click="showAd = false"
|
<component :is="CloseIcon" :size="20" absoluteStrokeWidth />
|
||||||
class="hide-button"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
:iconNode="x"
|
|
||||||
:size="20"
|
|
||||||
absoluteStrokeWidth
|
|
||||||
/>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" />
|
<VPDocAsideCarbonAds
|
||||||
|
:carbon-ads="theme.carbonAds"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -54,9 +51,7 @@ onMounted(() => {
|
|||||||
bottom: 32px;
|
bottom: 32px;
|
||||||
width: 224px;
|
width: 224px;
|
||||||
right: 32px;
|
right: 32px;
|
||||||
transition:
|
transition: opacity 0.5s, transform 0.25s ease;
|
||||||
opacity 0.5s,
|
|
||||||
transform 0.25s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-ad.drawer-open {
|
.floating-ad.drawer-open {
|
||||||
@@ -72,11 +67,8 @@ onMounted(() => {
|
|||||||
transform: translateY(-288px) translateX(224px);
|
transform: translateY(-288px) translateX(224px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-ad.drawer-open,
|
.floating-ad.drawer-open, .floating-ad.hide-ad {
|
||||||
.floating-ad.hide-ad {
|
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
transition:
|
|
||||||
opacity 0.25s,
|
|
||||||
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
@media (min-width: 1280px) {
|
||||||
|
|||||||
@@ -1,68 +1,70 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import ButtonMenu from '../base/ButtonMenu.vue';
|
import ButtonMenu from '../base/ButtonMenu.vue'
|
||||||
import { useIconStyleContext } from '../../composables/useIconStyle';
|
import { useIconStyleContext } from '../../composables/useIconStyle';
|
||||||
import useConfetti from '../../composables/useConfetti';
|
import useConfetti from '../../composables/useConfetti';
|
||||||
import getSVGIcon from '../../utils/getSVGIcon';
|
import getSVGIcon from '../../utils/getSVGIcon';
|
||||||
import downloadData from '../../utils/downloadData';
|
import downloadData from '../../utils/downloadData';
|
||||||
|
|
||||||
const downloadText = 'Download!';
|
const downloadText = 'Download!'
|
||||||
const copiedText = 'Copied!';
|
const copiedText = 'Copied!'
|
||||||
const confettiText = ref(copiedText);
|
const confettiText = ref(copiedText)
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string;
|
name: string
|
||||||
popoverPosition?: 'top' | 'bottom';
|
popoverPosition?: 'top' | 'bottom'
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const { size } = useIconStyleContext();
|
const { size } = useIconStyleContext()
|
||||||
|
|
||||||
const { animate, confetti } = useConfetti();
|
const { animate, confetti } = useConfetti()
|
||||||
|
|
||||||
function copySVG() {
|
function copySVG() {
|
||||||
confettiText.value = copiedText;
|
confettiText.value = copiedText
|
||||||
const svgString = getSVGIcon();
|
const svgString = getSVGIcon()
|
||||||
|
|
||||||
navigator.clipboard.writeText(svgString);
|
navigator.clipboard.writeText(svgString)
|
||||||
|
|
||||||
confetti();
|
confetti()
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyDataUrl() {
|
function copyDataUrl() {
|
||||||
confettiText.value = copiedText;
|
confettiText.value = copiedText
|
||||||
const svgString = getSVGIcon();
|
const svgString = getSVGIcon()
|
||||||
|
|
||||||
// Create SVG data url
|
// Create SVG data url
|
||||||
const dataUrl = `data:image/svg+xml;base64,${btoa(svgString)}`;
|
const dataUrl = `data:image/svg+xml;base64,${btoa(svgString)}`
|
||||||
navigator.clipboard.writeText(dataUrl);
|
navigator.clipboard.writeText(dataUrl)
|
||||||
|
|
||||||
confetti();
|
confetti()
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadSVG() {
|
function downloadSVG() {
|
||||||
confettiText.value = downloadText;
|
confettiText.value = downloadText
|
||||||
const svgString = getSVGIcon();
|
const svgString = getSVGIcon()
|
||||||
|
|
||||||
downloadData(`${props.name}.svg`, `data:image/svg+xml;base64,${btoa(svgString)}`);
|
downloadData(`${props.name}.svg`, `data:image/svg+xml;base64,${btoa(svgString)}`)
|
||||||
confetti();
|
confetti()
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadPNG() {
|
function downloadPNG() {
|
||||||
confettiText.value = downloadText;
|
confettiText.value = downloadText
|
||||||
const svgString = getSVGIcon();
|
const svgString = getSVGIcon()
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = size.value;
|
canvas.width = size.value;
|
||||||
canvas.height = size.value;
|
canvas.height = size.value;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
image.src = `data:image/svg+xml;base64,${btoa(svgString)}`;
|
image.src = `data:image/svg+xml;base64,${btoa(svgString)}`;
|
||||||
image.onload = function () {
|
image.onload = function() {
|
||||||
ctx.drawImage(image, 0, 0);
|
ctx.drawImage(image, 0, 0);
|
||||||
downloadData(`${props.name}.png`, canvas.toDataURL('image/png'));
|
downloadData(`${props.name}.png`, canvas.toDataURL('image/png'))
|
||||||
confetti();
|
confetti()
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -73,10 +75,10 @@ function downloadPNG() {
|
|||||||
:data-confetti-text="confettiText"
|
:data-confetti-text="confettiText"
|
||||||
:popoverPosition="popoverPosition"
|
:popoverPosition="popoverPosition"
|
||||||
:options="[
|
:options="[
|
||||||
{ text: 'Copy SVG', onClick: copySVG },
|
{ text: 'Copy SVG' , onClick: copySVG },
|
||||||
{ text: 'Copy Data URL', onClick: copyDataUrl },
|
{ text: 'Copy Data URL' , onClick: copyDataUrl },
|
||||||
{ text: 'Download SVG', onClick: downloadSVG },
|
{ text: 'Download SVG' , onClick: downloadSVG },
|
||||||
{ text: 'Download PNG', onClick: downloadPNG },
|
{ text: 'Download PNG' , onClick: downloadPNG },
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,44 +1,41 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, useSlots } from 'vue';
|
import { computed, useSlots } from 'vue';
|
||||||
import { copy } from '../../../data/iconNodes';
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
|
||||||
|
import { copy } from '../../../data/iconNodes'
|
||||||
import useConfetti from '../../composables/useConfetti';
|
import useConfetti from '../../composables/useConfetti';
|
||||||
import Icon from 'lucide-vue-next/src/Icon';
|
const { animate, confetti } = useConfetti()
|
||||||
const { animate, confetti } = useConfetti();
|
const slots = useSlots()
|
||||||
const slots = useSlots();
|
|
||||||
|
|
||||||
const copiedText = computed(() => slots.default?.()[0].children);
|
const copiedText = computed(() => slots.default?.()[0].children)
|
||||||
|
|
||||||
function copyText() {
|
function copyText() {
|
||||||
navigator.clipboard.writeText(copiedText.value);
|
navigator.clipboard.writeText(copiedText.value)
|
||||||
|
|
||||||
confetti();
|
confetti()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Copy = createLucideIcon('ChevronUp', copy)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1
|
<h1
|
||||||
class="icon-name confetti-button"
|
class="icon-name confetti-button"
|
||||||
:class="{ animate }"
|
:class="{animate}"
|
||||||
data-confetti-text="Copied!"
|
data-confetti-text="Copied!"
|
||||||
@click="copyText"
|
@click="copyText"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<Icon
|
<Copy :size="20" class="copy-icon"/>
|
||||||
:iconNode="copy"
|
|
||||||
:size="20"
|
|
||||||
class="copy-icon"
|
|
||||||
/>
|
|
||||||
</h1>
|
</h1>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import './confetti.css';
|
@import './confetti.css';
|
||||||
|
|
||||||
.icon-name {
|
.icon-name {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
transition: background ease-in 0.15s;
|
transition: background ease-in .15s;;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: auto;
|
width: auto;
|
||||||
@@ -51,7 +48,7 @@ function copyText() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-name:hover .copy-icon {
|
.icon-name:hover .copy-icon {
|
||||||
opacity: 0.9;
|
opacity: .9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-name:before,
|
.icon-name:before,
|
||||||
@@ -68,10 +65,10 @@ function copyText() {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
transition: ease 0.3s opacity;
|
transition:ease .3s opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-name:hover .copy-icon:hover {
|
.icon-name:hover .copy-icon:hover {
|
||||||
opacity: 0.6;
|
opacity: .6;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
21
docs/.vitepress/theme/components/icons/IconsOverview.data.ts
Normal file
21
docs/.vitepress/theme/components/icons/IconsOverview.data.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { getAllData } from '../../../lib/icons';
|
||||||
|
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
||||||
|
import iconsMetaData from '../../../data/iconMetaData';
|
||||||
|
import { fetchAllReleases } from '../../../../scripts/writeReleaseMetadata.mts';
|
||||||
|
import { satisfies } from 'semver';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async load() {
|
||||||
|
const versions = await fetchAllReleases();
|
||||||
|
|
||||||
|
const mappedVersions = versions
|
||||||
|
.map((tag) => tag.replace('v', ''))
|
||||||
|
.reverse()
|
||||||
|
|
||||||
|
mappedVersions.length = 100
|
||||||
|
|
||||||
|
return {
|
||||||
|
versions: mappedVersions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, defineAsyncComponent, onMounted, watch } from 'vue';
|
import { ref, computed, defineAsyncComponent, onMounted, watch, watchEffect } from 'vue';
|
||||||
import type { IconEntity } from '../../types';
|
import type { IconEntity } from '../../types';
|
||||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||||
import { useRoute } from 'vitepress';
|
import { useRoute } from 'vitepress';
|
||||||
@@ -11,9 +11,13 @@ import useSearchShortcut from '../../utils/useSearchShortcut';
|
|||||||
import StickyBar from './StickyBar.vue';
|
import StickyBar from './StickyBar.vue';
|
||||||
import useFetchTags from '../../composables/useFetchTags';
|
import useFetchTags from '../../composables/useFetchTags';
|
||||||
import useFetchCategories from '../../composables/useFetchCategories';
|
import useFetchCategories from '../../composables/useFetchCategories';
|
||||||
|
import useFetchVersionIcons from '../../composables/useFetchVersionIcons';
|
||||||
import chunkArray from '../../utils/chunkArray';
|
import chunkArray from '../../utils/chunkArray';
|
||||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||||
|
import VersionSelect from './VersionSelect.vue';
|
||||||
|
import { sort, satisfies } from 'semver';
|
||||||
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
|
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
|
||||||
|
import { data as versionData } from './IconsOverview.data';
|
||||||
|
|
||||||
const ICON_SIZE = 56;
|
const ICON_SIZE = 56;
|
||||||
const ICON_GRID_GAP = 8;
|
const ICON_GRID_GAP = 8;
|
||||||
@@ -32,9 +36,15 @@ const props = defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const activeIconName = ref(null);
|
const activeIconName = ref(null);
|
||||||
|
const selectedVersion = ref();
|
||||||
|
|
||||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||||
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
||||||
|
const { execute: fetchVersionIcons, data: versionIcons } = useFetchVersionIcons(selectedVersion);
|
||||||
|
|
||||||
|
watch(selectedVersion, () => {
|
||||||
|
fetchVersionIcons();
|
||||||
|
});
|
||||||
|
|
||||||
const overviewEl = ref<HTMLElement | null>(null);
|
const overviewEl = ref<HTMLElement | null>(null);
|
||||||
const { width: containerWidth } = useElementSize(overviewEl);
|
const { width: containerWidth } = useElementSize(overviewEl);
|
||||||
@@ -44,20 +54,31 @@ const columnSize = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mappedIcons = computed(() => {
|
const mappedIcons = computed(() => {
|
||||||
if (tags.value == null) {
|
let icons = props.icons;
|
||||||
return props.icons;
|
|
||||||
|
if (tags.value != null && categories.value != null) {
|
||||||
|
icons = props.icons.map((icon) => {
|
||||||
|
const iconTags = tags.value[icon.name];
|
||||||
|
const iconCategories = categories.value?.[icon.name] ?? [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...icon,
|
||||||
|
tags: iconTags,
|
||||||
|
categories: iconCategories,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.icons.map((icon) => {
|
if (selectedVersion.value == null || versionIcons.value == null) {
|
||||||
const iconTags = tags.value[icon.name];
|
console.log('no release info');
|
||||||
const iconCategories = categories.value?.[icon.name] ?? [];
|
|
||||||
|
|
||||||
return {
|
return icons;
|
||||||
...icon,
|
}
|
||||||
tags: iconTags,
|
|
||||||
categories: iconCategories,
|
return Object.values(versionIcons.value).filter(([name, iconNode]) => ({
|
||||||
};
|
name,
|
||||||
});
|
iconNode,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||||
@@ -107,6 +128,12 @@ function onFocusSearchInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function onFocusVersionSelect() {
|
||||||
|
// if (releaseInfo.value == null) {
|
||||||
|
// fetchReleaseInfo();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||||
|
|
||||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||||
@@ -129,13 +156,17 @@ function handleCloseDrawer() {
|
|||||||
>
|
>
|
||||||
<StickyBar>
|
<StickyBar>
|
||||||
<InputSearch
|
<InputSearch
|
||||||
:placeholder="`Search ${icons.length} icons ...`"
|
:placeholder="`Search ${mappedIcons.length} icons ...`"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
:shortcut="kbdSearchShortcut"
|
:shortcut="kbdSearchShortcut"
|
||||||
class="input-wrapper"
|
class="input-wrapper"
|
||||||
@focus="onFocusSearchInput"
|
@focus="onFocusSearchInput"
|
||||||
/>
|
/>
|
||||||
|
<VersionSelect
|
||||||
|
v-model="selectedVersion"
|
||||||
|
:versions="versionData.versions"
|
||||||
|
/>
|
||||||
</StickyBar>
|
</StickyBar>
|
||||||
<NoResults
|
<NoResults
|
||||||
v-if="searchPlaceholder.isNoResults"
|
v-if="searchPlaceholder.isNoResults"
|
||||||
|
|||||||
@@ -1,72 +1,75 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { shallowRef, type Ref, watch, computed } from 'vue';
|
import { shallowRef, type Ref, watch, computed } from 'vue'
|
||||||
import { useCssVar, syncRef } from '@vueuse/core';
|
import { useCssVar, syncRef } from '@vueuse/core'
|
||||||
import { STYLE_DEFAULTS, useIconStyleContext } from '../../composables/useIconStyle';
|
import { STYLE_DEFAULTS, useIconStyleContext } from '../../composables/useIconStyle'
|
||||||
import RangeSlider from '../base/RangeSlider.vue';
|
import RangeSlider from '../base/RangeSlider.vue'
|
||||||
import InputField from '../base/InputField.vue';
|
import InputField from '../base/InputField.vue'
|
||||||
import ColorPicker from '../base/ColorPicker.vue';
|
import ColorPicker from '../base/ColorPicker.vue'
|
||||||
import ResetButton from '../base/ResetButton.vue';
|
import ResetButton from '../base/ResetButton.vue'
|
||||||
import Switch from '../base/Switch.vue';
|
import Switch from '../base/Switch.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
rootEl?: Ref<HTMLElement>;
|
rootEl?: Ref<HTMLElement>
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const { color, strokeWidth, size, absoluteStrokeWidth } = useIconStyleContext();
|
const { color, strokeWidth, size, absoluteStrokeWidth } = useIconStyleContext()
|
||||||
const documentRef = shallowRef<HTMLElement | undefined>(
|
const documentRef = shallowRef<HTMLElement | undefined>(typeof document !== 'undefined' ? document?.documentElement : undefined)
|
||||||
typeof document !== 'undefined' ? document?.documentElement : undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const colorCssVar = useCssVar('--customize-color', props.rootEl?.value ?? documentRef.value, {
|
const colorCssVar = useCssVar(
|
||||||
initialValue: `${STYLE_DEFAULTS.color}`,
|
'--customize-color',
|
||||||
});
|
props.rootEl?.value ?? documentRef.value,
|
||||||
|
{
|
||||||
|
initialValue: `${STYLE_DEFAULTS.color}`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const strokeWidthCssVar = useCssVar(
|
const strokeWidthCssVar = useCssVar(
|
||||||
'--customize-strokeWidth',
|
'--customize-strokeWidth',
|
||||||
props.rootEl?.value ?? documentRef.value,
|
props.rootEl?.value ?? documentRef.value,
|
||||||
{
|
{
|
||||||
initialValue: `${STYLE_DEFAULTS.strokeWidth}`,
|
initialValue: `${STYLE_DEFAULTS.strokeWidth}`
|
||||||
},
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
const sizeCssVar = useCssVar('--customize-size', props.rootEl?.value ?? documentRef.value, {
|
const sizeCssVar = useCssVar(
|
||||||
initialValue: `${STYLE_DEFAULTS.size}`,
|
'--customize-size',
|
||||||
});
|
props.rootEl?.value ?? documentRef.value,
|
||||||
|
{
|
||||||
|
initialValue: `${STYLE_DEFAULTS.size}`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
syncRef(color, colorCssVar, { direction: 'ltr' });
|
syncRef(color, colorCssVar, { direction: 'ltr' })
|
||||||
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' });
|
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' })
|
||||||
syncRef(size, sizeCssVar, { direction: 'ltr' });
|
syncRef(size, sizeCssVar, { direction: 'ltr' })
|
||||||
|
|
||||||
function resetStyle() {
|
function resetStyle () {
|
||||||
color.value = STYLE_DEFAULTS.color;
|
color.value = STYLE_DEFAULTS.color
|
||||||
strokeWidth.value = STYLE_DEFAULTS.strokeWidth;
|
strokeWidth.value = STYLE_DEFAULTS.strokeWidth
|
||||||
size.value = STYLE_DEFAULTS.size;
|
size.value = STYLE_DEFAULTS.size
|
||||||
absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth;
|
absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(absoluteStrokeWidth, (enabled) => {
|
watch(absoluteStrokeWidth, (enabled) => {
|
||||||
const htmlEl = document.documentElement;
|
const htmlEl = document.documentElement
|
||||||
|
|
||||||
htmlEl.classList.toggle('absolute-stroke-width', enabled);
|
htmlEl.classList.toggle('absolute-stroke-width', enabled)
|
||||||
});
|
})
|
||||||
|
|
||||||
const customizingActive = computed(() => {
|
const customizingActive = computed(() => {
|
||||||
return (
|
return color.value !== STYLE_DEFAULTS.color
|
||||||
color.value !== STYLE_DEFAULTS.color ||
|
|| strokeWidth.value !== STYLE_DEFAULTS.strokeWidth
|
||||||
strokeWidth.value !== STYLE_DEFAULTS.strokeWidth ||
|
|| size.value !== STYLE_DEFAULTS.size
|
||||||
size.value !== STYLE_DEFAULTS.size ||
|
|| absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
|
||||||
absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
|
})
|
||||||
);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="customizer-card" :class="{ customized: customizingActive }">
|
||||||
class="customizer-card"
|
|
||||||
:class="{ customized: customizingActive }"
|
|
||||||
>
|
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-title">Customizer</h2>
|
<h2 class="card-title">
|
||||||
|
Customizer
|
||||||
|
</h2>
|
||||||
<ResetButton @click="resetStyle"></ResetButton>
|
<ResetButton @click="resetStyle"></ResetButton>
|
||||||
</div>
|
</div>
|
||||||
<InputField
|
<InputField
|
||||||
@@ -74,11 +77,7 @@ const customizingActive = computed(() => {
|
|||||||
label="Color"
|
label="Color"
|
||||||
>
|
>
|
||||||
<template #display>
|
<template #display>
|
||||||
<ColorPicker
|
<ColorPicker v-model="color" id="icon-color" class="color-picker"/>
|
||||||
v-model="color"
|
|
||||||
id="icon-color"
|
|
||||||
class="color-picker"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
@@ -118,7 +117,7 @@ const customizingActive = computed(() => {
|
|||||||
|
|
||||||
<InputField
|
<InputField
|
||||||
id="absolute-stroke-width"
|
id="absolute-stroke-width"
|
||||||
label="Absolute stroke width"
|
label="Absolute Stroke width"
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
id="absolute-stroke-width"
|
id="absolute-stroke-width"
|
||||||
@@ -144,7 +143,6 @@ const customizingActive = computed(() => {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
/* margin-bottom: 12px; */
|
/* margin-bottom: 12px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.customizer-card {
|
.customizer-card {
|
||||||
background: var(--vp-c-bg);
|
background: var(--vp-c-bg);
|
||||||
padding: 12px 24px 24px;
|
padding: 12px 24px 24px;
|
||||||
@@ -153,7 +151,7 @@ const customizingActive = computed(() => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
transition: border-color 0.4s ease-in-out;
|
transition: border-color .4s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customizer-card.customized {
|
.customizer-card.customized {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
margin-top: -32px;
|
margin-top: -32px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
background: var(--vp-c-bg);
|
background: var(--vp-c-bg);
|
||||||
box-shadow: 0 16px 24px var(--vp-c-bg);
|
box-shadow: 0 16px 24px var(--vp-c-bg);
|
||||||
|
|||||||
221
docs/.vitepress/theme/components/icons/VersionSelect.vue
Normal file
221
docs/.vitepress/theme/components/icons/VersionSelect.vue
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import {
|
||||||
|
Combobox,
|
||||||
|
ComboboxInput,
|
||||||
|
ComboboxButton,
|
||||||
|
ComboboxOptions,
|
||||||
|
ComboboxOption,
|
||||||
|
} from '@headlessui/vue';
|
||||||
|
|
||||||
|
import { chevronsUpDown, check } from '../../../data/iconNodes';
|
||||||
|
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
|
||||||
|
|
||||||
|
const model = defineModel<string | undefined>();
|
||||||
|
const props = defineProps<{
|
||||||
|
versions: string[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let query = ref('');
|
||||||
|
|
||||||
|
let filteredVersions = computed(() =>
|
||||||
|
query.value === ''
|
||||||
|
? [...props.versions]
|
||||||
|
: props.versions.filter((version) =>
|
||||||
|
version
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, '')
|
||||||
|
.includes(query.value.toLowerCase().replace(/\s+/g, '')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits(['focus']);
|
||||||
|
|
||||||
|
const ChevronUpDown = createLucideIcon('ChevronUpDown', chevronsUpDown);
|
||||||
|
const Check = createLucideIcon('Check', check);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Combobox v-model="model">
|
||||||
|
<div class="combobox-container">
|
||||||
|
<div class="combobox-input-container">
|
||||||
|
<ComboboxInput
|
||||||
|
class="combobox-input"
|
||||||
|
placeholder="Latest version"
|
||||||
|
@change="query = $event.target.value"
|
||||||
|
@focus="emit('focus')"
|
||||||
|
/>
|
||||||
|
<ComboboxButton class="combobox-button">
|
||||||
|
<ChevronUpDown
|
||||||
|
class="icon-chevron"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</ComboboxButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ComboboxOptions class="combobox-options">
|
||||||
|
<div
|
||||||
|
v-if="versions.length === 0 && query !== ''"
|
||||||
|
class="combobox-no-results"
|
||||||
|
>
|
||||||
|
No match!
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ComboboxOption
|
||||||
|
v-for="version in filteredVersions"
|
||||||
|
as="template"
|
||||||
|
:key="version"
|
||||||
|
:value="version"
|
||||||
|
v-slot="{ selected, active }"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="combobox-option"
|
||||||
|
:class="{ active: active }"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="combobox-option-text"
|
||||||
|
:class="{ selected: selected }"
|
||||||
|
>
|
||||||
|
{{ version }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="selected"
|
||||||
|
class="combobox-option-icon"
|
||||||
|
:class="{ active: active }"
|
||||||
|
>
|
||||||
|
<Check
|
||||||
|
class="icon-check"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ComboboxOption>
|
||||||
|
</ComboboxOptions>
|
||||||
|
</div>
|
||||||
|
</Combobox>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.combobox-input-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
min-width: 160px;
|
||||||
|
cursor: default;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
text-align: left;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-input {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: border-color 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-input:hover,
|
||||||
|
.combobox-input:focus {
|
||||||
|
border-color: var(--vp-c-brand);
|
||||||
|
background: var(--vp-c-bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
padding: 9px 8px;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-chevron {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-options {
|
||||||
|
position: absolute;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 160px;
|
||||||
|
padding: 12px;
|
||||||
|
min-width: 128px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
background-color: var(--vp-c-bg-elv);
|
||||||
|
box-shadow: var(--vp-shadow-3);
|
||||||
|
transition: background-color 0.5s;
|
||||||
|
max-height: 240px;
|
||||||
|
outline: none;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-no-results {
|
||||||
|
position: relative;
|
||||||
|
cursor: default;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-option {
|
||||||
|
position: relative;
|
||||||
|
cursor: default;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition:
|
||||||
|
background-color 0.25s,
|
||||||
|
color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-option.active {
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
background-color: var(--vp-c-default-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-option-text {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-option-text.selected {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-option-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
color: var(--vp-c-brand) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-option-icon.active {
|
||||||
|
color: var(--vp-c-brand) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-check {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
17
docs/.vitepress/theme/composables/useFetchVersionIcons.ts
Normal file
17
docs/.vitepress/theme/composables/useFetchVersionIcons.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { useFetch } from '@vueuse/core';
|
||||||
|
import { computed, Ref } from 'vue';
|
||||||
|
|
||||||
|
const useFetchVersionIcons = (version: Ref<string | undefined>) =>{
|
||||||
|
const fetchUrl = computed(() => {
|
||||||
|
if(version.value == null ) return ''
|
||||||
|
return `https://unpkg.com/lucide-static@${version.value}/icon-nodes.json`
|
||||||
|
})
|
||||||
|
return useFetch<Record<string, string>>(
|
||||||
|
fetchUrl,
|
||||||
|
{
|
||||||
|
refetch: true
|
||||||
|
}
|
||||||
|
).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useFetchVersionIcons;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type IconNode } from 'lucide-vue-next/src/types';
|
import { IconNode } from 'lucide-vue-next/src/createLucideIcon';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
declare module '*.vue' {
|
declare module '*.vue' {
|
||||||
@@ -20,6 +20,5 @@ declare module 'node:module' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare module '*.node.json' {
|
declare module '*.node.json' {
|
||||||
const value: IconNode;
|
export default IconNode;
|
||||||
export default value;
|
|
||||||
}
|
}
|
||||||
@@ -102,16 +102,10 @@ The example below imports all ES Modules, so exercise caution when using it. Imp
|
|||||||
|
|
||||||
### Icon Component Example
|
### Icon Component Example
|
||||||
|
|
||||||
```tsx
|
```jsx
|
||||||
import * as icons from 'lucide-react-native/icons';
|
import { icons } from 'lucide-react-native';
|
||||||
|
|
||||||
interface IconProps {
|
const Icon = ({ name, color, size }) => {
|
||||||
name: keyof typeof icons;
|
|
||||||
color?: string;
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Icon = ({ name, color, size }: IconProps) => {
|
|
||||||
const LucideIcon = icons[name];
|
const LucideIcon = icons[name];
|
||||||
|
|
||||||
return <LucideIcon color={color} size={size} />;
|
return <LucideIcon color={color} size={size} />;
|
||||||
@@ -122,11 +116,11 @@ export default Icon;
|
|||||||
|
|
||||||
#### Using the Icon Component
|
#### Using the Icon Component
|
||||||
|
|
||||||
```tsx
|
```jsx
|
||||||
import Icon from './Icon';
|
import Icon from './Icon';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return <Icon name="House" />;
|
return <Icon name="house" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -9,14 +9,14 @@
|
|||||||
"docs:build": "pnpm run /^prebuild:.*/ && vitepress build",
|
"docs:build": "pnpm run /^prebuild:.*/ && vitepress build",
|
||||||
"docs:preview": "vitepress preview",
|
"docs:preview": "vitepress preview",
|
||||||
"build:docs": "vitepress build",
|
"build:docs": "vitepress build",
|
||||||
"prebuild:iconNodes": "node ./scripts/writeIconNodes.mjs",
|
"prebuild:iconNodes": "node ./scripts/writeIconNodes.mts",
|
||||||
"prebuild:metaJson": "node ./scripts/writeIconMetaIndex.mjs",
|
"prebuild:metaJson": "node ./scripts/writeIconMetaIndex.mts",
|
||||||
"prebuild:releaseJson": "node ./scripts/writeReleaseMetadata.mjs",
|
"prebuild:releaseJson": "node ./scripts/writeReleaseMetadata.mts",
|
||||||
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mjs",
|
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mts",
|
||||||
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mjs",
|
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mts",
|
||||||
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mjs",
|
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mts",
|
||||||
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mjs",
|
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mts",
|
||||||
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mjs",
|
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mts",
|
||||||
"dev": "npx nitropack dev",
|
"dev": "npx nitropack dev",
|
||||||
"prebuild:api": "npx nitropack prepare",
|
"prebuild:api": "npx nitropack prepare",
|
||||||
"build:api": "npx nitropack build",
|
"build:api": "npx nitropack build",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const getRelatedIcons = (currentIcon, icons) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const iconsMetaDataPromises = svgFiles.map(async (iconName) => {
|
const iconsMetaDataPromises = svgFiles.map(async (iconName) => {
|
||||||
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`);
|
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`, 'utf-8');
|
||||||
const metaData = JSON.parse(metaDataFileContent);
|
const metaData = JSON.parse(metaDataFileContent);
|
||||||
|
|
||||||
const name = iconName.replace('.json', '');
|
const name = iconName.replace('.json', '');
|
||||||
@@ -29,7 +29,7 @@ if (!fs.existsSync(releaseMetaDataDirectory)) {
|
|||||||
fs.mkdirSync(releaseMetaDataDirectory);
|
fs.mkdirSync(releaseMetaDataDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchAllReleases = async () => {
|
export const fetchAllReleases = async () => {
|
||||||
await git.fetch('https://github.com/lucide-icons/lucide.git', '--tags');
|
await git.fetch('https://github.com/lucide-icons/lucide.git', '--tags');
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
@@ -73,7 +73,7 @@ const comparisonsPromises = tags.map(async (tag, index) => {
|
|||||||
const comparisons = await Promise.all(comparisonsPromises);
|
const comparisons = await Promise.all(comparisonsPromises);
|
||||||
const newReleaseMetaData = {};
|
const newReleaseMetaData = {};
|
||||||
|
|
||||||
comparisons.forEach(({ tag, iconFiles, date } = {}) => {
|
comparisons.forEach(({ tag, iconFiles, date } = {} as typeof comparisons[number]) => {
|
||||||
if (tag == null) return;
|
if (tag == null) return;
|
||||||
|
|
||||||
iconFiles.forEach(({ status, file, renamedFile }) => {
|
iconFiles.forEach(({ status, file, renamedFile }) => {
|
||||||
@@ -11,7 +11,7 @@ const iconMetaData = await getIconMetaData(path.resolve(scriptDir, '../../icons'
|
|||||||
const iconAliasesRedirectRoutes = Object.entries(iconMetaData)
|
const iconAliasesRedirectRoutes = Object.entries(iconMetaData)
|
||||||
.filter(([, { aliases }]) => aliases?.length)
|
.filter(([, { aliases }]) => aliases?.length)
|
||||||
.map(([iconName, { aliases }]) => {
|
.map(([iconName, { aliases }]) => {
|
||||||
const aliasRouteMatches = aliases.map((alias) => alias.name).join('|');
|
const aliasRouteMatches = aliases.map((alias) => typeof alias === 'string' ? alias : alias.name).join('|');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
src: `/icons/${aliasRouteMatches}`,
|
src: `/icons/${aliasRouteMatches}`,
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "../icon.schema.json",
|
|
||||||
"contributors": [
|
|
||||||
"nickveles"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"cannabis",
|
|
||||||
"weed",
|
|
||||||
"leaf"
|
|
||||||
],
|
|
||||||
"categories": [
|
|
||||||
"nature"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M12 22v-4c1.5 1.5 3.5 3 6 3 0-1.5-.5-3.5-2-5" />
|
|
||||||
<path d="M13.988 8.327C13.902 6.054 13.365 3.82 12 2a9.3 9.3 0 0 0-1.445 2.9" />
|
|
||||||
<path d="M17.375 11.725C18.882 10.53 21 7.841 21 6c-2.324 0-5.08 1.296-6.662 2.684" />
|
|
||||||
<path d="m2 2 20 20" />
|
|
||||||
<path d="M21.024 15.378A15 15 0 0 0 22 15c-.426-1.279-2.67-2.557-4.25-2.907" />
|
|
||||||
<path d="M6.995 6.992C5.714 6.4 4.29 6 3 6c0 2 2.5 5 4 6-1.5 0-4.5 1.5-5 3 3.5 1.5 6 1 6 1-1.5 1.5-2 3.5-2 5 2.5 0 4.5-1.5 6-3" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 681 B |
@@ -9,8 +9,8 @@
|
|||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path d="M11 7 6 2" />
|
<path d="M19 12H2" />
|
||||||
<path d="M18.992 12H2.041" />
|
|
||||||
<path d="M21.145 18.38A3.34 3.34 0 0 1 20 16.5a3.3 3.3 0 0 1-1.145 1.88c-.575.46-.855 1.02-.855 1.595A2 2 0 0 0 20 22a2 2 0 0 0 2-2.025c0-.58-.285-1.13-.855-1.595" />
|
<path d="M21.145 18.38A3.34 3.34 0 0 1 20 16.5a3.3 3.3 0 0 1-1.145 1.88c-.575.46-.855 1.02-.855 1.595A2 2 0 0 0 20 22a2 2 0 0 0 2-2.025c0-.58-.285-1.13-.855-1.595" />
|
||||||
|
<path d="m6 2 5 5" />
|
||||||
<path d="m8.5 4.5 2.148-2.148a1.205 1.205 0 0 1 1.704 0l7.296 7.296a1.205 1.205 0 0 1 0 1.704l-7.592 7.592a3.615 3.615 0 0 1-5.112 0l-3.888-3.888a3.615 3.615 0 0 1 0-5.112L5.67 7.33" />
|
<path d="m8.5 4.5 2.148-2.148a1.205 1.205 0 0 1 1.704 0l7.296 7.296a1.205 1.205 0 0 1 0 1.704l-7.592 7.592a3.615 3.615 0 0 1-5.112 0l-3.888-3.888a3.615 3.615 0 0 1 0-5.112L5.67 7.33" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 622 B After Width: | Height: | Size: 613 B |
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "../icon.schema.json",
|
|
||||||
"contributors": [
|
|
||||||
"Alportan",
|
|
||||||
"karsa-mistmere"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"mineral",
|
|
||||||
"geology",
|
|
||||||
"nature",
|
|
||||||
"solid",
|
|
||||||
"pebble",
|
|
||||||
"crystal",
|
|
||||||
"ore",
|
|
||||||
"hard",
|
|
||||||
"coal",
|
|
||||||
"stone",
|
|
||||||
"rock",
|
|
||||||
"boulder"
|
|
||||||
],
|
|
||||||
"categories": [
|
|
||||||
"nature"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M11.264 2.205A4 4 0 0 0 6.42 4.211l-4 8a4 4 0 0 0 1.359 5.117l6 4a4 4 0 0 0 4.438 0l6-4a4 4 0 0 0 1.576-4.592l-2-6a4 4 0 0 0-2.53-2.53z" />
|
|
||||||
<path d="M11.99 22 14 12l7.822 3.184" />
|
|
||||||
<path d="M14 12 8.47 2.302" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 435 B |
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "../icon.schema.json",
|
|
||||||
"contributors": [
|
|
||||||
"karsa-mistmere"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"toolkit",
|
|
||||||
"tools",
|
|
||||||
"trunk",
|
|
||||||
"chest",
|
|
||||||
"box",
|
|
||||||
"storage",
|
|
||||||
"utility",
|
|
||||||
"utilities",
|
|
||||||
"container",
|
|
||||||
"kit",
|
|
||||||
"set",
|
|
||||||
"repair",
|
|
||||||
"fix",
|
|
||||||
"service",
|
|
||||||
"maintenance",
|
|
||||||
"mechanic",
|
|
||||||
"workshop",
|
|
||||||
"construction",
|
|
||||||
"hardware",
|
|
||||||
"equipment",
|
|
||||||
"gear",
|
|
||||||
"handyman",
|
|
||||||
"engineering",
|
|
||||||
"craft",
|
|
||||||
"diy"
|
|
||||||
],
|
|
||||||
"categories": [
|
|
||||||
"tools",
|
|
||||||
"home"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M16 12v4" />
|
|
||||||
<path d="M16 6a2 2 0 0 1 1.414.586l4 4A2 2 0 0 1 22 12v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 .586-1.414l4-4A2 2 0 0 1 8 6z" />
|
|
||||||
<path d="M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" />
|
|
||||||
<path d="M2 14h20" />
|
|
||||||
<path d="M8 12v4" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 471 B |
0
lucide-font/lucide.svg
Normal file
0
lucide-font/lucide.svg
Normal file
@@ -16,7 +16,7 @@
|
|||||||
"lucide-svelte": "pnpm --filter lucide-svelte",
|
"lucide-svelte": "pnpm --filter lucide-svelte",
|
||||||
"lucide-static": "pnpm --filter lucide-static",
|
"lucide-static": "pnpm --filter lucide-static",
|
||||||
"build:outline-icons": "pnpm --filter outline-svg start",
|
"build:outline-icons": "pnpm --filter outline-svg start",
|
||||||
"build:font": "pnpm --filter build-font start",
|
"build:font": "pnpm --filter docs prebuild:releaseJson && pnpm --filter build-font start",
|
||||||
"optimize": "node ./scripts/optimizeSvgs.mts",
|
"optimize": "node ./scripts/optimizeSvgs.mts",
|
||||||
"addjsons": "node ./scripts/addMissingIconJsonFiles.mts",
|
"addjsons": "node ./scripts/addMissingIconJsonFiles.mts",
|
||||||
"checkIcons": "node ./scripts/checkIconsAndCategories.mts",
|
"checkIcons": "node ./scripts/checkIconsAndCategories.mts",
|
||||||
|
|||||||
@@ -24,23 +24,11 @@
|
|||||||
"author": "Eric Fennis",
|
"author": "Eric Fennis",
|
||||||
"amdName": "lucide-react-native",
|
"amdName": "lucide-react-native",
|
||||||
"main": "dist/cjs/lucide-react-native.js",
|
"main": "dist/cjs/lucide-react-native.js",
|
||||||
|
"main:umd": "dist/umd/lucide-react-native.js",
|
||||||
"module": "dist/esm/lucide-react-native.js",
|
"module": "dist/esm/lucide-react-native.js",
|
||||||
|
"unpkg": "dist/umd/lucide-react-native.min.js",
|
||||||
"typings": "dist/lucide-react-native.d.ts",
|
"typings": "dist/lucide-react-native.d.ts",
|
||||||
"react-native": "dist/esm/lucide-react-native.js",
|
"react-native": "dist/esm/lucide-react-native.js",
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"types": "./dist/lucide-react-native.d.ts",
|
|
||||||
"import": "./dist/esm/lucide-react-native.js",
|
|
||||||
"browser": "./dist/esm/lucide-react-native.js",
|
|
||||||
"require": "./dist/cjs/lucide-react-native.js"
|
|
||||||
},
|
|
||||||
"./icons": {
|
|
||||||
"types": "./dist/icons.d.ts",
|
|
||||||
"import": "./dist/esm/icons/index.js",
|
|
||||||
"browser": "./dist/esm/icons/index.js",
|
|
||||||
"require": "./dist/cjs/icons/index.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import pkg from './package.json' with { type: 'json' };
|
|||||||
const packageName = 'LucideReact';
|
const packageName = 'LucideReact';
|
||||||
const outputFileName = 'lucide-react-native';
|
const outputFileName = 'lucide-react-native';
|
||||||
const outputDir = 'dist';
|
const outputDir = 'dist';
|
||||||
const inputs = ['src/lucide-react-native.ts', 'src/icons/index.ts'];
|
const inputs = ['src/lucide-react-native.ts'];
|
||||||
const bundles = [
|
const bundles = [
|
||||||
{
|
{
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
@@ -60,16 +60,6 @@ export default [
|
|||||||
],
|
],
|
||||||
plugins: [dts()],
|
plugins: [dts()],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
input: inputs[1],
|
|
||||||
output: [
|
|
||||||
{
|
|
||||||
file: `dist/icons.d.ts`,
|
|
||||||
format: 'es',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
plugins: [dts()],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
input: `src/${outputFileName}.suffixed.ts`,
|
input: `src/${outputFileName}.suffixed.ts`,
|
||||||
output: [
|
output: [
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
|
|||||||
absoluteStrokeWidth,
|
absoluteStrokeWidth,
|
||||||
children,
|
children,
|
||||||
iconNode,
|
iconNode,
|
||||||
className,
|
|
||||||
...rest
|
...rest
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
@@ -47,7 +46,6 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
|
|||||||
{
|
{
|
||||||
ref,
|
ref,
|
||||||
...defaultAttributes,
|
...defaultAttributes,
|
||||||
className,
|
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
...customAttrs,
|
...customAttrs,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './icons';
|
export * from './icons';
|
||||||
|
export * as icons from './icons';
|
||||||
export * from './aliases/prefixed';
|
export * from './aliases/prefixed';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './icons';
|
export * from './icons';
|
||||||
|
export * as icons from './icons';
|
||||||
export * from './aliases/suffixed';
|
export * from './aliases/suffixed';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './icons';
|
export * from './icons';
|
||||||
|
export * as icons from './icons';
|
||||||
export * from './aliases';
|
export * from './aliases';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
||||||
|
|||||||
9841
pnpm-lock.yaml
generated
9841
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
149
tools/build-font/main.ts
Normal file
149
tools/build-font/main.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import { readJson } from 'fs-extra/esm';
|
||||||
|
import svgtofont from 'svgtofont';
|
||||||
|
import getArgumentOptions from 'minimist';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const fontName = 'lucide';
|
||||||
|
const classNamePrefix = 'icon';
|
||||||
|
const startUnicode = 57400;
|
||||||
|
|
||||||
|
const inputDir = path.join(process.cwd(), '../../', 'outlined');
|
||||||
|
const cliArguments = getArgumentOptions(process.argv.slice(2));
|
||||||
|
const { outputDir = 'lucide-font' } = cliArguments;
|
||||||
|
const targetDir = path.join(process.cwd(), '../../', outputDir);
|
||||||
|
const releaseMetaDataDir = path.join(process.cwd(), '../../', 'docs/.vitepress/data');
|
||||||
|
const releaseMetaDataPath = path.resolve(releaseMetaDataDir, 'releaseMetaData.json');
|
||||||
|
|
||||||
|
const releaseMetaData = convertReleaseMetaData(await getReleaseMetaData());
|
||||||
|
|
||||||
|
async function getReleaseMetaData() {
|
||||||
|
let releaseMetaData = {};
|
||||||
|
try {
|
||||||
|
releaseMetaData = await readJson(releaseMetaDataPath);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error('Execution stopped because no release information was found.');
|
||||||
|
}
|
||||||
|
return releaseMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Releases = Record<string, ReleaseMetaData>;
|
||||||
|
|
||||||
|
type ReleaseMetaData = {
|
||||||
|
createdRelease: {
|
||||||
|
version: string;
|
||||||
|
date: string;
|
||||||
|
};
|
||||||
|
changedRelease: {
|
||||||
|
version: string;
|
||||||
|
date: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReleaseMetaDataWithName = ReleaseMetaData & {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function convertReleaseMetaData(releases: Releases) {
|
||||||
|
return Object.entries(releases)
|
||||||
|
.map(([key, data]) => ({
|
||||||
|
...data,
|
||||||
|
name: key,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => sortMultiple(a, b, [sortByCreatedReleaseDate, sortByName]))
|
||||||
|
.map((value, index) => ({ ...value, index }))
|
||||||
|
.map((value, index) => ({
|
||||||
|
...value,
|
||||||
|
unicode: index + startUnicode,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollatorFunction = (a: ReleaseMetaDataWithName, b: ReleaseMetaDataWithName) => number;
|
||||||
|
|
||||||
|
function sortMultiple(
|
||||||
|
a: ReleaseMetaDataWithName,
|
||||||
|
b: ReleaseMetaDataWithName,
|
||||||
|
collators: CollatorFunction[] = [],
|
||||||
|
) {
|
||||||
|
const comparison = collators?.shift?.()?.(a, b) ?? 0;
|
||||||
|
if (comparison === 0 && collators.length > 0) return sortMultiple(a, b, collators);
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortByCreatedReleaseDate(a: ReleaseMetaDataWithName, b: ReleaseMetaDataWithName) {
|
||||||
|
const [dateA, dateB] = [a, b].map((value) => new Date(value.createdRelease.date).valueOf());
|
||||||
|
return Number(dateA > dateB) - Number(dateA < dateB);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortByName(a: ReleaseMetaDataWithName, b: ReleaseMetaDataWithName) {
|
||||||
|
return new Intl.Collator('en-US').compare(a.name, b.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIconUnicode(name: string): [string, number] {
|
||||||
|
const { unicode } = releaseMetaData.find(({ name: iconName }) => iconName === name) ?? {
|
||||||
|
unicode: startUnicode,
|
||||||
|
};
|
||||||
|
return [String.fromCharCode(unicode), startUnicode];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
console.time('Font generation');
|
||||||
|
try {
|
||||||
|
await svgtofont({
|
||||||
|
src: path.resolve(process.cwd(), inputDir),
|
||||||
|
dist: path.resolve(process.cwd(), targetDir),
|
||||||
|
// styleTemplates: path.resolve(process.cwd(), 'styles'), // Add different templates if needed
|
||||||
|
fontName,
|
||||||
|
classNamePrefix,
|
||||||
|
css: {
|
||||||
|
fontSize: 'inherit',
|
||||||
|
},
|
||||||
|
emptyDist: true,
|
||||||
|
useCSSVars: false,
|
||||||
|
outSVGReact: false,
|
||||||
|
outSVGPath: false,
|
||||||
|
svgicons2svgfont: {
|
||||||
|
fontHeight: 1000, // At least 1000 is recommended
|
||||||
|
normalize: false,
|
||||||
|
},
|
||||||
|
generateInfoData: true,
|
||||||
|
website: {
|
||||||
|
title: 'Lucide',
|
||||||
|
logo: undefined,
|
||||||
|
meta: {
|
||||||
|
description: 'Lucide icons as TTF/EOT/WOFF/WOFF2/SVG.',
|
||||||
|
keywords: 'Lucide,TTF,EOT,WOFF,WOFF2,SVG',
|
||||||
|
},
|
||||||
|
corners: {
|
||||||
|
url: 'https://github.com/lucide-icons/lucide',
|
||||||
|
width: 62, // default: 60
|
||||||
|
height: 62, // default: 60
|
||||||
|
bgColor: '#dc3545', // default: '#151513'
|
||||||
|
},
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
title: 'GitHub',
|
||||||
|
url: 'https://github.com/lucide-icons/lucide',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Feedback',
|
||||||
|
url: 'https://github.com/lucide-icons/lucide/issues',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Font Class',
|
||||||
|
url: 'index.html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Unicode',
|
||||||
|
url: 'unicode.html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
getIconUnicode,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
console.timeEnd('Font generation');
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
"main": "main.ts",
|
"main": "main.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./src/main.ts"
|
"start": "node ./main.ts"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -14,11 +14,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"minimist": "^1.2.8",
|
"minimist": "^1.2.8",
|
||||||
"oslllo-svg-fixer": "^5.0.0",
|
|
||||||
"svgtofont": "^6.5.0"
|
"svgtofont": "^6.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lucide/helpers": "workspace:*",
|
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/minimist": "^1.2.5",
|
"@types/minimist": "^1.2.5",
|
||||||
"@types/node": "^22"
|
"@types/node": "^22"
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import { type IconAliases } from "@lucide/helpers";
|
|
||||||
import path from "path";
|
|
||||||
import { promises as fs } from 'fs';
|
|
||||||
import { cwd } from "process";
|
|
||||||
|
|
||||||
export type CodePoints = Record<string, number>;
|
|
||||||
|
|
||||||
async function getLatestCodePoints(): Promise<CodePoints> {
|
|
||||||
// This is for the first release where no codepoints.json exists yet
|
|
||||||
const codepointsContents = await fs.readFile(path.join(cwd(), 'codepoints.json'), 'utf-8')
|
|
||||||
|
|
||||||
return JSON.parse(codepointsContents) as CodePoints
|
|
||||||
|
|
||||||
// Next releases will use the codepoints.json from latest release in lucide-static.
|
|
||||||
// const codepointsContents = await fetch('https://unpkg.com/lucide-static@latest/font/codepoints.json')
|
|
||||||
// return codepointsContents.json() as Promise<CodePoints>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AllocateCodePointsOptions {
|
|
||||||
saveCodePoints?: boolean;
|
|
||||||
iconsWithAliases: IconAliases
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function allocateCodePoints({
|
|
||||||
saveCodePoints = false,
|
|
||||||
iconsWithAliases
|
|
||||||
}: AllocateCodePointsOptions): Promise<CodePoints> {
|
|
||||||
const baseCodePoints = await getLatestCodePoints()
|
|
||||||
|
|
||||||
const endCodePoint = Math.max(...Object.values(baseCodePoints))
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
iconsWithAliases.map(async ([iconName, aliases]) => {
|
|
||||||
if(!baseCodePoints[iconName]) {
|
|
||||||
console.log('Code point not found creating new one for', iconName);
|
|
||||||
baseCodePoints[iconName] = endCodePoint + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
aliases.forEach((alias, index) => {
|
|
||||||
if (baseCodePoints[alias]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Code point not found creating new one for');
|
|
||||||
|
|
||||||
baseCodePoints[alias] = endCodePoint + index + 1;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
if (saveCodePoints) {
|
|
||||||
await fs.writeFile(
|
|
||||||
path.join(cwd(), 'codepoints.json'),
|
|
||||||
JSON.stringify(baseCodePoints, null, 2),
|
|
||||||
'utf-8'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseCodePoints;
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
import svgtofont from 'svgtofont';
|
|
||||||
import { type CodePoints } from './allocateCodepoints.ts';
|
|
||||||
|
|
||||||
interface BuildFontOptions {
|
|
||||||
inputDir: string;
|
|
||||||
targetDir: string;
|
|
||||||
fontName: string;
|
|
||||||
classNamePrefix: string;
|
|
||||||
codePoints: CodePoints
|
|
||||||
startUnicode: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function buildFont({
|
|
||||||
inputDir,
|
|
||||||
targetDir,
|
|
||||||
fontName,
|
|
||||||
classNamePrefix,
|
|
||||||
codePoints,
|
|
||||||
startUnicode
|
|
||||||
}: BuildFontOptions) {
|
|
||||||
console.time('Font generation');
|
|
||||||
try {
|
|
||||||
await svgtofont({
|
|
||||||
src: inputDir,
|
|
||||||
dist: targetDir,
|
|
||||||
fontName,
|
|
||||||
classNamePrefix,
|
|
||||||
css: {
|
|
||||||
fontSize: 'inherit',
|
|
||||||
},
|
|
||||||
emptyDist: true,
|
|
||||||
useCSSVars: false,
|
|
||||||
outSVGReact: false,
|
|
||||||
outSVGPath: false,
|
|
||||||
addLigatures: true,
|
|
||||||
svgicons2svgfont: {
|
|
||||||
fontHeight: 1000, // At least 1000 is recommended
|
|
||||||
normalize: false,
|
|
||||||
},
|
|
||||||
generateInfoData: true,
|
|
||||||
website: {
|
|
||||||
title: 'Lucide',
|
|
||||||
logo: undefined,
|
|
||||||
meta: {
|
|
||||||
description: 'Lucide icons as TTF/EOT/WOFF/WOFF2/SVG.',
|
|
||||||
keywords: 'Lucide,TTF,EOT,WOFF,WOFF2,SVG',
|
|
||||||
},
|
|
||||||
corners: {
|
|
||||||
url: 'https://github.com/lucide-icons/lucide',
|
|
||||||
width: 62, // default: 60
|
|
||||||
height: 62, // default: 60
|
|
||||||
bgColor: '#dc3545', // default: '#151513'
|
|
||||||
},
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
title: 'GitHub',
|
|
||||||
url: 'https://github.com/lucide-icons/lucide',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Feedback',
|
|
||||||
url: 'https://github.com/lucide-icons/lucide/issues',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Font Class',
|
|
||||||
url: 'index.html',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Unicode',
|
|
||||||
url: 'unicode.html',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
getIconUnicode: (name: string) => {
|
|
||||||
if (!codePoints[name]) {
|
|
||||||
throw new Error(`No codepoint found for icon: ${name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const unicode = codePoints[name];
|
|
||||||
return [String.fromCharCode(unicode), startUnicode];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
console.timeEnd('Font generation');
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { type IconAliases } from "@lucide/helpers";
|
|
||||||
import { type CodePoints } from "./allocateCodepoints.ts";
|
|
||||||
|
|
||||||
export function hasMissingCodePoints(iconsWithAliases: IconAliases, codePoints: CodePoints): boolean {
|
|
||||||
return iconsWithAliases.map(([iconName, aliases]) => ([iconName, ...aliases]))
|
|
||||||
.flat()
|
|
||||||
.some(name => {
|
|
||||||
if (!codePoints?.[name]) {
|
|
||||||
console.log(`Missing code point for icon/alias: ${name}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import getArgumentOptions from 'minimist';
|
|
||||||
import path from 'path';
|
|
||||||
import { promises as fs } from 'fs';
|
|
||||||
|
|
||||||
import { getAllIconAliases } from '@lucide/helpers';
|
|
||||||
import { outlineSVG } from './outlineSVGs.ts';
|
|
||||||
import { allocateCodePoints } from './allocateCodepoints.ts';
|
|
||||||
import { buildFont } from './buildFont.ts';
|
|
||||||
import { hasMissingCodePoints } from './helpers.ts';
|
|
||||||
|
|
||||||
const fontName = 'lucide';
|
|
||||||
const classNamePrefix = 'icon';
|
|
||||||
const startUnicode = 57400;
|
|
||||||
const outputDir = 'lucide-font';
|
|
||||||
|
|
||||||
const {
|
|
||||||
saveCodePoints = false,
|
|
||||||
} = getArgumentOptions(process.argv.slice(2)) ?? {}
|
|
||||||
|
|
||||||
const repoRoot = path.join(process.cwd(), '../../')
|
|
||||||
const iconsDir = path.join(repoRoot, 'icons');
|
|
||||||
const outlinedDir = path.join(repoRoot, 'outlined');
|
|
||||||
const targetDir = path.join(repoRoot, outputDir);
|
|
||||||
|
|
||||||
const iconsWithAliases = await getAllIconAliases(iconsDir)
|
|
||||||
|
|
||||||
await outlineSVG({
|
|
||||||
iconsDir,
|
|
||||||
outlinedDir,
|
|
||||||
iconsWithAliases
|
|
||||||
});
|
|
||||||
|
|
||||||
const codePoints = await allocateCodePoints({
|
|
||||||
saveCodePoints,
|
|
||||||
iconsWithAliases
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
if (hasMissingCodePoints(iconsWithAliases, codePoints)) {
|
|
||||||
throw new Error('Some icons or aliases are missing code points. See log for details.');
|
|
||||||
}
|
|
||||||
|
|
||||||
await buildFont({
|
|
||||||
inputDir: outlinedDir,
|
|
||||||
targetDir,
|
|
||||||
fontName,
|
|
||||||
classNamePrefix,
|
|
||||||
codePoints,
|
|
||||||
startUnicode,
|
|
||||||
});
|
|
||||||
|
|
||||||
await fs.copyFile(path.join(process.cwd(), 'codepoints.json'), path.join(targetDir, 'codepoints.json'));
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { promises as fs } from 'fs';
|
|
||||||
import SVGFixer from 'oslllo-svg-fixer';
|
|
||||||
import { getAllIconAliases, type IconAliases } from '@lucide/helpers';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
interface OutlineSVGOptions {
|
|
||||||
iconsDir: string;
|
|
||||||
outlinedDir: string;
|
|
||||||
iconsWithAliases: IconAliases
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function outlineSVG({
|
|
||||||
iconsDir,
|
|
||||||
outlinedDir,
|
|
||||||
iconsWithAliases
|
|
||||||
}: OutlineSVGOptions) {
|
|
||||||
console.time('icon outliner');
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
await fs.mkdir(outlinedDir);
|
|
||||||
} catch (error) { } // eslint-disable-line no-empty
|
|
||||||
|
|
||||||
await SVGFixer(iconsDir, outlinedDir, {
|
|
||||||
showProgressBar: true,
|
|
||||||
traceResolution: 800,
|
|
||||||
}).fix();
|
|
||||||
|
|
||||||
console.log('Duplicate icons with aliases..');
|
|
||||||
|
|
||||||
await Promise.all(iconsWithAliases.map(async ([iconName, aliases]) => {
|
|
||||||
const sourcePath = path.join(outlinedDir, `${iconName}.svg`);
|
|
||||||
|
|
||||||
await Promise.all(aliases.map(async (aliasName) => {
|
|
||||||
const destinationPath = path.join(outlinedDir, `${aliasName}.svg`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fs.copyFile(sourcePath, destinationPath);
|
|
||||||
console.log(`Copied ${iconName}.svg to ${aliasName}.svg`);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(`Failed to copy ${sourcePath} to ${destinationPath}:`, err);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.timeEnd('icon outliner');
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
tools/build-font/styles/.gitkeep
Normal file
0
tools/build-font/styles/.gitkeep
Normal file
@@ -7,7 +7,6 @@ export * from './src/appendFile.ts';
|
|||||||
export * from './src/writeFile.ts';
|
export * from './src/writeFile.ts';
|
||||||
export * from './src/writeFileIfNotExists.ts';
|
export * from './src/writeFileIfNotExists.ts';
|
||||||
export * from './src/readAllMetadata.ts';
|
export * from './src/readAllMetadata.ts';
|
||||||
export * from './src/getAllIconAliases.ts';
|
|
||||||
export * from './src/readMetadata.ts';
|
export * from './src/readMetadata.ts';
|
||||||
export * from './src/readSvgDirectory.ts';
|
export * from './src/readSvgDirectory.ts';
|
||||||
export * from './src/readSvg.ts';
|
export * from './src/readSvg.ts';
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { readAllMetadata } from "./readAllMetadata.ts";
|
|
||||||
|
|
||||||
export type IconAliases = [iconName: string, aliases: string[]][];
|
|
||||||
|
|
||||||
export const getAllIconAliases = async (iconsDir: string): Promise<IconAliases> => {
|
|
||||||
const metaDataFiles = await readAllMetadata(iconsDir)
|
|
||||||
|
|
||||||
return Object.entries(metaDataFiles).map(([iconName, metadata]) => {
|
|
||||||
const { aliases } = metadata;
|
|
||||||
|
|
||||||
if (!aliases?.length) return [iconName, []];
|
|
||||||
|
|
||||||
const aliasesNames = aliases.map(alias =>
|
|
||||||
typeof alias === 'string' ? alias : alias.name,
|
|
||||||
);
|
|
||||||
|
|
||||||
return [iconName, aliasesNames]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { readMetadata } from './readMetadata.ts';
|
import { readMetadata } from './readMetadata.ts';
|
||||||
import { type IconMetadata } from '../../build-icons/types.ts';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads metadata from the icons/categories directories
|
* Reads metadata from the icons/categories directories
|
||||||
@@ -9,7 +8,7 @@ import { type IconMetadata } from '../../build-icons/types.ts';
|
|||||||
* @param {string} directory
|
* @param {string} directory
|
||||||
* @returns {object} A map of icon or category metadata
|
* @returns {object} A map of icon or category metadata
|
||||||
*/
|
*/
|
||||||
export const readAllMetadata = async (directory: string): Promise<Record<string, IconMetadata>> => {
|
export const readAllMetadata = async (directory: string): Promise<Record<string, unknown>> => {
|
||||||
const directoryContent = await fs.readdir(directory);
|
const directoryContent = await fs.readdir(directory);
|
||||||
|
|
||||||
const metaDataPromises = directoryContent
|
const metaDataPromises = directoryContent
|
||||||
@@ -17,7 +16,6 @@ export const readAllMetadata = async (directory: string): Promise<Record<string,
|
|||||||
.map(async (file) => [path.basename(file, '.json'), await readMetadata(file, directory)]);
|
.map(async (file) => [path.basename(file, '.json'), await readMetadata(file, directory)]);
|
||||||
|
|
||||||
const metadata = await Promise.all(metaDataPromises);
|
const metadata = await Promise.all(metaDataPromises);
|
||||||
|
|
||||||
if (metadata.length === 0) {
|
if (metadata.length === 0) {
|
||||||
throw new Error(`No metadata files found in directory: ${directory}`);
|
throw new Error(`No metadata files found in directory: ${directory}`);
|
||||||
}
|
}
|
||||||
|
|||||||
3
tools/outline-svg/README.md
Normal file
3
tools/outline-svg/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# @lucide/outline-svg
|
||||||
|
|
||||||
|
A internal used package to outline SVGs.
|
||||||
29
tools/outline-svg/main.ts
Normal file
29
tools/outline-svg/main.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import SVGFixer from 'oslllo-svg-fixer';
|
||||||
|
import getArgumentOptions from 'minimist';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const inputDir = path.join(process.cwd(), '../../icons');
|
||||||
|
const cliArguments = getArgumentOptions(process.argv.slice(2));
|
||||||
|
const { outputDir = 'outlined' } = cliArguments;
|
||||||
|
const targetDir = path.join(process.cwd(), '../../', outputDir);
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
console.time('icon outliner');
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
await fs.mkdir(targetDir);
|
||||||
|
} catch (error) {} // eslint-disable-line no-empty
|
||||||
|
|
||||||
|
await SVGFixer(inputDir, targetDir, {
|
||||||
|
showProgressBar: true,
|
||||||
|
traceResolution: 800,
|
||||||
|
}).fix();
|
||||||
|
|
||||||
|
console.timeEnd('icon outliner');
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
18
tools/outline-svg/package.json
Normal file
18
tools/outline-svg/package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "@lucide/outline-svg",
|
||||||
|
"description": "A internal used package to outline SVGs.",
|
||||||
|
"private": true,
|
||||||
|
"version": "2.0.0",
|
||||||
|
"main": "main.ts",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./main.ts"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.8",
|
||||||
|
"oslllo-svg-fixer": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
tools/outline-svg/tsconfig.json
Normal file
18
tools/outline-svg/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"declaration": true,
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "ESNext",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["esnext"],
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
},
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user