diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index 831457ca07..1a3e03bcaa 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -31,9 +31,7 @@ import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {ScrollProvider} from '#/lib/ScrollContext' -import {logger} from '#/logger' import {useA11y} from '#/state/a11y' -import {useDialogStateControlContext} from '#/state/dialogs' import {List, type ListMethods, type ListProps} from '#/view/com/util/List' import {android, atoms as a, ios, platform, tokens, useTheme} from '#/alf' import {useThemeName} from '#/alf/util/useColorModeTheme' @@ -43,6 +41,7 @@ import { type DialogInnerProps, type DialogOuterProps, } from '#/components/Dialog/types' +import {useDialogCallbackQueue} from '#/components/Dialog/utils' import {createInput} from '#/components/forms/TextField' import {useOnKeyboard} from '#/components/hooks/useOnKeyboard' import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS} from '#/env' @@ -74,50 +73,29 @@ function OuterAlert({ testID, }: React.PropsWithChildren) { const t = useTheme() - const [isOpen, setIsOpen] = React.useState(false) - const closeCallbacks = React.useRef<(() => void)[]>([]) - const {setDialogIsOpen} = useDialogStateControlContext() - - const callQueuedCallbacks = React.useCallback(() => { - for (const cb of closeCallbacks.current) { - try { - cb() - } catch (e: any) { - logger.error(e || 'Error running close callback') - } - } - closeCallbacks.current = [] - }, []) - - const open = React.useCallback(() => { - callQueuedCallbacks() - setDialogIsOpen(control.id, true) - setIsOpen(true) - }, [setDialogIsOpen, control.id, callQueuedCallbacks]) - - const close = React.useCallback(cb => { - if (typeof cb === 'function') { - closeCallbacks.current.push(cb) - } - setIsOpen(false) - }, []) - - const onDismiss = React.useCallback(() => { - setDialogIsOpen(control.id, false) - callQueuedCallbacks() - onClose?.() - }, [callQueuedCallbacks, control.id, onClose, setDialogIsOpen]) - - useImperativeHandle( - control.ref, - () => ({ - open, - close, - }), - [open, close], + const [isOpen, setIsOpen] = useState(false) + const {enqueueCallback, handleOpen, handleClose} = useDialogCallbackQueue( + control, + onClose, ) - const context = React.useMemo( + const open: DialogControlProps['open'] = () => { + handleOpen() + setIsOpen(true) + } + + const close: DialogControlProps['close'] = cb => { + enqueueCallback(cb) + setIsOpen(false) + } + + const onDismiss = () => { + handleClose() + } + + useImperativeHandle(control.ref, () => ({open, close}), [open, close]) + + const context = useMemo( () => ({ close, isNativeDialog: true, @@ -175,9 +153,8 @@ function OuterSheet({ const themeName = useThemeName() const t = useTheme(themeName) const ref = useRef(null) - const closeCallbacks = useRef<(() => void)[]>([]) - const {setDialogIsOpen, setFullyExpandedCount} = - useDialogStateControlContext() + const {enqueueCallback, handleOpen, handleClose, setFullyExpandedCount} = + useDialogCallbackQueue(control, onClose) const prevSnapPoint = useRef( BottomSheetSnapPoint.Hidden, @@ -188,42 +165,20 @@ function OuterSheet({ BottomSheetSnapPoint.Partial, ) - const callQueuedCallbacks = useCallback(() => { - for (const cb of closeCallbacks.current) { - try { - cb() - } catch (e: any) { - logger.error(e || 'Error running close callback') - } - } - - closeCallbacks.current = [] - }, []) - - const open = useCallback(() => { - // Run any leftover callbacks that might have been queued up before calling `.open()` - callQueuedCallbacks() - setDialogIsOpen(control.id, true) + const open: DialogControlProps['open'] = () => { + handleOpen() ref.current?.present() - }, [setDialogIsOpen, control.id, callQueuedCallbacks]) + } - // This is the function that we call when we want to dismiss the dialog. - const close = useCallback(cb => { - if (typeof cb === 'function') { - closeCallbacks.current.push(cb) - } + const close: DialogControlProps['close'] = cb => { + enqueueCallback(cb) ref.current?.dismiss() - }, []) + } - // This is the actual thing we are doing once we "confirm" the dialog. We want the dialog's close animation to - // happen before we run this. It is passed to the `BottomSheet` component. - const onCloseAnimationComplete = useCallback(() => { - // This removes the dialog from our list of stored dialogs. Not super necessary on iOS, but on Android this - // tells us that we need to toggle the accessibility overlay setting - setDialogIsOpen(control.id, false) - callQueuedCallbacks() - onClose?.() - }, [callQueuedCallbacks, control.id, onClose, setDialogIsOpen]) + // Runs after the bottom sheet close animation completes. + const onCloseAnimationComplete = () => { + handleClose() + } const onSnapPointChange = (e: BottomSheetSnapPointChangeEvent) => { const {snapPoint} = e.nativeEvent @@ -254,14 +209,7 @@ function OuterSheet({ } } - useImperativeHandle( - control.ref, - () => ({ - open, - close, - }), - [open, close], - ) + useImperativeHandle(control.ref, () => ({open, close}), [open, close]) const context = useMemo( () => ({ diff --git a/src/components/Dialog/utils.ts b/src/components/Dialog/utils.ts index 4582200c27..cb2d337b46 100644 --- a/src/components/Dialog/utils.ts +++ b/src/components/Dialog/utils.ts @@ -1,7 +1,53 @@ -import {useEffect} from 'react' +import {useEffect, useRef} from 'react' +import {logger} from '#/logger' +import {useDialogStateControlContext} from '#/state/dialogs' import {type DialogControlProps} from '#/components/Dialog/types' +export function useDialogCallbackQueue( + control: DialogControlProps, + onClose?: () => void, +) { + const closeCallbacks = useRef<(() => void)[]>([]) + const {setDialogIsOpen, setFullyExpandedCount} = + useDialogStateControlContext() + + const callQueuedCallbacks = () => { + for (const cb of closeCallbacks.current) { + try { + cb() + } catch (e: any) { + logger.error(e || 'Error running close callback') + } + } + closeCallbacks.current = [] + } + + const enqueueCallback = (cb?: () => void) => { + if (typeof cb === 'function') { + closeCallbacks.current.push(cb) + } + } + + const handleOpen = () => { + callQueuedCallbacks() + setDialogIsOpen(control.id, true) + } + + const handleClose = () => { + setDialogIsOpen(control.id, false) + callQueuedCallbacks() + onClose?.() + } + + return { + enqueueCallback, + handleOpen, + handleClose, + setFullyExpandedCount, + } +} + export function useAutoOpen(control: DialogControlProps, showTimeout?: number) { useEffect(() => { if (showTimeout) {