Skip to content

Commit 3082179

Browse files
fix(iOS): forward scrollEdgeEffects to inner screen in modals (#3816) (#3825)
## Description When a modal has a header (`isHeaderInModal`), the content is wrapped in a nested `ScreenStack`/`Screen`. The `scrollEdgeEffects` prop was only set on the outer modal screen, which doesn't contain the scroll view. This forwards the prop to the inner screen where the scroll view actually lives. Also adds `scrollEdgeEffect` props to `ModalScreenNativeComponent` codegen spec for the `headerShown: false` case, where the modal screen directly contains the scroll view. Closes #3816. ## Changes - Destructured `scrollEdgeEffects` in `ScreenStackItem.tsx` and forwarded it to the inner `<Screen>` when `isHeaderInModal` is true. The outer modal screen receives `undefined` since it has no scroll view. - Added `bottomScrollEdgeEffect`, `leftScrollEdgeEffect`, `rightScrollEdgeEffect`, and `topScrollEdgeEffect` props to `ModalScreenNativeComponent.ts` codegen spec. ## Test plan Test with `Test3816` in the apps example project. 1. Open the app and navigate to `Test3816` 2. Tap "Open formSheet modal" 3. Verify that scroll edge effects on the modal's scroll view match the configured values (`hidden` for all edges) 4. Compare with a modal using `headerShown: false` to verify that case also works ## Checklist - [x] Included code example that can be used to test this change. - [x] Updated / created local changelog entries in relevant test files. - [ ] For visual changes, included screenshots / GIFs / recordings documenting the change. - [x] For API changes, updated relevant public types. - [ ] Ensured that CI passes --------- Co-authored-by: Krzysztof Ligarski <63918941+kligarski@users.noreply.github.com>
1 parent 6cf26f6 commit 3082179

4 files changed

Lines changed: 85 additions & 2 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Changelog:
3+
*
4+
* #3816 - scrollEdgeEffects not applied on modal screens. The prop was set on the outer modal
5+
* screen which doesn't contain the scroll view. Fix forwards it to the inner screen.
6+
*/
7+
8+
import React from 'react';
9+
import { NavigationContainer, ParamListBase } from '@react-navigation/native';
10+
import {
11+
NativeStackNavigationProp,
12+
createNativeStackNavigator,
13+
} from '@react-navigation/native-stack';
14+
import { Button, ScrollView } from 'react-native';
15+
import LongText from '../../shared/LongText';
16+
17+
type RouteParamList = {
18+
Home: undefined;
19+
Modal: undefined;
20+
};
21+
22+
type NavigationProp<ParamList extends ParamListBase> = {
23+
navigation: NativeStackNavigationProp<ParamList>;
24+
};
25+
26+
type StackNavigationProp = NavigationProp<RouteParamList>;
27+
28+
const Stack = createNativeStackNavigator<RouteParamList>();
29+
30+
function Home({ navigation }: StackNavigationProp) {
31+
return (
32+
<ScrollView contentInsetAdjustmentBehavior="automatic">
33+
<Button
34+
title="Open formSheet modal"
35+
onPress={() => navigation.navigate('Modal')}
36+
/>
37+
<LongText />
38+
</ScrollView>
39+
);
40+
}
41+
42+
function Modal({ navigation }: StackNavigationProp) {
43+
return (
44+
<ScrollView contentInsetAdjustmentBehavior="automatic">
45+
<Button title="Go back" onPress={() => navigation.goBack()} />
46+
<LongText />
47+
</ScrollView>
48+
);
49+
}
50+
51+
export default function App() {
52+
return (
53+
<NavigationContainer>
54+
<Stack.Navigator>
55+
<Stack.Screen name="Home" component={Home} />
56+
<Stack.Screen
57+
name="Modal"
58+
component={Modal}
59+
options={{
60+
presentation: 'modal',
61+
headerTransparent: true,
62+
scrollEdgeEffects: {
63+
bottom: 'hidden',
64+
left: 'hidden',
65+
right: 'hidden',
66+
top: 'hidden',
67+
},
68+
}}
69+
/>
70+
</Stack.Navigator>
71+
</NavigationContainer>
72+
);
73+
}

apps/src/tests/issue-tests/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ export { default as Test3611 } from './Test3611';
183183
export { default as Test3617 } from './Test3617';
184184
export { default as Test3760 } from './Test3760';
185185
export { default as Test3770 } from './Test3770';
186+
export { default as Test3816 } from './Test3816';
186187
export { default as TestScreenAnimation } from './TestScreenAnimation';
187188
// The following test was meant to demo the "go back" gesture using Reanimated
188189
// but the associated PR in react-navigation is currently put on hold

src/components/ScreenStackItem.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ function ScreenStackItem(
4545
style,
4646
screenId,
4747
onHeaderHeightChange,
48+
scrollEdgeEffects,
4849
// eslint-disable-next-line camelcase
4950
unstable_sheetFooter,
5051
...rest
@@ -79,8 +80,8 @@ function ScreenStackItem(
7980
}, [headerConfigHiddenWithDefault, stackPresentationWithDefault]);
8081

8182
const hasEdgeEffects =
82-
rest?.scrollEdgeEffects === undefined ||
83-
Object.values(rest.scrollEdgeEffects).some(
83+
scrollEdgeEffects === undefined ||
84+
Object.values(scrollEdgeEffects).some(
8485
propValue => propValue !== 'hidden',
8586
);
8687
const hasBlurEffect =
@@ -177,6 +178,7 @@ function ScreenStackItem(
177178
hasLargeHeader={headerConfig?.largeTitle ?? false}
178179
sheetAllowedDetents={sheetAllowedDetents}
179180
style={[style, internalScreenStyle]}
181+
scrollEdgeEffects={isHeaderInModal ? undefined : scrollEdgeEffects}
180182
onHeaderHeightChange={isHeaderInModal ? undefined : onHeaderHeightChange}
181183
{...rest}>
182184
{isHeaderInModal ? (
@@ -187,6 +189,7 @@ function ScreenStackItem(
187189
activityState={activityState}
188190
shouldFreeze={shouldFreeze}
189191
hasLargeHeader={headerConfig?.largeTitle ?? false}
192+
scrollEdgeEffects={scrollEdgeEffects}
190193
style={StyleSheet.absoluteFill}
191194
onHeaderHeightChange={onHeaderHeightChange}>
192195
{content}

src/fabric/ModalScreenNativeComponent.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ type SwipeDirection = 'vertical' | 'horizontal';
5959

6060
type ReplaceAnimation = 'pop' | 'push';
6161

62+
type ScrollEdgeEffect = 'automatic' | 'hard' | 'soft' | 'hidden';
63+
6264
type OptionalBoolean = 'undefined' | 'false' | 'true';
6365

6466
export interface NativeProps extends ViewProps {
@@ -107,6 +109,10 @@ export interface NativeProps extends ViewProps {
107109
navigationBarTranslucent?: boolean;
108110
navigationBarHidden?: boolean;
109111
nativeBackButtonDismissalEnabled?: boolean;
112+
bottomScrollEdgeEffect?: CT.WithDefault<ScrollEdgeEffect, 'automatic'>;
113+
leftScrollEdgeEffect?: CT.WithDefault<ScrollEdgeEffect, 'automatic'>;
114+
rightScrollEdgeEffect?: CT.WithDefault<ScrollEdgeEffect, 'automatic'>;
115+
topScrollEdgeEffect?: CT.WithDefault<ScrollEdgeEffect, 'automatic'>;
110116
synchronousShadowStateUpdatesEnabled?: CT.WithDefault<boolean, false>;
111117
}
112118

0 commit comments

Comments
 (0)