From 0f9f08b1ef795215975c7b041d0e94a992d22124 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 8 Mar 2024 14:31:24 -0800 Subject: [PATCH] Fix reactivity of dialogs (Dialogs Pt. 1) (#3146) * Improve a11y on ios * Format * Remove android * Fix android * Revert some changes * use sharedvalue for `importantForAccessibility` * add back `isOpen` * fix some more types --------- Co-authored-by: Eric Bailey --- src/components/Dialog/context.ts | 6 ++-- src/components/Dialog/types.ts | 2 +- src/state/dialogs/index.tsx | 60 ++++++++++++++++++++++---------- src/view/shell/index.tsx | 19 +++------- 4 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/components/Dialog/context.ts b/src/components/Dialog/context.ts index 9b571e8e..859f8edd 100644 --- a/src/components/Dialog/context.ts +++ b/src/components/Dialog/context.ts @@ -21,8 +21,7 @@ export function useDialogControl(): DialogOuterProps['control'] { open: () => {}, close: () => {}, }) - const {activeDialogs, openDialogs} = useDialogStateContext() - const isOpen = openDialogs.includes(id) + const {activeDialogs} = useDialogStateContext() React.useEffect(() => { activeDialogs.current.set(id, control) @@ -36,7 +35,6 @@ export function useDialogControl(): DialogOuterProps['control'] { () => ({ id, ref: control, - isOpen, open: () => { control.current.open() }, @@ -44,6 +42,6 @@ export function useDialogControl(): DialogOuterProps['control'] { control.current.close(cb) }, }), - [id, control, isOpen], + [id, control], ) } diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts index fa9398fe..4fc60ec3 100644 --- a/src/components/Dialog/types.ts +++ b/src/components/Dialog/types.ts @@ -22,7 +22,7 @@ export type DialogControlRefProps = { export type DialogControlProps = DialogControlRefProps & { id: string ref: React.RefObject - isOpen: boolean + isOpen?: boolean } export type DialogContextProps = { diff --git a/src/state/dialogs/index.tsx b/src/state/dialogs/index.tsx index 90aaca4f..951105a5 100644 --- a/src/state/dialogs/index.tsx +++ b/src/state/dialogs/index.tsx @@ -1,8 +1,9 @@ import React from 'react' +import {SharedValue, useSharedValue} from 'react-native-reanimated' import {DialogControlRefProps} from '#/components/Dialog' import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context' -const DialogContext = React.createContext<{ +interface IDialogContext { /** * The currently active `useDialogControl` hooks. */ @@ -13,13 +14,18 @@ const DialogContext = React.createContext<{ * The currently open dialogs, referenced by their IDs, generated from * `useId`. */ - openDialogs: string[] -}>({ - activeDialogs: { - current: new Map(), - }, - openDialogs: [], -}) + openDialogs: React.MutableRefObject> + /** + * The counterpart to `accessibilityViewIsModal` for Android. This property + * applies to the parent of all non-modal views, and prevents TalkBack from + * navigating within content beneath an open dialog. + * + * @see https://reactnative.dev/docs/accessibility#importantforaccessibility-android + */ + importantForAccessibility: SharedValue<'auto' | 'no-hide-descendants'> +} + +const DialogContext = React.createContext({} as IDialogContext) const DialogControlContext = React.createContext<{ closeAllDialogs(): boolean @@ -41,26 +47,42 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const activeDialogs = React.useRef< Map> >(new Map()) - const [openDialogs, setOpenDialogs] = React.useState([]) + const openDialogs = React.useRef>(new Set()) + const importantForAccessibility = useSharedValue< + 'auto' | 'no-hide-descendants' + >('auto') const closeAllDialogs = React.useCallback(() => { activeDialogs.current.forEach(dialog => dialog.current.close()) - return openDialogs.length > 0 - }, [openDialogs]) + return openDialogs.current.size > 0 + }, []) const setDialogIsOpen = React.useCallback( (id: string, isOpen: boolean) => { - setOpenDialogs(prev => { - const filtered = prev.filter(dialogId => dialogId !== id) as string[] - return isOpen ? [...filtered, id] : filtered - }) + if (isOpen) { + openDialogs.current.add(id) + importantForAccessibility.value = 'no-hide-descendants' + } else { + openDialogs.current.delete(id) + if (openDialogs.current.size < 1) { + importantForAccessibility.value = 'auto' + } + } }, - [setOpenDialogs], + [importantForAccessibility], ) - const context = React.useMemo( - () => ({activeDialogs, openDialogs}), - [openDialogs], + const context = React.useMemo( + () => ({ + activeDialogs: { + current: new Map(), + }, + openDialogs: { + current: new Set(), + }, + importantForAccessibility, + }), + [importantForAccessibility], ) const controls = React.useMemo( () => ({closeAllDialogs, setDialogIsOpen}), diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index bdba7917..76a7f8fb 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -30,7 +30,8 @@ import {useCloseAnyActiveElement} from '#/state/util' import * as notifications from 'lib/notifications/notifications' import {Outlet as PortalOutlet} from '#/components/Portal' import {MutedWordsDialog} from '#/components/dialogs/MutedWords' -import {useDialogStateContext} from '#/state/dialogs' +import {useDialogStateContext} from 'state/dialogs' +import Animated from 'react-native-reanimated' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -54,9 +55,9 @@ function ShellInner() { const canGoBack = useNavigationState(state => !isStateAtTabRoot(state)) const {hasSession, currentAccount} = useSession() const closeAnyActiveElement = useCloseAnyActiveElement() + const {importantForAccessibility} = useDialogStateContext() // start undefined const currentAccountDid = React.useRef(undefined) - const {openDialogs} = useDialogStateContext() React.useEffect(() => { let listener = {remove() {}} @@ -80,19 +81,9 @@ function ShellInner() { } }, [currentAccount]) - /** - * The counterpart to `accessibilityViewIsModal` for Android. This property - * applies to the parent of all non-modal views, and prevents TalkBack from - * navigating within content beneath an open dialog. - * - * @see https://reactnative.dev/docs/accessibility#importantforaccessibility-android - */ - const importantForAccessibility = - openDialogs.length > 0 ? 'no-hide-descendants' : undefined - return ( <> - @@ -106,7 +97,7 @@ function ShellInner() { - +