SPB Git

Repository Public

git-panel.tsx 2.5 KB 46 lines tsx

1'use client';2import { GitBranch, RefreshCw } from 'lucide-react';3import { useGitStatus } from '@/lib/queries';4import { IconButton } from '@/components/ui/button';5import { EmptyState, ErrorState, Spinner } from '@/components/ui/misc';6import { cn } from '@/lib/cn';78const LETTER: Record<string, string> = { M: 'text-warning', A: 'text-success', D: 'text-error', R: 'text-accent', '?': 'text-success', U: 'text-error' };910/** Git status of a project. Clicking a changed file opens its diff. */11export function GitPanel({ projectId, onOpenDiff }: { projectId: string; onOpenDiff: (path: string) => void }) {12  const q = useGitStatus(projectId);13  if (q.isPending) return <div className="flex justify-center p-6"><Spinner /></div>;14  if (q.isError) return <ErrorState error={q.error} onRetry={() => q.refetch()} />;15  const g = q.data;16  if (!g.isRepo) return <EmptyState icon={<GitBranch />} title="Not a Git repository" description="This project directory is not under version control." />;17  return (18    <div className="flex h-full flex-col" data-testid="git-panel">19      <div className="flex items-center gap-2 border-b border-line px-3 py-2 text-xs">20        <GitBranch className="size-3.5 text-muted" aria-hidden />21        <span className="font-mono text-text">{g.branch ?? 'detached HEAD'}</span>22        <span className="tnum text-muted">↑{g.ahead} ↓{g.behind}</span>23        <span className="flex-1" />24        <IconButton label="Refresh Git status" size="sm" onClick={() => q.refetch()}><RefreshCw className={cn('size-3.5', q.isFetching && 'animate-spin')} /></IconButton>25      </div>26      {g.files.length === 0 ? (27        <EmptyState title="Working tree clean" description="No changed files." />28      ) : (29        <ul className="min-h-0 flex-1 overflow-y-auto py-1">30          {g.files.map((f) => {31            const letter = (f.worktree.trim() || f.index.trim() || '?')[0] ?? '?';32            return (33              <li key={f.path}>34                <button onClick={() => onOpenDiff(f.path)} data-testid="git-file" className="flex min-h-7 w-full items-center gap-2 px-3 text-left text-xs hover:bg-surface-2 pointer-coarse:min-h-11">35                  <span className={cn('w-4 shrink-0 text-center font-mono font-semibold', LETTER[letter] ?? 'text-text-2')} title={`index “${f.index}”, worktree “${f.worktree}”`}>{letter === '?' ? 'U' : letter}</span>36                  <span className="truncate font-mono text-text-2">{f.path}</span>37                </button>38              </li>39            );40          })}41        </ul>42      )}43    </div>44  );45}46
Files main
Menu

Theme