[Sheets] [Pt. 14] Portaling behaviors for nested dialogs (#5588)

Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
Hailey
2024-10-02 22:03:52 -07:00
committed by GitHub
parent 52a8e4d616
commit 058ddb0c53
5 changed files with 376 additions and 349 deletions
+4 -21
View File
@@ -16,7 +16,7 @@ import {
DialogOuterProps, DialogOuterProps,
} from '#/components/Dialog/types' } from '#/components/Dialog/types'
import {createInput} from '#/components/forms/TextField' import {createInput} from '#/components/forms/TextField'
import {Portal} from '#/components/Portal' import {Portal as DefaultPortal} from '#/components/Portal'
import { import {
BottomSheetSnapPoint, BottomSheetSnapPoint,
BottomSheetView, BottomSheetView,
@@ -34,26 +34,7 @@ export function Outer({
onClose, onClose,
nativeOptions, nativeOptions,
testID, testID,
}: React.PropsWithChildren<DialogOuterProps>) { Portal = DefaultPortal,
return (
<Portal>
<OuterWithoutPortal
control={control}
onClose={onClose}
nativeOptions={nativeOptions}
testID={testID}>
{children}
</OuterWithoutPortal>
</Portal>
)
}
export function OuterWithoutPortal({
children,
control,
onClose,
nativeOptions,
testID,
}: React.PropsWithChildren<DialogOuterProps>) { }: React.PropsWithChildren<DialogOuterProps>) {
const t = useTheme() const t = useTheme()
const ref = React.useRef<BottomSheetView>(null) const ref = React.useRef<BottomSheetView>(null)
@@ -119,6 +100,7 @@ export function OuterWithoutPortal({
const Wrapper = isIOS ? View : GestureHandlerRootView const Wrapper = isIOS ? View : GestureHandlerRootView
return ( return (
<Portal>
<Context.Provider value={context}> <Context.Provider value={context}>
<BottomSheetView <BottomSheetView
ref={ref} ref={ref}
@@ -139,6 +121,7 @@ export function OuterWithoutPortal({
</Wrapper> </Wrapper>
</BottomSheetView> </BottomSheetView>
</Context.Provider> </Context.Provider>
</Portal>
) )
} }
+2
View File
@@ -6,6 +6,7 @@ import type {
} from 'react-native' } from 'react-native'
import {ViewStyleProp} from '#/alf' import {ViewStyleProp} from '#/alf'
import {PortalComponent} from '#/components/Portal'
import {BottomSheetViewProps} from '../../../modules/bottom-sheet' import {BottomSheetViewProps} from '../../../modules/bottom-sheet'
import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types' import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types'
@@ -58,6 +59,7 @@ export type DialogOuterProps = {
nativeOptions?: Omit<BottomSheetViewProps, 'children'> nativeOptions?: Omit<BottomSheetViewProps, 'children'>
webOptions?: {} webOptions?: {}
testID?: string testID?: string
Portal?: PortalComponent
} }
type DialogInnerPropsBase<T> = React.PropsWithChildren<ViewStyleProp> & T type DialogInnerPropsBase<T> = React.PropsWithChildren<ViewStyleProp> & T
+2
View File
@@ -12,6 +12,8 @@ type ComponentMap = {
[id: string]: Component [id: string]: Component
} }
export type PortalComponent = ({children}: {children?: React.ReactNode}) => null
export function createPortalGroup() { export function createPortalGroup() {
const Context = React.createContext<ContextType>({ const Context = React.createContext<ContextType>({
outlet: null, outlet: null,
+8 -14
View File
@@ -3,10 +3,10 @@ import {GestureResponderEvent, View} from 'react-native'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {isNative} from '#/platform/detection'
import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonColor, ButtonText} from '#/components/Button' import {Button, ButtonColor, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
export { export {
@@ -26,11 +26,11 @@ export function Outer({
children, children,
control, control,
testID, testID,
withoutPortal, Portal,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
testID?: string testID?: string
withoutPortal?: boolean Portal?: PortalComponent
}>) { }>) {
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const titleId = React.useId() const titleId = React.useId()
@@ -41,11 +41,8 @@ export function Outer({
[titleId, descriptionId], [titleId, descriptionId],
) )
const Wrapper =
withoutPortal && isNative ? Dialog.OuterWithoutPortal : Dialog.Outer
return ( return (
<Wrapper control={control} testID={testID}> <Dialog.Outer control={control} testID={testID} Portal={Portal}>
<Context.Provider value={context}> <Context.Provider value={context}>
<Dialog.ScrollableInner <Dialog.ScrollableInner
accessibilityLabelledBy={titleId} accessibilityLabelledBy={titleId}
@@ -56,7 +53,7 @@ export function Outer({
{children} {children}
</Dialog.ScrollableInner> </Dialog.ScrollableInner>
</Context.Provider> </Context.Provider>
</Wrapper> </Dialog.Outer>
) )
} }
@@ -185,7 +182,7 @@ export function Basic({
onConfirm, onConfirm,
confirmButtonColor, confirmButtonColor,
showCancel = true, showCancel = true,
withoutPortal, Portal,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
control: Dialog.DialogOuterProps['control'] control: Dialog.DialogOuterProps['control']
title: string title: string
@@ -202,13 +199,10 @@ export function Basic({
onConfirm: (e: GestureResponderEvent) => void onConfirm: (e: GestureResponderEvent) => void
confirmButtonColor?: ButtonColor confirmButtonColor?: ButtonColor
showCancel?: boolean showCancel?: boolean
withoutPortal?: boolean Portal?: PortalComponent
}>) { }>) {
return ( return (
<Outer <Outer control={control} testID="confirmModal" Portal={Portal}>
control={control}
testID="confirmModal"
withoutPortal={withoutPortal}>
<TitleText>{title}</TitleText> <TitleText>{title}</TitleText>
<DescriptionText>{description}</DescriptionText> <DescriptionText>{description}</DescriptionText>
<Actions> <Actions>
+57 -11
View File
@@ -31,11 +31,14 @@ import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable' import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable'
import {createPortalGroup} from '#/components/Portal'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
const ONE_DAY = 24 * 60 * 60 * 1000 const ONE_DAY = 24 * 60 * 60 * 1000
const Portal = createPortalGroup()
export function MutedWordsDialog() { export function MutedWordsDialog() {
const {mutedWordsDialogControl: control} = useGlobalDialogsControlContext() const {mutedWordsDialogControl: control} = useGlobalDialogsControlContext()
return ( return (
@@ -105,17 +108,23 @@ function MutedWordsInner() {
}, [_, field, targets, addMutedWord, setField, durations, excludeFollowing]) }, [_, field, targets, addMutedWord, setField, durations, excludeFollowing])
return ( return (
<Portal.Provider>
<Dialog.ScrollableInner label={_(msg`Manage your muted words and tags`)}> <Dialog.ScrollableInner label={_(msg`Manage your muted words and tags`)}>
<View> <View>
<Text <Text
style={[a.text_md, a.font_bold, a.pb_sm, t.atoms.text_contrast_high]}> style={[
a.text_md,
a.font_bold,
a.pb_sm,
t.atoms.text_contrast_high,
]}>
<Trans>Add muted words and tags</Trans> <Trans>Add muted words and tags</Trans>
</Text> </Text>
<Text style={[a.pb_lg, a.leading_snug, t.atoms.text_contrast_medium]}> <Text style={[a.pb_lg, a.leading_snug, t.atoms.text_contrast_medium]}>
<Trans> <Trans>
Posts can be muted based on their text, their tags, or both. We Posts can be muted based on their text, their tags, or both. We
recommend avoiding common words that appear in many posts, since it recommend avoiding common words that appear in many posts, since
can result in no posts being shown. it can result in no posts being shown.
</Trans> </Trans>
</Text> </Text>
@@ -173,7 +182,12 @@ function MutedWordsInner() {
PressableComponent={NormalizedRNGHPressable}> PressableComponent={NormalizedRNGHPressable}>
<TargetToggle> <TargetToggle>
<View <View
style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> style={[
a.flex_1,
a.flex_row,
a.align_center,
a.gap_sm,
]}>
<Toggle.Radio /> <Toggle.Radio />
<Toggle.LabelText style={[a.flex_1, a.leading_tight]}> <Toggle.LabelText style={[a.flex_1, a.leading_tight]}>
<Trans>Forever</Trans> <Trans>Forever</Trans>
@@ -189,7 +203,12 @@ function MutedWordsInner() {
PressableComponent={NormalizedRNGHPressable}> PressableComponent={NormalizedRNGHPressable}>
<TargetToggle> <TargetToggle>
<View <View
style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> style={[
a.flex_1,
a.flex_row,
a.align_center,
a.gap_sm,
]}>
<Toggle.Radio /> <Toggle.Radio />
<Toggle.LabelText style={[a.flex_1, a.leading_tight]}> <Toggle.LabelText style={[a.flex_1, a.leading_tight]}>
<Trans>24 hours</Trans> <Trans>24 hours</Trans>
@@ -214,7 +233,12 @@ function MutedWordsInner() {
PressableComponent={NormalizedRNGHPressable}> PressableComponent={NormalizedRNGHPressable}>
<TargetToggle> <TargetToggle>
<View <View
style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> style={[
a.flex_1,
a.flex_row,
a.align_center,
a.gap_sm,
]}>
<Toggle.Radio /> <Toggle.Radio />
<Toggle.LabelText style={[a.flex_1, a.leading_tight]}> <Toggle.LabelText style={[a.flex_1, a.leading_tight]}>
<Trans>7 days</Trans> <Trans>7 days</Trans>
@@ -230,7 +254,12 @@ function MutedWordsInner() {
PressableComponent={NormalizedRNGHPressable}> PressableComponent={NormalizedRNGHPressable}>
<TargetToggle> <TargetToggle>
<View <View
style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> style={[
a.flex_1,
a.flex_row,
a.align_center,
a.gap_sm,
]}>
<Toggle.Radio /> <Toggle.Radio />
<Toggle.LabelText style={[a.flex_1, a.leading_tight]}> <Toggle.LabelText style={[a.flex_1, a.leading_tight]}>
<Trans>30 days</Trans> <Trans>30 days</Trans>
@@ -243,7 +272,9 @@ function MutedWordsInner() {
</Toggle.Group> </Toggle.Group>
<Toggle.Group <Toggle.Group
label={_(msg`Select what content this mute word should apply to.`)} label={_(
msg`Select what content this mute word should apply to.`,
)}
type="radio" type="radio"
values={targets} values={targets}
onChange={setTargets}> onChange={setTargets}>
@@ -312,7 +343,8 @@ function MutedWordsInner() {
onChange={setExcludeFollowing} onChange={setExcludeFollowing}
PressableComponent={NormalizedRNGHPressable}> PressableComponent={NormalizedRNGHPressable}>
<TargetToggle> <TargetToggle>
<View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> <View
style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
<Toggle.Checkbox /> <Toggle.Checkbox />
<Toggle.LabelText style={[a.flex_1, a.leading_tight]}> <Toggle.LabelText style={[a.flex_1, a.leading_tight]}>
<Trans>Exclude users you follow</Trans> <Trans>Exclude users you follow</Trans>
@@ -380,7 +412,12 @@ function MutedWordsInner() {
<Loader /> <Loader />
) : preferencesError || !preferences ? ( ) : preferencesError || !preferences ? (
<View <View
style={[a.py_md, a.px_lg, a.rounded_md, t.atoms.bg_contrast_25]}> style={[
a.py_md,
a.px_lg,
a.rounded_md,
t.atoms.bg_contrast_25,
]}>
<Text style={[a.italic, t.atoms.text_contrast_high]}> <Text style={[a.italic, t.atoms.text_contrast_high]}>
<Trans> <Trans>
We're sorry, but we weren't able to load your muted words at We're sorry, but we weren't able to load your muted words at
@@ -400,7 +437,12 @@ function MutedWordsInner() {
)) ))
) : ( ) : (
<View <View
style={[a.py_md, a.px_lg, a.rounded_md, t.atoms.bg_contrast_25]}> style={[
a.py_md,
a.px_lg,
a.rounded_md,
t.atoms.bg_contrast_25,
]}>
<Text style={[a.italic, t.atoms.text_contrast_high]}> <Text style={[a.italic, t.atoms.text_contrast_high]}>
<Trans>You haven't muted any words or tags yet</Trans> <Trans>You haven't muted any words or tags yet</Trans>
</Text> </Text>
@@ -413,6 +455,9 @@ function MutedWordsInner() {
<Dialog.Close /> <Dialog.Close />
</Dialog.ScrollableInner> </Dialog.ScrollableInner>
<Portal.Outlet />
</Portal.Provider>
) )
} }
@@ -444,6 +489,7 @@ function MutedWordRow({
onConfirm={remove} onConfirm={remove}
confirmButtonCta={_(msg`Remove`)} confirmButtonCta={_(msg`Remove`)}
confirmButtonColor="negative" confirmButtonColor="negative"
Portal={Portal.Portal}
/> />
<View <View