| 1 | import { useNavigation } from "@react-navigation/native"; |
| 2 | import type { ReactNode } from "react"; |
| 3 | import { Platform, ScrollView, useWindowDimensions, View } from "react-native"; |
| 4 | import { KeyboardAvoidingView } from "react-native-keyboard-controller"; |
| 5 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 6 | import { AppText as Text } from "../../components/AppText"; |
| 7 | import { AndroidSheetHeader } from "../../components/AndroidScreenHeader"; |
| 8 | import { MaterialIconButton } from "../../components/MaterialIconButton"; |
| 9 | import { MaterialScreenContent } from "../../components/MaterialScreenContent"; |
| 10 | import { ControlPill } from "../../components/ControlPill"; |
| 11 | import { NativeStackScreenOptions } from "../../native/StackHeader"; |
| 12 | |
| 13 | export 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 | } |