+10
modules/bottom-sheet/index.ts
+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
+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
+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
+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
+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
+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
+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
-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
+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
+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
+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
+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
+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
+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
+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
+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
-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
+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
+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
+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
}