Skip to content

Commit 3a810fc

Browse files
authored
perf(ui): render icons through an svg sprite (anomalyco#26950)
1 parent e5af7ab commit 3a810fc

1 file changed

Lines changed: 42 additions & 6 deletions

File tree

packages/ui/src/components/icon.tsx

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { splitProps, type ComponentProps } from "solid-js"
1+
import { onMount, splitProps, type ComponentProps } from "solid-js"
22

33
const icons = {
44
"align-right": `<path d="M12.292 6.04167L16.2503 9.99998L12.292 13.9583M2.91699 9.99998H15.6253M17.0837 3.75V16.25" stroke="currentColor" stroke-linecap="square"/>`,
@@ -105,15 +105,50 @@ const icons = {
105105
"arrow-undo-down": `<path d="M4.08333 11.0859L1.75 8.7526L4.08333 6.41927M2.33333 8.7526L12.5417 8.7526L12.5417 3.21094L7 3.21094" stroke="currentColor" stroke-width="1" stroke-linecap="square"/>`,
106106
}
107107

108+
const spriteID = "opencode-icon-sprite"
109+
const symbol = (name: keyof typeof icons) => `opencode-icon-${name}`
110+
let spriteInserted = false
111+
112+
function viewBox(name: keyof typeof icons) {
113+
return name === "magnifying-glass" || name === "arrow-undo-down" ? "0 0 16 16" : "0 0 20 20"
114+
}
115+
116+
function ensureSprite() {
117+
if (spriteInserted) return
118+
if (typeof document === "undefined") return
119+
if (document.getElementById(spriteID)) {
120+
spriteInserted = true
121+
return
122+
}
123+
const body = document.body as HTMLElement | null
124+
if (!body) return
125+
126+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
127+
svg.id = spriteID
128+
svg.setAttribute("aria-hidden", "true")
129+
svg.setAttribute("width", "0")
130+
svg.setAttribute("height", "0")
131+
svg.style.position = "absolute"
132+
svg.style.overflow = "hidden"
133+
svg.innerHTML = Object.entries(icons)
134+
.map(([name, path]) => {
135+
const key = name as keyof typeof icons
136+
return `<symbol id="${symbol(key)}" viewBox="${viewBox(key)}">${path}</symbol>`
137+
})
138+
.join("")
139+
body.insertBefore(svg, body.firstChild)
140+
spriteInserted = true
141+
}
142+
108143
export interface IconProps extends ComponentProps<"svg"> {
109144
name: keyof typeof icons
110145
size?: "small" | "normal" | "medium" | "large"
111146
}
112147

113148
export function Icon(props: IconProps) {
114149
const [local, others] = splitProps(props, ["name", "size", "class", "classList"])
115-
const viewBox = () =>
116-
local.name === "magnifying-glass" || local.name === "arrow-undo-down" ? "0 0 16 16" : "0 0 20 20"
150+
onMount(ensureSprite)
151+
117152
return (
118153
<div data-component="icon" data-size={local.size || "normal"}>
119154
<svg
@@ -123,11 +158,12 @@ export function Icon(props: IconProps) {
123158
[local.class ?? ""]: !!local.class,
124159
}}
125160
fill="none"
126-
viewBox={viewBox()}
127-
innerHTML={icons[local.name as keyof typeof icons]}
161+
viewBox={viewBox(local.name)}
128162
aria-hidden="true"
129163
{...others}
130-
/>
164+
>
165+
<use href={`#${symbol(local.name)}`} />
166+
</svg>
131167
</div>
132168
)
133169
}

0 commit comments

Comments
 (0)