forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import React from 'react'
2
3type Component = React.ReactElement
4
5type ContextType = {
6 outlet: Component | null
7 append(id: string, component: Component): void
8 remove(id: string): void
9}
10
11type ComponentMap = {
12 [id: string]: Component
13}
14
15export function createPortalGroup_INTERNAL() {
16 const Context = React.createContext<ContextType>({
17 outlet: null,
18 append: () => {},
19 remove: () => {},
20 })
21 Context.displayName = 'BottomSheetPortalContext'
22
23 function Provider(props: React.PropsWithChildren<{}>) {
24 const map = React.useRef<ComponentMap>({})
25 const [outlet, setOutlet] = React.useState<ContextType['outlet']>(null)
26
27 const append = React.useCallback<ContextType['append']>((id, component) => {
28 if (map.current[id]) return
29 map.current[id] = <React.Fragment key={id}>{component}</React.Fragment>
30 setOutlet(<>{Object.values(map.current)}</>)
31 }, [])
32
33 const remove = React.useCallback<ContextType['remove']>(id => {
34 delete map.current[id]
35 setOutlet(<>{Object.values(map.current)}</>)
36 }, [])
37
38 const contextValue = React.useMemo(
39 () => ({
40 outlet,
41 append,
42 remove,
43 }),
44 [outlet, append, remove],
45 )
46
47 return (
48 <Context.Provider value={contextValue}>{props.children}</Context.Provider>
49 )
50 }
51
52 function Outlet() {
53 const ctx = React.useContext(Context)
54 return ctx.outlet
55 }
56
57 function Portal({children}: React.PropsWithChildren<{}>) {
58 const {append, remove} = React.useContext(Context)
59 const id = React.useId()
60 React.useEffect(() => {
61 append(id, children as Component)
62 return () => remove(id)
63 }, [id, children, append, remove])
64 return null
65 }
66
67 return {Provider, Outlet, Portal}
68}