import { useCallback, useEffect, useMemo, useState } from "react";
import { Pressable, Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
import { useTranslation } from "react-i18next";
import { useRetainedPanelActive } from "@/components/retained-panel";
import { useChangesPreferences } from "@/hooks/use-changes-preferences";
import { useCheckoutCommitsQuery, type CheckoutCommitsQueryResult } from "@/git/use-commits-query";
import { ThemedChevron, chevronColorMapping } from "@/git/themed-chevron";
import { normalizeBranchOptionName } from "@/utils/branch-suggestions";
import { CommitRow } from "./commit-row";
interface CommitsSectionProps {
serverId: string;
cwd: string;
onCommitPress: (sha: string) => void;
}
function CommitsSectionSkeleton() {
const { t } = useTranslation();
return (
);
}
function CommitsSectionContent({
query,
now,
onCommitPress,
}: {
query: Exclude;
now: Date;
onCommitPress: (sha: string) => void;
}) {
const { t } = useTranslation();
if (query.status === "error") {
return (
{t("workspace.git.diff.commits.loadError")}
);
}
if (query.status !== "loaded") {
return ;
}
const workspaceCommits = query.data.commits.filter((commit) => !commit.isOnBase);
const baseRef = normalizeBranchOptionName(query.data.baseRef) ?? t("workspace.git.diff.base");
if (workspaceCommits.length === 0) {
return (
{t("workspace.git.diff.commits.noneAhead", { baseRef })}
);
}
return (
{workspaceCommits.map((commit, index) => (
))}
);
}
export function CommitsSection({ serverId, cwd, onCommitPress }: CommitsSectionProps) {
const { t } = useTranslation();
const { preferences, updatePreferences } = useChangesPreferences();
const isPanelActive = useRetainedPanelActive();
const collapsed = preferences.commitsCollapsed;
const [now, setNow] = useState(() => new Date());
const displayNow = useMemo(() => (isPanelActive ? new Date() : now), [isPanelActive, now]);
const query = useCheckoutCommitsQuery({
serverId,
cwd,
enabled: !collapsed,
});
const handleToggleSection = useCallback(() => {
if (collapsed) {
setNow(new Date());
}
void updatePreferences({ commitsCollapsed: !collapsed });
}, [collapsed, updatePreferences]);
useEffect(() => {
if (collapsed || !isPanelActive) {
return;
}
const interval = setInterval(() => setNow(new Date()), 10_000);
return () => clearInterval(interval);
}, [collapsed, isPanelActive]);
const headerChevronStyle = useMemo(
() => [styles.headerChevron, !collapsed && styles.headerChevronExpanded],
[collapsed],
);
if (query.status === "unsupported") {
return null;
}
const commitCount =
query.status === "loaded"
? query.data.commits.filter((commit) => !commit.isOnBase).length
: null;
return (
{t("workspace.git.diff.commits.title")}
{commitCount === null ? (
) : (
{commitCount}
)}
{collapsed ? null : (
)}
);
}
const styles = StyleSheet.create((theme) => ({
container: {
borderTopWidth: theme.borderWidth[1],
borderTopColor: theme.colors.border,
},
header: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
paddingLeft: theme.spacing[2],
paddingRight: theme.spacing[3],
paddingVertical: theme.spacing[2],
flexShrink: 0,
},
headerChevron: {
width: 16,
height: 16,
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
},
headerChevronExpanded: {
transform: [{ rotate: "90deg" }],
},
title: {
fontSize: theme.fontSize.sm,
color: theme.colors.foreground,
},
count: {
fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted,
flex: 1,
},
countSpacer: {
flex: 1,
},
list: {
paddingBottom: theme.spacing[1],
},
noWorkspaceCommitsRow: {
flexDirection: "row",
alignItems: "center",
paddingLeft: theme.spacing[2],
paddingRight: theme.spacing[3],
paddingTop: theme.spacing[1],
paddingBottom: theme.spacing[2],
},
noWorkspaceCommitsText: {
fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted,
},
errorRow: {
fontSize: theme.fontSize.xs,
color: theme.colors.statusDanger,
paddingLeft: theme.spacing[2],
paddingRight: theme.spacing[3],
paddingVertical: theme.spacing[2],
},
skeleton: {
paddingBottom: theme.spacing[1],
gap: theme.spacing[2],
},
skeletonRow: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
paddingHorizontal: theme.spacing[2],
paddingVertical: theme.spacing[1],
minHeight: 20,
},
skeletonDot: {
width: 8,
height: 8,
borderRadius: theme.borderRadius.full,
backgroundColor: theme.colors.surface2,
},
skeletonSha: {
width: 48,
height: 10,
borderRadius: theme.borderRadius.sm,
backgroundColor: theme.colors.surface2,
},
skeletonSubject: {
flex: 1,
minWidth: 0,
height: 12,
borderRadius: theme.borderRadius.sm,
backgroundColor: theme.colors.surface2,
},
skeletonTimestamp: {
width: 40,
height: 10,
borderRadius: theme.borderRadius.sm,
backgroundColor: theme.colors.surface2,
flexShrink: 0,
},
skeletonCaret: {
width: 16,
height: 16,
flexShrink: 0,
},
}));