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:
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user