From b1a243af1f32ed57842601569c07bbe1cf775ec3 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sun, 21 Dec 2025 02:35:33 +0200 Subject: [PATCH] feat(git): enhance git change row --- .../ui/src/components/views/git/ChangeRow.tsx | 123 ++++++++++-------- packages/ui/src/lib/toolHelpers.ts | 4 +- 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/packages/ui/src/components/views/git/ChangeRow.tsx b/packages/ui/src/components/views/git/ChangeRow.tsx index a8397625..2f55279e 100644 --- a/packages/ui/src/components/views/git/ChangeRow.tsx +++ b/packages/ui/src/components/views/git/ChangeRow.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; import { RiCheckboxLine, RiCheckboxBlankLine, @@ -8,6 +8,38 @@ import { import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import type { GitStatus } from '@/lib/api/types'; +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; @@ -18,33 +50,7 @@ interface ChangeRowProps { stats?: { insertions: number; deletions: number }; } -function describeChange(file: GitStatus['files'][number]) { - const rawCode = - file.index && file.index.trim() && file.index.trim() !== '?' - ? file.index.trim() - : file.working_dir && file.working_dir.trim() - ? file.working_dir.trim() - : file.index || file.working_dir || ' '; - - const symbol = rawCode.trim().charAt(0) || rawCode.trim() || 'ยท'; - - switch (symbol) { - case '?': - return { code: '?', color: 'var(--status-info)', description: 'Untracked file' }; - case 'A': - return { code: 'A', color: 'var(--status-success)', description: 'New file' }; - case 'D': - return { code: 'D', color: 'var(--status-error)', description: 'Deleted file' }; - case 'R': - return { code: 'R', color: 'var(--status-info)', description: 'Renamed file' }; - case 'C': - return { code: 'C', color: 'var(--status-info)', description: 'Copied file' }; - default: - return { code: 'M', color: 'var(--status-warning)', description: 'Modified file' }; - } -} - -export const ChangeRow: React.FC = ({ +export const ChangeRow = React.memo(function ChangeRow({ file, checked, onToggle, @@ -52,12 +58,43 @@ export const ChangeRow: React.FC = ({ onRevert, isReverting, stats, -}) => { - const descriptor = React.useMemo(() => describeChange(file), [file]); - const indicatorLabel = descriptor.description ?? descriptor.code; +}) { + const descriptor = useMemo(() => describeChange(file), [file]); + 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 handleToggleClick = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + onToggle(); + }, + [onToggle] + ); + + const handleRevertClick = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + onRevert(); + }, + [onRevert] + ); + return (
  • = ({ role="button" tabIndex={0} onClick={onViewDiff} - onKeyDown={(event) => { - if (event.key === ' ') { - event.preventDefault(); - onToggle(); - } else if (event.key === 'Enter') { - event.preventDefault(); - onViewDiff(); - } - }} + onKeyDown={handleKeyDown} >
  • ); -}; +}); diff --git a/packages/ui/src/lib/toolHelpers.ts b/packages/ui/src/lib/toolHelpers.ts index 89bc1129..de6c484e 100644 --- a/packages/ui/src/lib/toolHelpers.ts +++ b/packages/ui/src/lib/toolHelpers.ts @@ -202,9 +202,9 @@ export function getLanguageFromExtension(filePath: string): string | null { const languageMap: Record = { 'js': 'javascript', - 'jsx': 'javascript', + 'jsx': 'jsx', 'ts': 'typescript', - 'tsx': 'typescript', + 'tsx': 'tsx', 'mjs': 'javascript', 'cjs': 'javascript',