Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .yarn/patches/react-native-screens-npm-4.18.0-fa7de65975.patch
Original file line number Diff line number Diff line change
@@ -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<RNSDismissibleModalProtocol>)firstModalToBeDismissed isDismissible]) {
+ BOOL shouldDismissFirstModal = ![firstModalToBeDismissed conformsToProtocol:@protocol(RNSDismissibleModalProtocol)] ||
+ [(id<RNSDismissibleModalProtocol>)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
22 changes: 16 additions & 6 deletions docs/docs/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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<TrueSheet>(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 (
Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2646,7 +2646,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- RNTrueSheet (3.0.0):
- RNTrueSheet (3.0.0-beta.0):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -3095,7 +3095,7 @@ SPEC CHECKSUMS:
RNGestureHandler: e1cf8ef3f11045536eed6bd4f132b003ef5f9a5f
RNReanimated: ac06da53579693ab451941ef89f5a55afeab0dd9
RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479
RNTrueSheet: b6a4f37f66d34d55f4ae0cc6f62ca8cae7c54209
RNTrueSheet: 88b29ccd3b93d28f8bd258a27e96464b16c3ed9a
RNWorklets: ab618bf7d1c7fd2cb793b9f0f39c3e29274b3ebf
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb
Expand Down
6 changes: 3 additions & 3 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
23 changes: 19 additions & 4 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
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<AppStackParamList>();
const ModalStack = createNativeStackNavigator<ModalStackParamList>();

const ModalStackScreen = () => {
return (
<ModalStack.Navigator screenOptions={{ headerTransparent: true, headerTintColor: 'white' }}>
<ModalStack.Screen name="Modal" component={ModalScreen} />
<ModalStack.Screen name="Test" component={TestScreen} />
</ModalStack.Navigator>
);
};

const App = () => {
return (
<ReanimatedTrueSheetProvider>
<NavigationContainer>
<Stack.Navigator
screenOptions={{ headerTransparent: true, headerTintColor: 'white' }}
initialRouteName="Map"
initialRouteName="Navigation"
>
<Stack.Screen options={{ headerShown: false }} name="Map" component={MapScreen} />
<Stack.Screen
options={{ headerShown: false, title: 'Home' }}
name="Navigation"
component={NavigationScreen}
/>
<Stack.Screen name="Child" component={ChildScreen} />
<Stack.Screen name="Test" component={TestScreen} />
<Stack.Screen
name="ModalStack"
component={ModalStackScreen}
options={{ presentation: 'fullScreenModal', headerShown: false }}
/>
</Stack.Navigator>
</NavigationContainer>
</ReanimatedTrueSheetProvider>
Expand Down
20 changes: 15 additions & 5 deletions example/src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<TouchableOpacity activeOpacity={0.6} style={[styles.button, $styleOverride]} {...rest}>
<Pressable
style={({ pressed }) => [
styles.button,
pressed && styles.pressed,
typeof style === 'function' ? style({ pressed }) : style,
]}
{...rest}
>
<Text style={constantStyles.whiteText}>{text}</Text>
</TouchableOpacity>
</Pressable>
);
};

Expand All @@ -23,4 +30,7 @@ const styles = StyleSheet.create({
backgroundColor: DARK_BLUE,
alignItems: 'center',
},
pressed: {
opacity: 0.8,
},
});
48 changes: 48 additions & 0 deletions example/src/components/sheets/NavigationSheet.tsx
Original file line number Diff line number Diff line change
@@ -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<TrueSheet, TrueSheetProps>((props, ref) => {
const navigation = useAppNavigation();

const handleOpenModal = () => {
navigation.navigate('ModalStack');
};

return (
<TrueSheet ref={ref} detents={['auto']} {...props}>
<View style={styles.container}>
<Text style={styles.title}>Navigation Sheet</Text>
<Text style={styles.description}>
This sheet demonstrates opening a React Navigation fullScreenModal from within a
TrueSheet.
</Text>
<Button text="Open Full Screen Modal" onPress={handleOpenModal} />
</View>
</TrueSheet>
);
});

NavigationSheet.displayName = 'NavigationSheet';

const styles = StyleSheet.create({
container: {
padding: SPACING * 2,
gap: GAP,
},
title: {
fontSize: 20,
fontWeight: '600',
marginBottom: SPACING / 2,
},
description: {
fontSize: 14,
color: '#666',
marginBottom: SPACING,
},
});
1 change: 1 addition & 0 deletions example/src/components/sheets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './ScrollViewSheet';
export * from './FlatListSheet';
export * from './GestureSheet';
export * from './BlankSheet';
export * from './NavigationSheet';
61 changes: 61 additions & 0 deletions example/src/screens/ModalScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import type { TrueSheet } from '@lodev09/react-native-true-sheet';

import { BLUE, GAP, LIGHT_GRAY, SPACING } from '../utils';
import { Button, Spacer } from '../components';
import { BasicSheet, PromptSheet, ScrollViewSheet } from '../components/sheets';
import { useAppNavigation } from '../hooks';

export const ModalScreen = () => {
const basicSheet = useRef<TrueSheet>(null);
const promptSheet = useRef<TrueSheet>(null);
const scrollViewSheet = useRef<TrueSheet>(null);

const navigation = useAppNavigation();

return (
<View style={styles.content}>
<View style={styles.heading}>
<Text style={styles.title}>Modal Screen</Text>
<Text style={styles.subtitle}>
This is a fullScreenModal opened from a TrueSheet. You can present sheets from here too!
</Text>
</View>

<Button text="TrueSheet View" onPress={() => basicSheet.current?.present()} />
<Button text="TrueSheet Prompt" onPress={() => promptSheet.current?.present()} />
<Button text="TrueSheet ScrollView" onPress={() => scrollViewSheet.current?.present()} />
<Spacer />
<Button text="Navigate Test" onPress={() => navigation.navigate('Test')} />
<Button text="Dimiss Modal" onPress={() => navigation.goBack()} />

<BasicSheet ref={basicSheet} />
<PromptSheet ref={promptSheet} />
<ScrollViewSheet ref={scrollViewSheet} />
</View>
);
};

const styles = StyleSheet.create({
content: {
backgroundColor: BLUE,
justifyContent: 'center',
flex: 1,
padding: SPACING,
gap: GAP,
},
heading: {
marginBottom: SPACING * 2,
},
title: {
fontSize: 24,
lineHeight: 30,
fontWeight: '500',
color: 'white',
},
subtitle: {
lineHeight: 24,
color: LIGHT_GRAY,
},
});
15 changes: 10 additions & 5 deletions example/src/screens/NavigationScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import {
BlankSheet,
FlatListSheet,
GestureSheet,
NavigationSheet,
PromptSheet,
ScrollViewSheet,
} from '../components/sheets';
import { Button, Spacer } from '../components';
import { BLUE, LIGHT_GRAY, SPACING } from '../utils';
import { BLUE, GAP, LIGHT_GRAY, SPACING } from '../utils';
import { useAppNavigation } from '../hooks';

export const NavigationScreen = () => {
Expand All @@ -21,6 +22,7 @@ export const NavigationScreen = () => {
const flatListSheet = useRef<TrueSheet>(null);
const gestureSheet = useRef<TrueSheet>(null);
const blankSheet = useRef<TrueSheet>(null);
const navigationSheet = useRef<TrueSheet>(null);

const navigation = useAppNavigation();

Expand All @@ -30,15 +32,16 @@ export const NavigationScreen = () => {
};

return (
<View style={styles.container}>
<View style={styles.content}>
<View style={styles.heading}>
<Text style={styles.title}>True Sheet 💩</Text>
<Text style={styles.subtitle}>The true native bottom sheet experience.</Text>
</View>

<Button text="Navigate to Screen" onPress={() => navigation.navigate('Child')} />
<Button text="Navigate to Screen" onPress={() => navigation.navigate('Test')} />
<Button text="Open RNScreen Modal" onPress={() => navigation.navigate('ModalStack')} />
<Button text="Open RNScreen Sheet" onPress={() => navigationSheet.current?.present()} />
<Spacer />

<Button text="TrueSheet View" onPress={() => presentBasicSheet(0)} />
<Button text="TrueSheet Prompt" onPress={() => promptSheet.current?.present()} />
<Button text="TrueSheet ScrollView" onPress={() => scrollViewSheet.current?.present()} />
Expand All @@ -52,16 +55,18 @@ export const NavigationScreen = () => {
<FlatListSheet ref={flatListSheet} />
<GestureSheet ref={gestureSheet} />
<BlankSheet ref={blankSheet} />
<NavigationSheet ref={navigationSheet} />
</View>
);
};

const styles = StyleSheet.create({
container: {
content: {
backgroundColor: BLUE,
justifyContent: 'center',
flex: 1,
padding: SPACING,
gap: GAP,
},
heading: {
marginBottom: SPACING * 2,
Expand Down
Loading
Loading