import React, { useCallback, useMemo } from 'react'; import { RiArrowGoBackLine, RiLoader4Line, } from '@remixicon/react'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Checkbox } from '@/components/ui/checkbox'; import { FileTypeIcon } from '@/components/icons/FileTypeIcon'; import type { GitStatus } from '@/lib/api/types'; import { useI18n } from '@/lib/i18n'; type ChangeDescriptor = { code: string; color: string; description: string; }; const CHANGE_DESCRIPTORS: Record = { '?': { code: '?', color: 'var(--status-info)', description: 'Untracked file' }, A: { code: 'A', color: 'var(--status-success)', description: 'New file' }, D: { code: 'D', color: 'var(--status-error)', description: 'Deleted file' }, R: { code: 'R', color: 'var(--status-info)', description: 'Renamed file' }, C: { code: 'C', color: 'var(--status-info)', description: 'Copied file' }, M: { code: 'M', color: 'var(--status-warning)', description: 'Modified file' }, }; const DEFAULT_DESCRIPTOR = CHANGE_DESCRIPTORS.M; function getChangeSymbol(file: GitStatus['files'][number]): string { const indexCode = file.index?.trim(); const workingCode = file.working_dir?.trim(); if (indexCode && indexCode !== '?') return indexCode.charAt(0); if (workingCode) return workingCode.charAt(0); return indexCode?.charAt(0) || workingCode?.charAt(0) || 'M'; } function describeChange(file: GitStatus['files'][number]): ChangeDescriptor { const symbol = getChangeSymbol(file); return CHANGE_DESCRIPTORS[symbol] ?? DEFAULT_DESCRIPTOR; } interface ChangeRowProps { file: GitStatus['files'][number]; checked: boolean; onToggle: () => void; onViewDiff: () => void; onRevert: () => void; isReverting: boolean; stats?: { insertions: number; deletions: number }; rowPaddingClassName?: string; indentPx?: number; } export const ChangeRow = React.memo(function ChangeRow({ file, checked, onToggle, onViewDiff, onRevert, isReverting, stats, rowPaddingClassName, indentPx = 0, }) { const descriptor = useMemo(() => describeChange(file), [file]); const { t } = useI18n(); const indicatorLabel = descriptor.description; const insertions = stats?.insertions ?? 0; const deletions = stats?.deletions ?? 0; const handleKeyDown = useCallback( (event: React.KeyboardEvent) => { if (event.key === ' ') { event.preventDefault(); onToggle(); } else if (event.key === 'Enter') { event.preventDefault(); onViewDiff(); } }, [onToggle, onViewDiff] ); const handleRevertClick = useCallback( (event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); onRevert(); }, [onRevert] ); return (
0 ? { paddingLeft: `${indentPx}px` } : undefined} >
{ e.stopPropagation(); }}> onToggle()} ariaLabel={t('gitView.changes.selectFileAria', { path: file.path })} />
{descriptor.code} {(() => { const lastSlash = file.path.lastIndexOf('/'); if (lastSlash === -1) { return ( {file.path} ); } const dir = file.path.slice(0, lastSlash); const name = file.path.slice(lastSlash); return ( {dir} /{name.slice(1)} ); })()} +{insertions} / -{deletions} {t('gitView.changes.revertFileTooltip')}
); });