+4
docs/.vitepress/config.ts
+4
docs/.vitepress/config.ts
+186
docs/.vitepress/data/categoriesData.json
+186
docs/.vitepress/data/categoriesData.json
···
1
+
[
2
+
{
3
+
"name": "accessibility",
4
+
"title": "Accessibility"
5
+
},
6
+
{
7
+
"name": "account",
8
+
"title": "Accounts & access"
9
+
},
10
+
{
11
+
"name": "animals",
12
+
"title": "Animals"
13
+
},
14
+
{
15
+
"name": "arrows",
16
+
"title": "Arrows"
17
+
},
18
+
{
19
+
"name": "brands",
20
+
"title": "Brands"
21
+
},
22
+
{
23
+
"name": "buildings",
24
+
"title": "Buildings"
25
+
},
26
+
{
27
+
"name": "charts",
28
+
"title": "Charts"
29
+
},
30
+
{
31
+
"name": "communication",
32
+
"title": "Communication"
33
+
},
34
+
{
35
+
"name": "connectivity",
36
+
"title": "Connectivity"
37
+
},
38
+
{
39
+
"name": "currency",
40
+
"title": "Currency"
41
+
},
42
+
{
43
+
"name": "cursors",
44
+
"title": "Cursors"
45
+
},
46
+
{
47
+
"name": "design",
48
+
"title": "Design"
49
+
},
50
+
{
51
+
"name": "development",
52
+
"title": "Coding & development"
53
+
},
54
+
{
55
+
"name": "devices",
56
+
"title": "Devices"
57
+
},
58
+
{
59
+
"name": "emoji",
60
+
"title": "Emoji"
61
+
},
62
+
{
63
+
"name": "files",
64
+
"title": "File icons"
65
+
},
66
+
{
67
+
"name": "food-beverage",
68
+
"title": "Food & beverage"
69
+
},
70
+
{
71
+
"name": "furniture",
72
+
"title": "Furniture"
73
+
},
74
+
{
75
+
"name": "gaming",
76
+
"title": "Gaming"
77
+
},
78
+
{
79
+
"name": "home",
80
+
"title": "Home"
81
+
},
82
+
{
83
+
"name": "layout",
84
+
"title": "Layout"
85
+
},
86
+
{
87
+
"name": "mail",
88
+
"title": "Mail"
89
+
},
90
+
{
91
+
"name": "maps",
92
+
"title": "Maps"
93
+
},
94
+
{
95
+
"name": "maths",
96
+
"title": "Maths"
97
+
},
98
+
{
99
+
"name": "medical",
100
+
"title": "Medical"
101
+
},
102
+
{
103
+
"name": "money",
104
+
"title": "Money"
105
+
},
106
+
{
107
+
"name": "multimedia",
108
+
"title": "Multimedia"
109
+
},
110
+
{
111
+
"name": "nature",
112
+
"title": "Nature"
113
+
},
114
+
{
115
+
"name": "navigation",
116
+
"title": "Navigation"
117
+
},
118
+
{
119
+
"name": "notifications",
120
+
"title": "Notifications"
121
+
},
122
+
{
123
+
"name": "people",
124
+
"title": "People"
125
+
},
126
+
{
127
+
"name": "photography",
128
+
"title": "Photography"
129
+
},
130
+
{
131
+
"name": "science",
132
+
"title": "Science"
133
+
},
134
+
{
135
+
"name": "seasons",
136
+
"title": "Seasons"
137
+
},
138
+
{
139
+
"name": "security",
140
+
"title": "Security"
141
+
},
142
+
{
143
+
"name": "shapes",
144
+
"title": "Shapes"
145
+
},
146
+
{
147
+
"name": "shopping",
148
+
"title": "Shopping"
149
+
},
150
+
{
151
+
"name": "social",
152
+
"title": "Social"
153
+
},
154
+
{
155
+
"name": "sports",
156
+
"title": "Sports"
157
+
},
158
+
{
159
+
"name": "sustainability",
160
+
"title": "Sustainability"
161
+
},
162
+
{
163
+
"name": "text",
164
+
"title": "Text formatting"
165
+
},
166
+
{
167
+
"name": "time",
168
+
"title": "Time & calendar"
169
+
},
170
+
{
171
+
"name": "tools",
172
+
"title": "Tools"
173
+
},
174
+
{
175
+
"name": "transportation",
176
+
"title": "Transportation"
177
+
},
178
+
{
179
+
"name": "travel",
180
+
"title": "Travel"
181
+
},
182
+
{
183
+
"name": "weather",
184
+
"title": "Weather"
185
+
}
186
+
]
+161
docs/.vitepress/lib/codeExamples/createLabCodeExamples.ts
+161
docs/.vitepress/lib/codeExamples/createLabCodeExamples.ts
···
1
+
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
2
+
import { getHighlighter } from 'shikiji';
3
+
4
+
type CodeExampleType = {
5
+
title: string;
6
+
language: string;
7
+
code: string;
8
+
}[];
9
+
10
+
const getIconCodes = (): CodeExampleType => {
11
+
return [
12
+
{
13
+
language: 'js',
14
+
title: 'Vanilla',
15
+
code: `\
16
+
import { createIcons, icons } from 'lucide';
17
+
import { $Name } from '@lucide/lab';
18
+
19
+
createIcons({
20
+
icons: {
21
+
$Name
22
+
}
23
+
});
24
+
25
+
document.body.append('<i data-lucide="$Name"></i>');\
26
+
`,
27
+
},
28
+
{
29
+
language: 'tsx',
30
+
title: 'React',
31
+
code: `import { Icon } from 'lucide-react';
32
+
import { $Name } from '@lucide/lab';
33
+
34
+
const App = () => {
35
+
return (
36
+
<Icon iconNode={$Name} />
37
+
);
38
+
};
39
+
40
+
export default App;
41
+
`,
42
+
},
43
+
{
44
+
language: 'vue',
45
+
title: 'Vue',
46
+
code: `<script setup>
47
+
import { Icon } from 'lucide-vue-next';
48
+
import { $Name } from '@lucide/lab';
49
+
</script>
50
+
51
+
<template>
52
+
<Icon :iconNode="burger" />
53
+
</template>
54
+
`,
55
+
},
56
+
{
57
+
language: 'svelte',
58
+
title: 'Svelte',
59
+
code: `<script>
60
+
import { Icon } from 'lucide-svelte';
61
+
import { $Name } from '@lucide/lab';
62
+
</script>
63
+
64
+
<Icon iconNode={burger} />
65
+
`,
66
+
},
67
+
{
68
+
language: 'tsx',
69
+
title: 'Preact',
70
+
code: `import { Icon } from 'lucide-preact';
71
+
import { $Name } from '@lucide/lab';
72
+
73
+
const App = () => {
74
+
return (
75
+
<Icon iconNode={$Name} />
76
+
);
77
+
};
78
+
79
+
export default App;
80
+
`,
81
+
},
82
+
{
83
+
language: 'tsx',
84
+
title: 'Solid',
85
+
code: `import { Icon } from 'lucide-solid';
86
+
import { $Name } from '@lucide/lab';
87
+
88
+
const App = () => {
89
+
return (
90
+
<Icon iconNode={$Name} />
91
+
);
92
+
};
93
+
94
+
export default App;
95
+
`,
96
+
},
97
+
{
98
+
language: 'tsx',
99
+
title: 'Angular',
100
+
code: `// app.module.ts
101
+
import { LucideAngularModule, $PascalCase } from 'lucide-angular';
102
+
import { $Name } from '@lucide/lab';
103
+
104
+
@NgModule({
105
+
imports: [
106
+
LucideAngularModule.pick({ $Name })
107
+
],
108
+
})
109
+
110
+
// app.component.html
111
+
<lucide-icon name="$Name"></lucide-icon>
112
+
`,
113
+
},
114
+
];
115
+
};
116
+
117
+
export type ThemeOptions =
118
+
| ThemeRegistration
119
+
| { light: ThemeRegistration; dark: ThemeRegistration };
120
+
121
+
const highLightCode = async (code: string, lang: string, active?: boolean) => {
122
+
const highlighter = await getHighlighter({
123
+
themes: ['github-light', 'github-dark'],
124
+
langs: Object.keys(bundledLanguages),
125
+
});
126
+
127
+
const highlightedCode = highlighter
128
+
.codeToHtml(code, {
129
+
lang,
130
+
themes: {
131
+
light: 'github-light',
132
+
dark: 'github-dark',
133
+
},
134
+
defaultColor: false,
135
+
})
136
+
.replace('shiki-themes', 'shiki-themes vp-code');
137
+
138
+
return `<div class="language-${lang} ${active ? 'active' : ''}">
139
+
<button title="Copy Code" class="copy"></button>
140
+
<span class="lang">${lang}</span>
141
+
${highlightedCode}
142
+
</div>`;
143
+
};
144
+
145
+
export default async function createCodeExamples() {
146
+
const codes = getIconCodes();
147
+
148
+
const codeExamplePromises = codes.map(async ({ title, language, code }, index) => {
149
+
const isFirst = index === 0;
150
+
151
+
const codeString = await highLightCode(code, language, isFirst);
152
+
153
+
return {
154
+
title,
155
+
language: language,
156
+
code: codeString,
157
+
};
158
+
});
159
+
160
+
return Promise.all(codeExamplePromises);
161
+
}
+32
docs/.vitepress/lib/codeExamples/highLightCode.ts
+32
docs/.vitepress/lib/codeExamples/highLightCode.ts
···
1
+
import { bundledLanguages, type ThemeRegistration } from 'shikiji';
2
+
import { getHighlighter } from 'shikiji';
3
+
4
+
export type ThemeOptions =
5
+
| ThemeRegistration
6
+
| { light: ThemeRegistration; dark: ThemeRegistration };
7
+
8
+
const highLightCode = async (code: string, lang: string, active?: boolean) => {
9
+
const highlighter = await getHighlighter({
10
+
themes: ['github-light', 'github-dark'],
11
+
langs: Object.keys(bundledLanguages),
12
+
});
13
+
14
+
const highlightedCode = highlighter
15
+
.codeToHtml(code, {
16
+
lang,
17
+
themes: {
18
+
light: 'github-light',
19
+
dark: 'github-dark',
20
+
},
21
+
defaultColor: false,
22
+
})
23
+
.replace('shiki-themes', 'shiki-themes vp-code');
24
+
25
+
return `<div class="language-${lang} ${active ? 'active' : ''}">
26
+
<button title="Copy Code" class="copy"></button>
27
+
<span class="lang">${lang}</span>
28
+
${highlightedCode}
29
+
</div>`;
30
+
};
31
+
32
+
export default highLightCode;
+5
docs/.vitepress/lib/codeExamples/types.ts
+5
docs/.vitepress/lib/codeExamples/types.ts
+23
-17
docs/.vitepress/lib/createCodeExamples.ts
docs/.vitepress/lib/codeExamples/createCodeExamples.ts
+23
-17
docs/.vitepress/lib/createCodeExamples.ts
docs/.vitepress/lib/codeExamples/createCodeExamples.ts
···
10
10
const getIconCodes = (): CodeExampleType => {
11
11
return [
12
12
{
13
-
language: 'html',
14
-
title: 'HTML',
15
-
code: `<i data-lucide="Name"></i>`,
13
+
language: 'js',
14
+
title: 'Vanilla',
15
+
code: `\
16
+
import { createIcons, icons } from 'lucide';
17
+
18
+
createIcons({ icons });
19
+
20
+
document.body.append('<i data-lucide="$Name"></i>');\
21
+
`,
16
22
},
17
23
{
18
24
language: 'tsx',
19
25
title: 'React',
20
-
code: `import { PascalCase } from 'lucide-react';
26
+
code: `import { $PascalCase } from 'lucide-react';
21
27
22
28
const App = () => {
23
29
return (
24
-
<PascalCase />
30
+
<$PascalCase />
25
31
);
26
32
};
27
33
···
32
38
language: 'vue',
33
39
title: 'Vue',
34
40
code: `<script setup>
35
-
import { PascalCase } from 'lucide-vue-next';
41
+
import { $PascalCase } from 'lucide-vue-next';
36
42
</script>
37
43
38
44
<template>
39
-
<PascalCase />
45
+
<$PascalCase />
40
46
</template>
41
47
`,
42
48
},
···
44
50
language: 'svelte',
45
51
title: 'Svelte',
46
52
code: `<script>
47
-
import { PascalCase } from 'lucide-svelte';
53
+
import { $PascalCase } from 'lucide-svelte';
48
54
</script>
49
55
50
-
<PascalCase />
56
+
<$PascalCase />
51
57
`,
52
58
},
53
59
{
54
60
language: 'tsx',
55
61
title: 'Preact',
56
-
code: `import { PascalCase } from 'lucide-preact';
62
+
code: `import { $PascalCase } from 'lucide-preact';
57
63
58
64
const App = () => {
59
65
return (
60
-
<PascalCase />
66
+
<$PascalCase />
61
67
);
62
68
};
63
69
···
67
73
{
68
74
language: 'tsx',
69
75
title: 'Solid',
70
-
code: `import { PascalCase } from 'lucide-solid';
76
+
code: `import { $PascalCase } from 'lucide-solid';
71
77
72
78
const App = () => {
73
79
return (
74
-
<PascalCase />
80
+
<$PascalCase />
75
81
);
76
82
};
77
83
···
82
88
language: 'tsx',
83
89
title: 'Angular',
84
90
code: `// app.module.ts
85
-
import { LucideAngularModule, PascalCase } from 'lucide-angular';
91
+
import { LucideAngularModule, $PascalCase } from 'lucide-angular';
86
92
87
93
@NgModule({
88
94
imports: [
89
-
LucideAngularModule.pick({ PascalCase })
95
+
LucideAngularModule.pick({ $PascalCase })
90
96
],
91
97
})
92
98
93
99
// app.component.html
94
-
<lucide-icon name="Name"></lucide-icon>
100
+
<lucide-icon name="$Name"></lucide-icon>
95
101
`,
96
102
},
97
103
{
···
101
107
@import ('~lucide-static/font/Lucide.css');
102
108
</style>
103
109
104
-
<div class="icon-Name"></div>
110
+
<div class="icon-$Name"></div>
105
111
`,
106
112
},
107
113
];
+90
docs/.vitepress/theme/components/base/Checkbox.vue
+90
docs/.vitepress/theme/components/base/Checkbox.vue
···
1
+
<script setup lang="ts">
2
+
import { computed } from 'vue';
3
+
4
+
const props = defineProps<{
5
+
label: string
6
+
id: string
7
+
value: string
8
+
modelValue: string | string[]
9
+
}>()
10
+
11
+
const emit = defineEmits(['change', 'input', 'update:modelValue'])
12
+
13
+
const model = computed({
14
+
get: () => {
15
+
if (Array.isArray(props.modelValue)) {
16
+
return props.modelValue.includes(props.value)
17
+
}
18
+
return props.modelValue === props.value
19
+
20
+
},
21
+
set: (value: string) => {
22
+
if (Array.isArray(props.modelValue)) {
23
+
const newValue = [...props.modelValue]
24
+
const index = newValue.indexOf(props.value)
25
+
if (value) {
26
+
if (index === -1) {
27
+
newValue.push(props.value)
28
+
}
29
+
} else {
30
+
if (index !== -1) {
31
+
newValue.splice(index, 1)
32
+
}
33
+
}
34
+
emit('update:modelValue', newValue)
35
+
} else {
36
+
emit('update:modelValue', value)
37
+
}
38
+
}
39
+
})
40
+
</script>
41
+
42
+
<template>
43
+
<div class="checkbox-wrapper">
44
+
<input
45
+
type="checkbox"
46
+
class="checkbox"
47
+
ref="input"
48
+
:id="id"
49
+
v-model="model"
50
+
v-bind="$attrs"
51
+
/>
52
+
<label :for="id" class="checkbox-label">
53
+
{{ label }}
54
+
</label>
55
+
</div>
56
+
</template>
57
+
58
+
<style scoped>
59
+
.checkbox-wrapper {
60
+
display: flex;
61
+
align-items: center;
62
+
gap: 8px;
63
+
}
64
+
65
+
.checkbox-label {
66
+
line-height: 20px;
67
+
font-size: 13px;
68
+
color: var(--vt-c-text-1);
69
+
transition: color .5s;
70
+
display: block;
71
+
}
72
+
73
+
.checkbox {
74
+
-webkit-appearance: none;
75
+
appearance: none;
76
+
width: 16px;
77
+
height: 16px;
78
+
cursor: pointer;
79
+
border: 1px solid var(--vp-input-border-color);
80
+
background-color: var(--vp-input-switch-bg-color);
81
+
border-radius: 4px;
82
+
}
83
+
84
+
.checkbox:checked {
85
+
border-color: transparent;
86
+
background: url("data:image/svg+xml,%3Csvg width='12px' height='12px' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='4' stroke-linecap='round' stroke-linejoin='round' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E")
87
+
center no-repeat var(--vp-c-brand);;
88
+
}
89
+
90
+
</style>
+1
-1
docs/.vitepress/theme/components/home/TeamMemberCard.vue
+1
-1
docs/.vitepress/theme/components/home/TeamMemberCard.vue
+14
-7
docs/.vitepress/theme/components/icons/CategoryList.vue
+14
-7
docs/.vitepress/theme/components/icons/CategoryList.vue
···
6
6
import { useActiveAnchor } from '../../composables/useActiveAnchor'
7
7
import { data } from './CategoryList.data'
8
8
import CategoryListItem from './CategoryListItem.vue'
9
+
import SidebarTitle from './SidebarTitle.vue'
9
10
import { useCategoryView } from '../../composables/useCategoryView'
10
11
11
12
const { page } = useData()
···
46
47
47
48
<template>
48
49
<div class="category-list" ref="container">
49
-
<VPLink class="sidebar-title" href="/icons/" :class="{ 'active': overviewIsActive } ">
50
+
<SidebarTitle>
51
+
View
52
+
</SidebarTitle>
53
+
<VPLink class="sidebar-link sidebar-text" href="/icons/" :class="{ 'active': overviewIsActive } ">
50
54
All
51
55
</VPLink>
52
-
<VPLink class="sidebar-title" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
56
+
<VPLink class="sidebar-link sidebar-text" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
53
57
Categories
54
58
</VPLink>
55
59
<div class="content">
···
62
66
</template>
63
67
64
68
<style scoped>
65
-
.sidebar-title {
66
-
font-weight: 500;
67
-
color: var(--vp-c-text-2);
68
-
margin-bottom: 6px;
69
+
.sidebar-text {
69
70
line-height: 24px;
70
71
font-size: 14px;
71
72
display: block;
72
73
transition: color 0.25s;
74
+
padding: 4px 0;
73
75
}
74
76
75
-
.sidebar-title:hover, .sidebar-title.active {
77
+
.sidebar-link {
78
+
font-weight: 500;
79
+
color: var(--vp-c-text-2);
80
+
}
81
+
82
+
.sidebar-link:hover, .sidebar-link.active {
76
83
color: var(--vp-c-brand);
77
84
}
78
85
.content {
+14
-3
docs/.vitepress/theme/components/icons/IconDetailOverlay.vue
+14
-3
docs/.vitepress/theme/components/icons/IconDetailOverlay.vue
···
11
11
import Badge from '../base/Badge.vue';
12
12
import { computedAsync } from '@vueuse/core';
13
13
import { satisfies } from 'semver';
14
+
import { useExternalLibs } from '../../composables/useExternalLibs';
14
15
15
16
const props = defineProps<{
16
17
iconName: string | null
17
18
}>()
18
19
20
+
const { externalIconNodes } = useExternalLibs()
21
+
19
22
const { go } = useRouter()
20
23
21
24
const icon = computedAsync<IconEntity | null>(async () => {
22
25
if (props.iconName) {
23
26
try {
24
-
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
27
+
if (props.iconName.includes(':')) {
28
+
const [library, name] = props.iconName.split(':')
29
+
30
+
return externalIconNodes.value[library].find((icon) => icon.name === name)
31
+
} else {
32
+
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
33
+
}
25
34
} catch (err) {
26
-
go(`/icons/${props.iconName}`)
35
+
if (!props.iconName.includes(':')) {
36
+
go(`/icons/${props.iconName}`)
37
+
}
27
38
}
28
39
}
29
40
return null
···
56
67
class="version"
57
68
:href="releaseTagLink(icon.createdRelease.version)"
58
69
>v{{ icon.createdRelease.version }}</Badge>
59
-
<IconButton @click="go(`/icons/${icon.name}`)">
70
+
<IconButton @click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}` : `/icons/${icon.name}`)">
60
71
<component :is="Expand" />
61
72
</IconButton>
62
73
<IconButton @click="onClose">
+4
-2
docs/.vitepress/theme/components/icons/IconGrid.vue
+4
-2
docs/.vitepress/theme/components/icons/IconGrid.vue
···
25
25
:key="icon.name"
26
26
>
27
27
<IconItem
28
-
v-bind="icon"
29
-
@setActiveIcon="setActiveIcon(icon.name)"
28
+
:iconNode="icon.iconNode"
29
+
:name="icon.name"
30
+
:externalLibrary="icon.externalLibrary"
31
+
@setActiveIcon="setActiveIcon"
30
32
:active="activeIcon === icon.name"
31
33
customizable
32
34
:overlayMode="overlayMode"
+34
-6
docs/.vitepress/theme/components/icons/IconInfo.vue
+34
-6
docs/.vitepress/theme/components/icons/IconInfo.vue
···
7
7
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
8
8
import {useData, useRouter} from 'vitepress';
9
9
import { computed } from 'vue';
10
+
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
11
+
import { diamond } from '../../../data/iconNodes'
10
12
11
13
const props = defineProps<{
12
14
icon: IconEntity
···
20
22
if (!props.icon || !props?.icon?.tags) return []
21
23
return props.icon.tags.join(' • ')
22
24
})
25
+
26
+
const DiamondIcon = createLucideIcon('Diamond', diamond)
23
27
</script>
24
28
25
29
<template>
26
30
<div class="icon-info">
27
-
<IconDetailName class="icon-name">
28
-
{{ icon.name }}
29
-
</IconDetailName>
31
+
<div class="icon-name-wrapper">
32
+
<IconDetailName class="icon-name">
33
+
{{ icon.name }}
34
+
</IconDetailName>
35
+
<div v-if="icon.externalLibrary" class="icon-external-lib">
36
+
<DiamondIcon fill="currentColor" :size="12"/>
37
+
{{ icon.externalLibrary }}
38
+
</div>
39
+
</div>
30
40
<div class="tags-scroller" v-if="tags.length">
31
41
<p class="icon-tags horizontal-scroller">
32
42
{{ tags }}
···
44
54
45
55
<div class="group buttons">
46
56
<VPButton
47
-
v-if="!page?.relativePath?.startsWith?.(`icons/${icon.name}`)"
48
-
:href="`/icons/${icon.name}`"
57
+
v-if="!page?.relativePath?.startsWith?.(icon.externalLibrary ? `icons/${icon.externalLibrary}/${icon.name}`: `icons/${icon.name}`)"
58
+
:href="icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`"
49
59
text="See in action"
50
-
@click="go(`/icons/${icon.name}`)"
60
+
@click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`)"
51
61
/>
52
62
<CopySVGButton :name="icon.name" :popoverPosition="popoverPosition"/>
53
63
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
···
67
77
text-transform: capitalize;
68
78
}
69
79
.icon-name {
80
+
margin-right: -36px;
81
+
}
82
+
83
+
.icon-name-wrapper {
84
+
display: flex;
85
+
align-items: center;
86
+
gap: 2px;
70
87
margin-bottom: 4px;
88
+
}
89
+
90
+
.icon-external-lib {
91
+
color: var(--vp-c-brand-dark);
92
+
padding: 4px 12px;
93
+
font-size: 16px;
94
+
font-weight: 600;
95
+
line-height: 28px;
96
+
display: flex;
97
+
gap: 8px;
98
+
align-items: center;
71
99
}
72
100
73
101
.icon-tags {
+26
-5
docs/.vitepress/theme/components/icons/IconItem.vue
+26
-5
docs/.vitepress/theme/components/icons/IconItem.vue
···
6
6
import getSVGIcon from '../../utils/getSVGIcon';
7
7
import useConfetti from '../../composables/useConfetti';
8
8
import Tooltip from '../base/Tooltip.vue';
9
+
import { diamond } from '../../../data/iconNodes'
9
10
10
11
const downloadText = 'Download!'
11
12
const copiedText = 'Copied!'
···
16
17
name: string;
17
18
iconNode: IconNode;
18
19
active: boolean;
20
+
externalLibrary?: string;
19
21
customizable?: boolean;
20
22
overlayMode?: boolean
21
23
hideIcon?: boolean
···
33
35
return createLucideIcon(props.name, props.iconNode)
34
36
})
35
37
36
-
async function navigateToIcon(event) {
38
+
const href = computed(() => props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
37
39
40
+
async function navigateToIcon(event) {
38
41
if (event.shiftKey) {
39
42
event.preventDefault()
40
43
const svgString = getSVGIcon(event.target.firstChild, {
···
50
53
51
54
if(props.overlayMode && showOverlay.value) {
52
55
event.preventDefault()
53
-
window.history.pushState({}, '', `/icons/${props.name}`)
54
-
emit('setActiveIcon', props.name)
56
+
57
+
window.history.pushState({}, '', props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
58
+
emit('setActiveIcon', props.externalLibrary ? `${props.externalLibrary}:${props.name}`: props.name)
55
59
} else {
56
60
event.preventDefault()
57
-
go(`/icons/${props.name}`)
61
+
go(props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
58
62
}
59
63
}
64
+
65
+
const DiamondIcon = createLucideIcon('Diamond', diamond)
60
66
</script>
61
67
62
68
<template>
···
66
72
@click="navigateToIcon"
67
73
:class="{ active, animate }"
68
74
:aria-label="name"
69
-
:href="`/icons/${props.name}`"
75
+
70
76
:data-confetti-text="confettiText"
71
77
ref="ref"
72
78
>
···
80
86
}"
81
87
/>
82
88
</KeepAlive>
89
+
<div
90
+
v-if="externalLibrary"
91
+
class="floating-diamond"
92
+
aria-hidden="true"
93
+
>
94
+
<DiamondIcon fill="currentColor" :size="8"/>
95
+
</div>
83
96
</a>
84
97
</Tooltip>
85
98
</template>
···
88
101
89
102
<style scoped>
90
103
.icon-button {
104
+
position: relative;
91
105
display: inline-block;
92
106
border: 1px solid transparent;
93
107
text-align: center;
···
102
116
height: 56px;
103
117
font-size: 24px;
104
118
color: var(--vp-c-text-1);
119
+
}
120
+
121
+
.floating-diamond {
122
+
position: absolute;
123
+
top: 4px;
124
+
right: 4px;
125
+
color: var(--vp-c-brand);
105
126
}
106
127
107
128
.confetti-button:before,
+1
-1
docs/.vitepress/theme/components/icons/IconsCategory.vue
+1
-1
docs/.vitepress/theme/components/icons/IconsCategory.vue
+8
-2
docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue
+8
-2
docs/.vitepress/theme/components/icons/IconsCategoryOverview.vue
···
1
1
<script setup lang="ts">
2
-
import { ref, computed, defineAsyncComponent, onMounted } from 'vue';
2
+
import { ref, computed, defineAsyncComponent, onMounted, watch, watchEffect } from 'vue';
3
3
import type { IconEntity, Category } from '../../types';
4
4
import useSearch from '../../composables/useSearch';
5
5
import InputSearch from '../base/InputSearch.vue';
···
69
69
return props.categories
70
70
.map(({ name, title }) => {
71
71
const categoryIcons = props.icons.filter((icon) => {
72
-
const iconCategories = props.iconCategories[icon.name];
72
+
const iconCategories = icon?.externalLibrary ? icon.categories : props.iconCategories[icon.name]
73
73
74
74
return iconCategories?.includes(name);
75
75
});
···
140
140
141
141
window.history.pushState({}, '', '/icons/categories');
142
142
}
143
+
144
+
watchEffect(() => {
145
+
146
+
console.log(props.icons.find((icon) => icon.name === 'burger'));
147
+
148
+
});
143
149
</script>
144
150
145
151
<template>
+47
docs/.vitepress/theme/components/icons/SidebarExternalLibrarySelect.vue
+47
docs/.vitepress/theme/components/icons/SidebarExternalLibrarySelect.vue
···
1
+
<script setup lang="ts">
2
+
import Checkbox from '../base/Checkbox.vue'
3
+
import SidebarTitle from './SidebarTitle.vue'
4
+
import { useExternalLibs } from '../../composables/useExternalLibs';
5
+
import { ExternalLibs } from '../../types';
6
+
7
+
interface ExternalLibrary {
8
+
name: string;
9
+
value: ExternalLibs;
10
+
}
11
+
12
+
const externalLibraries: ExternalLibrary[] = [
13
+
{
14
+
name: 'Lab',
15
+
value: 'lab'
16
+
},
17
+
];
18
+
19
+
const { selectedLibs } = useExternalLibs();
20
+
</script>
21
+
22
+
<template>
23
+
<div class="external-library-select">
24
+
<SidebarTitle>
25
+
Include external libs
26
+
</SidebarTitle>
27
+
<ul>
28
+
<li
29
+
v-for="library in externalLibraries"
30
+
:key="library.name"
31
+
>
32
+
<Checkbox
33
+
:label="library.name"
34
+
:id="library.name"
35
+
v-model="selectedLibs"
36
+
:value="library.value"
37
+
/>
38
+
</li>
39
+
</ul>
40
+
</div>
41
+
</template>
42
+
43
+
<style scoped>
44
+
.external-library-select {
45
+
margin-bottom: 24px;
46
+
}
47
+
</style>
+19
docs/.vitepress/theme/components/icons/SidebarTitle.vue
+19
docs/.vitepress/theme/components/icons/SidebarTitle.vue
···
1
+
<template>
2
+
<h2 class="sidebar-title sidebar-text">
3
+
<slot />
4
+
</h2>
5
+
</template>
6
+
7
+
<style scoped>
8
+
.sidebar-title {
9
+
font-weight: 700;
10
+
color: var(--vp-c-text-1);
11
+
}
12
+
.sidebar-text {
13
+
line-height: 24px;
14
+
font-size: 14px;
15
+
display: block;
16
+
transition: color 0.25s;
17
+
padding: 4px 0;
18
+
}
19
+
</style>
+57
docs/.vitepress/theme/composables/useExternalLibs.ts
+57
docs/.vitepress/theme/composables/useExternalLibs.ts
···
1
+
import { ref, inject, Ref, watch } from 'vue';
2
+
import { ExternalLibs, IconEntity } from '../types';
3
+
4
+
export const EXTERNAL_LIBS_CONTEXT = Symbol('externalLibs');
5
+
6
+
type ExternalIconNodes = Partial<Record<ExternalLibs, IconEntity[]>>;
7
+
8
+
interface ExternalLibContext {
9
+
selectedLibs: Ref<[ExternalLibs]>;
10
+
externalIconNodes: Ref<ExternalIconNodes>;
11
+
}
12
+
13
+
export const externalLibContext = {
14
+
selectedLibs: ref([]),
15
+
externalIconNodes: ref({}),
16
+
};
17
+
18
+
const externalLibIconNodesAPI = {
19
+
lab: 'https://lab.lucide.dev/api/icon-details',
20
+
};
21
+
22
+
export function useExternalLibs(): ExternalLibContext {
23
+
const context = inject<ExternalLibContext>(EXTERNAL_LIBS_CONTEXT);
24
+
25
+
watch(context?.selectedLibs, async (selectedLibs) => {
26
+
const savedIconNodes = { ...context?.externalIconNodes.value };
27
+
const newExternalIconNodes: ExternalIconNodes = {};
28
+
29
+
try {
30
+
for (const lib of selectedLibs) {
31
+
if (savedIconNodes[lib]) {
32
+
newExternalIconNodes[lib] = savedIconNodes[lib];
33
+
} else {
34
+
const response = await fetch(externalLibIconNodesAPI[lib]);
35
+
const iconNodes = await response.json();
36
+
37
+
if (iconNodes) {
38
+
newExternalIconNodes[lib] = Object.values(iconNodes).map((iconEntity: IconEntity) => ({
39
+
...iconEntity,
40
+
externalLibrary: lib,
41
+
}));
42
+
}
43
+
}
44
+
}
45
+
46
+
context.externalIconNodes.value = newExternalIconNodes;
47
+
} catch (error) {
48
+
console.error(error);
49
+
}
50
+
});
51
+
52
+
if (!context) {
53
+
throw new Error('useExternalLibs must be used with externalLibs context');
54
+
}
55
+
56
+
return context;
57
+
}
+29
docs/.vitepress/theme/composables/useIconsWithExternalLibs.ts
+29
docs/.vitepress/theme/composables/useIconsWithExternalLibs.ts
···
1
+
import { computed } from 'vue';
2
+
import { useExternalLibs } from '~/.vitepress/theme/composables/useExternalLibs';
3
+
import { IconEntity } from '../types';
4
+
5
+
const useIconsWithExternalLibs = (initialIcons?: IconEntity[]) => {
6
+
const { externalIconNodes } = useExternalLibs();
7
+
8
+
return computed(() => {
9
+
let icons = [];
10
+
11
+
if (initialIcons) {
12
+
icons = icons.concat(initialIcons);
13
+
}
14
+
15
+
const externalIconNodesArray = Object.values(externalIconNodes.value);
16
+
17
+
if (externalIconNodesArray?.length) {
18
+
externalIconNodesArray.forEach((iconNodes) => {
19
+
if (iconNodes?.length) {
20
+
icons = icons.concat(iconNodes);
21
+
}
22
+
});
23
+
}
24
+
25
+
return icons;
26
+
});
27
+
};
28
+
29
+
export default useIconsWithExternalLibs;
+2
docs/.vitepress/theme/index.ts
+2
docs/.vitepress/theme/index.ts
···
7
7
import HomeHeroBefore from './components/home/HomeHeroBefore.vue';
8
8
import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle';
9
9
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView';
10
+
import { EXTERNAL_LIBS_CONTEXT, externalLibContext } from './composables/useExternalLibs';
10
11
11
12
const theme: Partial<Theme> = {
12
13
extends: DefaultTheme,
···
20
21
enhanceApp({ app }) {
21
22
app.provide(ICON_STYLE_CONTEXT, iconStyleContext);
22
23
app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext);
24
+
app.provide(EXTERNAL_LIBS_CONTEXT, externalLibContext);
23
25
},
24
26
};
25
27
+7
-2
docs/.vitepress/theme/types.ts
+7
-2
docs/.vitepress/theme/types.ts
···
1
1
export type IconNode = [elementName: string, attrs: Record<string, string>][];
2
2
export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][];
3
3
4
-
export interface IconEntity {
5
-
name: string;
4
+
export interface IconMetaData {
6
5
tags: string[];
7
6
categories: string[];
8
7
contributors: string[];
9
8
aliases?: string[];
9
+
}
10
+
11
+
export type ExternalLibs = 'lab';
12
+
export interface IconEntity extends IconMetaData {
13
+
name: string;
10
14
iconNode: IconNode;
15
+
externalLibrary?: ExternalLibs;
11
16
createdRelease?: Release;
12
17
changedRelease?: Release;
13
18
}
+1
-1
docs/guide/packages/lucide-vue-next.md
+1
-1
docs/guide/packages/lucide-vue-next.md
+13
-10
docs/icons/[name].md
+13
-10
docs/icons/[name].md
···
10
10
<script setup>
11
11
import { computed } from 'vue'
12
12
import { useData } from 'vitepress'
13
-
import IconPreview from '../.vitepress/theme/components/icons/IconPreview.vue'
14
-
import IconPreviewSmall from '../.vitepress/theme/components/icons/IconPreviewSmall.vue'
15
-
import IconInfo from '../.vitepress/theme/components/icons/IconInfo.vue'
16
-
import IconContributors from '../.vitepress/theme/components/icons/IconContributors.vue'
17
-
import RelatedIcons from '../.vitepress/theme/components/icons/RelatedIcons.vue'
18
-
import CodeGroup from '../.vitepress/theme/components/base/CodeGroup.vue'
19
-
import Badge from '../.vitepress/theme/components/base/Badge.vue'
20
-
import Label from '../.vitepress/theme/components/base/Label.vue'
13
+
import IconPreview from '~/.vitepress/theme/components/icons/IconPreview.vue'
14
+
import IconPreviewSmall from '~/.vitepress/theme/components/icons/IconPreviewSmall.vue'
15
+
import IconInfo from '~/.vitepress/theme/components/icons/IconInfo.vue'
16
+
import IconContributors from '~/.vitepress/theme/components/icons/IconContributors.vue'
17
+
import RelatedIcons from '~/.vitepress/theme/components/icons/RelatedIcons.vue'
18
+
import CodeGroup from '~/.vitepress/theme/components/base/CodeGroup.vue'
19
+
import Badge from '~/.vitepress/theme/components/base/Badge.vue'
20
+
import Label from '~/.vitepress/theme/components/base/Label.vue'
21
21
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
22
22
import { data } from './codeExamples.data'
23
23
import { camelCase, startCase } from 'lodash-es'
···
32
32
const codeExample = computed(() => data.codeExamples?.map(
33
33
(codeExample) => {
34
34
const pascalCase = startCase(camelCase( params.value.name)).replace(/\s/g, '')
35
-
return codeExample.code.replace(/PascalCase/g, pascalCase).replace(/Name/g, params.value.name)
35
+
return codeExample.code.replace(/\$PascalCase/g, pascalCase).replace(/\$Name/g, params.value.name)
36
36
}
37
37
).join('') ?? []
38
38
)
···
100
100
</div>
101
101
</div>
102
102
103
-
<RelatedIcons :icons="params.relatedIcons" />
103
+
<RelatedIcons
104
+
v-if="params.relatedIcons"
105
+
:icons="params.relatedIcons"
106
+
/>
104
107
105
108
<style module>
106
109
.preview {
+4
-1
docs/icons/categories.md
+4
-1
docs/icons/categories.md
···
10
10
import { data as categoriesData } from './categories.data.ts'
11
11
import PageContainer from '../.vitepress/theme/components/PageContainer.vue'
12
12
import IconsCategoryOverview from '../.vitepress/theme/components/icons/IconsCategoryOverview.vue'
13
+
import useIconsWithExternalLibs from '~/.vitepress/theme/composables/useIconsWithExternalLibs'
14
+
15
+
const icons = useIconsWithExternalLibs(data.icons)
13
16
</script>
14
17
15
18
<div class="VPDoc content">
16
19
<PageContainer>
17
20
<IconsCategoryOverview
18
21
:categories="categoriesData.categories"
19
-
:icons="data.icons"
22
+
:icons="icons"
20
23
:iconCategories="categoriesData.iconCategories"
21
24
/>
22
25
</PageContainer>
+1
-3
docs/icons/codeExamples.data.ts
+1
-3
docs/icons/codeExamples.data.ts
···
1
-
import createCodeExamples from '../.vitepress/lib/createCodeExamples';
1
+
import createCodeExamples from '../.vitepress/lib/codeExamples/createCodeExamples';
2
2
3
3
export default {
4
4
async load() {
5
5
const codeExamples = await createCodeExamples();
6
-
7
-
// const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons))
8
6
9
7
return {
10
8
codeExamples,
+7
-3
docs/icons/index.md
+7
-3
docs/icons/index.md
···
10
10
---
11
11
12
12
<script setup>
13
+
import { computed } from 'vue'
13
14
import { data } from './icons.data.ts'
14
-
import IconsOverview from '../.vitepress/theme/components/icons/IconsOverview.vue'
15
-
import PageContainer from '../.vitepress/theme/components/PageContainer.vue'
15
+
import IconsOverview from '~/.vitepress/theme/components/icons/IconsOverview.vue'
16
+
import PageContainer from '~/.vitepress/theme/components/PageContainer.vue'
17
+
import useIconsWithExternalLibs from '~/.vitepress/theme/composables/useIconsWithExternalLibs'
18
+
19
+
const icons = useIconsWithExternalLibs(data.icons)
16
20
</script>
17
21
18
22
<div class="VPDoc content">
19
23
<PageContainer>
20
-
<IconsOverview :icons="data.icons" />
24
+
<IconsOverview :icons="icons" />
21
25
</PageContainer>
22
26
</div>
+10
docs/icons/lab/[name].md
+10
docs/icons/lab/[name].md
+19
docs/icons/lab/[name].paths.ts
+19
docs/icons/lab/[name].paths.ts
···
1
+
import { IconEntity } from '../../.vitepress/theme/types';
2
+
3
+
export default {
4
+
paths: async () => {
5
+
const iconDetailsResponse = await fetch('https://lab.lucide.dev/api/icon-details');
6
+
const iconDetails = (await iconDetailsResponse.json()) as Record<string, IconEntity>;
7
+
8
+
return Object.values(iconDetails).map((iconEntity) => {
9
+
const params = {
10
+
externalLibrary: 'lab',
11
+
...iconEntity,
12
+
};
13
+
14
+
return {
15
+
params,
16
+
};
17
+
});
18
+
},
19
+
};
+11
docs/icons/lab/codeExamples.data.ts
+11
docs/icons/lab/codeExamples.data.ts