From 7f147568ea41a454ceca9ffedd287979456dbc9b Mon Sep 17 00:00:00 2001 From: LABCAT Date: Wed, 9 Sep 2026 21:43:18 +1200 Subject: [PATCH] fix(hunk-actions): use native menu items, drop hardcoded label - Render each hunk action as a DropdownMenuItem (Stage/Unstage/Discard hunk N with +/- counts) so Base UI keyboard navigation, highlight, Escape, and close-on-activate apply. Plain div/button rows are gone. - Row labels reuse the already-translated diffView.hunk.stageTitle / unstageTitle / discardTitle strings; no new i18n keys, no hardcoded English. - Disable the file-level actions while a hunk action is in flight for consistency (server serializes per directory regardless). --- packages/ui/src/components/views/DiffView.tsx | 2 +- .../src/components/views/git/HunkActions.tsx | 73 ++++++++++--------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/packages/ui/src/components/views/DiffView.tsx b/packages/ui/src/components/views/DiffView.tsx index aa6b883c..e269b8cc 100644 --- a/packages/ui/src/components/views/DiffView.tsx +++ b/packages/ui/src/components/views/DiffView.tsx @@ -1005,7 +1005,7 @@ const MultiFileDiffEntry = React.memo(({ filePath={file.path} staged={staged} busyAction={fileAction} - disabled={fileAction !== null} + disabled={fileAction !== null || hunkAction !== null} onAction={handleFileAction} /> ) : null} diff --git a/packages/ui/src/components/views/git/HunkActions.tsx b/packages/ui/src/components/views/git/HunkActions.tsx index 045062a9..612d7822 100644 --- a/packages/ui/src/components/views/git/HunkActions.tsx +++ b/packages/ui/src/components/views/git/HunkActions.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import { DropdownMenu, DropdownMenuContent, + DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, @@ -27,7 +28,6 @@ interface HunkActionsProps { } interface HunkSummary { - patch: string; insertions: number; deletions: number; } @@ -41,9 +41,29 @@ const summarizeHunks = (patch: string): HunkSummary[] => if (line.startsWith('+')) insertions += 1; else if (line.startsWith('-')) deletions += 1; } - return { patch: hunkPatch, insertions, deletions }; + return { insertions, deletions }; }); +const HunkCounts = React.memo<{ insertions: number; deletions: number }>(function HunkCounts({ + insertions, + deletions, +}) { + if (insertions === 0 && deletions === 0) return null; + return ( + + {insertions > 0 ? ( + +{insertions} + ) : null} + {insertions > 0 && deletions > 0 ? ( + / + ) : null} + {deletions > 0 ? ( + -{deletions} + ) : null} + + ); +}); + export const HunkActions = React.memo(function HunkActions({ filePath, patch, @@ -84,51 +104,31 @@ export const HunkActions = React.memo(function HunkActions({ const displayIndex = index + 1; const busyPrimary = busyHunk?.index === index && busyHunk.action === primaryAction; const busyDiscard = busyHunk?.index === index && busyHunk.action === 'discard'; - const rowBusy = busyPrimary || busyDiscard; const primaryTitle = staged ? t('diffView.hunk.unstageTitle', { index: displayIndex }) : t('diffView.hunk.stageTitle', { index: displayIndex }); const discardTitle = t('diffView.hunk.discardTitle', { index: displayIndex }); return ( -
- - Hunk {displayIndex} - - {hunk.insertions > 0 ? ( - +{hunk.insertions} - ) : null} - {hunk.insertions > 0 && hunk.deletions > 0 ? ( - / - ) : null} - {hunk.deletions > 0 ? ( - -{hunk.deletions} - ) : null} - - - + {primaryTitle} + + {!staged ? ( - + {discardTitle} + ) : null} -
+ ); })}