Skip to content

Commit 6f6197c

Browse files
fix(mobile): restore native scroll-edge effects on forms and PRs (#18507)
1 parent d514a8c commit 6f6197c

8 files changed

Lines changed: 47 additions & 9 deletions

‎apps/mobile/src/components/ScreenScrollView.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ComponentProps } from "react";
2-
import { Platform, ScrollView } from "react-native";
2+
import { Platform } from "react-native";
3+
import { NativeScrollView as ScrollView } from "../native/NativeScrollViewMarker";
34

45
/** Keeps forms and settings readable inside a wide pane while its surface fills the screen. */
56
export function ScreenScrollView(props: ComponentProps<typeof ScrollView>) {

‎apps/mobile/src/features/pull-requests/PullRequestDiscussionScreen.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { PullRequestThreadComment } from "@t3tools/contracts";
55
import { useRef, useState } from "react";
66
import { ActivityIndicator, Platform, ScrollView, View } from "react-native";
77
import { KeyboardAvoidingView, useKeyboardState } from "react-native-keyboard-controller";
8-
import { ScrollViewMarker } from "react-native-screens";
8+
import { NativeScrollViewMarker as ScrollViewMarker } from "../../native/NativeScrollViewMarker";
99
import { useSafeAreaInsets } from "react-native-safe-area-context";
1010
import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText";
1111
import { AndroidScreenHeader } from "../../components/AndroidScreenHeader";
@@ -153,7 +153,7 @@ function Discussion({ params }: { params: PullRequestParams & { readonly discuss
153153
/>
154154
</NativeHeaderToolbar>
155155
) : null}
156-
<ScrollViewMarker style={{ flex: 1 }} scrollEdgeEffects={{ top: "soft", bottom: "soft" }}>
156+
<ScrollViewMarker style={{ flex: 1 }}>
157157
<ScrollView
158158
className="flex-1"
159159
contentInsetAdjustmentBehavior="automatic"

‎apps/mobile/src/features/pull-requests/PullRequestRouteScreen.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import {
5252
StyleSheet,
5353
View,
5454
} from "react-native";
55-
import { ScrollViewMarker } from "react-native-screens";
55+
import { NativeScrollViewMarker as ScrollViewMarker } from "../../native/NativeScrollViewMarker";
5656
import { useSafeAreaInsets } from "react-native-safe-area-context";
5757

5858
import { AppText as Text } from "../../components/AppText";
@@ -115,7 +115,7 @@ const EMPTY_IDS: readonly string[] = [];
115115
function PullRequestTabScrollView(props: ScrollViewProps) {
116116
const insets = useSafeAreaInsets();
117117
return (
118-
<ScrollViewMarker style={{ flex: 1 }} scrollEdgeEffects={{ top: "soft", bottom: "soft" }}>
118+
<ScrollViewMarker style={{ flex: 1 }}>
119119
<ScrollView
120120
{...props}
121121
automaticallyAdjustContentInsets={false}

‎apps/mobile/src/features/settings/SettingsFollowUpRouteScreen.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
12
import { useAtomSet, useAtomValue } from "@effect/atom-react";
23
import { useNavigation } from "@react-navigation/native";
34
import { AsyncResult } from "effect/reactivity";
4-
import { Platform, ScrollView, View } from "react-native";
5+
import { Platform, View } from "react-native";
56
import { useSafeAreaInsets } from "react-native-safe-area-context";
67

78
import { AndroidScreenHeader } from "../../components/AndroidScreenHeader";

‎apps/mobile/src/features/settings/SettingsKeyboardRouteScreen.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
12
import { useAtomSet, useAtomValue } from "@effect/atom-react";
23
import { useNavigation } from "@react-navigation/native";
34
import { AsyncResult } from "effect/reactivity";
4-
import { Platform, ScrollView, View } from "react-native";
5+
import { Platform, View } from "react-native";
56
import { useSafeAreaInsets } from "react-native-safe-area-context";
67

78
import { AndroidScreenHeader } from "../../components/AndroidScreenHeader";

‎apps/mobile/src/features/settings/SettingsMicrophoneRouteScreen.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
12
import { useAtomSet, useAtomValue } from "@effect/atom-react";
23
import { AsyncResult } from "effect/reactivity";
34
import { getRecordingPermissionsAsync, requestRecordingPermissionsAsync } from "expo-audio";
45
import { useCallback, useEffect, useState } from "react";
5-
import { AppState, Linking, Pressable, ScrollView, View } from "react-native";
6+
import { AppState, Linking, Pressable, View } from "react-native";
67
import Reanimated, { ReduceMotion, useAnimatedStyle, withTiming } from "react-native-reanimated";
78
import { useSafeAreaInsets } from "react-native-safe-area-context";
89

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { createContext, use, type ComponentProps } from "react";
2+
import { Platform, ScrollView } from "react-native";
3+
import { ScrollViewMarker } from "react-native-screens";
4+
import { nativeHeaderScrollEdgeEffects } from "./scrollEdgeEffects";
5+
6+
type ScrollEdgeEffects = ComponentProps<typeof ScrollViewMarker>["scrollEdgeEffects"];
7+
8+
const DEFAULT_SCROLL_EDGE_EFFECTS = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version);
9+
10+
export const NativeScrollEdgeEffectsContext = createContext<ScrollEdgeEffects>(undefined);
11+
12+
/** Register the content scroll view and apply its route's native edge settings. */
13+
export function NativeScrollViewMarker(props: ComponentProps<typeof ScrollViewMarker>) {
14+
const effects = use(NativeScrollEdgeEffectsContext);
15+
return (
16+
<ScrollViewMarker
17+
{...props}
18+
scrollEdgeEffects={props.scrollEdgeEffects ?? effects ?? DEFAULT_SCROLL_EDGE_EFFECTS}
19+
/>
20+
);
21+
}
22+
23+
/** Primary screen scrolling; Android keeps its existing native view hierarchy. */
24+
export function NativeScrollView(props: ComponentProps<typeof ScrollView>) {
25+
const scrollView = <ScrollView {...props} />;
26+
return Platform.OS === "ios" ? (
27+
<NativeScrollViewMarker style={{ flex: 1 }}>{scrollView}</NativeScrollViewMarker>
28+
) : (
29+
scrollView
30+
);
31+
}

‎apps/mobile/src/native/createV5StackNavigator.ios.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import { useCallback, useRef, useState, type ComponentProps } from "react";
2424
import { StyleSheet, View } from "react-native";
2525
import { FormSheet, Stack } from "react-native-screens";
26+
import { NativeScrollEdgeEffectsContext } from "./NativeScrollViewMarker";
2627
import { V5StackHeader } from "./V5StackHeader.ios";
2728
import type { AppNativeStackNavigationOptions } from "./StackHeader";
2829
import { NativeColumnContent } from "./NativeColumnContent.ios";
@@ -243,7 +244,9 @@ export function V5CardStackView(
243244
.nativeContentInsetHorizontally
244245
}
245246
>
246-
{descriptor.render()}
247+
<NativeScrollEdgeEffectsContext value={descriptor.options.scrollEdgeEffects}>
248+
{descriptor.render()}
249+
</NativeScrollEdgeEffectsContext>
247250
</NativeColumnContent>
248251
</NavigationRouteContext>
249252
</NavigationContext>

0 commit comments

Comments
 (0)