An ATproto social media client -- with an independent Appview.

Allow nested sheets without boilerplate (#5660)

Co-authored-by: Hailey <me@haileyok.com>

authored by samuel.fm Hailey and committed by GitHub cca344a3 b3ade19b

+10
modules/bottom-sheet/index.ts
··· 4 4 BottomSheetState, 5 5 BottomSheetViewProps, 6 6 } from './src/BottomSheet.types' 7 + import {BottomSheetNativeComponent} from './src/BottomSheetNativeComponent' 8 + import { 9 + BottomSheetOutlet, 10 + BottomSheetPortalProvider, 11 + BottomSheetProvider, 12 + } from './src/BottomSheetPortal' 7 13 8 14 export { 9 15 BottomSheet, 16 + BottomSheetNativeComponent, 17 + BottomSheetOutlet, 18 + BottomSheetPortalProvider, 19 + BottomSheetProvider, 10 20 BottomSheetSnapPoint, 11 21 type BottomSheetState, 12 22 type BottomSheetViewProps,
+19 -95
modules/bottom-sheet/src/BottomSheet.tsx
··· 1 - import * as React from 'react' 2 - import { 3 - Dimensions, 4 - NativeSyntheticEvent, 5 - Platform, 6 - StyleProp, 7 - View, 8 - ViewStyle, 9 - } from 'react-native' 10 - import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core' 11 - 12 - import {BottomSheetState, BottomSheetViewProps} from './BottomSheet.types' 13 - 14 - const screenHeight = Dimensions.get('screen').height 15 - 16 - const NativeView: React.ComponentType< 17 - BottomSheetViewProps & { 18 - ref: React.RefObject<any> 19 - style: StyleProp<ViewStyle> 20 - } 21 - > = requireNativeViewManager('BottomSheet') 22 - 23 - const NativeModule = requireNativeModule('BottomSheet') 24 - 25 - export class BottomSheet extends React.Component< 26 - BottomSheetViewProps, 27 - { 28 - open: boolean 29 - } 30 - > { 31 - ref = React.createRef<any>() 32 - 33 - constructor(props: BottomSheetViewProps) { 34 - super(props) 35 - this.state = { 36 - open: false, 37 - } 38 - } 39 - 40 - present() { 41 - this.setState({open: true}) 42 - } 43 - 44 - dismiss() { 45 - this.ref.current?.dismiss() 46 - } 47 - 48 - private onStateChange = ( 49 - event: NativeSyntheticEvent<{state: BottomSheetState}>, 50 - ) => { 51 - const {state} = event.nativeEvent 52 - const isOpen = state !== 'closed' 53 - this.setState({open: isOpen}) 54 - this.props.onStateChange?.(event) 55 - } 56 - 57 - private updateLayout = () => { 58 - this.ref.current?.updateLayout() 59 - } 1 + import React from 'react' 60 2 61 - static dismissAll = async () => { 62 - await NativeModule.dismissAll() 63 - } 3 + import {BottomSheetViewProps} from './BottomSheet.types' 4 + import {BottomSheetNativeComponent} from './BottomSheetNativeComponent' 5 + import {useBottomSheetPortal_INTERNAL} from './BottomSheetPortal' 64 6 65 - render() { 66 - const {children, backgroundColor, ...rest} = this.props 67 - const cornerRadius = rest.cornerRadius ?? 0 7 + export const BottomSheet = React.forwardRef< 8 + BottomSheetNativeComponent, 9 + BottomSheetViewProps 10 + >(function BottomSheet(props, ref) { 11 + const Portal = useBottomSheetPortal_INTERNAL() 68 12 69 - if (!this.state.open) { 70 - return null 71 - } 72 - 73 - return ( 74 - <NativeView 75 - {...rest} 76 - onStateChange={this.onStateChange} 77 - ref={this.ref} 78 - style={{ 79 - position: 'absolute', 80 - height: screenHeight, 81 - width: '100%', 82 - }} 83 - containerBackgroundColor={backgroundColor}> 84 - <View 85 - style={[ 86 - { 87 - flex: 1, 88 - backgroundColor, 89 - }, 90 - Platform.OS === 'android' && { 91 - borderTopLeftRadius: cornerRadius, 92 - borderTopRightRadius: cornerRadius, 93 - }, 94 - ]}> 95 - <View onLayout={this.updateLayout}>{children}</View> 96 - </View> 97 - </NativeView> 13 + if (__DEV__ && !Portal) { 14 + throw new Error( 15 + 'BottomSheet: You need to wrap your component tree with a <BottomSheetPortalProvider> to use the bottom sheet.', 98 16 ) 99 17 } 100 - } 18 + 19 + return ( 20 + <Portal> 21 + <BottomSheetNativeComponent {...props} ref={ref} /> 22 + </Portal> 23 + ) 24 + })
+103
modules/bottom-sheet/src/BottomSheetNativeComponent.tsx
··· 1 + import * as React from 'react' 2 + import { 3 + Dimensions, 4 + NativeSyntheticEvent, 5 + Platform, 6 + StyleProp, 7 + View, 8 + ViewStyle, 9 + } from 'react-native' 10 + import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core' 11 + 12 + import {BottomSheetState, BottomSheetViewProps} from './BottomSheet.types' 13 + import {BottomSheetPortalProvider} from './BottomSheetPortal' 14 + 15 + const screenHeight = Dimensions.get('screen').height 16 + 17 + const NativeView: React.ComponentType< 18 + BottomSheetViewProps & { 19 + ref: React.RefObject<any> 20 + style: StyleProp<ViewStyle> 21 + } 22 + > = requireNativeViewManager('BottomSheet') 23 + 24 + const NativeModule = requireNativeModule('BottomSheet') 25 + 26 + export class BottomSheetNativeComponent extends React.Component< 27 + BottomSheetViewProps, 28 + { 29 + open: boolean 30 + } 31 + > { 32 + ref = React.createRef<any>() 33 + 34 + constructor(props: BottomSheetViewProps) { 35 + super(props) 36 + this.state = { 37 + open: false, 38 + } 39 + } 40 + 41 + present() { 42 + this.setState({open: true}) 43 + } 44 + 45 + dismiss() { 46 + this.ref.current?.dismiss() 47 + } 48 + 49 + private onStateChange = ( 50 + event: NativeSyntheticEvent<{state: BottomSheetState}>, 51 + ) => { 52 + const {state} = event.nativeEvent 53 + const isOpen = state !== 'closed' 54 + this.setState({open: isOpen}) 55 + this.props.onStateChange?.(event) 56 + } 57 + 58 + private updateLayout = () => { 59 + this.ref.current?.updateLayout() 60 + } 61 + 62 + static dismissAll = async () => { 63 + await NativeModule.dismissAll() 64 + } 65 + 66 + render() { 67 + const {children, backgroundColor, ...rest} = this.props 68 + const cornerRadius = rest.cornerRadius ?? 0 69 + 70 + if (!this.state.open) { 71 + return null 72 + } 73 + 74 + return ( 75 + <NativeView 76 + {...rest} 77 + onStateChange={this.onStateChange} 78 + ref={this.ref} 79 + style={{ 80 + position: 'absolute', 81 + height: screenHeight, 82 + width: '100%', 83 + }} 84 + containerBackgroundColor={backgroundColor}> 85 + <View 86 + style={[ 87 + { 88 + flex: 1, 89 + backgroundColor, 90 + }, 91 + Platform.OS === 'android' && { 92 + borderTopLeftRadius: cornerRadius, 93 + borderTopRightRadius: cornerRadius, 94 + }, 95 + ]}> 96 + <View onLayout={this.updateLayout}> 97 + <BottomSheetPortalProvider>{children}</BottomSheetPortalProvider> 98 + </View> 99 + </View> 100 + </NativeView> 101 + ) 102 + } 103 + }
+40
modules/bottom-sheet/src/BottomSheetPortal.tsx
··· 1 + import React from 'react' 2 + 3 + import {createPortalGroup_INTERNAL} from './lib/Portal' 4 + 5 + type PortalContext = React.ElementType<{children: React.ReactNode}> 6 + 7 + const Context = React.createContext({} as PortalContext) 8 + 9 + export const useBottomSheetPortal_INTERNAL = () => React.useContext(Context) 10 + 11 + export function BottomSheetPortalProvider({ 12 + children, 13 + }: { 14 + children: React.ReactNode 15 + }) { 16 + const portal = React.useMemo(() => { 17 + return createPortalGroup_INTERNAL() 18 + }, []) 19 + 20 + return ( 21 + <Context.Provider value={portal.Portal}> 22 + <portal.Provider> 23 + {children} 24 + <portal.Outlet /> 25 + </portal.Provider> 26 + </Context.Provider> 27 + ) 28 + } 29 + 30 + const defaultPortal = createPortalGroup_INTERNAL() 31 + 32 + export const BottomSheetOutlet = defaultPortal.Outlet 33 + 34 + export function BottomSheetProvider({children}: {children: React.ReactNode}) { 35 + return ( 36 + <Context.Provider value={defaultPortal.Portal}> 37 + <defaultPortal.Provider>{children}</defaultPortal.Provider> 38 + </Context.Provider> 39 + ) 40 + }
+67
modules/bottom-sheet/src/lib/Portal.tsx
··· 1 + import React from 'react' 2 + 3 + type Component = React.ReactElement 4 + 5 + type ContextType = { 6 + outlet: Component | null 7 + append(id: string, component: Component): void 8 + remove(id: string): void 9 + } 10 + 11 + type ComponentMap = { 12 + [id: string]: Component 13 + } 14 + 15 + export function createPortalGroup_INTERNAL() { 16 + const Context = React.createContext<ContextType>({ 17 + outlet: null, 18 + append: () => {}, 19 + remove: () => {}, 20 + }) 21 + 22 + function Provider(props: React.PropsWithChildren<{}>) { 23 + const map = React.useRef<ComponentMap>({}) 24 + const [outlet, setOutlet] = React.useState<ContextType['outlet']>(null) 25 + 26 + const append = React.useCallback<ContextType['append']>((id, component) => { 27 + if (map.current[id]) return 28 + map.current[id] = <React.Fragment key={id}>{component}</React.Fragment> 29 + setOutlet(<>{Object.values(map.current)}</>) 30 + }, []) 31 + 32 + const remove = React.useCallback<ContextType['remove']>(id => { 33 + delete map.current[id] 34 + setOutlet(<>{Object.values(map.current)}</>) 35 + }, []) 36 + 37 + const contextValue = React.useMemo( 38 + () => ({ 39 + outlet, 40 + append, 41 + remove, 42 + }), 43 + [outlet, append, remove], 44 + ) 45 + 46 + return ( 47 + <Context.Provider value={contextValue}>{props.children}</Context.Provider> 48 + ) 49 + } 50 + 51 + function Outlet() { 52 + const ctx = React.useContext(Context) 53 + return ctx.outlet 54 + } 55 + 56 + function Portal({children}: React.PropsWithChildren<{}>) { 57 + const {append, remove} = React.useContext(Context) 58 + const id = React.useId() 59 + React.useEffect(() => { 60 + append(id, children as Component) 61 + return () => remove(id) 62 + }, [id, children, append, remove]) 63 + return null 64 + } 65 + 66 + return {Provider, Outlet, Portal} 67 + }
+11 -8
src/App.native.tsx
··· 68 68 import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs' 69 69 import {Provider as PortalProvider} from '#/components/Portal' 70 70 import {Splash} from '#/Splash' 71 + import {BottomSheetProvider} from '../modules/bottom-sheet' 71 72 import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 72 73 73 74 SplashScreen.preventAutoHideAsync() ··· 197 198 <DialogStateProvider> 198 199 <LightboxStateProvider> 199 200 <PortalProvider> 200 - <StarterPackProvider> 201 - <SafeAreaProvider 202 - initialMetrics={initialWindowMetrics}> 203 - <IntentDialogProvider> 204 - <InnerApp /> 205 - </IntentDialogProvider> 206 - </SafeAreaProvider> 207 - </StarterPackProvider> 201 + <BottomSheetProvider> 202 + <StarterPackProvider> 203 + <SafeAreaProvider 204 + initialMetrics={initialWindowMetrics}> 205 + <IntentDialogProvider> 206 + <InnerApp /> 207 + </IntentDialogProvider> 208 + </SafeAreaProvider> 209 + </StarterPackProvider> 210 + </BottomSheetProvider> 208 211 </PortalProvider> 209 212 </LightboxStateProvider> 210 213 </DialogStateProvider>
+14 -17
src/components/Dialog/index.tsx
··· 31 31 DialogOuterProps, 32 32 } from '#/components/Dialog/types' 33 33 import {createInput} from '#/components/forms/TextField' 34 - import {Portal as DefaultPortal} from '#/components/Portal' 35 34 import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet' 36 35 import { 37 36 BottomSheetSnapPointChangeEvent, 38 37 BottomSheetStateChangeEvent, 39 38 } from '../../../modules/bottom-sheet/src/BottomSheet.types' 39 + import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet/src/BottomSheetNativeComponent' 40 40 41 41 export {useDialogContext, useDialogControl} from '#/components/Dialog/context' 42 42 export * from '#/components/Dialog/types' ··· 50 50 onClose, 51 51 nativeOptions, 52 52 testID, 53 - Portal = DefaultPortal, 54 53 }: React.PropsWithChildren<DialogOuterProps>) { 55 54 const t = useTheme() 56 - const ref = React.useRef<BottomSheet>(null) 55 + const ref = React.useRef<BottomSheetNativeComponent>(null) 57 56 const closeCallbacks = React.useRef<(() => void)[]>([]) 58 57 const {setDialogIsOpen, setFullyExpandedCount} = 59 58 useDialogStateControlContext() ··· 154 153 ) 155 154 156 155 return ( 157 - <Portal> 158 - <Context.Provider value={context}> 159 - <BottomSheet 160 - ref={ref} 161 - cornerRadius={20} 162 - backgroundColor={t.atoms.bg.backgroundColor} 163 - {...nativeOptions} 164 - onSnapPointChange={onSnapPointChange} 165 - onStateChange={onStateChange} 166 - disableDrag={disableDrag}> 167 - <View testID={testID}>{children}</View> 168 - </BottomSheet> 169 - </Context.Provider> 170 - </Portal> 156 + <Context.Provider value={context}> 157 + <BottomSheet 158 + ref={ref} 159 + cornerRadius={20} 160 + backgroundColor={t.atoms.bg.backgroundColor} 161 + {...nativeOptions} 162 + onSnapPointChange={onSnapPointChange} 163 + onStateChange={onStateChange} 164 + disableDrag={disableDrag}> 165 + <View testID={testID}>{children}</View> 166 + </BottomSheet> 167 + </Context.Provider> 171 168 ) 172 169 } 173 170
-4
src/components/dialogs/GifSelect.tsx
··· 30 30 import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' 31 31 import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2' 32 32 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 33 - import {PortalComponent} from '#/components/Portal' 34 33 35 34 export function GifSelectDialog({ 36 35 controlRef, 37 36 onClose, 38 37 onSelectGif: onSelectGifProp, 39 - Portal, 40 38 }: { 41 39 controlRef: React.RefObject<{open: () => void}> 42 40 onClose: () => void 43 41 onSelectGif: (gif: Gif) => void 44 - Portal?: PortalComponent 45 42 }) { 46 43 const control = Dialog.useDialogControl() 47 44 ··· 65 62 <Dialog.Outer 66 63 control={control} 67 64 onClose={onClose} 68 - Portal={Portal} 69 65 nativeOptions={{ 70 66 bottomInset: 0, 71 67 // use system corner radius on iOS
+240 -286
src/components/dialogs/MutedWords.tsx
··· 30 30 import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 31 31 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 32 32 import {Loader} from '#/components/Loader' 33 - import {createPortalGroup} from '#/components/Portal' 34 33 import * as Prompt from '#/components/Prompt' 35 34 import {Text} from '#/components/Typography' 36 35 37 36 const ONE_DAY = 24 * 60 * 60 * 1000 38 - 39 - const Portal = createPortalGroup() 40 37 41 38 export function MutedWordsDialog() { 42 39 const {mutedWordsDialogControl: control} = useGlobalDialogsControlContext() ··· 108 105 }, [_, field, targets, addMutedWord, setField, durations, excludeFollowing]) 109 106 110 107 return ( 111 - <Portal.Provider> 112 - <Dialog.ScrollableInner label={_(msg`Manage your muted words and tags`)}> 113 - <View> 114 - <Text 115 - style={[ 116 - a.text_md, 117 - a.font_bold, 118 - a.pb_sm, 119 - t.atoms.text_contrast_high, 120 - ]}> 121 - <Trans>Add muted words and tags</Trans> 122 - </Text> 123 - <Text style={[a.pb_lg, a.leading_snug, t.atoms.text_contrast_medium]}> 124 - <Trans> 125 - Posts can be muted based on their text, their tags, or both. We 126 - recommend avoiding common words that appear in many posts, since 127 - it can result in no posts being shown. 128 - </Trans> 129 - </Text> 108 + <Dialog.ScrollableInner label={_(msg`Manage your muted words and tags`)}> 109 + <View> 110 + <Text 111 + style={[a.text_md, a.font_bold, a.pb_sm, t.atoms.text_contrast_high]}> 112 + <Trans>Add muted words and tags</Trans> 113 + </Text> 114 + <Text style={[a.pb_lg, a.leading_snug, t.atoms.text_contrast_medium]}> 115 + <Trans> 116 + Posts can be muted based on their text, their tags, or both. We 117 + recommend avoiding common words that appear in many posts, since it 118 + can result in no posts being shown. 119 + </Trans> 120 + </Text> 130 121 131 - <View style={[a.pb_sm]}> 132 - <Dialog.Input 133 - autoCorrect={false} 134 - autoCapitalize="none" 135 - autoComplete="off" 136 - label={_(msg`Enter a word or tag`)} 137 - placeholder={_(msg`Enter a word or tag`)} 138 - value={field} 139 - onChangeText={value => { 140 - if (error) { 141 - setError('') 142 - } 143 - setField(value) 144 - }} 145 - onSubmitEditing={submit} 146 - /> 147 - </View> 122 + <View style={[a.pb_sm]}> 123 + <Dialog.Input 124 + autoCorrect={false} 125 + autoCapitalize="none" 126 + autoComplete="off" 127 + label={_(msg`Enter a word or tag`)} 128 + placeholder={_(msg`Enter a word or tag`)} 129 + value={field} 130 + onChangeText={value => { 131 + if (error) { 132 + setError('') 133 + } 134 + setField(value) 135 + }} 136 + onSubmitEditing={submit} 137 + /> 138 + </View> 148 139 149 - <View style={[a.pb_xl, a.gap_sm]}> 150 - <Toggle.Group 151 - label={_(msg`Select how long to mute this word for.`)} 152 - type="radio" 153 - values={durations} 154 - onChange={setDurations}> 155 - <Text 156 - style={[ 157 - a.pb_xs, 158 - a.text_sm, 159 - a.font_bold, 160 - t.atoms.text_contrast_medium, 161 - ]}> 162 - <Trans>Duration:</Trans> 163 - </Text> 140 + <View style={[a.pb_xl, a.gap_sm]}> 141 + <Toggle.Group 142 + label={_(msg`Select how long to mute this word for.`)} 143 + type="radio" 144 + values={durations} 145 + onChange={setDurations}> 146 + <Text 147 + style={[ 148 + a.pb_xs, 149 + a.text_sm, 150 + a.font_bold, 151 + t.atoms.text_contrast_medium, 152 + ]}> 153 + <Trans>Duration:</Trans> 154 + </Text> 164 155 156 + <View 157 + style={[ 158 + gtMobile && [a.flex_row, a.align_center, a.justify_start], 159 + a.gap_sm, 160 + ]}> 165 161 <View 166 162 style={[ 167 - gtMobile && [a.flex_row, a.align_center, a.justify_start], 163 + a.flex_1, 164 + a.flex_row, 165 + a.justify_start, 166 + a.align_center, 168 167 a.gap_sm, 169 168 ]}> 170 - <View 171 - style={[ 172 - a.flex_1, 173 - a.flex_row, 174 - a.justify_start, 175 - a.align_center, 176 - a.gap_sm, 177 - ]}> 178 - <Toggle.Item 179 - label={_(msg`Mute this word until you unmute it`)} 180 - name="forever" 181 - style={[a.flex_1]}> 182 - <TargetToggle> 183 - <View 184 - style={[ 185 - a.flex_1, 186 - a.flex_row, 187 - a.align_center, 188 - a.gap_sm, 189 - ]}> 190 - <Toggle.Radio /> 191 - <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 192 - <Trans>Forever</Trans> 193 - </Toggle.LabelText> 194 - </View> 195 - </TargetToggle> 196 - </Toggle.Item> 169 + <Toggle.Item 170 + label={_(msg`Mute this word until you unmute it`)} 171 + name="forever" 172 + style={[a.flex_1]}> 173 + <TargetToggle> 174 + <View 175 + style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> 176 + <Toggle.Radio /> 177 + <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 178 + <Trans>Forever</Trans> 179 + </Toggle.LabelText> 180 + </View> 181 + </TargetToggle> 182 + </Toggle.Item> 197 183 198 - <Toggle.Item 199 - label={_(msg`Mute this word for 24 hours`)} 200 - name="24_hours" 201 - style={[a.flex_1]}> 202 - <TargetToggle> 203 - <View 204 - style={[ 205 - a.flex_1, 206 - a.flex_row, 207 - a.align_center, 208 - a.gap_sm, 209 - ]}> 210 - <Toggle.Radio /> 211 - <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 212 - <Trans>24 hours</Trans> 213 - </Toggle.LabelText> 214 - </View> 215 - </TargetToggle> 216 - </Toggle.Item> 217 - </View> 218 - 219 - <View 220 - style={[ 221 - a.flex_1, 222 - a.flex_row, 223 - a.justify_start, 224 - a.align_center, 225 - a.gap_sm, 226 - ]}> 227 - <Toggle.Item 228 - label={_(msg`Mute this word for 7 days`)} 229 - name="7_days" 230 - style={[a.flex_1]}> 231 - <TargetToggle> 232 - <View 233 - style={[ 234 - a.flex_1, 235 - a.flex_row, 236 - a.align_center, 237 - a.gap_sm, 238 - ]}> 239 - <Toggle.Radio /> 240 - <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 241 - <Trans>7 days</Trans> 242 - </Toggle.LabelText> 243 - </View> 244 - </TargetToggle> 245 - </Toggle.Item> 246 - 247 - <Toggle.Item 248 - label={_(msg`Mute this word for 30 days`)} 249 - name="30_days" 250 - style={[a.flex_1]}> 251 - <TargetToggle> 252 - <View 253 - style={[ 254 - a.flex_1, 255 - a.flex_row, 256 - a.align_center, 257 - a.gap_sm, 258 - ]}> 259 - <Toggle.Radio /> 260 - <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 261 - <Trans>30 days</Trans> 262 - </Toggle.LabelText> 263 - </View> 264 - </TargetToggle> 265 - </Toggle.Item> 266 - </View> 184 + <Toggle.Item 185 + label={_(msg`Mute this word for 24 hours`)} 186 + name="24_hours" 187 + style={[a.flex_1]}> 188 + <TargetToggle> 189 + <View 190 + style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> 191 + <Toggle.Radio /> 192 + <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 193 + <Trans>24 hours</Trans> 194 + </Toggle.LabelText> 195 + </View> 196 + </TargetToggle> 197 + </Toggle.Item> 267 198 </View> 268 - </Toggle.Group> 269 199 270 - <Toggle.Group 271 - label={_( 272 - msg`Select what content this mute word should apply to.`, 273 - )} 274 - type="radio" 275 - values={targets} 276 - onChange={setTargets}> 277 - <Text 200 + <View 278 201 style={[ 279 - a.pb_xs, 280 - a.text_sm, 281 - a.font_bold, 282 - t.atoms.text_contrast_medium, 202 + a.flex_1, 203 + a.flex_row, 204 + a.justify_start, 205 + a.align_center, 206 + a.gap_sm, 283 207 ]}> 284 - <Trans>Mute in:</Trans> 285 - </Text> 286 - 287 - <View style={[a.flex_row, a.align_center, a.gap_sm, a.flex_wrap]}> 288 208 <Toggle.Item 289 - label={_(msg`Mute this word in post text and tags`)} 290 - name="content" 209 + label={_(msg`Mute this word for 7 days`)} 210 + name="7_days" 291 211 style={[a.flex_1]}> 292 212 <TargetToggle> 293 213 <View 294 214 style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> 295 215 <Toggle.Radio /> 296 216 <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 297 - <Trans>Text & tags</Trans> 217 + <Trans>7 days</Trans> 298 218 </Toggle.LabelText> 299 219 </View> 300 - <PageText size="sm" /> 301 220 </TargetToggle> 302 221 </Toggle.Item> 303 222 304 223 <Toggle.Item 305 - label={_(msg`Mute this word in tags only`)} 306 - name="tag" 224 + label={_(msg`Mute this word for 30 days`)} 225 + name="30_days" 307 226 style={[a.flex_1]}> 308 227 <TargetToggle> 309 228 <View 310 229 style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> 311 230 <Toggle.Radio /> 312 231 <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 313 - <Trans>Tags only</Trans> 232 + <Trans>30 days</Trans> 314 233 </Toggle.LabelText> 315 234 </View> 316 - <Hashtag size="sm" /> 317 235 </TargetToggle> 318 236 </Toggle.Item> 319 237 </View> 320 - </Toggle.Group> 238 + </View> 239 + </Toggle.Group> 321 240 322 - <View> 323 - <Text 324 - style={[ 325 - a.pb_xs, 326 - a.text_sm, 327 - a.font_bold, 328 - t.atoms.text_contrast_medium, 329 - ]}> 330 - <Trans>Options:</Trans> 331 - </Text> 241 + <Toggle.Group 242 + label={_(msg`Select what content this mute word should apply to.`)} 243 + type="radio" 244 + values={targets} 245 + onChange={setTargets}> 246 + <Text 247 + style={[ 248 + a.pb_xs, 249 + a.text_sm, 250 + a.font_bold, 251 + t.atoms.text_contrast_medium, 252 + ]}> 253 + <Trans>Mute in:</Trans> 254 + </Text> 255 + 256 + <View style={[a.flex_row, a.align_center, a.gap_sm, a.flex_wrap]}> 332 257 <Toggle.Item 333 - label={_(msg`Do not apply this mute word to users you follow`)} 334 - name="exclude_following" 335 - style={[a.flex_row, a.justify_between]} 336 - value={excludeFollowing} 337 - onChange={setExcludeFollowing}> 258 + label={_(msg`Mute this word in post text and tags`)} 259 + name="content" 260 + style={[a.flex_1]}> 338 261 <TargetToggle> 339 262 <View 340 263 style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> 341 - <Toggle.Checkbox /> 264 + <Toggle.Radio /> 342 265 <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 343 - <Trans>Exclude users you follow</Trans> 266 + <Trans>Text & tags</Trans> 344 267 </Toggle.LabelText> 345 268 </View> 269 + <PageText size="sm" /> 346 270 </TargetToggle> 347 271 </Toggle.Item> 348 - </View> 349 272 350 - <View style={[a.pt_xs]}> 351 - <Button 352 - disabled={isPending || !field} 353 - label={_(msg`Add mute word for configured settings`)} 354 - size="large" 355 - color="primary" 356 - variant="solid" 357 - style={[]} 358 - onPress={submit}> 359 - <ButtonText> 360 - <Trans>Add</Trans> 361 - </ButtonText> 362 - <ButtonIcon icon={isPending ? Loader : Plus} position="right" /> 363 - </Button> 273 + <Toggle.Item 274 + label={_(msg`Mute this word in tags only`)} 275 + name="tag" 276 + style={[a.flex_1]}> 277 + <TargetToggle> 278 + <View 279 + style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> 280 + <Toggle.Radio /> 281 + <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 282 + <Trans>Tags only</Trans> 283 + </Toggle.LabelText> 284 + </View> 285 + <Hashtag size="sm" /> 286 + </TargetToggle> 287 + </Toggle.Item> 364 288 </View> 365 - 366 - {error && ( 367 - <View 368 - style={[ 369 - a.mb_lg, 370 - a.flex_row, 371 - a.rounded_sm, 372 - a.p_md, 373 - a.mb_xs, 374 - t.atoms.bg_contrast_25, 375 - { 376 - backgroundColor: t.palette.negative_400, 377 - }, 378 - ]}> 379 - <Text 380 - style={[ 381 - a.italic, 382 - {color: t.palette.white}, 383 - native({marginTop: 2}), 384 - ]}> 385 - {error} 386 - </Text> 387 - </View> 388 - )} 389 - </View> 289 + </Toggle.Group> 390 290 391 - <Divider /> 392 - 393 - <View style={[a.pt_2xl]}> 291 + <View> 394 292 <Text 395 293 style={[ 396 - a.text_md, 294 + a.pb_xs, 295 + a.text_sm, 397 296 a.font_bold, 398 - a.pb_md, 399 - t.atoms.text_contrast_high, 297 + t.atoms.text_contrast_medium, 400 298 ]}> 401 - <Trans>Your muted words</Trans> 299 + <Trans>Options:</Trans> 402 300 </Text> 301 + <Toggle.Item 302 + label={_(msg`Do not apply this mute word to users you follow`)} 303 + name="exclude_following" 304 + style={[a.flex_row, a.justify_between]} 305 + value={excludeFollowing} 306 + onChange={setExcludeFollowing}> 307 + <TargetToggle> 308 + <View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> 309 + <Toggle.Checkbox /> 310 + <Toggle.LabelText style={[a.flex_1, a.leading_tight]}> 311 + <Trans>Exclude users you follow</Trans> 312 + </Toggle.LabelText> 313 + </View> 314 + </TargetToggle> 315 + </Toggle.Item> 316 + </View> 317 + 318 + <View style={[a.pt_xs]}> 319 + <Button 320 + disabled={isPending || !field} 321 + label={_(msg`Add mute word for configured settings`)} 322 + size="large" 323 + color="primary" 324 + variant="solid" 325 + style={[]} 326 + onPress={submit}> 327 + <ButtonText> 328 + <Trans>Add</Trans> 329 + </ButtonText> 330 + <ButtonIcon icon={isPending ? Loader : Plus} position="right" /> 331 + </Button> 332 + </View> 403 333 404 - {isPreferencesLoading ? ( 405 - <Loader /> 406 - ) : preferencesError || !preferences ? ( 407 - <View 334 + {error && ( 335 + <View 336 + style={[ 337 + a.mb_lg, 338 + a.flex_row, 339 + a.rounded_sm, 340 + a.p_md, 341 + a.mb_xs, 342 + t.atoms.bg_contrast_25, 343 + { 344 + backgroundColor: t.palette.negative_400, 345 + }, 346 + ]}> 347 + <Text 408 348 style={[ 409 - a.py_md, 410 - a.px_lg, 411 - a.rounded_md, 412 - t.atoms.bg_contrast_25, 349 + a.italic, 350 + {color: t.palette.white}, 351 + native({marginTop: 2}), 413 352 ]}> 414 - <Text style={[a.italic, t.atoms.text_contrast_high]}> 415 - <Trans> 416 - We're sorry, but we weren't able to load your muted words at 417 - this time. Please try again. 418 - </Trans> 419 - </Text> 420 - </View> 421 - ) : preferences.moderationPrefs.mutedWords.length ? ( 422 - [...preferences.moderationPrefs.mutedWords] 423 - .reverse() 424 - .map((word, i) => ( 425 - <MutedWordRow 426 - key={word.value + i} 427 - word={word} 428 - style={[i % 2 === 0 && t.atoms.bg_contrast_25]} 429 - /> 430 - )) 431 - ) : ( 432 - <View 433 - style={[ 434 - a.py_md, 435 - a.px_lg, 436 - a.rounded_md, 437 - t.atoms.bg_contrast_25, 438 - ]}> 439 - <Text style={[a.italic, t.atoms.text_contrast_high]}> 440 - <Trans>You haven't muted any words or tags yet</Trans> 441 - </Text> 442 - </View> 443 - )} 444 - </View> 353 + {error} 354 + </Text> 355 + </View> 356 + )} 357 + </View> 358 + 359 + <Divider /> 360 + 361 + <View style={[a.pt_2xl]}> 362 + <Text 363 + style={[ 364 + a.text_md, 365 + a.font_bold, 366 + a.pb_md, 367 + t.atoms.text_contrast_high, 368 + ]}> 369 + <Trans>Your muted words</Trans> 370 + </Text> 445 371 446 - {isNative && <View style={{height: 20}} />} 372 + {isPreferencesLoading ? ( 373 + <Loader /> 374 + ) : preferencesError || !preferences ? ( 375 + <View 376 + style={[a.py_md, a.px_lg, a.rounded_md, t.atoms.bg_contrast_25]}> 377 + <Text style={[a.italic, t.atoms.text_contrast_high]}> 378 + <Trans> 379 + We're sorry, but we weren't able to load your muted words at 380 + this time. Please try again. 381 + </Trans> 382 + </Text> 383 + </View> 384 + ) : preferences.moderationPrefs.mutedWords.length ? ( 385 + [...preferences.moderationPrefs.mutedWords] 386 + .reverse() 387 + .map((word, i) => ( 388 + <MutedWordRow 389 + key={word.value + i} 390 + word={word} 391 + style={[i % 2 === 0 && t.atoms.bg_contrast_25]} 392 + /> 393 + )) 394 + ) : ( 395 + <View 396 + style={[a.py_md, a.px_lg, a.rounded_md, t.atoms.bg_contrast_25]}> 397 + <Text style={[a.italic, t.atoms.text_contrast_high]}> 398 + <Trans>You haven't muted any words or tags yet</Trans> 399 + </Text> 400 + </View> 401 + )} 447 402 </View> 448 403 449 - <Dialog.Close /> 450 - </Dialog.ScrollableInner> 404 + {isNative && <View style={{height: 20}} />} 405 + </View> 451 406 452 - <Portal.Outlet /> 453 - </Portal.Provider> 407 + <Dialog.Close /> 408 + </Dialog.ScrollableInner> 454 409 ) 455 410 } 456 411 ··· 482 437 onConfirm={remove} 483 438 confirmButtonCta={_(msg`Remove`)} 484 439 confirmButtonColor="negative" 485 - Portal={Portal.Portal} 486 440 /> 487 441 488 442 <View
+4 -5
src/components/dialogs/PostInteractionSettingsDialog.tsx
··· 37 37 import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 38 38 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 39 39 import {Loader} from '#/components/Loader' 40 - import {PortalComponent} from '#/components/Portal' 41 40 import {Text} from '#/components/Typography' 42 41 43 42 export type PostInteractionSettingsFormProps = { ··· 55 54 56 55 export function PostInteractionSettingsControlledDialog({ 57 56 control, 58 - Portal, 59 57 ...rest 60 58 }: PostInteractionSettingsFormProps & { 61 59 control: Dialog.DialogControlProps 62 - Portal?: PortalComponent 63 60 }) { 64 61 const {_} = useLingui() 65 62 return ( 66 - <Dialog.Outer control={control} Portal={Portal}> 63 + <Dialog.Outer control={control}> 67 64 <Dialog.Handle /> 68 65 <Dialog.ScrollableInner 69 66 label={_(msg`Edit post interaction settings`)} ··· 207 204 label={_(msg`Edit post interaction settings`)} 208 205 style={[{maxWidth: 500}, a.w_full]}> 209 206 {isLoading ? ( 210 - <Loader size="xl" /> 207 + <View style={[a.flex_1, a.py_4xl, a.align_center, a.justify_center]}> 208 + <Loader size="xl" /> 209 + </View> 211 210 ) : ( 212 211 <PostInteractionSettingsForm 213 212 replySettingsDisabled={!isThreadgateOwnedByViewer}
+2 -2
src/state/dialogs/index.tsx
··· 3 3 import {isWeb} from '#/platform/detection' 4 4 import {DialogControlRefProps} from '#/components/Dialog' 5 5 import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context' 6 - import {BottomSheet} from '../../../modules/bottom-sheet' 6 + import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet' 7 7 8 8 interface IDialogContext { 9 9 /** ··· 61 61 62 62 return openDialogs.current.size > 0 63 63 } else { 64 - BottomSheet.dismissAll() 64 + BottomSheetNativeComponent.dismissAll() 65 65 return false 66 66 } 67 67 }, [])
+4 -16
src/view/com/composer/Composer.tsx
··· 107 107 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 108 108 import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' 109 109 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 110 - import {createPortalGroup} from '#/components/Portal' 111 110 import * as Prompt from '#/components/Prompt' 112 111 import {Text as NewText} from '#/components/Typography' 112 + import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet' 113 113 import { 114 114 composerReducer, 115 115 createComposerState, 116 116 MAX_IMAGES, 117 117 } from './state/composer' 118 118 import {NO_VIDEO, NoVideoState, processVideo, VideoState} from './state/video' 119 - 120 - const Portal = createPortalGroup() 121 119 122 120 type CancelRef = { 123 121 onPressCancel: () => void ··· 522 520 const keyboardVerticalOffset = useKeyboardVerticalOffset() 523 521 524 522 return ( 525 - <Portal.Provider> 523 + <BottomSheetPortalProvider> 526 524 <KeyboardAvoidingView 527 525 testID="composePostView" 528 526 behavior={isIOS ? 'padding' : 'height'} ··· 666 664 /> 667 665 </View> 668 666 669 - <Gallery 670 - images={images} 671 - dispatch={dispatch} 672 - Portal={Portal.Portal} 673 - /> 667 + <Gallery images={images} dispatch={dispatch} /> 674 668 675 669 {extGif && ( 676 670 <View style={a.relative} key={extGif.url}> ··· 684 678 gif={extGif} 685 679 altText={extGifAlt ?? ''} 686 680 onSubmit={handleChangeGifAltText} 687 - Portal={Portal.Portal} 688 681 /> 689 682 </View> 690 683 )} ··· 744 737 }, 745 738 }) 746 739 }} 747 - Portal={Portal.Portal} 748 740 /> 749 741 </Animated.View> 750 742 )} ··· 782 774 }) 783 775 }} 784 776 style={bottomBarAnimatedStyle} 785 - Portal={Portal.Portal} 786 777 /> 787 778 )} 788 779 <View ··· 819 810 onClose={focusTextInput} 820 811 onSelectGif={onSelectGif} 821 812 disabled={hasMedia} 822 - Portal={Portal.Portal} 823 813 /> 824 814 {!isMobile ? ( 825 815 <Button ··· 849 839 onConfirm={onClose} 850 840 confirmButtonCta={_(msg`Discard`)} 851 841 confirmButtonColor="negative" 852 - Portal={Portal.Portal} 853 842 /> 854 843 </KeyboardAvoidingView> 855 - <Portal.Outlet /> 856 - </Portal.Provider> 844 + </BottomSheetPortalProvider> 857 845 ) 858 846 } 859 847
+1 -8
src/view/com/composer/GifAltText.tsx
··· 21 21 import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 22 22 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 23 23 import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 24 - import {PortalComponent} from '#/components/Portal' 25 24 import {Text} from '#/components/Typography' 26 25 import {GifEmbed} from '../util/post-embeds/GifEmbed' 27 26 import {AltTextReminder} from './photos/Gallery' ··· 30 29 gif, 31 30 altText, 32 31 onSubmit, 33 - Portal, 34 32 }: { 35 33 gif: Gif 36 34 altText: string 37 35 onSubmit: (alt: string) => void 38 - Portal: PortalComponent 39 36 }) { 40 37 const {data} = useResolveGifQuery(gif) 41 38 const vendorAltText = parseAltFromGIFDescription(data?.description ?? '').alt ··· 50 47 thumb={data.thumb?.source.path} 51 48 params={params} 52 49 onSubmit={onSubmit} 53 - Portal={Portal} 54 50 /> 55 51 ) 56 52 } ··· 61 57 onSubmit, 62 58 params, 63 59 thumb, 64 - Portal, 65 60 }: { 66 61 vendorAltText: string 67 62 altText: string 68 63 onSubmit: (alt: string) => void 69 64 params: EmbedPlayerParams 70 65 thumb: string | undefined 71 - Portal: PortalComponent 72 66 }) { 73 67 const control = Dialog.useDialogControl() 74 68 const {_} = useLingui() ··· 113 107 control={control} 114 108 onClose={() => { 115 109 onSubmit(altTextDraft) 116 - }} 117 - Portal={Portal}> 110 + }}> 118 111 <Dialog.Handle /> 119 112 <AltTextInner 120 113 vendorAltText={vendorAltText}
+1 -12
src/view/com/composer/photos/Gallery.tsx
··· 21 21 import {Text} from '#/view/com/util/text/Text' 22 22 import {useTheme} from '#/alf' 23 23 import * as Dialog from '#/components/Dialog' 24 - import {PortalComponent} from '#/components/Portal' 25 24 import {ComposerAction} from '../state/composer' 26 25 import {EditImageDialog} from './EditImageDialog' 27 26 import {ImageAltTextDialog} from './ImageAltTextDialog' ··· 31 30 interface GalleryProps { 32 31 images: ComposerImage[] 33 32 dispatch: (action: ComposerAction) => void 34 - Portal: PortalComponent 35 33 } 36 34 37 35 export let Gallery = (props: GalleryProps): React.ReactNode => { ··· 59 57 containerInfo: Dimensions 60 58 } 61 59 62 - const GalleryInner = ({ 63 - images, 64 - containerInfo, 65 - dispatch, 66 - Portal, 67 - }: GalleryInnerProps) => { 60 + const GalleryInner = ({images, containerInfo, dispatch}: GalleryInnerProps) => { 68 61 const {isMobile} = useWebMediaQueries() 69 62 70 63 const {altTextControlStyle, imageControlsStyle, imageStyle} = ··· 118 111 onRemove={() => { 119 112 dispatch({type: 'embed_remove_image', image}) 120 113 }} 121 - Portal={Portal} 122 114 /> 123 115 ) 124 116 })} ··· 135 127 imageStyle?: ViewStyle 136 128 onChange: (next: ComposerImage) => void 137 129 onRemove: () => void 138 - Portal: PortalComponent 139 130 } 140 131 141 132 const GalleryItem = ({ ··· 145 136 imageStyle, 146 137 onChange, 147 138 onRemove, 148 - Portal, 149 139 }: GalleryItemProps): React.ReactNode => { 150 140 const {_} = useLingui() 151 141 const t = useTheme() ··· 240 230 control={altTextControl} 241 231 image={image} 242 232 onChange={onChange} 243 - Portal={Portal} 244 233 /> 245 234 246 235 <EditImageDialog
+1 -5
src/view/com/composer/photos/ImageAltTextDialog.tsx
··· 15 15 import {DialogControlProps} from '#/components/Dialog' 16 16 import * as TextField from '#/components/forms/TextField' 17 17 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 18 - import {PortalComponent} from '#/components/Portal' 19 18 import {Text} from '#/components/Typography' 20 19 21 20 type Props = { 22 21 control: Dialog.DialogOuterProps['control'] 23 22 image: ComposerImage 24 23 onChange: (next: ComposerImage) => void 25 - Portal: PortalComponent 26 24 } 27 25 28 26 export const ImageAltTextDialog = ({ 29 27 control, 30 28 image, 31 29 onChange, 32 - Portal, 33 30 }: Props): React.ReactNode => { 34 31 const [altText, setAltText] = React.useState(image.alt) 35 32 ··· 41 38 ...image, 42 39 alt: enforceLen(altText, MAX_ALT_TEXT, true), 43 40 }) 44 - }} 45 - Portal={Portal}> 41 + }}> 46 42 <Dialog.Handle /> 47 43 <ImageAltTextInner 48 44 control={control}
+1 -4
src/view/com/composer/photos/SelectGifBtn.tsx
··· 9 9 import {Button} from '#/components/Button' 10 10 import {GifSelectDialog} from '#/components/dialogs/GifSelect' 11 11 import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif' 12 - import {PortalComponent} from '#/components/Portal' 13 12 14 13 type Props = { 15 14 onClose: () => void 16 15 onSelectGif: (gif: Gif) => void 17 16 disabled?: boolean 18 - Portal?: PortalComponent 19 17 } 20 18 21 - export function SelectGifBtn({onClose, onSelectGif, disabled, Portal}: Props) { 19 + export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) { 22 20 const {_} = useLingui() 23 21 const ref = useRef<{open: () => void}>(null) 24 22 const t = useTheme() ··· 48 46 controlRef={ref} 49 47 onClose={onClose} 50 48 onSelectGif={onSelectGif} 51 - Portal={Portal} 52 49 /> 53 50 </> 54 51 )
-5
src/view/com/composer/threadgate/ThreadgateBtn.tsx
··· 13 13 import {PostInteractionSettingsControlledDialog} from '#/components/dialogs/PostInteractionSettingsDialog' 14 14 import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe' 15 15 import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' 16 - import {PortalComponent} from '#/components/Portal' 17 16 18 17 export function ThreadgateBtn({ 19 18 postgate, ··· 21 20 threadgateAllowUISettings, 22 21 onChangeThreadgateAllowUISettings, 23 22 style, 24 - Portal, 25 23 }: { 26 24 postgate: AppBskyFeedPostgate.Record 27 25 onChangePostgate: (v: AppBskyFeedPostgate.Record) => void ··· 30 28 onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void 31 29 32 30 style?: StyleProp<AnimatedStyle<ViewStyle>> 33 - 34 - Portal: PortalComponent 35 31 }) { 36 32 const {_} = useLingui() 37 33 const t = useTheme() ··· 81 77 onChangePostgate={onChangePostgate} 82 78 threadgateAllowUISettings={threadgateAllowUISettings} 83 79 onChangeThreadgateAllowUISettings={onChangeThreadgateAllowUISettings} 84 - Portal={Portal} 85 80 /> 86 81 </> 87 82 )
+1 -3
src/view/com/composer/videos/SubtitleDialog.tsx
··· 17 17 import {PageText_Stroke2_Corner0_Rounded as PageTextIcon} from '#/components/icons/PageText' 18 18 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 19 19 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 20 - import {PortalComponent} from '#/components/Portal' 21 20 import {Text} from '#/components/Typography' 22 21 import {SubtitleFilePicker} from './SubtitleFilePicker' 23 22 ··· 30 29 captions: CaptionsTrack[] 31 30 saveAltText: (altText: string) => void 32 31 setCaptions: (updater: (prev: CaptionsTrack[]) => CaptionsTrack[]) => void 33 - Portal: PortalComponent 34 32 } 35 33 36 34 export function SubtitleDialogBtn(props: Props) { ··· 58 56 {isWeb ? <Trans>Captions & alt text</Trans> : <Trans>Alt text</Trans>} 59 57 </ButtonText> 60 58 </Button> 61 - <Dialog.Outer control={control} Portal={props.Portal}> 59 + <Dialog.Outer control={control}> 62 60 <Dialog.Handle /> 63 61 <SubtitleDialogInner {...props} /> 64 62 </Dialog.Outer>
+28 -38
src/view/screens/Storybook/Dialogs.tsx
··· 8 8 import {Button, ButtonText} from '#/components/Button' 9 9 import * as Dialog from '#/components/Dialog' 10 10 import * as Menu from '#/components/Menu' 11 - import {createPortalGroup} from '#/components/Portal' 12 11 import * as Prompt from '#/components/Prompt' 13 12 import {H3, P, Text} from '#/components/Typography' 14 13 import {PlatformInfo} from '../../../../modules/expo-bluesky-swiss-army' 15 - 16 - const Portal = createPortalGroup() 17 14 18 15 export function Dialogs() { 19 16 const scrollable = Dialog.useDialogControl() ··· 201 198 </Dialog.Outer> 202 199 203 200 <Dialog.Outer control={withMenu}> 204 - <Portal.Provider> 205 - <Dialog.Inner label="test"> 206 - <H3 nativeID="dialog-title">Dialog with Menu</H3> 207 - <Menu.Root> 208 - <Menu.Trigger label="Open menu"> 209 - {({props}) => ( 210 - <Button 211 - style={a.mt_2xl} 212 - label="Open menu" 213 - color="primary" 214 - variant="solid" 215 - size="large" 216 - {...props}> 217 - <ButtonText>Open Menu</ButtonText> 218 - </Button> 219 - )} 220 - </Menu.Trigger> 221 - <Menu.Outer Portal={Portal.Portal}> 222 - <Menu.Group> 223 - <Menu.Item 224 - label="Item 1" 225 - onPress={() => console.log('item 1')}> 226 - <Menu.ItemText>Item 1</Menu.ItemText> 227 - </Menu.Item> 228 - <Menu.Item 229 - label="Item 2" 230 - onPress={() => console.log('item 2')}> 231 - <Menu.ItemText>Item 2</Menu.ItemText> 232 - </Menu.Item> 233 - </Menu.Group> 234 - </Menu.Outer> 235 - </Menu.Root> 236 - </Dialog.Inner> 237 - <Portal.Outlet /> 238 - </Portal.Provider> 201 + <Dialog.Inner label="test"> 202 + <H3 nativeID="dialog-title">Dialog with Menu</H3> 203 + <Menu.Root> 204 + <Menu.Trigger label="Open menu"> 205 + {({props}) => ( 206 + <Button 207 + style={a.mt_2xl} 208 + label="Open menu" 209 + color="primary" 210 + variant="solid" 211 + size="large" 212 + {...props}> 213 + <ButtonText>Open Menu</ButtonText> 214 + </Button> 215 + )} 216 + </Menu.Trigger> 217 + <Menu.Outer> 218 + <Menu.Group> 219 + <Menu.Item label="Item 1" onPress={() => console.log('item 1')}> 220 + <Menu.ItemText>Item 1</Menu.ItemText> 221 + </Menu.Item> 222 + <Menu.Item label="Item 2" onPress={() => console.log('item 2')}> 223 + <Menu.ItemText>Item 2</Menu.ItemText> 224 + </Menu.Item> 225 + </Menu.Group> 226 + </Menu.Outer> 227 + </Menu.Root> 228 + </Dialog.Inner> 239 229 </Dialog.Outer> 240 230 241 231 <Dialog.Outer control={scrollable}>
+2
src/view/shell/index.tsx
··· 34 34 import {MutedWordsDialog} from '#/components/dialogs/MutedWords' 35 35 import {SigninDialog} from '#/components/dialogs/Signin' 36 36 import {Outlet as PortalOutlet} from '#/components/Portal' 37 + import {BottomSheetOutlet} from '../../../modules/bottom-sheet' 37 38 import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView' 38 39 import {RoutesContainer, TabsNavigator} from '../../Navigation' 39 40 import {Composer} from './Composer' ··· 119 120 <SigninDialog /> 120 121 <Lightbox /> 121 122 <PortalOutlet /> 123 + <BottomSheetOutlet /> 122 124 </> 123 125 ) 124 126 }