Files
openchamber/packages/ui/src/components/icon/Icon.tsx
T
Serhii DziupinandSerhii Dziupin 70f8f2e15b fix(icons): inject missing sprite symbols for newly added icons
The icon sprite was injected once and never updated, so HMR/new glyphs
like telegram-fill left <use> refs empty. Append missing symbols on render.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-14 15:19:25 +00:00

81 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from "react"
import { cn } from "@/lib/utils"
import { iconSpriteData } from "./sprite"
import type { IconName } from "./icons"
const SPRITE_ID = "openchamber-icon-sprite"
let spriteInjected = false
function ensureSpriteOnce() {
if (spriteInjected) return
if (typeof document === "undefined") return
const body = document.body
if (!body) return
const existing = document.getElementById(SPRITE_ID)
if (existing) {
spriteInjected = true
return
}
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
svg.id = SPRITE_ID
svg.setAttribute("aria-hidden", "true")
svg.style.display = "none"
svg.innerHTML = Object.entries(iconSpriteData)
.map(([name, content]) => `<symbol id="oc-${name}" viewBox="0 0 24 24">${content}</symbol>`)
.join("")
body.insertBefore(svg, body.firstChild)
spriteInjected = true
}
/**
* Append a single missing symbol. Needed when the sprite was injected before a
* newly generated icon landed (HMR / late sprite regenerate) — a one-shot inject
* would otherwise leave `<use href="#oc-…"/>` pointing at nothing.
*/
function ensureSpriteSymbol(name: IconName) {
if (typeof document === "undefined") return
ensureSpriteOnce()
if (document.getElementById(`oc-${name}`)) return
const content = iconSpriteData[name]
if (typeof content !== "string") return
const sprite = document.getElementById(SPRITE_ID)
if (!sprite) return
const symbol = document.createElementNS("http://www.w3.org/2000/svg", "symbol")
symbol.id = `oc-${name}`
symbol.setAttribute("viewBox", "0 0 24 24")
symbol.innerHTML = content
sprite.appendChild(symbol)
}
export interface IconProps extends React.ComponentPropsWithoutRef<"svg"> {
name: IconName
}
export const Icon = React.memo(({ name, className, ...rest }: IconProps) => {
// Inline sprite injection during render must run before <use> tries
// to resolve the #oc-* reference during the same commit.
if (typeof document !== "undefined") {
ensureSpriteSymbol(name)
}
return (
<svg
className={cn("remixicon", className)}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
{...rest}
>
<use href={`#oc-${name}`} />
</svg>
)
})
Icon.displayName = "Icon"