import React, { useCallback, useContext, useRef } from 'react'; import { useForceUpdate } from '@sendbird/uikit-utils'; import type { ActionMenuItem } from '../ActionMenu'; import ActionMenu from '../ActionMenu'; import type { AlertItem } from '../Alert'; import Alert from '../Alert'; import type { BottomSheetItem } from '../BottomSheet'; import BottomSheet from '../BottomSheet'; import type { PromptItem } from '../Prompt'; import Prompt from '../Prompt'; type DialogJob = | { type: 'ActionMenu'; props: ActionMenuItem; } | { type: 'Alert'; props: AlertItem; } | { type: 'Prompt'; props: PromptItem; } | { type: 'BottomSheet'; props: BottomSheetItem; }; type DialogPropsBy = U extends { type: T; props: infer P } ? P : never; type DialogContextType = { openMenu: (props: DialogPropsBy<'ActionMenu'>) => void; alert: (props: DialogPropsBy<'Alert'>) => void; openPrompt: (props: DialogPropsBy<'Prompt'>) => void; openSheet: (props: DialogPropsBy<'BottomSheet'>) => void; }; const AlertContext = React.createContext | null>(null); const ActionMenuContext = React.createContext | null>(null); const PromptContext = React.createContext | null>(null); const BottomSheetContext = React.createContext | null>(null); type Props = React.PropsWithChildren<{ defaultLabels?: { alert?: { ok?: string }; prompt?: { placeholder?: string; ok?: string; cancel?: string }; }; }>; const DISMISS_TIMEOUT = 3000; export const DialogProvider = ({ defaultLabels, children }: Props) => { const waitDismissTimeout = useRef(undefined); const waitDismissPromise = useRef<(() => void) | undefined>(undefined); const waitDismiss = useCallback((resolver: () => void) => { waitDismissPromise.current = resolver; waitDismissTimeout.current = setTimeout(completeDismiss, DISMISS_TIMEOUT); }, []); const completeDismiss = useCallback(() => { if (waitDismissTimeout.current) clearTimeout(waitDismissTimeout.current); if (waitDismissPromise.current) waitDismissPromise.current(); waitDismissTimeout.current = undefined; waitDismissPromise.current = undefined; }, []); const render = useForceUpdate(); const dialogQueue = useRef([]); const workingDialogJob = useRef(undefined); const visibleState = useRef(false); const isProcessing = () => Boolean(workingDialogJob.current); const updateToShow = useCallback(() => { visibleState.current = true; render(); }, []); const updateToHide = useCallback((): Promise => { return new Promise((resolve) => { visibleState.current = false; render(); waitDismiss(resolve); }); }, []); const consumeQueue = useCallback(() => { completeDismiss(); const job = dialogQueue.current.shift(); if (job) { workingDialogJob.current = job; updateToShow(); } else { workingDialogJob.current = undefined; } }, []); const createJob = (type: T) => (props: DialogPropsBy) => { const jobItem = { type, props } as DialogJob; if (isProcessing()) dialogQueue.current.push(jobItem); else { workingDialogJob.current = jobItem; updateToShow(); } }; const alert = useCallback(createJob('Alert'), []); const openMenu = useCallback(createJob('ActionMenu'), []); const openPrompt = useCallback(createJob('Prompt'), []); const openSheet = useCallback(createJob('BottomSheet'), []); return ( {children} {workingDialogJob.current?.type === 'ActionMenu' && ( )} {workingDialogJob.current?.type === 'Alert' && ( )} {workingDialogJob.current?.type === 'Prompt' && ( )} {workingDialogJob.current?.type === 'BottomSheet' && ( )} ); }; export const useActionMenu = () => { const context = useContext(ActionMenuContext); if (!context) throw new Error('ActionMenuContext is not provided, wrap your app with DialogProvider'); return context; }; export const useAlert = () => { const context = useContext(AlertContext); if (!context) throw new Error('AlertContext is not provided, wrap your app with DialogProvider'); return context; }; export const usePrompt = () => { const context = useContext(PromptContext); if (!context) throw new Error('PromptContext is not provided, wrap your app with DialogProvider'); return context; }; export const useBottomSheet = () => { const context = useContext(BottomSheetContext); if (!context) throw new Error('BottomSheetContext is not provided, wrap your app with DialogProvider'); return context; };