1import { AppActivityIndicator } from "../../components/ui/AppActivityIndicator";
2import type { ChangeRequestTerminology } from "@t3tools/client-runtime/source-control-clients";
3import type { PullRequestActor } from "@t3tools/contracts";
4import IconAlertTriangle from "@tabler/icons-react-native/IconAlertTriangle";
5import { Image } from "expo-image";
6import { View } from "react-native";
7import { useResolveClassNames } from "uniwind";
8
9import { AppText as Text } from "../../components/ui/AppText";
10import { cn } from "../../lib/cn";
11import { EmptyState } from "../../components/ui/EmptyState";
12
13export function PullRequestReadStatus(props: {
14 readonly pending: boolean;
15 readonly error: string | null;
16 readonly terminology: ChangeRequestTerminology;
17}) {
18 return props.pending ? (
19 <AppActivityIndicator className="my-12" colorClassName="accent-icon" />
20 ) : props.error ? (
21 <EmptyState
22 title={`Couldn't load this ${props.terminology.singular}`}
23 detail={props.error}
24 variant="plain"
25 />
26 ) : null;
27}
28
29export function PullRequestConflictBadge(props: { readonly surfaceClassName: string }) {
30 const { color } = useResolveClassNames("text-danger-foreground");
31 const { color: surface } = useResolveClassNames(props.surfaceClassName);
32 return (
33 <View pointerEvents="none" className="absolute -right-1 -bottom-1">
34 <IconAlertTriangle size={15} strokeWidth={2.5} color={surface} fill={surface} />
35 <View className="absolute inset-0">
36 <IconAlertTriangle size={15} strokeWidth={2.5} color={color} />
37 </View>
38 </View>
39 );
40}
41
42export function PullRequestAvatar(props: {
43 readonly actor: PullRequestActor | null;
44 readonly size?: number;
45}) {
46 const size = props.size ?? 24;
47 return props.actor?.avatarUrl ? (
48 <Image
49 source={{ uri: props.actor.avatarUrl }}
50 recyclingKey={props.actor.avatarUrl}
51 accessibilityLabel={props.actor.login}
52 style={{ width: size, height: size, borderRadius: size / 2 }}
53 cachePolicy="memory-disk"
54 />
55 ) : (
56 <View
57 className="items-center justify-center rounded-full bg-grouped-card"
58 style={{ width: size, height: size }}
59 >
60 <Text className="font-t3-medium text-foreground-muted" style={{ fontSize: size * 0.45 }}>
61 {props.actor?.login.slice(0, 1).toUpperCase() ?? "?"}
62 </Text>
63 </View>
64 );
65}
66
67export function PullRequestDiffStats(props: {
68 readonly additions: number;
69 readonly deletions: number;
70 readonly compact?: boolean;
71}) {
72 const textSize = props.compact ? "text-2xs" : "text-xs";
73 return (
74 <View className="shrink-0 flex-row items-center gap-2">
75 {!props.compact || props.additions > 0 ? (
76 <Text className={cn(textSize, "font-t3-medium tabular-nums text-adaptive-emerald-600-400")}>
77 +{props.additions.toLocaleString()}
78 </Text>
79 ) : null}
80 {!props.compact || props.deletions > 0 ? (
81 <Text className={cn(textSize, "font-t3-medium tabular-nums text-danger-foreground")}>
82 −{props.deletions.toLocaleString()}
83 </Text>
84 ) : null}
85 </View>
86 );
87}