Allow nested sheets without boilerplate (#5660)

Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
Samuel Newman
2024-10-09 21:30:42 +03:00
committed by GitHub
parent b3ade19bbe
commit cca344a3d1
20 changed files with 596 additions and 555 deletions
+10
View File
@@ -4,9 +4,19 @@ import {
BottomSheetState,
BottomSheetViewProps,
} from './src/BottomSheet.types'
import {BottomSheetNativeComponent} from './src/BottomSheetNativeComponent'
import {
BottomSheetOutlet,
BottomSheetPortalProvider,
BottomSheetProvider,
} from './src/BottomSheetPortal'
export {
BottomSheet,
BottomSheetNativeComponent,
BottomSheetOutlet,
BottomSheetPortalProvider,
BottomSheetProvider,
BottomSheetSnapPoint,
type BottomSheetState,
type BottomSheetViewProps,
+17 -93
View File
@@ -1,100 +1,24 @@
import * as React from 'react'
import {
Dimensions,
NativeSyntheticEvent,
Platform,
StyleProp,
View,
ViewStyle,
} from 'react-native'
import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'
import React from 'react'
import {BottomSheetState, BottomSheetViewProps} from './BottomSheet.types'
import {BottomSheetViewProps} from './BottomSheet.types'
import {BottomSheetNativeComponent} from './BottomSheetNativeComponent'
import {useBottomSheetPortal_INTERNAL} from './BottomSheetPortal'
const screenHeight = Dimensions.get('screen').height
export const BottomSheet = React.forwardRef<
BottomSheetNativeComponent,
BottomSheetViewProps
>(function BottomSheet(props, ref) {
const Portal = useBottomSheetPortal_INTERNAL()
const NativeView: React.ComponentType<
BottomSheetViewProps & {
ref: React.RefObject<any>
style: StyleProp<ViewStyle>
}
> = requireNativeViewManager('BottomSheet')
const NativeModule = requireNativeModule('BottomSheet')
export class BottomSheet extends React.Component<
BottomSheetViewProps,
{
open: boolean
}
> {
ref = React.createRef<any>()
constructor(props: BottomSheetViewProps) {
super(props)
this.state = {
open: false,
}
}
present() {
this.setState({open: true})
}
dismiss() {
this.ref.current?.dismiss()
}
private onStateChange = (
event: NativeSyntheticEvent<{state: BottomSheetState}>,
) => {
const {state} = event.nativeEvent
const isOpen = state !== 'closed'
this.setState({open: isOpen})
this.props.onStateChange?.(event)
}
private updateLayout = () => {
this.ref.current?.updateLayout()
}
static dismissAll = async () => {
await NativeModule.dismissAll()
}
render() {
const {children, backgroundColor, ...rest} = this.props
const cornerRadius = rest.cornerRadius ?? 0
if (!this.state.open) {
return null
if (__DEV__ && !Portal) {
throw new Error(
'BottomSheet: You need to wrap your component tree with a <BottomSheetPortalProvider> to use the bottom sheet.',
)
}
return (
<NativeView
{...rest}
onStateChange={this.onStateChange}
ref={this.ref}
style={{
position: 'absolute',
height: screenHeight,
width: '100%',
}}
containerBackgroundColor={backgroundColor}>
<View
style={[
{
flex: 1,
backgroundColor,
},
Platform.OS === 'android' && {
borderTopLeftRadius: cornerRadius,
borderTopRightRadius: cornerRadius,
},
]}>
<View onLayout={this.updateLayout}>{children}</View>
</View>
</NativeView>
<Portal>
<BottomSheetNativeComponent {...props} ref={ref} />
</Portal>
)
}
}
})
@@ -0,0 +1,103 @@
import * as React from 'react'
import {
Dimensions,
NativeSyntheticEvent,
Platform,
StyleProp,
View,
ViewStyle,
} from 'react-native'
import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'
import {BottomSheetState, BottomSheetViewProps} from './BottomSheet.types'
import {BottomSheetPortalProvider} from './BottomSheetPortal'
const screenHeight = Dimensions.get('screen').height
const NativeView: React.ComponentType<
BottomSheetViewProps & {
ref: React.RefObject<any>
style: StyleProp<ViewStyle>
}
> = requireNativeViewManager('BottomSheet')
const NativeModule = requireNativeModule('BottomSheet')
export class BottomSheetNativeComponent extends React.Component<
BottomSheetViewProps,
{
open: boolean
}
> {
ref = React.createRef<any>()
constructor(props: BottomSheetViewProps) {
super(props)
this.state = {
open: false,
}
}
present() {
this.setState({open: true})
}
dismiss() {
this.ref.current?.dismiss()
}
private onStateChange = (
event: NativeSyntheticEvent<{state: BottomSheetState}>,
) => {
const {state} = event.nativeEvent
const isOpen = state !== 'closed'
this.setState({open: isOpen})
this.props.onStateChange?.(event)
}
private updateLayout = () => {
this.ref.current?.updateLayout()
}
static dismissAll = async () => {
await NativeModule.dismissAll()
}
render() {
const {children, backgroundColor, ...rest} = this.props
const cornerRadius = rest.cornerRadius ?? 0
if (!this.state.open) {
return null
}
return (
<NativeView
{...rest}
onStateChange={this.onStateChange}
ref={this.ref}
style={{
position: 'absolute',
height: screenHeight,
width: '100%',
}}
containerBackgroundColor={backgroundColor}>
<View
style={[
{
flex: 1,
backgroundColor,
},
Platform.OS === 'android' && {
borderTopLeftRadius: cornerRadius,
borderTopRightRadius: cornerRadius,
},
]}>
<View onLayout={this.updateLayout}>
<BottomSheetPortalProvider>{children}</BottomSheetPortalProvider>
</View>
</View>
</NativeView>
)
}
}
@@ -0,0 +1,40 @@
import React from 'react'
import {createPortalGroup_INTERNAL} from './lib/Portal'
type PortalContext = React.ElementType<{children: React.ReactNode}>
const Context = React.createContext({} as PortalContext)
export const useBottomSheetPortal_INTERNAL = () => React.useContext(Context)
export function BottomSheetPortalProvider({
children,
}: {
children: React.ReactNode
}) {
const portal = React.useMemo(() => {
return createPortalGroup_INTERNAL()
}, [])
return (
<Context.Provider value={portal.Portal}>
<portal.Provider>
{children}
<portal.Outlet />
</portal.Provider>
</Context.Provider>
)
}
const defaultPortal = createPortalGroup_INTERNAL()
export const BottomSheetOutlet = defaultPortal.Outlet
export function BottomSheetProvider({children}: {children: React.ReactNode}) {
return (
<Context.Provider value={defaultPortal.Portal}>
<defaultPortal.Provider>{children}</defaultPortal.Provider>
</Context.Provider>
)
}
+67
View File
@@ -0,0 +1,67 @@
import React from 'react'
type Component = React.ReactElement
type ContextType = {
outlet: Component | null
append(id: string, component: Component): void
remove(id: string): void
}
type ComponentMap = {
[id: string]: Component
}
export function createPortalGroup_INTERNAL() {
const Context = React.createContext<ContextType>({
outlet: null,
append: () => {},
remove: () => {},
})
function Provider(props: React.PropsWithChildren<{}>) {
const map = React.useRef<ComponentMap>({})
const [outlet, setOutlet] = React.useState<ContextType['outlet']>(null)
const append = React.useCallback<ContextType['append']>((id, component) => {
if (map.current[id]) return
map.current[id] = <React.Fragment key={id}>{component}</React.Fragment>
setOutlet(<>{Object.values(map.current)}</>)
}, [])
const remove = React.useCallback<ContextType['remove']>(id => {
delete map.current[id]
setOutlet(<>{Object.values(map.current)}</>)
}, [])
const contextValue = React.useMemo(
() => ({
outlet,
append,
remove,
}),
[outlet, append, remove],
)
return (
<Context.Provider value={contextValue}>{props.children}</Context.Provider>
)
}
function Outlet() {
const ctx = React.useContext(Context)
return ctx.outlet
}
function Portal({children}: React.PropsWithChildren<{}>) {
const {append, remove} = React.useContext(Context)
const id = React.useId()
React.useEffect(() => {
append(id, children as Component)
return () => remove(id)
}, [id, children, append, remove])
return null
}
return {Provider, Outlet, Portal}
}
+3
View File
@@ -68,6 +68,7 @@ import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PortalProvider} from '#/components/Portal'
import {Splash} from '#/Splash'
import {BottomSheetProvider} from '../modules/bottom-sheet'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
SplashScreen.preventAutoHideAsync()
@@ -197,6 +198,7 @@ function App() {
<DialogStateProvider>
<LightboxStateProvider>
<PortalProvider>
<BottomSheetProvider>
<StarterPackProvider>
<SafeAreaProvider
initialMetrics={initialWindowMetrics}>
@@ -205,6 +207,7 @@ function App() {
</IntentDialogProvider>
</SafeAreaProvider>
</StarterPackProvider>
</BottomSheetProvider>
</PortalProvider>
</LightboxStateProvider>
</DialogStateProvider>
+2 -5
View File
@@ -31,12 +31,12 @@ import {
DialogOuterProps,
} from '#/components/Dialog/types'
import {createInput} from '#/components/forms/TextField'
import {Portal as DefaultPortal} from '#/components/Portal'
import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet'
import {
BottomSheetSnapPointChangeEvent,
BottomSheetStateChangeEvent,
} from '../../../modules/bottom-sheet/src/BottomSheet.types'
import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet/src/BottomSheetNativeComponent'
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
@@ -50,10 +50,9 @@ export function Outer({
onClose,
nativeOptions,
testID,
Portal = DefaultPortal,
}: React.PropsWithChildren<DialogOuterProps>) {
const t = useTheme()
const ref = React.useRef<BottomSheet>(null)
const ref = React.useRef<BottomSheetNativeComponent>(null)
const closeCallbacks = React.useRef<(() => void)[]>([])
const {setDialogIsOpen, setFullyExpandedCount} =
useDialogStateControlContext()
@@ -154,7 +153,6 @@ export function Outer({
)
return (
<Portal>
<Context.Provider value={context}>
<BottomSheet
ref={ref}
@@ -167,7 +165,6 @@ export function Outer({
<View testID={testID}>{children}</View>
</BottomSheet>
</Context.Provider>
</Portal>
)
}
-4
View File
@@ -30,18 +30,15 @@ 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 {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {PortalComponent} from '#/components/Portal'
export function GifSelectDialog({
controlRef,
onClose,
onSelectGif: onSelectGifProp,
Portal,
}: {
controlRef: React.RefObject<{open: () => void}>
onClose: () => void
onSelectGif: (gif: Gif) => void
Portal?: PortalComponent
}) {
const control = Dialog.useDialogControl()
@@ -65,7 +62,6 @@ export function GifSelectDialog({
<Dialog.Outer
control={control}
onClose={onClose}
Portal={Portal}
nativeOptions={{
bottomInset: 0,
// use system corner radius on iOS
+11 -57
View File
@@ -30,14 +30,11 @@ import {PageText_Stroke2_Corner0_Rounded as PageText} from '#/components/icons/P
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Loader} from '#/components/Loader'
import {createPortalGroup} from '#/components/Portal'
import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography'
const ONE_DAY = 24 * 60 * 60 * 1000
const Portal = createPortalGroup()
export function MutedWordsDialog() {
const {mutedWordsDialogControl: control} = useGlobalDialogsControlContext()
return (
@@ -108,23 +105,17 @@ function MutedWordsInner() {
}, [_, field, targets, addMutedWord, setField, durations, excludeFollowing])
return (
<Portal.Provider>
<Dialog.ScrollableInner label={_(msg`Manage your muted words and tags`)}>
<View>
<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>
</Text>
<Text style={[a.pb_lg, a.leading_snug, t.atoms.text_contrast_medium]}>
<Trans>
Posts can be muted based on their text, their tags, or both. We
recommend avoiding common words that appear in many posts, since
it can result in no posts being shown.
recommend avoiding common words that appear in many posts, since it
can result in no posts being shown.
</Trans>
</Text>
@@ -181,12 +172,7 @@ function MutedWordsInner() {
style={[a.flex_1]}>
<TargetToggle>
<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.LabelText style={[a.flex_1, a.leading_tight]}>
<Trans>Forever</Trans>
@@ -201,12 +187,7 @@ function MutedWordsInner() {
style={[a.flex_1]}>
<TargetToggle>
<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.LabelText style={[a.flex_1, a.leading_tight]}>
<Trans>24 hours</Trans>
@@ -230,12 +211,7 @@ function MutedWordsInner() {
style={[a.flex_1]}>
<TargetToggle>
<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.LabelText style={[a.flex_1, a.leading_tight]}>
<Trans>7 days</Trans>
@@ -250,12 +226,7 @@ function MutedWordsInner() {
style={[a.flex_1]}>
<TargetToggle>
<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.LabelText style={[a.flex_1, a.leading_tight]}>
<Trans>30 days</Trans>
@@ -268,9 +239,7 @@ function MutedWordsInner() {
</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"
values={targets}
onChange={setTargets}>
@@ -336,8 +305,7 @@ function MutedWordsInner() {
value={excludeFollowing}
onChange={setExcludeFollowing}>
<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.LabelText style={[a.flex_1, a.leading_tight]}>
<Trans>Exclude users you follow</Trans>
@@ -405,12 +373,7 @@ function MutedWordsInner() {
<Loader />
) : preferencesError || !preferences ? (
<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]}>
<Trans>
We're sorry, but we weren't able to load your muted words at
@@ -430,12 +393,7 @@ function MutedWordsInner() {
))
) : (
<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]}>
<Trans>You haven't muted any words or tags yet</Trans>
</Text>
@@ -448,9 +406,6 @@ function MutedWordsInner() {
<Dialog.Close />
</Dialog.ScrollableInner>
<Portal.Outlet />
</Portal.Provider>
)
}
@@ -482,7 +437,6 @@ function MutedWordRow({
onConfirm={remove}
confirmButtonCta={_(msg`Remove`)}
confirmButtonColor="negative"
Portal={Portal.Portal}
/>
<View
@@ -37,7 +37,6 @@ import * as Toggle from '#/components/forms/Toggle'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {Loader} from '#/components/Loader'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography'
export type PostInteractionSettingsFormProps = {
@@ -55,15 +54,13 @@ export type PostInteractionSettingsFormProps = {
export function PostInteractionSettingsControlledDialog({
control,
Portal,
...rest
}: PostInteractionSettingsFormProps & {
control: Dialog.DialogControlProps
Portal?: PortalComponent
}) {
const {_} = useLingui()
return (
<Dialog.Outer control={control} Portal={Portal}>
<Dialog.Outer control={control}>
<Dialog.Handle />
<Dialog.ScrollableInner
label={_(msg`Edit post interaction settings`)}
@@ -207,7 +204,9 @@ export function PostInteractionSettingsDialogControlledInner(
label={_(msg`Edit post interaction settings`)}
style={[{maxWidth: 500}, a.w_full]}>
{isLoading ? (
<View style={[a.flex_1, a.py_4xl, a.align_center, a.justify_center]}>
<Loader size="xl" />
</View>
) : (
<PostInteractionSettingsForm
replySettingsDisabled={!isThreadgateOwnedByViewer}
+2 -2
View File
@@ -3,7 +3,7 @@ import React from 'react'
import {isWeb} from '#/platform/detection'
import {DialogControlRefProps} from '#/components/Dialog'
import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context'
import {BottomSheet} from '../../../modules/bottom-sheet'
import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet'
interface IDialogContext {
/**
@@ -61,7 +61,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
return openDialogs.current.size > 0
} else {
BottomSheet.dismissAll()
BottomSheetNativeComponent.dismissAll()
return false
}
}, [])
+4 -16
View File
@@ -107,9 +107,9 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {createPortalGroup} from '#/components/Portal'
import * as Prompt from '#/components/Prompt'
import {Text as NewText} from '#/components/Typography'
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
import {
composerReducer,
createComposerState,
@@ -117,8 +117,6 @@ import {
} from './state/composer'
import {NO_VIDEO, NoVideoState, processVideo, VideoState} from './state/video'
const Portal = createPortalGroup()
type CancelRef = {
onPressCancel: () => void
}
@@ -522,7 +520,7 @@ export const ComposePost = ({
const keyboardVerticalOffset = useKeyboardVerticalOffset()
return (
<Portal.Provider>
<BottomSheetPortalProvider>
<KeyboardAvoidingView
testID="composePostView"
behavior={isIOS ? 'padding' : 'height'}
@@ -666,11 +664,7 @@ export const ComposePost = ({
/>
</View>
<Gallery
images={images}
dispatch={dispatch}
Portal={Portal.Portal}
/>
<Gallery images={images} dispatch={dispatch} />
{extGif && (
<View style={a.relative} key={extGif.url}>
@@ -684,7 +678,6 @@ export const ComposePost = ({
gif={extGif}
altText={extGifAlt ?? ''}
onSubmit={handleChangeGifAltText}
Portal={Portal.Portal}
/>
</View>
)}
@@ -744,7 +737,6 @@ export const ComposePost = ({
},
})
}}
Portal={Portal.Portal}
/>
</Animated.View>
)}
@@ -782,7 +774,6 @@ export const ComposePost = ({
})
}}
style={bottomBarAnimatedStyle}
Portal={Portal.Portal}
/>
)}
<View
@@ -819,7 +810,6 @@ export const ComposePost = ({
onClose={focusTextInput}
onSelectGif={onSelectGif}
disabled={hasMedia}
Portal={Portal.Portal}
/>
{!isMobile ? (
<Button
@@ -849,11 +839,9 @@ export const ComposePost = ({
onConfirm={onClose}
confirmButtonCta={_(msg`Discard`)}
confirmButtonColor="negative"
Portal={Portal.Portal}
/>
</KeyboardAvoidingView>
<Portal.Outlet />
</Portal.Provider>
</BottomSheetPortalProvider>
)
}
+1 -8
View File
@@ -21,7 +21,6 @@ import * as TextField from '#/components/forms/TextField'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography'
import {GifEmbed} from '../util/post-embeds/GifEmbed'
import {AltTextReminder} from './photos/Gallery'
@@ -30,12 +29,10 @@ export function GifAltTextDialog({
gif,
altText,
onSubmit,
Portal,
}: {
gif: Gif
altText: string
onSubmit: (alt: string) => void
Portal: PortalComponent
}) {
const {data} = useResolveGifQuery(gif)
const vendorAltText = parseAltFromGIFDescription(data?.description ?? '').alt
@@ -50,7 +47,6 @@ export function GifAltTextDialog({
thumb={data.thumb?.source.path}
params={params}
onSubmit={onSubmit}
Portal={Portal}
/>
)
}
@@ -61,14 +57,12 @@ export function GifAltTextDialogLoaded({
onSubmit,
params,
thumb,
Portal,
}: {
vendorAltText: string
altText: string
onSubmit: (alt: string) => void
params: EmbedPlayerParams
thumb: string | undefined
Portal: PortalComponent
}) {
const control = Dialog.useDialogControl()
const {_} = useLingui()
@@ -113,8 +107,7 @@ export function GifAltTextDialogLoaded({
control={control}
onClose={() => {
onSubmit(altTextDraft)
}}
Portal={Portal}>
}}>
<Dialog.Handle />
<AltTextInner
vendorAltText={vendorAltText}
+1 -12
View File
@@ -21,7 +21,6 @@ import {ComposerImage, cropImage} from '#/state/gallery'
import {Text} from '#/view/com/util/text/Text'
import {useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {PortalComponent} from '#/components/Portal'
import {ComposerAction} from '../state/composer'
import {EditImageDialog} from './EditImageDialog'
import {ImageAltTextDialog} from './ImageAltTextDialog'
@@ -31,7 +30,6 @@ const IMAGE_GAP = 8
interface GalleryProps {
images: ComposerImage[]
dispatch: (action: ComposerAction) => void
Portal: PortalComponent
}
export let Gallery = (props: GalleryProps): React.ReactNode => {
@@ -59,12 +57,7 @@ interface GalleryInnerProps extends GalleryProps {
containerInfo: Dimensions
}
const GalleryInner = ({
images,
containerInfo,
dispatch,
Portal,
}: GalleryInnerProps) => {
const GalleryInner = ({images, containerInfo, dispatch}: GalleryInnerProps) => {
const {isMobile} = useWebMediaQueries()
const {altTextControlStyle, imageControlsStyle, imageStyle} =
@@ -118,7 +111,6 @@ const GalleryInner = ({
onRemove={() => {
dispatch({type: 'embed_remove_image', image})
}}
Portal={Portal}
/>
)
})}
@@ -135,7 +127,6 @@ type GalleryItemProps = {
imageStyle?: ViewStyle
onChange: (next: ComposerImage) => void
onRemove: () => void
Portal: PortalComponent
}
const GalleryItem = ({
@@ -145,7 +136,6 @@ const GalleryItem = ({
imageStyle,
onChange,
onRemove,
Portal,
}: GalleryItemProps): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
@@ -240,7 +230,6 @@ const GalleryItem = ({
control={altTextControl}
image={image}
onChange={onChange}
Portal={Portal}
/>
<EditImageDialog
@@ -15,21 +15,18 @@ import * as Dialog from '#/components/Dialog'
import {DialogControlProps} from '#/components/Dialog'
import * as TextField from '#/components/forms/TextField'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography'
type Props = {
control: Dialog.DialogOuterProps['control']
image: ComposerImage
onChange: (next: ComposerImage) => void
Portal: PortalComponent
}
export const ImageAltTextDialog = ({
control,
image,
onChange,
Portal,
}: Props): React.ReactNode => {
const [altText, setAltText] = React.useState(image.alt)
@@ -41,8 +38,7 @@ export const ImageAltTextDialog = ({
...image,
alt: enforceLen(altText, MAX_ALT_TEXT, true),
})
}}
Portal={Portal}>
}}>
<Dialog.Handle />
<ImageAltTextInner
control={control}
@@ -9,16 +9,14 @@ import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import {GifSelectDialog} from '#/components/dialogs/GifSelect'
import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif'
import {PortalComponent} from '#/components/Portal'
type Props = {
onClose: () => void
onSelectGif: (gif: Gif) => void
disabled?: boolean
Portal?: PortalComponent
}
export function SelectGifBtn({onClose, onSelectGif, disabled, Portal}: Props) {
export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) {
const {_} = useLingui()
const ref = useRef<{open: () => void}>(null)
const t = useTheme()
@@ -48,7 +46,6 @@ export function SelectGifBtn({onClose, onSelectGif, disabled, Portal}: Props) {
controlRef={ref}
onClose={onClose}
onSelectGif={onSelectGif}
Portal={Portal}
/>
</>
)
@@ -13,7 +13,6 @@ import * as Dialog from '#/components/Dialog'
import {PostInteractionSettingsControlledDialog} from '#/components/dialogs/PostInteractionSettingsDialog'
import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
import {PortalComponent} from '#/components/Portal'
export function ThreadgateBtn({
postgate,
@@ -21,7 +20,6 @@ export function ThreadgateBtn({
threadgateAllowUISettings,
onChangeThreadgateAllowUISettings,
style,
Portal,
}: {
postgate: AppBskyFeedPostgate.Record
onChangePostgate: (v: AppBskyFeedPostgate.Record) => void
@@ -30,8 +28,6 @@ export function ThreadgateBtn({
onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void
style?: StyleProp<AnimatedStyle<ViewStyle>>
Portal: PortalComponent
}) {
const {_} = useLingui()
const t = useTheme()
@@ -81,7 +77,6 @@ export function ThreadgateBtn({
onChangePostgate={onChangePostgate}
threadgateAllowUISettings={threadgateAllowUISettings}
onChangeThreadgateAllowUISettings={onChangeThreadgateAllowUISettings}
Portal={Portal}
/>
</>
)
@@ -17,7 +17,6 @@ import {CC_Stroke2_Corner0_Rounded as CCIcon} from '#/components/icons/CC'
import {PageText_Stroke2_Corner0_Rounded as PageTextIcon} from '#/components/icons/PageText'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography'
import {SubtitleFilePicker} from './SubtitleFilePicker'
@@ -30,7 +29,6 @@ interface Props {
captions: CaptionsTrack[]
saveAltText: (altText: string) => void
setCaptions: (updater: (prev: CaptionsTrack[]) => CaptionsTrack[]) => void
Portal: PortalComponent
}
export function SubtitleDialogBtn(props: Props) {
@@ -58,7 +56,7 @@ export function SubtitleDialogBtn(props: Props) {
{isWeb ? <Trans>Captions & alt text</Trans> : <Trans>Alt text</Trans>}
</ButtonText>
</Button>
<Dialog.Outer control={control} Portal={props.Portal}>
<Dialog.Outer control={control}>
<Dialog.Handle />
<SubtitleDialogInner {...props} />
</Dialog.Outer>
+3 -13
View File
@@ -8,13 +8,10 @@ import {atoms as a} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import * as Menu from '#/components/Menu'
import {createPortalGroup} from '#/components/Portal'
import * as Prompt from '#/components/Prompt'
import {H3, P, Text} from '#/components/Typography'
import {PlatformInfo} from '../../../../modules/expo-bluesky-swiss-army'
const Portal = createPortalGroup()
export function Dialogs() {
const scrollable = Dialog.useDialogControl()
const basic = Dialog.useDialogControl()
@@ -201,7 +198,6 @@ export function Dialogs() {
</Dialog.Outer>
<Dialog.Outer control={withMenu}>
<Portal.Provider>
<Dialog.Inner label="test">
<H3 nativeID="dialog-title">Dialog with Menu</H3>
<Menu.Root>
@@ -218,24 +214,18 @@ export function Dialogs() {
</Button>
)}
</Menu.Trigger>
<Menu.Outer Portal={Portal.Portal}>
<Menu.Outer>
<Menu.Group>
<Menu.Item
label="Item 1"
onPress={() => console.log('item 1')}>
<Menu.Item label="Item 1" onPress={() => console.log('item 1')}>
<Menu.ItemText>Item 1</Menu.ItemText>
</Menu.Item>
<Menu.Item
label="Item 2"
onPress={() => console.log('item 2')}>
<Menu.Item label="Item 2" onPress={() => console.log('item 2')}>
<Menu.ItemText>Item 2</Menu.ItemText>
</Menu.Item>
</Menu.Group>
</Menu.Outer>
</Menu.Root>
</Dialog.Inner>
<Portal.Outlet />
</Portal.Provider>
</Dialog.Outer>
<Dialog.Outer control={scrollable}>
+2
View File
@@ -34,6 +34,7 @@ import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
import {SigninDialog} from '#/components/dialogs/Signin'
import {Outlet as PortalOutlet} from '#/components/Portal'
import {BottomSheetOutlet} from '../../../modules/bottom-sheet'
import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView'
import {RoutesContainer, TabsNavigator} from '../../Navigation'
import {Composer} from './Composer'
@@ -119,6 +120,7 @@ function ShellInner() {
<SigninDialog />
<Lightbox />
<PortalOutlet />
<BottomSheetOutlet />
</>
)
}