1import { useNavigation } from "@react-navigation/native";
2import type { ReactNode } from "react";
3import { Platform, ScrollView, useWindowDimensions, View } from "react-native";
4import { KeyboardAvoidingView } from "react-native-keyboard-controller";
5import { useSafeAreaInsets } from "react-native-safe-area-context";
6import { AppText as Text } from "../../components/AppText";
7import { AndroidSheetHeader } from "../../components/AndroidScreenHeader";
8import { MaterialIconButton } from "../../components/MaterialIconButton";
9import { MaterialScreenContent } from "../../components/MaterialScreenContent";
10import { ControlPill } from "../../components/ControlPill";
11import { NativeStackScreenOptions } from "../../native/StackHeader";
12
13export function PullRequestSheetContent(props: {
14 readonly title: string;
15 readonly subtitle: string;
16 readonly busy?: boolean;
17 readonly children: ReactNode;
18 readonly footer?: ReactNode;
19 readonly scrollable?: boolean;
20}) {
21 const navigation = useNavigation();
22 const insets = useSafeAreaInsets();
23 const { height: windowHeight } = useWindowDimensions();
24 const android = Platform.OS === "android";
25 const fitToContents = android && props.scrollable !== false;
26 const footer = props.footer ? (
27 <View
28 className={
29 android && props.scrollable !== false ? "bg-sheet-solid pt-3" : "bg-sheet px-4 pt-3"
30 }
31 style={{ paddingBottom: Math.max(insets.bottom, 16) }}
32 >
33 {android ? <View style={{ minHeight: 48 }}>{props.footer}</View> : props.footer}
34 </View>
35 ) : null;
36 const content = (
37 <>
38 {android ? (
39 <AndroidSheetHeader
40 title={props.title}
41 subtitle={props.subtitle}
42 hideBottomBorder
43 leading={
44 <MaterialIconButton
45 icon="arrow.left"
46 accessibilityLabel="Close"
47 disabled={props.busy}
48 tintColorClassName="accent-header-foreground"
49 onPress={() => navigation.goBack()}
50 />
51 }
52 />
53 ) : (
54 <View className="flex-row items-center gap-2 px-4 pt-5 pb-5">
55 <ControlPill
56 icon="xmark"
57 accessibilityLabel="Close"
58 disabled={props.busy}
59 onPress={() => navigation.goBack()}
60 />
61 <View className="min-w-0 flex-1 items-center gap-1">
62 <Text className="text-center text-base font-t3-bold">{props.title}</Text>
63 <Text numberOfLines={1} className="text-center text-xs text-foreground-muted">
64 {props.subtitle}
65 </Text>
66 </View>
67 <View className="w-11" />
68 </View>
69 )}
70 <MaterialScreenContent fitToContents={fitToContents}>
71 {props.scrollable === false ? (
72 <View className="flex-1" pointerEvents={props.busy ? "none" : "auto"}>
73 {props.children}
74 </View>
75 ) : (
76 <ScrollView
77 nestedScrollEnabled
78 className="android:shrink android:grow-0 ios:flex-1"
79 keyboardShouldPersistTaps="handled"
80 contentContainerStyle={{
81 paddingHorizontal: 16,
82 paddingTop: android ? 16 : 0,
83 paddingBottom: 20,
84 }}
85 >
86 <View className="gap-4" pointerEvents={props.busy ? "none" : "auto"}>
87 {props.children}
88 {/* Android partial-height sheets keep submit actions within the scrollable form. */}
89 {android ? footer : null}
90 </View>
91 </ScrollView>
92 )}
93
94 {!android || props.scrollable === false ? footer : null}
95 </MaterialScreenContent>
96 </>
97 );
98 return (
99 <KeyboardAvoidingView
100 automaticOffset
101 behavior="padding"
102 collapsable={false}
103 className={props.scrollable === false ? "flex-1 bg-sheet" : "bg-sheet ios:flex-1"}
104 style={fitToContents ? { maxHeight: windowHeight * 0.92 } : undefined}
105 >
106 <NativeStackScreenOptions
107 options={{
108 gestureEnabled: !props.busy,
109 ...(android ? { sheetCornerRadius: 28 } : {}),
110 ...(fitToContents ? { sheetAllowedDetents: "fitToContents" } : {}),
111 }}
112 />
113 {content}
114 </KeyboardAvoidingView>
115 );
116}