|
1 | | -import { FC, useLayoutEffect, useRef } from "react"; |
| 1 | +import { FC } from "react"; |
2 | 2 | import type { IconName } from "./iconNames"; |
3 | 3 |
|
4 | 4 | const svgModules = import.meta.glob("./icons/*.svg", { |
5 | 5 | query: "?raw", |
6 | 6 | import: "default", |
7 | | -}) as Record<string, () => Promise<string>>; |
| 7 | + eager: true, |
| 8 | +}) as Record<string, string>; |
8 | 9 |
|
9 | | -const icons: Record<string, () => Promise<string>> = {}; |
10 | | -for (const [path, loadIcon] of Object.entries(svgModules)) { |
| 10 | +const icons: Record<string, string> = {}; |
| 11 | +for (const [path, svg] of Object.entries(svgModules)) { |
11 | 12 | const name = path.replace(/^.*\/(.+)\.svg$/, "$1"); |
12 | | - icons[name] = loadIcon; |
13 | | -} |
14 | | - |
15 | | -const iconCache: Record<string, string> = {}; |
16 | | -const iconLoadCache: Record<string, Promise<string | undefined> | undefined> = {}; |
17 | | - |
18 | | -function getIconMarkup(name: IconName): Promise<string | undefined> { |
19 | | - const cachedIcon = iconCache[name]; |
20 | | - if (cachedIcon !== undefined) return Promise.resolve(cachedIcon); |
21 | | - |
22 | | - const loadIcon = icons[name]; |
23 | | - if (!loadIcon) return Promise.resolve(undefined); |
24 | | - |
25 | | - const pending = iconLoadCache[name]; |
26 | | - if (pending !== undefined) return pending; |
27 | | - |
28 | | - const promise = loadIcon() |
29 | | - .then((svgMarkup) => { |
30 | | - iconCache[name] = svgMarkup; |
31 | | - iconLoadCache[name] = undefined; |
32 | | - return svgMarkup; |
33 | | - }) |
34 | | - .catch((err) => { |
35 | | - iconLoadCache[name] = undefined; |
36 | | - // oxlint-disable-next-line no-console |
37 | | - console.error(`Failed to load icon "${name}":`, err); |
38 | | - return undefined; |
39 | | - }); |
40 | | - |
41 | | - iconLoadCache[name] = promise; |
42 | | - return promise; |
| 13 | + icons[name] = svg; |
43 | 14 | } |
44 | 15 |
|
45 | 16 | type Props = { |
46 | 17 | name: IconName; |
47 | 18 | size?: number; |
48 | 19 | }; |
49 | 20 |
|
50 | | -export const Icon: FC<Props> = ({ name, size = 16 }) => { |
51 | | - const ref = useRef<HTMLElement>(null); |
52 | | - |
53 | | - useLayoutEffect(() => { |
54 | | - const node = ref.current; |
55 | | - if (!node) return; |
56 | | - |
57 | | - let cancelled = false; |
58 | | - void getIconMarkup(name).then((svgMarkup) => { |
59 | | - if (cancelled || ref.current !== node) return; |
60 | | - node.innerHTML = svgMarkup ?? ""; |
61 | | - }); |
62 | | - |
63 | | - return () => { |
64 | | - cancelled = true; |
65 | | - }; |
66 | | - }, [name]); |
67 | | - |
68 | | - return ( |
69 | | - <i |
70 | | - ref={ref} |
71 | | - className="icon" |
72 | | - aria-hidden="true" |
73 | | - style={{ display: "inline-flex", width: size, height: size }} |
74 | | - /> |
75 | | - ); |
76 | | -}; |
| 21 | +export const Icon: FC<Props> = ({ name, size = 16 }) => ( |
| 22 | + <i |
| 23 | + className="icon" |
| 24 | + aria-hidden="true" |
| 25 | + style={{ display: "inline-flex", width: size, height: size }} |
| 26 | + // oxlint-disable-next-line react/no-danger |
| 27 | + dangerouslySetInnerHTML={{ __html: icons[name] ?? "" }} |
| 28 | + /> |
| 29 | +); |
0 commit comments