"use client"; import { IconGitCommit, IconGitPullRequest, IconCloudUpload, IconChevronDown, IconArrowBackUp, } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@kandev/ui/dropdown-menu"; import { cn } from "@/lib/utils"; import type { FileInfo } from "@/lib/state/store"; // --- Timeline visual components --- type ChangedFile = { path: string; status: FileInfo["status"]; staged: boolean; plus: number | undefined; minus: number | undefined; oldPath: string | undefined; }; // --- Timeline section dot colors --- const DOT_COLORS = { unstaged: "bg-yellow-500", staged: "bg-emerald-500", commits: "bg-blue-500", action: "bg-muted-foreground/25", } as const; function TimelineDot({ color }: { color: string }) { return
; } function TimelineSection({ dotColor, label, count, action, isLast, children, }: { dotColor: string; label?: string; count?: number; action?: React.ReactNode; isLast?: boolean; children?: React.ReactNode; }) { return (
{/* Vertical line + dot */}
{!isLast &&
}
{/* Content */}
{/* Header */} {label && (
{label} {typeof count === "number" && ( ({count}) )} {action}
)} {/* Children (file list, buttons, etc.) */} {children}
); } // --- Commits section --- type CommitItem = { commit_sha: string; commit_message: string; insertions: number; deletions: number; }; type CommitsSectionProps = { commits: CommitItem[]; isLast: boolean; onOpenCommitDetail?: (sha: string) => void; onRevertCommit?: (sha: string) => void; }; export function CommitsSection({ commits, isLast, onOpenCommitDetail, onRevertCommit, }: CommitsSectionProps) { return (
    {commits.map((commit, index) => (
  • onOpenCommitDetail?.(commit.commit_sha)} > {commit.commit_sha.slice(0, 7)} {commit.commit_message} +{commit.insertions}{" "} -{commit.deletions} {index === 0 && onRevertCommit && ( Revert commit )}
  • ))}
); } // --- Action buttons section (Create PR / Push) --- type ActionButtonsSectionProps = { onOpenPRDialog: () => void; onPush: () => void; onForcePush: () => void; isLoading: boolean; aheadCount: number; canPush: boolean; canCreatePR: boolean; existingPrUrl?: string; }; export function ActionButtonsSection({ onOpenPRDialog, onPush, onForcePush, isLoading, aheadCount, canPush, canCreatePR, existingPrUrl, }: ActionButtonsSectionProps) { const prExists = !!existingPrUrl; const createPrDisabled = !canCreatePR || prExists; const pushDisabled = !canPush || isLoading; let pushTooltip: string | null = null; if (isLoading) pushTooltip = "A git operation is in progress"; else if (!canPush) pushTooltip = "No commits ahead of remote"; return (
{prExists && A pull request already exists for this task} Force Push (with lease) {pushTooltip && {pushTooltip}}
); } // --- File list sections (Unstaged / Staged) --- import { FileRow } from "./changes-panel-file-row"; type FileListSectionProps = { variant: "unstaged" | "staged"; files: ChangedFile[]; pendingStageFiles: Set; isLast: boolean; actionLabel: string; onAction: () => void; onOpenDiff: (path: string) => void; onEditFile: (path: string) => void; onStage: (path: string) => void; onUnstage: (path: string) => void; onDiscard: (path: string) => void; }; export function FileListSection({ variant, files, pendingStageFiles, isLast, actionLabel, onAction, onOpenDiff, onEditFile, onStage, onUnstage, onDiscard, }: FileListSectionProps) { const dotColor = variant === "unstaged" ? DOT_COLORS.unstaged : DOT_COLORS.staged; const label = variant === "unstaged" ? "Unstaged" : "Staged"; return ( {files.length > 0 && (
    {files.map((file) => ( ))}
)} {files.length > 0 && (
)}
); } // --- Review progress bar --- type ReviewProgressBarProps = { reviewedCount: number; totalFileCount: number; onOpenReview?: () => void; }; export function ReviewProgressBar({ reviewedCount, totalFileCount, onOpenReview, }: ReviewProgressBarProps) { const progressPercent = totalFileCount > 0 ? (reviewedCount / totalFileCount) * 100 : 0; if (totalFileCount <= 0) return null; return (
{reviewedCount}/{totalFileCount} reviewed
{reviewedCount} of {totalFileCount} files reviewed ); }