@@ -6,25 +6,32 @@ import React, {
6
6
useEffect ,
7
7
useCallback ,
8
8
useMemo ,
9
+ useRef ,
9
10
} from 'react' ;
10
11
import { renderToStaticMarkup } from 'react-dom/server' ;
12
+ import { createPortal } from 'react-dom' ;
11
13
12
- const spriteSheetId = '__SVG_SPRITE_SHEET__' ;
13
-
14
- const ssrEmptySpriteSheet = `<svg id="${ spriteSheetId } " style="display:none"></svg>` ;
14
+ const internalSpriteSheetId = '__SVG_SPRITE_SHEET__' ;
15
+ const isSSR = typeof document === 'undefined' ;
15
16
16
17
const globalIconsCache : IconsCache = new Map ( ) ;
17
18
18
- export const renderSpriteSheetToString = async (
19
- markupString : string ,
20
- knownIcons : IconsCache ,
21
- ) => {
19
+ export const createSpriteSheetString = async ( knownIcons : IconsCache ) => {
22
20
const arr = await Promise . all ( Array . from ( knownIcons . values ( ) ) ) ;
23
21
24
- const spriteSheet = renderToStaticMarkup (
22
+ return renderToStaticMarkup (
25
23
< SpriteSheet icons = { arr . filter ( ( a ) : a is IconData => a != null ) } /> ,
26
24
) ;
25
+ } ;
26
+
27
+ export const renderSpriteSheetToString = async (
28
+ markupString : string ,
29
+ knownIcons : IconsCache ,
30
+ spriteSheetId = internalSpriteSheetId ,
31
+ ) => {
32
+ const spriteSheet = await createSpriteSheetString ( knownIcons ) ;
27
33
34
+ const ssrEmptySpriteSheet = `<svg id="${ spriteSheetId } " style="display:none"></svg>` ;
28
35
return markupString . replace ( ssrEmptySpriteSheet , spriteSheet ) ;
29
36
} ;
30
37
@@ -135,12 +142,14 @@ export interface SpriteContext {
135
142
*/
136
143
loadSVG : ( url : string ) => Promise < string | undefined > ;
137
144
knownIcons ?: IconsCache ;
145
+ embeddedSSR ?: boolean ;
138
146
}
139
147
140
148
export const SpriteContextProvider : FC < SpriteContext > = ( {
141
149
children,
142
150
loadSVG,
143
151
knownIcons = globalIconsCache ,
152
+ embeddedSSR = false ,
144
153
} ) => {
145
154
const icons = useIcons ( ) ;
146
155
@@ -162,7 +171,7 @@ export const SpriteContextProvider: FC<SpriteContext> = ({
162
171
return (
163
172
< spriteContext . Provider value = { contextValue } >
164
173
{ children }
165
- < SpriteSheet icons = { icons } > </ SpriteSheet >
174
+ { ( ! isSSR || embeddedSSR ) && < SpriteSheet icons = { icons } > </ SpriteSheet > }
166
175
</ spriteContext . Provider >
167
176
) ;
168
177
} ;
@@ -173,7 +182,7 @@ export const Icon: FC<{ url: string } & React.SVGProps<SVGSVGElement>> = ({
173
182
} ) => {
174
183
const { registerSVG } = useContext ( spriteContext ) ;
175
184
176
- if ( typeof document === 'undefined' ) {
185
+ if ( isSSR ) {
177
186
registerSVG ( url ) ;
178
187
} else {
179
188
useEffect ( ( ) => {
@@ -189,31 +198,39 @@ export const Icon: FC<{ url: string } & React.SVGProps<SVGSVGElement>> = ({
189
198
} ;
190
199
191
200
const hidden = { display : 'none' } ;
192
- const SpriteSheet : FC < { icons : IconData [ ] } > = ( { icons } ) => {
201
+ const SpriteSheet : FC < {
202
+ icons : IconData [ ] ;
203
+ spriteSheetId ?: string ;
204
+ } > = ( { icons, spriteSheetId = internalSpriteSheetId } ) => {
205
+ const spriteSheetContainer = useRef (
206
+ ! isSSR ? document . getElementById ( spriteSheetId ) : null ,
207
+ ) ;
208
+
209
+ const renderedIcons = icons . map (
210
+ ( {
211
+ id,
212
+ svgString,
213
+ attributes : { width, height, [ 'xmlns:xlink' ] : xmlnsXlink , ...attributes } ,
214
+ } ) => {
215
+ return (
216
+ < symbol
217
+ key = { id }
218
+ id = { id }
219
+ xmlnsXlink = { xmlnsXlink }
220
+ { ...attributes }
221
+ dangerouslySetInnerHTML = { svgString }
222
+ />
223
+ ) ;
224
+ } ,
225
+ ) ;
226
+
227
+ if ( spriteSheetContainer . current ) {
228
+ return createPortal ( renderedIcons , spriteSheetContainer . current ) ;
229
+ }
230
+
193
231
return (
194
232
< svg id = { spriteSheetId } style = { hidden } >
195
- { icons . map (
196
- ( {
197
- id,
198
- svgString,
199
- attributes : {
200
- width,
201
- height,
202
- [ 'xmlns:xlink' ] : xmlnsXlink ,
203
- ...attributes
204
- } ,
205
- } ) => {
206
- return (
207
- < symbol
208
- key = { id }
209
- id = { id }
210
- xmlnsXlink = { xmlnsXlink }
211
- { ...attributes }
212
- dangerouslySetInnerHTML = { svgString }
213
- />
214
- ) ;
215
- } ,
216
- ) }
233
+ { renderedIcons }
217
234
</ svg >
218
235
) ;
219
236
} ;
@@ -228,7 +245,10 @@ const mapNodeAttributes = (rawAttributes: NamedNodeMap) =>
228
245
{ } ,
229
246
) ;
230
247
231
- export const initOnClient = ( knownIcons : IconsCache = globalIconsCache ) => {
248
+ export const initOnClient = (
249
+ knownIcons : IconsCache = globalIconsCache ,
250
+ spriteSheetId = internalSpriteSheetId ,
251
+ ) => {
232
252
knownIcons . clear ( ) ;
233
253
const spriteSheet = document . getElementById ( spriteSheetId ) ;
234
254
0 commit comments