Skip to content

Commit 0135e81

Browse files
Apply PR #26950: perf(ui): render icons through an svg sprite
2 parents e275b09 + 3751dec commit 0135e81

1 file changed

Lines changed: 36 additions & 6 deletions

File tree

packages/ui/src/components/icon.tsx

Lines changed: 36 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,44 @@ 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+
111+
function viewBox(name: keyof typeof icons) {
112+
return name === "magnifying-glass" || name === "arrow-undo-down" ? "0 0 16 16" : "0 0 20 20"
113+
}
114+
115+
function ensureSprite() {
116+
if (typeof document === "undefined") return
117+
if (document.getElementById(spriteID)) return
118+
const body = document.body as HTMLElement | null
119+
if (!body) return
120+
121+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
122+
svg.id = spriteID
123+
svg.setAttribute("aria-hidden", "true")
124+
svg.setAttribute("width", "0")
125+
svg.setAttribute("height", "0")
126+
svg.style.position = "absolute"
127+
svg.style.overflow = "hidden"
128+
svg.innerHTML = Object.entries(icons)
129+
.map(([name, path]) => {
130+
const key = name as keyof typeof icons
131+
return `<symbol id="${symbol(key)}" viewBox="${viewBox(key)}">${path}</symbol>`
132+
})
133+
.join("")
134+
body.insertBefore(svg, body.firstChild)
135+
}
136+
108137
export interface IconProps extends ComponentProps<"svg"> {
109138
name: keyof typeof icons
110139
size?: "small" | "normal" | "medium" | "large"
111140
}
112141

113142
export function Icon(props: IconProps) {
114143
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"
144+
onMount(ensureSprite)
145+
117146
return (
118147
<div data-component="icon" data-size={local.size || "normal"}>
119148
<svg
@@ -123,11 +152,12 @@ export function Icon(props: IconProps) {
123152
[local.class ?? ""]: !!local.class,
124153
}}
125154
fill="none"
126-
viewBox={viewBox()}
127-
innerHTML={icons[local.name as keyof typeof icons]}
155+
viewBox={viewBox(local.name)}
128156
aria-hidden="true"
129157
{...others}
130-
/>
158+
>
159+
<use href={`#${symbol(local.name)}`} />
160+
</svg>
131161
</div>
132162
)
133163
}

0 commit comments

Comments
 (0)