feat: implement root portal provider

This commit is contained in:
Hossein Mehrabi 2023-02-13 13:23:25 +03:30
parent 36df81df77
commit 225a32f43f
6 changed files with 99 additions and 3 deletions

View File

@ -0,0 +1,23 @@
import React from 'react'
import { createPortal } from 'react-dom'
import { useCanUsePortal } from './PortalContext'
import { usePortal } from './usePortal'
export type PortalProps = React.PropsWithChildren & {
id: string
}
export const Portal: React.FC<PortalProps> = ({ id, children }) => {
const canUse = useCanUsePortal()
if (!canUse) return <></>
return <PortalContent id={id}>{children}</PortalContent>
}
const PortalContent: React.FC<PortalProps> = ({ id, children }) => {
const element = usePortal({ parentId: 'lsd-presentation' })
if (!element) return <></>
return createPortal(children, element, id)
}

View File

@ -0,0 +1,12 @@
import React, { useContext } from 'react'
export type PortalContextType = {
initialized?: boolean
}
export const PortalContext = React.createContext<PortalContextType>({
initialized: false,
})
export const useCanUsePortal = (): boolean =>
useContext(PortalContext)?.initialized ?? false

View File

@ -0,0 +1,29 @@
import React, { useEffect, useState } from 'react'
import { PortalContext } from './PortalContext'
export type PortalProviderProps = React.PropsWithChildren
export const PortalProvider: React.FC<PortalProviderProps> = ({ children }) => {
const [initialized, setInitialized] = useState(false)
useEffect(() => {
if (typeof window === 'undefined') return
const body = document.querySelector('body')!
const container = document.createElement('div')
container.id = 'lsd-presentation'
body.appendChild(container)
setInitialized(true)
return () => {
body.removeChild(container)
}
}, [])
return (
<PortalContext.Provider value={{ initialized }}>
{children}
</PortalContext.Provider>
)
}

View File

@ -0,0 +1 @@
export * from './PortalProvider'

View File

@ -0,0 +1,28 @@
import { useEffect, useRef } from 'react'
interface Props {
parentId: string
}
export const usePortal = ({ parentId }: Props) => {
const elementRef = useRef<HTMLElement>()
if (typeof window !== 'undefined' && !elementRef.current) {
elementRef.current = document.createElement('div')
}
useEffect(() => {
if (typeof window === 'undefined' || !elementRef.current) return
document.getElementById(parentId)?.appendChild(elementRef.current)
return () => {
try {
document
.getElementById(parentId)
?.removeChild(elementRef.current as Node)
} catch (error) {}
}
}, [parentId, elementRef.current])
return elementRef.current
}

View File

@ -1,6 +1,7 @@
import { Global, ThemeProvider as EmotionThemeProvider } from '@emotion/react'
import React from 'react'
import { CSSBaseline } from '../CSSBaseline'
import { PortalProvider } from '../PortalProvider'
import { Theme } from './types'
export type ThemeProviderProps = React.PropsWithChildren<{
@ -13,9 +14,11 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
}) => {
return (
<ThemeContext.Provider value={{ theme }}>
<EmotionThemeProvider theme={theme}>{children}</EmotionThemeProvider>
<CSSBaseline theme={theme} />
<Global styles={theme.globalStyles} />
<PortalProvider>
<EmotionThemeProvider theme={theme}>{children}</EmotionThemeProvider>
<CSSBaseline theme={theme} />
<Global styles={theme.globalStyles} />
</PortalProvider>
</ThemeContext.Provider>
)
}