import { SymbolView } from "../../../components/AppSymbol";import type { ComponentProps } from "react";import { Pressable, View } from "react-native";import { AppText as Text } from "../../../components/AppText";import { cn } from "../../../lib/cn"; /* ─── Shared sheet components ──────────────────────────────────────── */ export function SheetActionButton(props: { readonly icon: ComponentProps<typeof SymbolView>["name"]; readonly label: string; readonly disabled?: boolean; readonly tone?: "primary" | "secondary" | "danger"; readonly onPress: () => void;}) { const tone = props.tone ?? "secondary"; const textColorClassName = tone === "primary" ? "accent-primary-foreground" : tone === "danger" ? "accent-danger-foreground" : "accent-secondary-foreground"; return ( <Pressable className={cn( "min-h-[48px] flex-1 flex-row items-center justify-center gap-2 rounded-[18px] px-4 py-3 disabled:opacity-[0.45]", tone === "primary" ? "bg-primary" : tone === "danger" ? "border border-danger-border bg-danger" : "border border-secondary-border bg-secondary", )} disabled={props.disabled} onPress={props.onPress} > <SymbolView name={props.icon} size={16} tintColorClassName={textColorClassName} type="monochrome" /> <Text className={cn( "text-xs font-t3-bold tracking-[0.9px] uppercase", tone === "primary" ? "text-primary-foreground" : tone === "danger" ? "text-danger-foreground" : "text-secondary-foreground", )} > {props.label} </Text> </Pressable> );} export function MetaCard(props: { readonly label: string; readonly value: string }) { return ( <View className="rounded-[18px] border border-border bg-card px-4 py-3"> <Text className="text-foreground-muted text-2xs font-t3-bold tracking-[0.9px] uppercase"> {props.label} </Text> <Text selectable className="text-foreground text-sm font-medium" numberOfLines={1}> {props.value} </Text> </View> );} export function SheetListRow(props: { readonly icon: ComponentProps<typeof SymbolView>["name"]; readonly title: string; readonly subtitle?: string | null; readonly disabled?: boolean; readonly onPress: () => void;}) { return ( <Pressable className="flex-row items-center gap-3 px-1 py-3 disabled:opacity-[0.45]" disabled={props.disabled} onPress={props.onPress} > <View className="bg-subtle h-9 w-9 items-center justify-center rounded-full"> <SymbolView name={props.icon} size={16} tintColorClassName={"accent-icon"} type="monochrome" /> </View> <View className="flex-1 gap-0.5"> <Text className="text-foreground text-base font-t3-bold">{props.title}</Text> {props.subtitle ? ( <Text className="text-foreground-muted text-xs leading-snug">{props.subtitle}</Text> ) : null} </View> <SymbolView name="chevron.right" size={13} tintColorClassName={"accent-icon-subtle"} type="monochrome" /> </Pressable> );} /* ─── Shared utilities ──────────────────────────────────────────────── */ export function menuItemIconName( icon: "commit" | "push" | "pr",): ComponentProps<typeof SymbolView>["name"] { if (icon === "commit") return "checkmark.circle"; if (icon === "push") return "arrow.up.circle"; return "arrow.up.right.circle";} export function statusSummary( gitStatus: { readonly isRepo?: boolean; readonly hasWorkingTreeChanges?: boolean; readonly workingTree?: { readonly files: readonly { readonly path: string }[] }; readonly aheadCount?: number; readonly behindCount?: number; readonly pr?: { readonly state?: string; readonly number?: number } | null; } | null,): string { if (!gitStatus) { return "Loading branch status\u2026"; } if (!gitStatus.isRepo) { return "Not a git repository"; } const parts: string[] = []; if (gitStatus.hasWorkingTreeChanges) { const fileCount = gitStatus.workingTree?.files.length ?? 0; parts.push(`${fileCount} file${fileCount === 1 ? "" : "s"} changed`); } else { parts.push("Clean"); } if ((gitStatus.aheadCount ?? 0) > 0) { parts.push(`${gitStatus.aheadCount} ahead`); } if ((gitStatus.behindCount ?? 0) > 0) { parts.push(`${gitStatus.behindCount} behind`); } if (gitStatus.pr?.state === "open") { parts.push(`PR #${gitStatus.pr.number} open`); } return parts.join(" \u00b7 ");}
import { SymbolView } from "../../../components/AppSymbol";
import type { ComponentProps } from "react";
import { Pressable, View } from "react-native";
import { AppText as Text } from "../../../components/AppText";
import { cn } from "../../../lib/cn";
/* ─── Shared sheet components ──────────────────────────────────────── */
export function SheetActionButton(props: {
readonly icon: ComponentProps<typeof SymbolView>["name"];
readonly label: string;
readonly disabled?: boolean;
readonly tone?: "primary" | "secondary" | "danger";
readonly onPress: () => void;
}) {
const tone = props.tone ?? "secondary";
const textColorClassName =
tone === "primary"
? "accent-primary-foreground"
: tone === "danger"
? "accent-danger-foreground"
: "accent-secondary-foreground";
return (
<Pressable
className={cn(
"min-h-[48px] flex-1 flex-row items-center justify-center gap-2 rounded-[18px] px-4 py-3 disabled:opacity-[0.45]",
tone === "primary"
? "bg-primary"
: tone === "danger"
? "border border-danger-border bg-danger"
: "border border-secondary-border bg-secondary",
)}
disabled={props.disabled}
onPress={props.onPress}
>
<SymbolView
name={props.icon}
size={16}
tintColorClassName={textColorClassName}
type="monochrome"
/>
<Text
className={cn(
"text-xs font-t3-bold tracking-[0.9px] uppercase",
tone === "primary"
? "text-primary-foreground"
: tone === "danger"
? "text-danger-foreground"
: "text-secondary-foreground",
)}
>
{props.label}
</Text>
</Pressable>
);
}
export function MetaCard(props: { readonly label: string; readonly value: string }) {
return (
<View className="rounded-[18px] border border-border bg-card px-4 py-3">
<Text className="text-foreground-muted text-2xs font-t3-bold tracking-[0.9px] uppercase">
{props.label}
</Text>
<Text selectable className="text-foreground text-sm font-medium" numberOfLines={1}>
{props.value}
</Text>
</View>
);
}
export function SheetListRow(props: {
readonly icon: ComponentProps<typeof SymbolView>["name"];
readonly title: string;
readonly subtitle?: string | null;
readonly disabled?: boolean;
readonly onPress: () => void;
}) {
return (
<Pressable
className="flex-row items-center gap-3 px-1 py-3 disabled:opacity-[0.45]"
disabled={props.disabled}
onPress={props.onPress}
>
<View className="bg-subtle h-9 w-9 items-center justify-center rounded-full">
<SymbolView
name={props.icon}
size={16}
tintColorClassName={"accent-icon"}
type="monochrome"
/>
</View>
<View className="flex-1 gap-0.5">
<Text className="text-foreground text-base font-t3-bold">{props.title}</Text>
{props.subtitle ? (
<Text className="text-foreground-muted text-xs leading-snug">{props.subtitle}</Text>
) : null}
</View>
<SymbolView
name="chevron.right"
size={13}
tintColorClassName={"accent-icon-subtle"}
type="monochrome"
/>
</Pressable>
);
}
/* ─── Shared utilities ──────────────────────────────────────────────── */
export function menuItemIconName(
icon: "commit" | "push" | "pr",
): ComponentProps<typeof SymbolView>["name"] {
if (icon === "commit") return "checkmark.circle";
if (icon === "push") return "arrow.up.circle";
return "arrow.up.right.circle";
}
export function statusSummary(
gitStatus: {
readonly isRepo?: boolean;
readonly hasWorkingTreeChanges?: boolean;
readonly workingTree?: { readonly files: readonly { readonly path: string }[] };
readonly aheadCount?: number;
readonly behindCount?: number;
readonly pr?: { readonly state?: string; readonly number?: number } | null;
} | null,
): string {
if (!gitStatus) {
return "Loading branch status\u2026";
}
if (!gitStatus.isRepo) {
return "Not a git repository";
}
const parts: string[] = [];
if (gitStatus.hasWorkingTreeChanges) {
const fileCount = gitStatus.workingTree?.files.length ?? 0;
parts.push(`${fileCount} file${fileCount === 1 ? "" : "s"} changed`);
} else {
parts.push("Clean");
}
if ((gitStatus.aheadCount ?? 0) > 0) {
parts.push(`${gitStatus.aheadCount} ahead`);
}
if ((gitStatus.behindCount ?? 0) > 0) {
parts.push(`${gitStatus.behindCount} behind`);
}
if (gitStatus.pr?.state === "open") {
parts.push(`PR #${gitStatus.pr.number} open`);
}
return parts.join(" \u00b7 ");
}