Skip to content

Commit 07200ec

Browse files
committed
feat: fetch raw iconify svg to make them color-reactive
1 parent dc3e40e commit 07200ec

File tree

10 files changed

+379
-215
lines changed

10 files changed

+379
-215
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"type": "module",
33
"version": "0.0.0-alpha.22",
44
"private": true,
5-
"packageManager": "pnpm@10.26.1",
5+
"packageManager": "pnpm@10.26.2",
66
"scripts": {
77
"build": "turbo run build",
88
"build:debug": "NUXT_DEBUG_BUILD=true pnpm -r run build",

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"@vitejs/plugin-vue": "catalog:build",
7777
"@xterm/addon-fit": "catalog:frontend",
7878
"@xterm/xterm": "catalog:frontend",
79+
"dompurify": "catalog:frontend",
7980
"tsdown": "catalog:build",
8081
"typescript": "catalog:devtools",
8182
"unplugin-vue": "catalog:build",

packages/core/src/client/webcomponents/.generated/css.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,33 @@
11
<script setup lang="ts">
22
import { computed } from 'vue'
3+
import IconifyIcon from './IconifyIcon.vue'
34
import VitePlusCore from './icons/VitePlusCore.vue'
45
56
const props = defineProps<{
67
icon: string | { dark: string, light: string }
78
title?: string
89
}>()
910
10-
function getIconUrl(str: string, color: 'dark' | 'light') {
11-
if (str.includes('/') || str.startsWith('data:') || str.startsWith('builtin:'))
12-
return str
13-
const match = str.match(/^([\w-]+):([\w-]+)$/)
14-
if (match) {
15-
const [, collection, icon] = match
16-
return `https://api.iconify.design/${collection}/${icon}.svg${color === 'dark' ? '?color=%23eee' : '?color=%23111'}`
17-
}
18-
return str
19-
}
20-
2111
const icon = computed(() => {
2212
if (typeof props.icon === 'string') {
2313
return {
24-
dark: getIconUrl(props.icon, 'dark'),
25-
light: getIconUrl(props.icon, 'light'),
14+
dark: props.icon,
15+
light: props.icon,
2616
}
2717
}
28-
return {
29-
dark: getIconUrl(props.icon.dark, 'dark'),
30-
light: getIconUrl(props.icon.light, 'light'),
31-
}
18+
return props.icon
3219
})
3320
</script>
3421

3522
<template>
3623
<VitePlusCore v-if="icon.light === 'builtin:vite-plus-core'" />
37-
<picture v-else>
38-
<source :srcset="icon.dark" media="(prefers-color-scheme: dark)">
39-
<source :srcset="icon.light" media="(prefers-color-scheme: light)">
40-
<img
41-
:src="icon.light"
42-
:alt="title"
43-
class="w-full h-full m-auto"
44-
draggable="false"
45-
>
46-
</picture>
24+
<div v-else>
25+
<template v-if="icon.light === icon.dark">
26+
<IconifyIcon :icon="icon.light" :title="title" />
27+
</template>
28+
<template v-else>
29+
<IconifyIcon class="dark-hidden" :icon="icon.light" :title="title" />
30+
<IconifyIcon class="light-hidden" :icon="icon.dark" :title="title" />
31+
</template>
32+
</div>
4733
</template>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script setup lang="ts">
2+
import { computed, ref, watchEffect } from 'vue'
3+
import { getIconifySvg } from '../utils/iconify'
4+
5+
const props = defineProps<{
6+
icon: string
7+
}>()
8+
9+
const isUrlIcon = computed(() => props.icon.includes('/') || props.icon.startsWith('data:') || props.icon.startsWith('builtin:'))
10+
const iconifyParsed = computed(() => {
11+
if (isUrlIcon.value)
12+
return undefined
13+
const match = props.icon.match(/^([\w-]+):([\w-]+)$/)
14+
if (!match)
15+
return undefined
16+
return {
17+
collection: match[1]!,
18+
icon: match[2]!,
19+
}
20+
})
21+
22+
const iconifyLoaded = ref<string | undefined>(undefined)
23+
watchEffect(async () => {
24+
if (!iconifyParsed.value) {
25+
iconifyLoaded.value = undefined
26+
return
27+
}
28+
iconifyLoaded.value = await getIconifySvg(iconifyParsed.value.collection, iconifyParsed.value.icon)
29+
})
30+
</script>
31+
32+
<template>
33+
<div
34+
v-if="iconifyParsed"
35+
v-html="iconifyLoaded"
36+
/>
37+
<img
38+
v-else :src="icon"
39+
class="w-full h-full m-auto"
40+
draggable="false"
41+
>
42+
</template>

packages/core/src/client/webcomponents/style.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
#vite-devtools-anchor #vite-devtools-dock {
2121
--uno: h-full rounded-full select-none touch-none ma h-[40px];
22-
--uno: shadow text-white bg-glass;
22+
--uno: "shadow text-[#333] dark:text-white bg-glass";
2323
--uno: transition-all duration-500;
2424
width: calc-size(max-content, size);
2525
}

packages/core/src/client/webcomponents/uno.config.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@ export default defineConfig({
2626
transformers: [
2727
transformerDirectives(),
2828
],
29+
theme: {
30+
colors: {
31+
primary: {
32+
50: '#fcf4ff',
33+
100: '#f7e5ff',
34+
200: '#f0d0ff',
35+
300: '#e5acff',
36+
400: '#d577ff',
37+
DEFAULT: '#d577ff',
38+
500: '#c543ff',
39+
600: '#bd34fe',
40+
700: '#9f0fe1',
41+
800: '#8512b7',
42+
900: '#6d1093',
43+
950: '#4d006e',
44+
},
45+
},
46+
},
2947
presets: [
3048
presetWind3({
3149
dark: 'media',
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import createDOMPurify from 'dompurify'
2+
3+
const getIconifySvgMap = new Map<string, Promise<string> | string>()
4+
5+
const purify = createDOMPurify()
6+
7+
export async function getIconifySvg(collection: string, icon: string) {
8+
const id = `${collection}:${icon}`
9+
if (getIconifySvgMap.has(id)) {
10+
return getIconifySvgMap.get(id)!
11+
}
12+
const promise = _get()
13+
.then((svg) => {
14+
getIconifySvgMap.set(id, svg)
15+
return svg
16+
})
17+
getIconifySvgMap.set(id, promise)
18+
return promise
19+
20+
async function _get() {
21+
const url = `https://api.iconify.design/${collection}/${icon}.svg?color=currentColor&width=full`
22+
const svg = await fetch(url).then(res => res.text())
23+
return purify.sanitize(svg)
24+
}
25+
}

0 commit comments

Comments
 (0)