From b5f29cfda5727a48192f12159d4a9b6990605d53 Mon Sep 17 00:00:00 2001 From: ShortArrow Date: Thu, 15 Oct 2026 16:49:06 +0900 Subject: [PATCH] feat(ui): make "Diff all" a toggle that closes the diff it opened Pressed again it closes the diff, so opening and closing no longer means reaching for the pane's close button (#19). --- CHANGELOG.md | 6 ++++ docs/FEATURES.jp.md | 2 +- docs/FEATURES.md | 5 ++-- frontend/e2e/graph.spec.ts | 29 +++++++++++++++++++ frontend/src/CommitDetailPanel.tsx | 21 +++++++++----- frontend/src/LabeledButton.tsx | 4 ++- frontend/src/RepoCard.tsx | 7 ++++- .../src/stories/CommitDetailPanel.stories.tsx | 3 +- 8 files changed, 64 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5d5f84..270d3e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- "Diff all" is a toggle: pressed while every file's diff is open, it closes + the diff and brings the graph back + ([#19](https://github.com/ShortArrow/gitreant/issues/19)). + ### Fixed - Commit diffs and untracked files follow the `diff` attribute of diff --git a/docs/FEATURES.jp.md b/docs/FEATURES.jp.md index cd66cf7..e61b051 100644 --- a/docs/FEATURES.jp.md +++ b/docs/FEATURES.jp.md @@ -27,7 +27,7 @@ gitreant でできることを分野別にまとめる。起動方法とコマ - **詳細ペイン。** コミットをクリックすると、メッセージ全文・作者・日時・署名の状態・親コミット・変更ファイルの一覧を表示する。作者名は GitHub のプロフィールへのリンクで、メールアドレスや親コミットの ID はクリックでコピーできる。 - **変更ファイル。** ファイルはフラット表示とディレクトリのツリー表示を切り替えられ、増減した行数を添える。右クリックで相対パスと絶対パスをコピーしたり、OS の既定のアプリで開いたりできる。 -- **差分ペイン。** ファイル(または「Diff all」)をクリックすると差分を開く。インラインと左右分割を切り替えられ、行内で変わった箇所を強調する。 +- **差分ペイン。** ファイルをクリックすると差分を開く。インラインと左右分割を切り替えられ、行内で変わった箇所を強調する。「Diff all」は全ファイルの差分をまとめて開き、もう一度押すと閉じる。 - **改行コードの変更。** 改行コード(LF と CRLF)だけが変わったファイルには「改行コードのみ」のラベルを付ける。差分では変わった行末に印を出し、見出しに変換の向き(`LF → CRLF` など)を示す。詳細は [ADR 0029](adr/0029-line-ending-changes.jp.md)。 - **行の選択。** 差分の行番号をクリックして行を選び(Shift+クリックで範囲)、その行のコピーや GitHub のパーマリンクのコピーができる。 - **ペインの幅。** ドロワーと詳細ペインの幅はドラッグで変えられ、次回も保たれる。 diff --git a/docs/FEATURES.md b/docs/FEATURES.md index dbd46c6..4de1205 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -57,8 +57,9 @@ see the [README](../README.md); design decisions live in [adr/](adr/). - **Changed files.** Files show as a flat list or a directory tree, with added and removed line counts. A file's context menu copies its relative or absolute path, or opens it with the OS default handler. -- **The diff pane.** Clicking a file (or "Diff all") opens its diff, inline - or side by side, with the changed part of each line highlighted. +- **The diff pane.** Clicking a file opens its diff, inline or side by + side, with the changed part of each line highlighted. "Diff all" opens + every file's diff at once, and pressed again closes it. - **Line-ending changes.** A file whose only change is its line endings (LF against CRLF) is labelled "Line endings only"; its diff marks the changed line ends and the header names the direction (`LF → CRLF` and so diff --git a/frontend/e2e/graph.spec.ts b/frontend/e2e/graph.spec.ts index 86a959e..76e3ccf 100644 --- a/frontend/e2e/graph.spec.ts +++ b/frontend/e2e/graph.spec.ts @@ -577,6 +577,35 @@ test.describe.serial("gitreant UI", () => { await expect(page.getByTestId("graph")).toBeVisible(); }); + test("the diff-all button toggles the whole commit diff", async ({ + page, + }) => { + await page.locator('[data-repo-name="repoA"]').click(); + await page + .getByTestId("commit-row") + .filter({ hasText: "main-2" }) + .click(); + const diffAll = page.getByTestId("diff-all"); + const sections = page.getByTestId("diff-file-section"); + await expect(diffAll).toHaveAttribute("aria-pressed", "false"); + + // From one file's diff, the button switches to every file's diff. + await page + .getByTestId("detail-file") + .filter({ hasText: "one.ts" }) + .click(); + await expect(sections).toHaveCount(1); + await diffAll.click(); + await expect(sections).toHaveCount(2); + await expect(diffAll).toHaveAttribute("aria-pressed", "true"); + + // Pressed again, it closes the diff and brings the graph back. + await diffAll.click(); + await expect(page.getByTestId("diff-pane")).toHaveCount(0); + await expect(page.getByTestId("graph")).toBeVisible(); + await expect(diffAll).toHaveAttribute("aria-pressed", "false"); + }); + test("fetch button pulls new remote commits into the graph", async ({ page, }) => { diff --git a/frontend/src/CommitDetailPanel.tsx b/frontend/src/CommitDetailPanel.tsx index 1e31add..4695f16 100644 --- a/frontend/src/CommitDetailPanel.tsx +++ b/frontend/src/CommitDetailPanel.tsx @@ -105,7 +105,8 @@ export function CommitDetailPanel({ signatureKey, onSelectFile, onOpenFile, - onShowAllDiffs, + allDiffsShown, + onToggleAllDiffs, onClose, }: { /** null while loading. */ @@ -121,8 +122,10 @@ export function CommitDetailPanel({ onSelectFile: (path: string) => void; /** Open a changed file with the OS default handler. */ onOpenFile: (path: string) => void; - /** Called when every changed file's diff should open at once. */ - onShowAllDiffs: () => void; + /** Every changed file's diff is open. */ + allDiffsShown: boolean; + /** Opens every changed file's diff at once, or closes it when open. */ + onToggleAllDiffs: () => void; onClose: () => void; }) { const t = useT(); @@ -199,7 +202,8 @@ export function CommitDetailPanel({ e.preventDefault(); setFileMenu({ x: e.clientX, y: e.clientY, path }); }} - onShowAllDiffs={onShowAllDiffs} + allDiffsShown={allDiffsShown} + onToggleAllDiffs={onToggleAllDiffs} /> )} @@ -244,7 +248,8 @@ function DetailBody({ onChangeView, onSelectFile, onFileMenu, - onShowAllDiffs, + allDiffsShown, + onToggleAllDiffs, }: { detail: CommitDetail; verified?: boolean; @@ -255,7 +260,8 @@ function DetailBody({ onChangeView: (view: FilesView) => void; onSelectFile: (path: string) => void; onFileMenu: (e: React.MouseEvent, path: string) => void; - onShowAllDiffs: () => void; + allDiffsShown: boolean; + onToggleAllDiffs: () => void; }) { const t = useT(); const time = useTime(); @@ -397,7 +403,8 @@ function DetailBody({ label={t("diffAll")} testId="diff-all" className="diff-all-btn" - onClick={onShowAllDiffs} + active={allDiffsShown} + onClick={onToggleAllDiffs} /> )}
diff --git a/frontend/src/LabeledButton.tsx b/frontend/src/LabeledButton.tsx index 2067673..934b6ea 100644 --- a/frontend/src/LabeledButton.tsx +++ b/frontend/src/LabeledButton.tsx @@ -19,7 +19,8 @@ export function LabeledButton({ label: string; testId: string; className?: string; - /** Adds the "active" class (toggle groups). */ + /** Marks a toggle as pressed: the "active" class and `aria-pressed`. + * Left undefined, the button is not a toggle. */ active?: boolean; disabled?: boolean; type?: "button" | "submit"; @@ -35,6 +36,7 @@ export function LabeledButton({ data-testid={testId} title={label} aria-label={label} + aria-pressed={active} disabled={disabled} type={type} onClick={onClick} diff --git a/frontend/src/RepoCard.tsx b/frontend/src/RepoCard.tsx index c8573e1..c8d5ccf 100644 --- a/frontend/src/RepoCard.tsx +++ b/frontend/src/RepoCard.tsx @@ -834,7 +834,12 @@ export function RepoCard({ onOpenFile={(path) => { revealPath(repo.id, path).catch(() => {}); }} - onShowAllDiffs={() => setDiffTarget({ kind: "all" })} + allDiffsShown={diffTarget?.kind === "all"} + onToggleAllDiffs={() => + setDiffTarget((target) => + target?.kind === "all" ? null : { kind: "all" }, + ) + } onClose={() => { setSelected(null); setDiffTarget(null); diff --git a/frontend/src/stories/CommitDetailPanel.stories.tsx b/frontend/src/stories/CommitDetailPanel.stories.tsx index 7ff7226..eecb2d0 100644 --- a/frontend/src/stories/CommitDetailPanel.stories.tsx +++ b/frontend/src/stories/CommitDetailPanel.stories.tsx @@ -16,7 +16,8 @@ const meta: Meta = { onClose: () => {}, onSelectFile: () => {}, onOpenFile: () => {}, - onShowAllDiffs: () => {}, + allDiffsShown: false, + onToggleAllDiffs: () => {}, }, }; export default meta;