forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {
2 createContext,
3 useCallback,
4 useContext,
5 useMemo,
6 useRef,
7 useState,
8} from 'react'
9
10import {useHotkeysContext} from '#/lib/hotkeys'
11import {type DialogControlRefProps} from '#/components/Dialog'
12import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context'
13import {IS_WEB} from '#/env'
14import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet'
15
16interface IDialogContext {
17 /**
18 * The currently active `useDialogControl` hooks.
19 */
20 activeDialogs: React.MutableRefObject<
21 Map<string, React.MutableRefObject<DialogControlRefProps>>
22 >
23 /**
24 * The currently open dialogs, referenced by their IDs, generated from
25 * `useId`.
26 */
27 openDialogs: React.MutableRefObject<Set<string>>
28}
29
30interface IDialogControlContext {
31 closeAllDialogs(): boolean
32 setDialogIsOpen(id: string, isOpen: boolean): void
33 setFullyExpandedCount: React.Dispatch<React.SetStateAction<number>>
34}
35
36const DialogContext = createContext<IDialogContext>({} as IDialogContext)
37DialogContext.displayName = 'DialogContext'
38
39const DialogControlContext = createContext<IDialogControlContext>(
40 {} as IDialogControlContext,
41)
42DialogControlContext.displayName = 'DialogControlContext'
43
44/**
45 * The number of dialogs that are fully expanded. This is used to determine the background color of the status bar
46 * on iOS.
47 */
48const DialogFullyExpandedCountContext = createContext<number>(0)
49DialogFullyExpandedCountContext.displayName = 'DialogFullyExpandedCountContext'
50
51export function useDialogStateContext() {
52 return useContext(DialogContext)
53}
54
55export function useDialogStateControlContext() {
56 return useContext(DialogControlContext)
57}
58
59/** The number of dialogs that are fully expanded */
60export function useDialogFullyExpandedCountContext() {
61 return useContext(DialogFullyExpandedCountContext)
62}
63
64export function Provider({children}: React.PropsWithChildren<{}>) {
65 const [fullyExpandedCount, setFullyExpandedCount] = useState(0)
66 const {disableScope, enableScope} = useHotkeysContext()
67
68 const activeDialogs = useRef<
69 Map<string, React.MutableRefObject<DialogControlRefProps>>
70 >(new Map())
71 const openDialogs = useRef<Set<string>>(new Set())
72
73 const closeAllDialogs = useCallback(() => {
74 if (IS_WEB) {
75 openDialogs.current.forEach(id => {
76 const dialog = activeDialogs.current.get(id)
77 if (dialog) dialog.current.close()
78 })
79
80 return openDialogs.current.size > 0
81 } else {
82 void BottomSheetNativeComponent.dismissAll()
83 return false
84 }
85 }, [])
86
87 const setDialogIsOpen = useCallback(
88 (id: string, isOpen: boolean) => {
89 if (isOpen) {
90 openDialogs.current.add(id)
91 } else {
92 openDialogs.current.delete(id)
93 }
94 if (openDialogs.current.size > 0) {
95 disableScope('global')
96 } else {
97 enableScope('global')
98 }
99 },
100 [disableScope, enableScope],
101 )
102
103 const context = useMemo<IDialogContext>(
104 () => ({
105 activeDialogs,
106 openDialogs,
107 }),
108 [activeDialogs, openDialogs],
109 )
110 const controls = useMemo(
111 () => ({
112 closeAllDialogs,
113 setDialogIsOpen,
114 setFullyExpandedCount,
115 }),
116 [closeAllDialogs, setDialogIsOpen, setFullyExpandedCount],
117 )
118
119 return (
120 <DialogContext.Provider value={context}>
121 <DialogControlContext.Provider value={controls}>
122 <DialogFullyExpandedCountContext.Provider value={fullyExpandedCount}>
123 <GlobalDialogsProvider>{children}</GlobalDialogsProvider>
124 </DialogFullyExpandedCountContext.Provider>
125 </DialogControlContext.Provider>
126 </DialogContext.Provider>
127 )
128}
129Provider.displayName = 'DialogsProvider'