Move Dialogs to Radix (#5648)

* Use Redix FocusTrap (#5638)

* Use Redix FocusTrap

* force resolutions on radix libs

* add focus guards

* use @radix-ui/dismissable-layer for escape handling

* fix banner menu keypress by using `Pressable`

* add menu in dialog example to storybook

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* use DismissableLayer/FocusScope for composer

* fix storybook dialog

* thread Portal through Prompt and avatar/banner

* fix dialog style regression

* remove tamagui

---------

Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
Samuel Newman
2024-10-08 20:00:49 +03:00
committed by GitHub
parent efcf8a6ae5
commit fc82d2f6d5
14 changed files with 383 additions and 443 deletions
+16 -20
View File
@@ -10,7 +10,9 @@ import {
import Animated, {FadeIn, FadeInDown} from 'react-native-reanimated'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {FocusScope} from '@tamagui/focus-scope'
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
import {useFocusGuards} from '@radix-ui/react-focus-guards'
import {FocusScope} from '@radix-ui/react-focus-scope'
import {logger} from '#/logger'
import {useDialogStateControlContext} from '#/state/dialogs'
@@ -31,6 +33,7 @@ export * from '#/components/Dialog/utils'
export {Input} from '#/components/forms/TextField'
const stopPropagation = (e: any) => e.stopPropagation()
const preventDefault = (e: any) => e.preventDefault()
export function Outer({
children,
@@ -85,21 +88,6 @@ export function Outer({
[close, open],
)
React.useEffect(() => {
if (!isOpen) return
function handler(e: KeyboardEvent) {
if (e.key === 'Escape') {
e.stopPropagation()
close()
}
}
document.addEventListener('keydown', handler)
return () => document.removeEventListener('keydown', handler)
}, [close, isOpen])
const context = React.useMemo(
() => ({
close,
@@ -168,9 +156,11 @@ export function Inner({
accessibilityDescribedBy,
}: DialogInnerProps) {
const t = useTheme()
const {close} = React.useContext(Context)
const {gtMobile} = useBreakpoints()
useFocusGuards()
return (
<FocusScope loop enabled trapped>
<FocusScope loop asChild trapped>
<Animated.View
role="dialog"
aria-role="dialog"
@@ -183,7 +173,7 @@ export function Inner({
onTouchEnd={stopPropagation}
entering={FadeInDown.duration(100)}
// exiting={FadeOut.duration(100)}
style={[
style={flatten([
a.relative,
a.rounded_md,
a.w_full,
@@ -198,8 +188,14 @@ export function Inner({
shadowRadius: 30,
},
flatten(style),
]}>
{children}
])}>
<DismissableLayer
onInteractOutside={preventDefault}
onFocusOutside={preventDefault}
onDismiss={close}
style={{display: 'flex', flexDirection: 'column'}}>
{children}
</DismissableLayer>
</Animated.View>
</FocusScope>
)
+6 -3
View File
@@ -18,6 +18,7 @@ import {
ItemTextProps,
TriggerProps,
} from '#/components/Menu/types'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography'
export {
@@ -77,9 +78,11 @@ export function Trigger({children, label}: TriggerProps) {
export function Outer({
children,
showCancel,
Portal,
}: React.PropsWithChildren<{
showCancel?: boolean
style?: StyleProp<ViewStyle>
Portal?: PortalComponent
}>) {
const context = React.useContext(Context)
const {_} = useLingui()
@@ -87,15 +90,15 @@ export function Outer({
return (
<Dialog.Outer
control={context.control}
nativeOptions={{preventExpansion: true}}>
nativeOptions={{preventExpansion: true}}
Portal={Portal}>
<Dialog.Handle />
{/* Re-wrap with context since Dialogs are portal-ed to root */}
<Context.Provider value={context}>
<Dialog.ScrollableInner label={_(msg`Menu`)} style={[a.pt_sm]}>
<Dialog.ScrollableInner label={_(msg`Menu`)} style={[a.py_sm]}>
<View style={[a.gap_lg]}>
{children}
{isNative && showCancel && <Cancel />}
<View style={[{height: a.pb_lg.paddingBottom}]} />
</View>
</Dialog.ScrollableInner>
</Context.Provider>
+8 -1
View File
@@ -8,6 +8,7 @@ import {Button, ButtonColor, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography'
import {BottomSheetViewProps} from '../../modules/bottom-sheet'
export {
type DialogControlProps as PromptControlProps,
@@ -27,10 +28,12 @@ export function Outer({
control,
testID,
Portal,
nativeOptions,
}: React.PropsWithChildren<{
control: Dialog.DialogControlProps
testID?: string
Portal?: PortalComponent
nativeOptions?: Omit<BottomSheetViewProps, 'children'>
}>) {
const {gtMobile} = useBreakpoints()
const titleId = React.useId()
@@ -42,7 +45,11 @@ export function Outer({
)
return (
<Dialog.Outer control={control} testID={testID} Portal={Portal}>
<Dialog.Outer
control={control}
testID={testID}
Portal={Portal}
nativeOptions={{preventExpansion: true, ...nativeOptions}}>
<Dialog.Handle />
<Context.Provider value={context}>
<Dialog.ScrollableInner
+3 -3
View File
@@ -23,14 +23,14 @@ import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {ListMethods} from '#/view/com/util/List'
import {atoms as a, ios, native, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import * as TextField from '#/components/forms/TextField'
import {useThrottledValue} from '#/components/hooks/useThrottledValue'
import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow'
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
import {Button, ButtonIcon, ButtonText} from '../Button'
import {ListFooter, ListMaybePlaceholder} from '../Lists'
import {PortalComponent} from '../Portal'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {PortalComponent} from '#/components/Portal'
export function GifSelectDialog({
controlRef,