Allow nested sheets without boilerplate (#5660)
Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
@@ -4,9 +4,19 @@ import {
|
|||||||
BottomSheetState,
|
BottomSheetState,
|
||||||
BottomSheetViewProps,
|
BottomSheetViewProps,
|
||||||
} from './src/BottomSheet.types'
|
} from './src/BottomSheet.types'
|
||||||
|
import {BottomSheetNativeComponent} from './src/BottomSheetNativeComponent'
|
||||||
|
import {
|
||||||
|
BottomSheetOutlet,
|
||||||
|
BottomSheetPortalProvider,
|
||||||
|
BottomSheetProvider,
|
||||||
|
} from './src/BottomSheetPortal'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
BottomSheet,
|
BottomSheet,
|
||||||
|
BottomSheetNativeComponent,
|
||||||
|
BottomSheetOutlet,
|
||||||
|
BottomSheetPortalProvider,
|
||||||
|
BottomSheetProvider,
|
||||||
BottomSheetSnapPoint,
|
BottomSheetSnapPoint,
|
||||||
type BottomSheetState,
|
type BottomSheetState,
|
||||||
type BottomSheetViewProps,
|
type BottomSheetViewProps,
|
||||||
|
|||||||
@@ -1,100 +1,24 @@
|
|||||||
import * as React from 'react'
|
import 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 {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<
|
if (__DEV__ && !Portal) {
|
||||||
BottomSheetViewProps & {
|
throw new Error(
|
||||||
ref: React.RefObject<any>
|
'BottomSheet: You need to wrap your component tree with a <BottomSheetPortalProvider> to use the bottom sheet.',
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NativeView
|
<Portal>
|
||||||
{...rest}
|
<BottomSheetNativeComponent {...props} ref={ref} />
|
||||||
onStateChange={this.onStateChange}
|
</Portal>
|
||||||
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>
|
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
|
}
|
||||||
@@ -68,6 +68,7 @@ import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
|||||||
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
|
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
|
||||||
import {Provider as PortalProvider} from '#/components/Portal'
|
import {Provider as PortalProvider} from '#/components/Portal'
|
||||||
import {Splash} from '#/Splash'
|
import {Splash} from '#/Splash'
|
||||||
|
import {BottomSheetProvider} from '../modules/bottom-sheet'
|
||||||
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||||
|
|
||||||
SplashScreen.preventAutoHideAsync()
|
SplashScreen.preventAutoHideAsync()
|
||||||
@@ -197,6 +198,7 @@ function App() {
|
|||||||
<DialogStateProvider>
|
<DialogStateProvider>
|
||||||
<LightboxStateProvider>
|
<LightboxStateProvider>
|
||||||
<PortalProvider>
|
<PortalProvider>
|
||||||
|
<BottomSheetProvider>
|
||||||
<StarterPackProvider>
|
<StarterPackProvider>
|
||||||
<SafeAreaProvider
|
<SafeAreaProvider
|
||||||
initialMetrics={initialWindowMetrics}>
|
initialMetrics={initialWindowMetrics}>
|
||||||
@@ -205,6 +207,7 @@ function App() {
|
|||||||
</IntentDialogProvider>
|
</IntentDialogProvider>
|
||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
</StarterPackProvider>
|
</StarterPackProvider>
|
||||||
|
</BottomSheetProvider>
|
||||||
</PortalProvider>
|
</PortalProvider>
|
||||||
</LightboxStateProvider>
|
</LightboxStateProvider>
|
||||||
</DialogStateProvider>
|
</DialogStateProvider>
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ 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 as DefaultPortal} from '#/components/Portal'
|
|
||||||
import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet'
|
import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet'
|
||||||
import {
|
import {
|
||||||
BottomSheetSnapPointChangeEvent,
|
BottomSheetSnapPointChangeEvent,
|
||||||
BottomSheetStateChangeEvent,
|
BottomSheetStateChangeEvent,
|
||||||
} from '../../../modules/bottom-sheet/src/BottomSheet.types'
|
} from '../../../modules/bottom-sheet/src/BottomSheet.types'
|
||||||
|
import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet/src/BottomSheetNativeComponent'
|
||||||
|
|
||||||
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
||||||
export * from '#/components/Dialog/types'
|
export * from '#/components/Dialog/types'
|
||||||
@@ -50,10 +50,9 @@ export function Outer({
|
|||||||
onClose,
|
onClose,
|
||||||
nativeOptions,
|
nativeOptions,
|
||||||
testID,
|
testID,
|
||||||
Portal = DefaultPortal,
|
|
||||||
}: React.PropsWithChildren<DialogOuterProps>) {
|
}: React.PropsWithChildren<DialogOuterProps>) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const ref = React.useRef<BottomSheet>(null)
|
const ref = React.useRef<BottomSheetNativeComponent>(null)
|
||||||
const closeCallbacks = React.useRef<(() => void)[]>([])
|
const closeCallbacks = React.useRef<(() => void)[]>([])
|
||||||
const {setDialogIsOpen, setFullyExpandedCount} =
|
const {setDialogIsOpen, setFullyExpandedCount} =
|
||||||
useDialogStateControlContext()
|
useDialogStateControlContext()
|
||||||
@@ -154,7 +153,6 @@ export function Outer({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
|
||||||
<Context.Provider value={context}>
|
<Context.Provider value={context}>
|
||||||
<BottomSheet
|
<BottomSheet
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -167,7 +165,6 @@ export function Outer({
|
|||||||
<View testID={testID}>{children}</View>
|
<View testID={testID}>{children}</View>
|
||||||
</BottomSheet>
|
</BottomSheet>
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
</Portal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,18 +30,15 @@ import {useThrottledValue} from '#/components/hooks/useThrottledValue'
|
|||||||
import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow'
|
import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow'
|
||||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
|
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
|
||||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||||
import {PortalComponent} from '#/components/Portal'
|
|
||||||
|
|
||||||
export function GifSelectDialog({
|
export function GifSelectDialog({
|
||||||
controlRef,
|
controlRef,
|
||||||
onClose,
|
onClose,
|
||||||
onSelectGif: onSelectGifProp,
|
onSelectGif: onSelectGifProp,
|
||||||
Portal,
|
|
||||||
}: {
|
}: {
|
||||||
controlRef: React.RefObject<{open: () => void}>
|
controlRef: React.RefObject<{open: () => void}>
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSelectGif: (gif: Gif) => void
|
onSelectGif: (gif: Gif) => void
|
||||||
Portal?: PortalComponent
|
|
||||||
}) {
|
}) {
|
||||||
const control = Dialog.useDialogControl()
|
const control = Dialog.useDialogControl()
|
||||||
|
|
||||||
@@ -65,7 +62,6 @@ export function GifSelectDialog({
|
|||||||
<Dialog.Outer
|
<Dialog.Outer
|
||||||
control={control}
|
control={control}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
Portal={Portal}
|
|
||||||
nativeOptions={{
|
nativeOptions={{
|
||||||
bottomInset: 0,
|
bottomInset: 0,
|
||||||
// use system corner radius on iOS
|
// use system corner radius on iOS
|
||||||
|
|||||||
@@ -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 {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 {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 (
|
||||||
@@ -108,23 +105,17 @@ 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={[
|
style={[a.text_md, a.font_bold, a.pb_sm, t.atoms.text_contrast_high]}>
|
||||||
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
|
recommend avoiding common words that appear in many posts, since it
|
||||||
it can result in no posts being shown.
|
can result in no posts being shown.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
@@ -181,12 +172,7 @@ function MutedWordsInner() {
|
|||||||
style={[a.flex_1]}>
|
style={[a.flex_1]}>
|
||||||
<TargetToggle>
|
<TargetToggle>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
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>
|
||||||
@@ -201,12 +187,7 @@ function MutedWordsInner() {
|
|||||||
style={[a.flex_1]}>
|
style={[a.flex_1]}>
|
||||||
<TargetToggle>
|
<TargetToggle>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
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>
|
||||||
@@ -230,12 +211,7 @@ function MutedWordsInner() {
|
|||||||
style={[a.flex_1]}>
|
style={[a.flex_1]}>
|
||||||
<TargetToggle>
|
<TargetToggle>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
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>
|
||||||
@@ -250,12 +226,7 @@ function MutedWordsInner() {
|
|||||||
style={[a.flex_1]}>
|
style={[a.flex_1]}>
|
||||||
<TargetToggle>
|
<TargetToggle>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
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>
|
||||||
@@ -268,9 +239,7 @@ function MutedWordsInner() {
|
|||||||
</Toggle.Group>
|
</Toggle.Group>
|
||||||
|
|
||||||
<Toggle.Group
|
<Toggle.Group
|
||||||
label={_(
|
label={_(msg`Select what content this mute word should apply to.`)}
|
||||||
msg`Select what content this mute word should apply to.`,
|
|
||||||
)}
|
|
||||||
type="radio"
|
type="radio"
|
||||||
values={targets}
|
values={targets}
|
||||||
onChange={setTargets}>
|
onChange={setTargets}>
|
||||||
@@ -336,8 +305,7 @@ function MutedWordsInner() {
|
|||||||
value={excludeFollowing}
|
value={excludeFollowing}
|
||||||
onChange={setExcludeFollowing}>
|
onChange={setExcludeFollowing}>
|
||||||
<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.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>
|
||||||
@@ -405,12 +373,7 @@ function MutedWordsInner() {
|
|||||||
<Loader />
|
<Loader />
|
||||||
) : preferencesError || !preferences ? (
|
) : preferencesError || !preferences ? (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[a.py_md, a.px_lg, a.rounded_md, t.atoms.bg_contrast_25]}>
|
||||||
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
|
||||||
@@ -430,12 +393,7 @@ function MutedWordsInner() {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[a.py_md, a.px_lg, a.rounded_md, t.atoms.bg_contrast_25]}>
|
||||||
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>
|
||||||
@@ -448,9 +406,6 @@ function MutedWordsInner() {
|
|||||||
|
|
||||||
<Dialog.Close />
|
<Dialog.Close />
|
||||||
</Dialog.ScrollableInner>
|
</Dialog.ScrollableInner>
|
||||||
|
|
||||||
<Portal.Outlet />
|
|
||||||
</Portal.Provider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +437,6 @@ function MutedWordRow({
|
|||||||
onConfirm={remove}
|
onConfirm={remove}
|
||||||
confirmButtonCta={_(msg`Remove`)}
|
confirmButtonCta={_(msg`Remove`)}
|
||||||
confirmButtonColor="negative"
|
confirmButtonColor="negative"
|
||||||
Portal={Portal.Portal}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import * as Toggle from '#/components/forms/Toggle'
|
|||||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {PortalComponent} from '#/components/Portal'
|
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
export type PostInteractionSettingsFormProps = {
|
export type PostInteractionSettingsFormProps = {
|
||||||
@@ -55,15 +54,13 @@ export type PostInteractionSettingsFormProps = {
|
|||||||
|
|
||||||
export function PostInteractionSettingsControlledDialog({
|
export function PostInteractionSettingsControlledDialog({
|
||||||
control,
|
control,
|
||||||
Portal,
|
|
||||||
...rest
|
...rest
|
||||||
}: PostInteractionSettingsFormProps & {
|
}: PostInteractionSettingsFormProps & {
|
||||||
control: Dialog.DialogControlProps
|
control: Dialog.DialogControlProps
|
||||||
Portal?: PortalComponent
|
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
return (
|
return (
|
||||||
<Dialog.Outer control={control} Portal={Portal}>
|
<Dialog.Outer control={control}>
|
||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<Dialog.ScrollableInner
|
<Dialog.ScrollableInner
|
||||||
label={_(msg`Edit post interaction settings`)}
|
label={_(msg`Edit post interaction settings`)}
|
||||||
@@ -207,7 +204,9 @@ export function PostInteractionSettingsDialogControlledInner(
|
|||||||
label={_(msg`Edit post interaction settings`)}
|
label={_(msg`Edit post interaction settings`)}
|
||||||
style={[{maxWidth: 500}, a.w_full]}>
|
style={[{maxWidth: 500}, a.w_full]}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
<View style={[a.flex_1, a.py_4xl, a.align_center, a.justify_center]}>
|
||||||
<Loader size="xl" />
|
<Loader size="xl" />
|
||||||
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<PostInteractionSettingsForm
|
<PostInteractionSettingsForm
|
||||||
replySettingsDisabled={!isThreadgateOwnedByViewer}
|
replySettingsDisabled={!isThreadgateOwnedByViewer}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react'
|
|||||||
import {isWeb} from '#/platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
import {DialogControlRefProps} from '#/components/Dialog'
|
import {DialogControlRefProps} from '#/components/Dialog'
|
||||||
import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context'
|
import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context'
|
||||||
import {BottomSheet} from '../../../modules/bottom-sheet'
|
import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet'
|
||||||
|
|
||||||
interface IDialogContext {
|
interface IDialogContext {
|
||||||
/**
|
/**
|
||||||
@@ -61,7 +61,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
|
|
||||||
return openDialogs.current.size > 0
|
return openDialogs.current.size > 0
|
||||||
} else {
|
} else {
|
||||||
BottomSheet.dismissAll()
|
BottomSheetNativeComponent.dismissAll()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|||||||
@@ -107,9 +107,9 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
|||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
|
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||||
import {createPortalGroup} from '#/components/Portal'
|
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {Text as NewText} from '#/components/Typography'
|
import {Text as NewText} from '#/components/Typography'
|
||||||
|
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
|
||||||
import {
|
import {
|
||||||
composerReducer,
|
composerReducer,
|
||||||
createComposerState,
|
createComposerState,
|
||||||
@@ -117,8 +117,6 @@ import {
|
|||||||
} from './state/composer'
|
} from './state/composer'
|
||||||
import {NO_VIDEO, NoVideoState, processVideo, VideoState} from './state/video'
|
import {NO_VIDEO, NoVideoState, processVideo, VideoState} from './state/video'
|
||||||
|
|
||||||
const Portal = createPortalGroup()
|
|
||||||
|
|
||||||
type CancelRef = {
|
type CancelRef = {
|
||||||
onPressCancel: () => void
|
onPressCancel: () => void
|
||||||
}
|
}
|
||||||
@@ -522,7 +520,7 @@ export const ComposePost = ({
|
|||||||
const keyboardVerticalOffset = useKeyboardVerticalOffset()
|
const keyboardVerticalOffset = useKeyboardVerticalOffset()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal.Provider>
|
<BottomSheetPortalProvider>
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
testID="composePostView"
|
testID="composePostView"
|
||||||
behavior={isIOS ? 'padding' : 'height'}
|
behavior={isIOS ? 'padding' : 'height'}
|
||||||
@@ -666,11 +664,7 @@ export const ComposePost = ({
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Gallery
|
<Gallery images={images} dispatch={dispatch} />
|
||||||
images={images}
|
|
||||||
dispatch={dispatch}
|
|
||||||
Portal={Portal.Portal}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{extGif && (
|
{extGif && (
|
||||||
<View style={a.relative} key={extGif.url}>
|
<View style={a.relative} key={extGif.url}>
|
||||||
@@ -684,7 +678,6 @@ export const ComposePost = ({
|
|||||||
gif={extGif}
|
gif={extGif}
|
||||||
altText={extGifAlt ?? ''}
|
altText={extGifAlt ?? ''}
|
||||||
onSubmit={handleChangeGifAltText}
|
onSubmit={handleChangeGifAltText}
|
||||||
Portal={Portal.Portal}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@@ -744,7 +737,6 @@ export const ComposePost = ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
Portal={Portal.Portal}
|
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)}
|
)}
|
||||||
@@ -782,7 +774,6 @@ export const ComposePost = ({
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
style={bottomBarAnimatedStyle}
|
style={bottomBarAnimatedStyle}
|
||||||
Portal={Portal.Portal}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<View
|
<View
|
||||||
@@ -819,7 +810,6 @@ export const ComposePost = ({
|
|||||||
onClose={focusTextInput}
|
onClose={focusTextInput}
|
||||||
onSelectGif={onSelectGif}
|
onSelectGif={onSelectGif}
|
||||||
disabled={hasMedia}
|
disabled={hasMedia}
|
||||||
Portal={Portal.Portal}
|
|
||||||
/>
|
/>
|
||||||
{!isMobile ? (
|
{!isMobile ? (
|
||||||
<Button
|
<Button
|
||||||
@@ -849,11 +839,9 @@ export const ComposePost = ({
|
|||||||
onConfirm={onClose}
|
onConfirm={onClose}
|
||||||
confirmButtonCta={_(msg`Discard`)}
|
confirmButtonCta={_(msg`Discard`)}
|
||||||
confirmButtonColor="negative"
|
confirmButtonColor="negative"
|
||||||
Portal={Portal.Portal}
|
|
||||||
/>
|
/>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
<Portal.Outlet />
|
</BottomSheetPortalProvider>
|
||||||
</Portal.Provider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import * as TextField from '#/components/forms/TextField'
|
|||||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||||
import {PortalComponent} from '#/components/Portal'
|
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {GifEmbed} from '../util/post-embeds/GifEmbed'
|
import {GifEmbed} from '../util/post-embeds/GifEmbed'
|
||||||
import {AltTextReminder} from './photos/Gallery'
|
import {AltTextReminder} from './photos/Gallery'
|
||||||
@@ -30,12 +29,10 @@ export function GifAltTextDialog({
|
|||||||
gif,
|
gif,
|
||||||
altText,
|
altText,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
Portal,
|
|
||||||
}: {
|
}: {
|
||||||
gif: Gif
|
gif: Gif
|
||||||
altText: string
|
altText: string
|
||||||
onSubmit: (alt: string) => void
|
onSubmit: (alt: string) => void
|
||||||
Portal: PortalComponent
|
|
||||||
}) {
|
}) {
|
||||||
const {data} = useResolveGifQuery(gif)
|
const {data} = useResolveGifQuery(gif)
|
||||||
const vendorAltText = parseAltFromGIFDescription(data?.description ?? '').alt
|
const vendorAltText = parseAltFromGIFDescription(data?.description ?? '').alt
|
||||||
@@ -50,7 +47,6 @@ export function GifAltTextDialog({
|
|||||||
thumb={data.thumb?.source.path}
|
thumb={data.thumb?.source.path}
|
||||||
params={params}
|
params={params}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
Portal={Portal}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -61,14 +57,12 @@ export function GifAltTextDialogLoaded({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
params,
|
params,
|
||||||
thumb,
|
thumb,
|
||||||
Portal,
|
|
||||||
}: {
|
}: {
|
||||||
vendorAltText: string
|
vendorAltText: string
|
||||||
altText: string
|
altText: string
|
||||||
onSubmit: (alt: string) => void
|
onSubmit: (alt: string) => void
|
||||||
params: EmbedPlayerParams
|
params: EmbedPlayerParams
|
||||||
thumb: string | undefined
|
thumb: string | undefined
|
||||||
Portal: PortalComponent
|
|
||||||
}) {
|
}) {
|
||||||
const control = Dialog.useDialogControl()
|
const control = Dialog.useDialogControl()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
@@ -113,8 +107,7 @@ export function GifAltTextDialogLoaded({
|
|||||||
control={control}
|
control={control}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
onSubmit(altTextDraft)
|
onSubmit(altTextDraft)
|
||||||
}}
|
}}>
|
||||||
Portal={Portal}>
|
|
||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<AltTextInner
|
<AltTextInner
|
||||||
vendorAltText={vendorAltText}
|
vendorAltText={vendorAltText}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {ComposerImage, cropImage} from '#/state/gallery'
|
|||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {PortalComponent} from '#/components/Portal'
|
|
||||||
import {ComposerAction} from '../state/composer'
|
import {ComposerAction} from '../state/composer'
|
||||||
import {EditImageDialog} from './EditImageDialog'
|
import {EditImageDialog} from './EditImageDialog'
|
||||||
import {ImageAltTextDialog} from './ImageAltTextDialog'
|
import {ImageAltTextDialog} from './ImageAltTextDialog'
|
||||||
@@ -31,7 +30,6 @@ const IMAGE_GAP = 8
|
|||||||
interface GalleryProps {
|
interface GalleryProps {
|
||||||
images: ComposerImage[]
|
images: ComposerImage[]
|
||||||
dispatch: (action: ComposerAction) => void
|
dispatch: (action: ComposerAction) => void
|
||||||
Portal: PortalComponent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export let Gallery = (props: GalleryProps): React.ReactNode => {
|
export let Gallery = (props: GalleryProps): React.ReactNode => {
|
||||||
@@ -59,12 +57,7 @@ interface GalleryInnerProps extends GalleryProps {
|
|||||||
containerInfo: Dimensions
|
containerInfo: Dimensions
|
||||||
}
|
}
|
||||||
|
|
||||||
const GalleryInner = ({
|
const GalleryInner = ({images, containerInfo, dispatch}: GalleryInnerProps) => {
|
||||||
images,
|
|
||||||
containerInfo,
|
|
||||||
dispatch,
|
|
||||||
Portal,
|
|
||||||
}: GalleryInnerProps) => {
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
|
|
||||||
const {altTextControlStyle, imageControlsStyle, imageStyle} =
|
const {altTextControlStyle, imageControlsStyle, imageStyle} =
|
||||||
@@ -118,7 +111,6 @@ const GalleryInner = ({
|
|||||||
onRemove={() => {
|
onRemove={() => {
|
||||||
dispatch({type: 'embed_remove_image', image})
|
dispatch({type: 'embed_remove_image', image})
|
||||||
}}
|
}}
|
||||||
Portal={Portal}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -135,7 +127,6 @@ type GalleryItemProps = {
|
|||||||
imageStyle?: ViewStyle
|
imageStyle?: ViewStyle
|
||||||
onChange: (next: ComposerImage) => void
|
onChange: (next: ComposerImage) => void
|
||||||
onRemove: () => void
|
onRemove: () => void
|
||||||
Portal: PortalComponent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const GalleryItem = ({
|
const GalleryItem = ({
|
||||||
@@ -145,7 +136,6 @@ const GalleryItem = ({
|
|||||||
imageStyle,
|
imageStyle,
|
||||||
onChange,
|
onChange,
|
||||||
onRemove,
|
onRemove,
|
||||||
Portal,
|
|
||||||
}: GalleryItemProps): React.ReactNode => {
|
}: GalleryItemProps): React.ReactNode => {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
@@ -240,7 +230,6 @@ const GalleryItem = ({
|
|||||||
control={altTextControl}
|
control={altTextControl}
|
||||||
image={image}
|
image={image}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
Portal={Portal}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EditImageDialog
|
<EditImageDialog
|
||||||
|
|||||||
@@ -15,21 +15,18 @@ import * as Dialog from '#/components/Dialog'
|
|||||||
import {DialogControlProps} from '#/components/Dialog'
|
import {DialogControlProps} from '#/components/Dialog'
|
||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {PortalComponent} from '#/components/Portal'
|
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
control: Dialog.DialogOuterProps['control']
|
control: Dialog.DialogOuterProps['control']
|
||||||
image: ComposerImage
|
image: ComposerImage
|
||||||
onChange: (next: ComposerImage) => void
|
onChange: (next: ComposerImage) => void
|
||||||
Portal: PortalComponent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ImageAltTextDialog = ({
|
export const ImageAltTextDialog = ({
|
||||||
control,
|
control,
|
||||||
image,
|
image,
|
||||||
onChange,
|
onChange,
|
||||||
Portal,
|
|
||||||
}: Props): React.ReactNode => {
|
}: Props): React.ReactNode => {
|
||||||
const [altText, setAltText] = React.useState(image.alt)
|
const [altText, setAltText] = React.useState(image.alt)
|
||||||
|
|
||||||
@@ -41,8 +38,7 @@ export const ImageAltTextDialog = ({
|
|||||||
...image,
|
...image,
|
||||||
alt: enforceLen(altText, MAX_ALT_TEXT, true),
|
alt: enforceLen(altText, MAX_ALT_TEXT, true),
|
||||||
})
|
})
|
||||||
}}
|
}}>
|
||||||
Portal={Portal}>
|
|
||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<ImageAltTextInner
|
<ImageAltTextInner
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@@ -9,16 +9,14 @@ import {atoms as a, useTheme} from '#/alf'
|
|||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import {GifSelectDialog} from '#/components/dialogs/GifSelect'
|
import {GifSelectDialog} from '#/components/dialogs/GifSelect'
|
||||||
import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif'
|
import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif'
|
||||||
import {PortalComponent} from '#/components/Portal'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSelectGif: (gif: Gif) => void
|
onSelectGif: (gif: Gif) => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
Portal?: PortalComponent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectGifBtn({onClose, onSelectGif, disabled, Portal}: Props) {
|
export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const ref = useRef<{open: () => void}>(null)
|
const ref = useRef<{open: () => void}>(null)
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
@@ -48,7 +46,6 @@ export function SelectGifBtn({onClose, onSelectGif, disabled, Portal}: Props) {
|
|||||||
controlRef={ref}
|
controlRef={ref}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSelectGif={onSelectGif}
|
onSelectGif={onSelectGif}
|
||||||
Portal={Portal}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import * as Dialog from '#/components/Dialog'
|
|||||||
import {PostInteractionSettingsControlledDialog} from '#/components/dialogs/PostInteractionSettingsDialog'
|
import {PostInteractionSettingsControlledDialog} from '#/components/dialogs/PostInteractionSettingsDialog'
|
||||||
import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
|
import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
|
||||||
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
|
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
|
||||||
import {PortalComponent} from '#/components/Portal'
|
|
||||||
|
|
||||||
export function ThreadgateBtn({
|
export function ThreadgateBtn({
|
||||||
postgate,
|
postgate,
|
||||||
@@ -21,7 +20,6 @@ export function ThreadgateBtn({
|
|||||||
threadgateAllowUISettings,
|
threadgateAllowUISettings,
|
||||||
onChangeThreadgateAllowUISettings,
|
onChangeThreadgateAllowUISettings,
|
||||||
style,
|
style,
|
||||||
Portal,
|
|
||||||
}: {
|
}: {
|
||||||
postgate: AppBskyFeedPostgate.Record
|
postgate: AppBskyFeedPostgate.Record
|
||||||
onChangePostgate: (v: AppBskyFeedPostgate.Record) => void
|
onChangePostgate: (v: AppBskyFeedPostgate.Record) => void
|
||||||
@@ -30,8 +28,6 @@ export function ThreadgateBtn({
|
|||||||
onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void
|
onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void
|
||||||
|
|
||||||
style?: StyleProp<AnimatedStyle<ViewStyle>>
|
style?: StyleProp<AnimatedStyle<ViewStyle>>
|
||||||
|
|
||||||
Portal: PortalComponent
|
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
@@ -81,7 +77,6 @@ export function ThreadgateBtn({
|
|||||||
onChangePostgate={onChangePostgate}
|
onChangePostgate={onChangePostgate}
|
||||||
threadgateAllowUISettings={threadgateAllowUISettings}
|
threadgateAllowUISettings={threadgateAllowUISettings}
|
||||||
onChangeThreadgateAllowUISettings={onChangeThreadgateAllowUISettings}
|
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 {PageText_Stroke2_Corner0_Rounded as PageTextIcon} from '#/components/icons/PageText'
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
||||||
import {PortalComponent} from '#/components/Portal'
|
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {SubtitleFilePicker} from './SubtitleFilePicker'
|
import {SubtitleFilePicker} from './SubtitleFilePicker'
|
||||||
|
|
||||||
@@ -30,7 +29,6 @@ interface Props {
|
|||||||
captions: CaptionsTrack[]
|
captions: CaptionsTrack[]
|
||||||
saveAltText: (altText: string) => void
|
saveAltText: (altText: string) => void
|
||||||
setCaptions: (updater: (prev: CaptionsTrack[]) => CaptionsTrack[]) => void
|
setCaptions: (updater: (prev: CaptionsTrack[]) => CaptionsTrack[]) => void
|
||||||
Portal: PortalComponent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SubtitleDialogBtn(props: Props) {
|
export function SubtitleDialogBtn(props: Props) {
|
||||||
@@ -58,7 +56,7 @@ export function SubtitleDialogBtn(props: Props) {
|
|||||||
{isWeb ? <Trans>Captions & alt text</Trans> : <Trans>Alt text</Trans>}
|
{isWeb ? <Trans>Captions & alt text</Trans> : <Trans>Alt text</Trans>}
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog.Outer control={control} Portal={props.Portal}>
|
<Dialog.Outer control={control}>
|
||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<SubtitleDialogInner {...props} />
|
<SubtitleDialogInner {...props} />
|
||||||
</Dialog.Outer>
|
</Dialog.Outer>
|
||||||
|
|||||||
@@ -8,13 +8,10 @@ import {atoms as a} from '#/alf'
|
|||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import * as Menu from '#/components/Menu'
|
import * as Menu from '#/components/Menu'
|
||||||
import {createPortalGroup} from '#/components/Portal'
|
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {H3, P, Text} from '#/components/Typography'
|
import {H3, P, Text} from '#/components/Typography'
|
||||||
import {PlatformInfo} from '../../../../modules/expo-bluesky-swiss-army'
|
import {PlatformInfo} from '../../../../modules/expo-bluesky-swiss-army'
|
||||||
|
|
||||||
const Portal = createPortalGroup()
|
|
||||||
|
|
||||||
export function Dialogs() {
|
export function Dialogs() {
|
||||||
const scrollable = Dialog.useDialogControl()
|
const scrollable = Dialog.useDialogControl()
|
||||||
const basic = Dialog.useDialogControl()
|
const basic = Dialog.useDialogControl()
|
||||||
@@ -201,7 +198,6 @@ export function Dialogs() {
|
|||||||
</Dialog.Outer>
|
</Dialog.Outer>
|
||||||
|
|
||||||
<Dialog.Outer control={withMenu}>
|
<Dialog.Outer control={withMenu}>
|
||||||
<Portal.Provider>
|
|
||||||
<Dialog.Inner label="test">
|
<Dialog.Inner label="test">
|
||||||
<H3 nativeID="dialog-title">Dialog with Menu</H3>
|
<H3 nativeID="dialog-title">Dialog with Menu</H3>
|
||||||
<Menu.Root>
|
<Menu.Root>
|
||||||
@@ -218,24 +214,18 @@ export function Dialogs() {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Menu.Trigger>
|
</Menu.Trigger>
|
||||||
<Menu.Outer Portal={Portal.Portal}>
|
<Menu.Outer>
|
||||||
<Menu.Group>
|
<Menu.Group>
|
||||||
<Menu.Item
|
<Menu.Item label="Item 1" onPress={() => console.log('item 1')}>
|
||||||
label="Item 1"
|
|
||||||
onPress={() => console.log('item 1')}>
|
|
||||||
<Menu.ItemText>Item 1</Menu.ItemText>
|
<Menu.ItemText>Item 1</Menu.ItemText>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item label="Item 2" onPress={() => console.log('item 2')}>
|
||||||
label="Item 2"
|
|
||||||
onPress={() => console.log('item 2')}>
|
|
||||||
<Menu.ItemText>Item 2</Menu.ItemText>
|
<Menu.ItemText>Item 2</Menu.ItemText>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.Group>
|
</Menu.Group>
|
||||||
</Menu.Outer>
|
</Menu.Outer>
|
||||||
</Menu.Root>
|
</Menu.Root>
|
||||||
</Dialog.Inner>
|
</Dialog.Inner>
|
||||||
<Portal.Outlet />
|
|
||||||
</Portal.Provider>
|
|
||||||
</Dialog.Outer>
|
</Dialog.Outer>
|
||||||
|
|
||||||
<Dialog.Outer control={scrollable}>
|
<Dialog.Outer control={scrollable}>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
|
|||||||
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
|
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
|
||||||
import {SigninDialog} from '#/components/dialogs/Signin'
|
import {SigninDialog} from '#/components/dialogs/Signin'
|
||||||
import {Outlet as PortalOutlet} from '#/components/Portal'
|
import {Outlet as PortalOutlet} from '#/components/Portal'
|
||||||
|
import {BottomSheetOutlet} from '../../../modules/bottom-sheet'
|
||||||
import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView'
|
import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView'
|
||||||
import {RoutesContainer, TabsNavigator} from '../../Navigation'
|
import {RoutesContainer, TabsNavigator} from '../../Navigation'
|
||||||
import {Composer} from './Composer'
|
import {Composer} from './Composer'
|
||||||
@@ -119,6 +120,7 @@ function ShellInner() {
|
|||||||
<SigninDialog />
|
<SigninDialog />
|
||||||
<Lightbox />
|
<Lightbox />
|
||||||
<PortalOutlet />
|
<PortalOutlet />
|
||||||
|
<BottomSheetOutlet />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user