3e2c181c40
* update dependencies * rm `import type React from 'react'` * run codemods * patch discord types * update types/react-dom * Update yarn.lock
84 lines
1.9 KiB
TypeScript
84 lines
1.9 KiB
TypeScript
import {
|
|
createContext,
|
|
Fragment,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useId,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from 'react'
|
|
|
|
type Component = React.ReactElement<any>
|
|
|
|
type ContextType = {
|
|
outlet: Component | null
|
|
append(id: string, component: Component): void
|
|
remove(id: string): void
|
|
}
|
|
|
|
type ComponentMap = {
|
|
[id: string]: Component | null
|
|
}
|
|
|
|
export function createPortalGroup() {
|
|
const Context = createContext<ContextType>({
|
|
outlet: null,
|
|
append: () => {},
|
|
remove: () => {},
|
|
})
|
|
Context.displayName = 'PortalContext'
|
|
|
|
function Provider(props: React.PropsWithChildren<{}>) {
|
|
const map = useRef<ComponentMap>({})
|
|
const [outlet, setOutlet] = useState<ContextType['outlet']>(null)
|
|
|
|
const append = useCallback<ContextType['append']>((id, component) => {
|
|
if (map.current[id]) return
|
|
map.current[id] = <Fragment key={id}>{component}</Fragment>
|
|
setOutlet(<>{Object.values(map.current)}</>)
|
|
}, [])
|
|
|
|
const remove = useCallback<ContextType['remove']>(id => {
|
|
map.current[id] = null
|
|
setOutlet(<>{Object.values(map.current)}</>)
|
|
}, [])
|
|
|
|
const contextValue = useMemo(
|
|
() => ({
|
|
outlet,
|
|
append,
|
|
remove,
|
|
}),
|
|
[outlet, append, remove],
|
|
)
|
|
|
|
return (
|
|
<Context.Provider value={contextValue}>{props.children}</Context.Provider>
|
|
)
|
|
}
|
|
|
|
function Outlet() {
|
|
const ctx = useContext(Context)
|
|
return ctx.outlet
|
|
}
|
|
|
|
function Portal({children}: React.PropsWithChildren<{}>) {
|
|
const {append, remove} = useContext(Context)
|
|
const id = useId()
|
|
useEffect(() => {
|
|
append(id, children as Component)
|
|
return () => remove(id)
|
|
}, [id, children, append, remove])
|
|
return null
|
|
}
|
|
|
|
return {Provider, Outlet, Portal}
|
|
}
|
|
|
|
const DefaultPortal = createPortalGroup()
|
|
export const Provider = DefaultPortal.Provider
|
|
export const Outlet = DefaultPortal.Outlet
|
|
export const Portal = DefaultPortal.Portal
|