dedupe some shared hooks

This commit is contained in:
vineyardbovines
2026-03-05 12:16:01 -05:00
parent aa1fbf4628
commit 7f4c81a837
2 changed files with 89 additions and 95 deletions
+42 -94
View File
@@ -1,4 +1,4 @@
import React, {useImperativeHandle} from 'react' import React, {useImperativeHandle, useMemo, useRef, useState} from 'react'
import { import {
type LayoutChangeEvent, type LayoutChangeEvent,
Modal, Modal,
@@ -22,9 +22,7 @@ import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {ScrollProvider} from '#/lib/ScrollContext' import {ScrollProvider} from '#/lib/ScrollContext'
import {logger} from '#/logger'
import {useA11y} from '#/state/a11y' import {useA11y} from '#/state/a11y'
import {useDialogStateControlContext} from '#/state/dialogs'
import {List, type ListMethods, type ListProps} from '#/view/com/util/List' import {List, type ListMethods, type ListProps} from '#/view/com/util/List'
import {android, atoms as a, ios, platform, tokens, useTheme} from '#/alf' import {android, atoms as a, ios, platform, tokens, useTheme} from '#/alf'
import {useThemeName} from '#/alf/util/useColorModeTheme' import {useThemeName} from '#/alf/util/useColorModeTheme'
@@ -34,6 +32,7 @@ import {
type DialogInnerProps, type DialogInnerProps,
type DialogOuterProps, type DialogOuterProps,
} from '#/components/Dialog/types' } from '#/components/Dialog/types'
import {useDialogCallbackQueue} from '#/components/Dialog/utils'
import {createInput} from '#/components/forms/TextField' import {createInput} from '#/components/forms/TextField'
import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS} from '#/env' import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS} from '#/env'
import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet' import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet'
@@ -64,50 +63,29 @@ function OuterAlert({
testID, testID,
}: React.PropsWithChildren<DialogOuterProps>) { }: React.PropsWithChildren<DialogOuterProps>) {
const t = useTheme() const t = useTheme()
const [isOpen, setIsOpen] = React.useState(false) const [isOpen, setIsOpen] = useState(false)
const closeCallbacks = React.useRef<(() => void)[]>([]) const {enqueueCallback, handleOpen, handleClose} = useDialogCallbackQueue(
const {setDialogIsOpen} = useDialogStateControlContext() control,
onClose,
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<DialogControlProps['open']>(() => {
callQueuedCallbacks()
setDialogIsOpen(control.id, true)
setIsOpen(true)
}, [setDialogIsOpen, control.id, callQueuedCallbacks])
const close = React.useCallback<DialogControlProps['close']>(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 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, close,
isNativeDialog: true, isNativeDialog: true,
@@ -164,56 +142,33 @@ function OuterSheet({
}: React.PropsWithChildren<DialogOuterProps>) { }: React.PropsWithChildren<DialogOuterProps>) {
const themeName = useThemeName() const themeName = useThemeName()
const t = useTheme(themeName) const t = useTheme(themeName)
const ref = React.useRef<BottomSheetNativeComponent>(null) const ref = useRef<BottomSheetNativeComponent>(null)
const closeCallbacks = React.useRef<(() => void)[]>([]) const {enqueueCallback, handleOpen, handleClose, setFullyExpandedCount} =
const {setDialogIsOpen, setFullyExpandedCount} = useDialogCallbackQueue(control, onClose)
useDialogStateControlContext()
const prevSnapPoint = React.useRef<BottomSheetSnapPoint>( const prevSnapPoint = useRef<BottomSheetSnapPoint>(
BottomSheetSnapPoint.Hidden, BottomSheetSnapPoint.Hidden,
) )
const [disableDrag, setDisableDrag] = React.useState(false) const [disableDrag, setDisableDrag] = useState(false)
const [snapPoint, setSnapPoint] = React.useState<BottomSheetSnapPoint>( const [snapPoint, setSnapPoint] = useState<BottomSheetSnapPoint>(
BottomSheetSnapPoint.Partial, BottomSheetSnapPoint.Partial,
) )
const callQueuedCallbacks = React.useCallback(() => { const open: DialogControlProps['open'] = () => {
for (const cb of closeCallbacks.current) { handleOpen()
try {
cb()
} catch (e: any) {
logger.error(e || 'Error running close callback')
}
}
closeCallbacks.current = []
}, [])
const open = React.useCallback<DialogControlProps['open']>(() => {
// Run any leftover callbacks that might have been queued up before calling `.open()`
callQueuedCallbacks()
setDialogIsOpen(control.id, true)
ref.current?.present() ref.current?.present()
}, [setDialogIsOpen, control.id, callQueuedCallbacks])
// This is the function that we call when we want to dismiss the dialog.
const close = React.useCallback<DialogControlProps['close']>(cb => {
if (typeof cb === 'function') {
closeCallbacks.current.push(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 const close: DialogControlProps['close'] = cb => {
// happen before we run this. It is passed to the `BottomSheet` component. enqueueCallback(cb)
const onCloseAnimationComplete = React.useCallback(() => { ref.current?.dismiss()
// 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) // Runs after the bottom sheet close animation completes.
callQueuedCallbacks() const onCloseAnimationComplete = () => {
onClose?.() handleClose()
}, [callQueuedCallbacks, control.id, onClose, setDialogIsOpen]) }
const onSnapPointChange = (e: BottomSheetSnapPointChangeEvent) => { const onSnapPointChange = (e: BottomSheetSnapPointChangeEvent) => {
const {snapPoint} = e.nativeEvent const {snapPoint} = e.nativeEvent
@@ -244,16 +199,9 @@ function OuterSheet({
} }
} }
useImperativeHandle( useImperativeHandle(control.ref, () => ({open, close}), [open, close])
control.ref,
() => ({
open,
close,
}),
[open, close],
)
const context = React.useMemo( const context = useMemo(
() => ({ () => ({
close, close,
isNativeDialog: true, isNativeDialog: true,
+48 -2
View File
@@ -1,9 +1,55 @@
import React from 'react' import {useEffect, useRef} from 'react'
import {logger} from '#/logger'
import {useDialogStateControlContext} from '#/state/dialogs'
import {type DialogControlProps} from '#/components/Dialog/types' 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) { export function useAutoOpen(control: DialogControlProps, showTimeout?: number) {
React.useEffect(() => { useEffect(() => {
if (showTimeout) { if (showTimeout) {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
control.open() control.open()