diff --git a/.yarn/patches/react-native-screens-npm-4.18.0-fa7de65975.patch b/.yarn/patches/react-native-screens-npm-4.18.0-fa7de65975.patch new file mode 100644 index 00000000..006c5e30 --- /dev/null +++ b/.yarn/patches/react-native-screens-npm-4.18.0-fa7de65975.patch @@ -0,0 +1,40 @@ +diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm +index 51f021831aed26a4eed3c85014020423b7b3108b..31bc0ff2a878fd23064c4f48de59accd51a75000 100644 +--- a/ios/RNSScreenStack.mm ++++ b/ios/RNSScreenStack.mm +@@ -640,8 +640,10 @@ RNS_IGNORE_SUPER_CALL_END + + // This check is for external modals that are not owned by this stack. They can prevent the dismissal of the modal by + // extending RNSDismissibleModalProtocol and returning NO from isDismissible method. +- if (![firstModalToBeDismissed conformsToProtocol:@protocol(RNSDismissibleModalProtocol)] || +- [(id)firstModalToBeDismissed isDismissible]) { ++ BOOL shouldDismissFirstModal = ![firstModalToBeDismissed conformsToProtocol:@protocol(RNSDismissibleModalProtocol)] || ++ [(id)firstModalToBeDismissed isDismissible]; ++ ++ if (shouldDismissFirstModal) { + if (firstModalToBeDismissed != nil) { + const BOOL firstModalToBeDismissedIsOwned = [firstModalToBeDismissed isKindOfClass:RNSScreen.class]; + const BOOL firstModalToBeDismissedIsOwnedByThisStack = +@@ -699,6 +701,22 @@ RNS_IGNORE_SUPER_CALL_END + return; + } + } ++ } else { ++ // Modal is non-dismissible (e.g., third-party modal like TrueSheet) ++ // We need to update changeRootController to this modal so new modals can be presented from it ++ if (firstModalToBeDismissed != nil) { ++ changeRootController = firstModalToBeDismissed; ++ ++ // Check if the non-dismissible modal itself has presented modals that need to be dismissed ++ UIViewController *modalPresentedByNonDismissible = firstModalToBeDismissed.presentedViewController; ++ if (modalPresentedByNonDismissible != nil && ![modalPresentedByNonDismissible isBeingDismissed] && ++ [_presentedModals containsObject:modalPresentedByNonDismissible]) { ++ // The non-dismissible modal has presented one of our modals ++ // We need to dismiss it before presenting new ones ++ [firstModalToBeDismissed dismissViewControllerAnimated:YES completion:finish]; ++ return; ++ } ++ } + } + + // We didn't detect any controllers for dismissal, thus we start presenting new VCs diff --git a/docs/docs/troubleshooting.mdx b/docs/docs/troubleshooting.mdx index a329e1eb..ca558462 100644 --- a/docs/docs/troubleshooting.mdx +++ b/docs/docs/troubleshooting.mdx @@ -34,17 +34,17 @@ Note that we are using `flexGrow` instead of `flex` here. For some weird reason, [`react-navigation`](https://reactnavigation.org) -### Navigation Crash +### Navigating from Sheet -On IOS, navigating to a screen from within the Sheet can cause issues. To resolve this, dismiss the sheet before navigating! +You can now navigate to other screens from within a sheet without any issues! The sheet will remain visible in the background when presenting modals on top. Example: -```tsx {4-5} +```tsx const sheet = useRef(null) -const navigate = async () => { - await sheet.current?.dismiss() // wait for the sheet to dismiss ✅ - navigation.navigate('SomeScreen') // navigate to the screen 🎉 +const navigate = () => { + // Navigate directly - no need to dismiss first! ✅ + navigation.navigate('SomeScreen') } return ( @@ -55,6 +55,16 @@ return ( ) ``` +:::tip +If you prefer to dismiss the sheet before navigating for UX reasons, you can still do so: +```tsx +const navigate = async () => { + await sheet.current?.dismiss() + navigation.navigate('SomeScreen') +} +``` +::: + ### Present during Mount On iOS, when setting [`initialDetentIndex`](/reference/props#initialdetentindex) and enabling `initialDetentAnimated` (default is `true`) to present during mount, the presentation animation becomes weird. This happens because RNN is not yet finished when the sheet is trying to present. diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index cfe48060..e2588967 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2646,7 +2646,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNTrueSheet (3.0.0): + - RNTrueSheet (3.0.0-beta.0): - boost - DoubleConversion - fast_float @@ -3095,7 +3095,7 @@ SPEC CHECKSUMS: RNGestureHandler: e1cf8ef3f11045536eed6bd4f132b003ef5f9a5f RNReanimated: ac06da53579693ab451941ef89f5a55afeab0dd9 RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479 - RNTrueSheet: b6a4f37f66d34d55f4ae0cc6f62ca8cae7c54209 + RNTrueSheet: 88b29ccd3b93d28f8bd258a27e96464b16c3ed9a RNWorklets: ab618bf7d1c7fd2cb793b9f0f39c3e29274b3ebf SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb diff --git a/example/package.json b/example/package.json index 3c0e8438..840487bf 100644 --- a/example/package.json +++ b/example/package.json @@ -10,15 +10,15 @@ "build:ios": "react-native build-ios --mode Debug" }, "dependencies": { - "@react-navigation/native": "^7.1.20", - "@react-navigation/native-stack": "^7.6.3", + "@react-navigation/native": "^7.1.21", + "@react-navigation/native-stack": "^7.7.0", "react": "19.1.1", "react-native": "0.82.1", "react-native-gesture-handler": "^2.29.1", "react-native-maps": "^1.26.18", "react-native-reanimated": "^4.1.5", "react-native-safe-area-context": "^5.5.2", - "react-native-screens": "^4.18.0", + "react-native-screens": "patch:react-native-screens@npm%3A4.18.0#~/.yarn/patches/react-native-screens-npm-4.18.0-fa7de65975.patch", "react-native-worklets": "^0.6.1" }, "devDependencies": { diff --git a/example/src/App.tsx b/example/src/App.tsx index 71b6f256..2c354375 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,11 +1,21 @@ import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { MapScreen, NavigationScreen, ChildScreen } from './screens'; -import type { AppStackParamList } from './types'; +import { MapScreen, NavigationScreen, TestScreen, ModalScreen } from './screens'; +import type { AppStackParamList, ModalStackParamList } from './types'; import { ReanimatedTrueSheetProvider } from '@lodev09/react-native-true-sheet'; const Stack = createNativeStackNavigator(); +const ModalStack = createNativeStackNavigator(); + +const ModalStackScreen = () => { + return ( + + + + + ); +}; const App = () => { return ( @@ -13,7 +23,7 @@ const App = () => { { name="Navigation" component={NavigationScreen} /> - + + diff --git a/example/src/components/Button.tsx b/example/src/components/Button.tsx index a4457029..9fd7b1dc 100644 --- a/example/src/components/Button.tsx +++ b/example/src/components/Button.tsx @@ -1,17 +1,24 @@ -import { StyleSheet, Text, TouchableOpacity, type TouchableOpacityProps } from 'react-native'; +import { Pressable, StyleSheet, Text, type PressableProps } from 'react-native'; import { styles as constantStyles, BORDER_RADIUS, DARK_BLUE } from '../utils'; -interface ButtonProps extends TouchableOpacityProps { +interface ButtonProps extends PressableProps { text: string; } export const Button = (props: ButtonProps) => { - const { text, style: $styleOverride, ...rest } = props; + const { text, style, ...rest } = props; return ( - + [ + styles.button, + pressed && styles.pressed, + typeof style === 'function' ? style({ pressed }) : style, + ]} + {...rest} + > {text} - + ); }; @@ -23,4 +30,7 @@ const styles = StyleSheet.create({ backgroundColor: DARK_BLUE, alignItems: 'center', }, + pressed: { + opacity: 0.8, + }, }); diff --git a/example/src/components/sheets/NavigationSheet.tsx b/example/src/components/sheets/NavigationSheet.tsx new file mode 100644 index 00000000..24c5fa06 --- /dev/null +++ b/example/src/components/sheets/NavigationSheet.tsx @@ -0,0 +1,48 @@ +import { forwardRef } from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { TrueSheet } from '@lodev09/react-native-true-sheet'; +import type { TrueSheetProps } from '@lodev09/react-native-true-sheet'; + +import { Button } from '../Button'; +import { GAP, SPACING } from '../../utils'; +import { useAppNavigation } from '../../hooks'; + +export const NavigationSheet = forwardRef((props, ref) => { + const navigation = useAppNavigation(); + + const handleOpenModal = () => { + navigation.navigate('ModalStack'); + }; + + return ( + + + Navigation Sheet + + This sheet demonstrates opening a React Navigation fullScreenModal from within a + TrueSheet. + +