Rip out Modal system completely (#9903)

This commit is contained in:
Samuel Newman
2026-06-17 12:45:01 +03:00
committed by GitHub
parent 6bf1572ffc
commit 1b5242c1d6
19 changed files with 29 additions and 499 deletions
+2 -4
View File
@@ -86,7 +86,6 @@ import {
createComposerImage,
pasteImage,
} from '#/state/gallery'
import {useModalControls} from '#/state/modals'
import {useRequireAltTextEnabled} from '#/state/preferences'
import {
fromPostLanguages,
@@ -283,7 +282,6 @@ export const ComposePost = ({
useSaveDraftMutation()
const {mutate: cleanupPublishedDraft} = useCleanupPublishedDraftMutation()
const {closeAllDialogs} = useDialogStateControlContext()
const {closeAllModals} = useModalControls()
const {data: preferences} = usePreferencesQuery()
const navigation = useNavigation<NavigationProp>()
@@ -835,7 +833,7 @@ export const ComposePost = ({
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
() => {
if (closeAllDialogs() || closeAllModals()) {
if (closeAllDialogs()) {
return true
}
onPressCancel()
@@ -845,7 +843,7 @@ export const ComposePost = ({
return () => {
backHandler.remove()
}
}, [onPressCancel, closeAllDialogs, closeAllModals])
}, [onPressCancel, closeAllDialogs])
const missingAltError = useMemo(() => {
if (!requireAltTextEnabled) {
-91
View File
@@ -1,91 +0,0 @@
import {Fragment, useEffect, useRef} from 'react'
import {StyleSheet} from 'react-native'
import {SafeAreaView} from 'react-native-safe-area-context'
import BottomSheet from '@discord/bottom-sheet/src'
import {usePalette} from '#/lib/hooks/usePalette'
import {useModalControls, useModals} from '#/state/modals'
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
const DEFAULT_SNAPPOINTS = ['90%']
const HANDLE_HEIGHT = 24
export function ModalsContainer() {
const {isModalActive, activeModals} = useModals()
const {closeModal} = useModalControls()
const bottomSheetRef = useRef<BottomSheet>(null)
const pal = usePalette('default')
const activeModal = activeModals[activeModals.length - 1]
const onBottomSheetChange = async (snapPoint: number) => {
if (snapPoint === -1) {
closeModal()
}
}
const onClose = () => {
bottomSheetRef.current?.close()
closeModal()
}
useEffect(() => {
if (isModalActive) {
bottomSheetRef.current?.snapToIndex(0)
} else {
bottomSheetRef.current?.close()
}
}, [isModalActive, bottomSheetRef, activeModal?.name])
let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS
let element
{
return null
}
if (snapPoints[0] === 'fullscreen') {
return (
<SafeAreaView style={[styles.fullscreenContainer, pal.view]}>
{element}
</SafeAreaView>
)
}
const Container = activeModal ? FullWindowOverlay : Fragment
return (
<Container>
<BottomSheet
ref={bottomSheetRef}
snapPoints={snapPoints}
handleHeight={HANDLE_HEIGHT}
index={isModalActive ? 0 : -1}
enablePanDownToClose
android_keyboardInputMode="adjustResize"
keyboardBlurBehavior="restore"
backdropComponent={
isModalActive ? createCustomBackdrop(onClose) : undefined
}
handleIndicatorStyle={{backgroundColor: pal.text.color}}
handleStyle={[styles.handle, pal.view]}
backgroundStyle={pal.view}
onChange={onBottomSheetChange}>
{element}
</BottomSheet>
</Container>
)
}
const styles = StyleSheet.create({
handle: {
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
},
fullscreenContainer: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
})
-101
View File
@@ -1,101 +0,0 @@
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {RemoveScrollBar} from 'react-remove-scroll-bar'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {type Modal as ModalIface} from '#/state/modals'
import {useModalControls, useModals} from '#/state/modals'
export function ModalsContainer() {
const {isModalActive, activeModals} = useModals()
if (!isModalActive) {
return null
}
return (
<>
<RemoveScrollBar />
{activeModals.map((modal, i) => (
<Modal key={`modal-${i}`} modal={modal} />
))}
</>
)
}
function Modal({modal: _modal}: {modal: ModalIface}) {
const {isModalActive} = useModals()
const {closeModal} = useModalControls()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
if (!isModalActive) {
return null
}
const onPressMask = () => {
closeModal()
}
const onInnerPress = () => {
// TODO: can we use prevent default?
// do nothing, we just want to stop it from bubbling
}
let element
{
return null
}
return (
// eslint-disable-next-line react-native-a11y/has-valid-accessibility-descriptors
<TouchableWithoutFeedback onPress={onPressMask}>
<Animated.View
style={styles.mask}
entering={FadeIn.duration(150)}
exiting={FadeOut}>
{/* eslint-disable-next-line react-native-a11y/has-valid-accessibility-descriptors */}
<TouchableWithoutFeedback onPress={onInnerPress}>
<View
style={[
styles.container,
isMobile && styles.containerMobile,
pal.view,
pal.border,
]}>
{element}
</View>
</TouchableWithoutFeedback>
</Animated.View>
</TouchableWithoutFeedback>
)
}
const styles = StyleSheet.create({
mask: {
// @ts-ignore
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: '#000c',
alignItems: 'center',
justifyContent: 'center',
},
container: {
width: 600,
// @ts-ignore web only
maxWidth: '100vw',
// @ts-ignore web only
maxHeight: '90vh',
paddingVertical: 20,
paddingHorizontal: 24,
borderRadius: 8,
borderWidth: 1,
},
containerMobile: {
borderRadius: 0,
paddingHorizontal: 0,
},
})
-4
View File
@@ -1,4 +0,0 @@
export {
BottomSheetScrollView as ScrollView,
BottomSheetTextInput as TextInput,
} from '@discord/bottom-sheet/src'
-1
View File
@@ -1 +0,0 @@
export {ScrollView, TextInput} from 'react-native'
@@ -1,48 +0,0 @@
import {useMemo} from 'react'
import {TouchableWithoutFeedback} from 'react-native'
import Animated, {
Extrapolation,
interpolate,
useAnimatedStyle,
} from 'react-native-reanimated'
import {type BottomSheetBackdropProps} from '@discord/bottom-sheet/src'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
export function createCustomBackdrop(
onClose?: () => void,
): React.FC<BottomSheetBackdropProps> {
const CustomBackdrop = ({animatedIndex, style}: BottomSheetBackdropProps) => {
const {_} = useLingui()
// animated variables
const opacity = useAnimatedStyle(() => ({
opacity: interpolate(
animatedIndex.get(), // current snap index
[-1, 0], // input range
[0, 0.5], // output range
Extrapolation.CLAMP,
),
}))
const containerStyle = useMemo(
() => [style, {backgroundColor: '#000'}, opacity],
[style, opacity],
)
return (
<TouchableWithoutFeedback
onPress={onClose}
accessibilityLabel={_(msg`Close bottom drawer`)}
accessibilityHint=""
onAccessibilityEscape={() => {
if (onClose !== undefined) {
onClose()
}
}}>
<Animated.View style={containerStyle} />
</TouchableWithoutFeedback>
)
}
return CustomBackdrop
}
-10
View File
@@ -28,7 +28,6 @@ import {
} from '#/lib/strings/url-helpers'
import {type TypographyVariant} from '#/lib/ThemeContext'
import {emitSoftReset} from '#/state/events'
import {useModalControls} from '#/state/modals'
import {WebAuxClickWrapper} from '#/view/com/util/WebAuxClickWrapper'
import {useTheme} from '#/alf'
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
@@ -79,7 +78,6 @@ export const Link = memo(function Link({
...props
}: Props) {
const t = useTheme()
const {closeModal} = useModalControls()
const navigation = useNavigationDeduped()
const anchorHref = asAnchor ? sanitizeUrl(href) : undefined
const openLink = useOpenLink()
@@ -90,7 +88,6 @@ export const Link = memo(function Link({
onBeforePress?.()
if (typeof href === 'string') {
return onPressInner(
closeModal,
navigation,
sanitizeUrl(href),
navigationAction,
@@ -101,7 +98,6 @@ export const Link = memo(function Link({
}
},
[
closeModal,
navigation,
navigationAction,
href,
@@ -205,7 +201,6 @@ export const TextLink = memo(function TextLink({
onBeforePress?: () => void
} & TextProps) {
const navigation = useNavigationDeduped()
const {closeModal} = useModalControls()
const {linkWarningDialogControl} = useGlobalDialogsControlContext()
const openLink = useOpenLink()
const groupChatJoinIntent = useGroupChatJoinIntent()
@@ -246,7 +241,6 @@ export const TextLink = memo(function TextLink({
return onPressProp()
}
return onPressInner(
closeModal,
navigation,
sanitizeUrl(href),
navigationAction,
@@ -258,7 +252,6 @@ export const TextLink = memo(function TextLink({
[
onBeforePress,
onPressProp,
closeModal,
navigation,
href,
text,
@@ -383,7 +376,6 @@ const EXEMPT_PATHS = ['/robots.txt', '/security.txt', '/.well-known/']
// needed customizations
// -prf
function onPressInner(
closeModal = () => {},
navigation: DebouncedNavigationProp,
href: string,
navigationAction: 'push' | 'replace' | 'navigate' = 'push',
@@ -429,8 +421,6 @@ function onPressInner(
) {
openLink(href)
} else {
closeModal() // close any active modals
const [routeName, params] = router.matchPath(href)
if (navigationAction === 'push') {
// @ts-ignore we're not able to type check on this one -prf