1import { useNavigation, type StaticScreenProps } from "@react-navigation/native";
2import { LegendList } from "@legendapp/list/react-native";
3import { useMemo } from "react";
4import { View } from "react-native";
5import { useSafeAreaInsets } from "react-native-safe-area-context";
6
7import { AppText as Text } from "../../components/ui/AppText";
8import { ScreenContent } from "../../components/ui/ScreenContent";
9import { SheetHeader } from "../../components/ui/SheetHeader";
10import { useOpenPullRequest } from "../../native/useOpenPullRequest";
11import { useThreadSelection } from "../../state/use-thread-selection";
12import {
13 LinkedPullRequestRow,
14 linkedPullRequestKey,
15 useLinkedPullRequestLines,
16} from "./LinkedPullRequestList";
17import { useLinkedPullRequestActions } from "./useLinkedPullRequestActions";
18
19export function ThreadPullRequestsSheet(
20 _props: StaticScreenProps<{ readonly environmentId: string; readonly threadId: string }>,
21) {
22 const navigation = useNavigation();
23 const insets = useSafeAreaInsets();
24 const { selectedThread, selectedThreadProject, selectedEnvironmentRuntime } =
25 useThreadSelection();
26 const lines = useLinkedPullRequestLines(selectedThread, selectedEnvironmentRuntime);
27 const openCount = useMemo(
28 () =>
29 lines.filter((line) => line.link.snapshot === null || line.link.snapshot.state === "open")
30 .length,
31 [lines],
32 );
33 const openPullRequest = useOpenPullRequest({
34 replace: true,
35 context: selectedThread ?? undefined,
36 });
37 const actions = useLinkedPullRequestActions({
38 thread: selectedThread,
39 project: selectedThreadProject,
40 runtime: selectedEnvironmentRuntime,
41 });
42 const rowInputs = { lines, actions };
43 const { singular } = actions.terminology;
44 const title = `Linked ${actions.terminology.plural}`;
45
46 const content = (
47 <View className="flex-1 bg-screen">
48 {/* oxlint-disable-next-line t3code/require-registered-list-scroll -- Sheet content; sheets sit outside workspace columns and keep their own header handling. */}
49 <LegendList
50 data={lines}
51 keyExtractor={linkedPullRequestKey}
52 extraData={rowInputs}
53 renderItem={({ item, index }) => (
54 <LinkedPullRequestRow
55 line={item}
56 separator={index < lines.length - 1}
57 surfaceClassName="text-screen"
58 openPullRequest={openPullRequest}
59 actions={actions}
60 />
61 )}
62 estimatedItemSize={88}
63 recycleItems
64 alwaysBounceVertical
65 style={{ flex: 1 }}
66 contentInsetAdjustmentBehavior="automatic"
67 showsVerticalScrollIndicator={false}
68 contentContainerStyle={{
69 paddingBottom: Math.max(insets.bottom, 18) + 18,
70 paddingHorizontal: 8,
71 }}
72 ListHeaderComponent={
73 lines.length > 0 ? (
74 <Text className="px-4 pt-2 pb-1 text-xs text-foreground-muted">
75 {openCount} open · {lines.length} linked
76 </Text>
77 ) : null
78 }
79 ListEmptyComponent={
80 <Text className="pt-6 text-center text-sm text-foreground-muted">
81 No linked {actions.terminology.plural}.
82 </Text>
83 }
84 />
85 </View>
86 );
87
88 return (
89 <View collapsable={false} className="flex-1 bg-sheet">
90 <SheetHeader
91 title={title}
92 onClose={() => navigation.goBack()}
93 actions={
94 actions.canLink
95 ? [
96 {
97 accessibilityLabel: `Link ${singular}`,
98 icon: "plus",
99 onPress: actions.promptLink,
100 },
101 ]
102 : undefined
103 }
104 />
105 <ScreenContent>{content}</ScreenContent>
106 </View>
107 );
108}