From 03e01311e17ff71a1f3c23d0fa2bd0ffeade17bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Seokjin=20Shin=20=28=EC=8B=A0=EC=84=9D=EC=A7=84=29?= Date: Sun, 8 Nov 2026 15:00:00 +0900 Subject: [PATCH] fix(web): keep diff options reachable in narrow panels --- apps/web/src/components/DiffPanel.tsx | 223 ++++++++++-------- apps/web/src/components/DiffPanelShell.tsx | 2 +- .../src/components/diffs/DiffOptionsMenu.tsx | 97 ++++++++ .../pullRequest/PullRequestCodeTab.tsx | 204 +++++++--------- 4 files changed, 315 insertions(+), 211 deletions(-) create mode 100644 apps/web/src/components/diffs/DiffOptionsMenu.tsx diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index e7e6d614ef77f..bfbf820021d35 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -1,3 +1,4 @@ +import { DiffOptionsMenu } from "./diffs/DiffOptionsMenu"; import { RefreshIcon } from "~/components/ui/refresh-icon"; import { useAtomValue } from "@effect/atom-react"; import type { FileDiffContentsLoader, FileDiffMetadata } from "@pierre/diffs"; @@ -1146,132 +1147,160 @@ export default function DiffPanel({ )} - {diffFileKeys.length > 0 && ( - - updateClientSettings({ diffLayout: value })} + files={ + diffFileKeys.length > 0 + ? { + collapsed: allDiffFilesCollapsed, + onToggleCollapsed: toggleDiffFileCollapse, + treeOpen: fileTreeOpen, + onTreeOpenChange: setFileTreeOpen, + } + : null + } + testFiles={ + testFileCount > 0 + ? { + hidden: hideTestFiles, + onHiddenChange: setHideTestFiles, + } + : null + } + /> +
+ {diffFileKeys.length > 0 && ( + + + } + > + + + + {allDiffFilesCollapsed ? "Expand all files" : "Collapse all files"} + + + )} + { + const next = value[0]; + if (next === "stacked" || next === "split") { + updateClientSettings({ diffLayout: next }); } - > - - - - {allDiffFilesCollapsed ? "Expand all files" : "Collapse all files"} - - - )} - { - const next = value[0]; - if (next === "stacked" || next === "split") { - updateClientSettings({ diffLayout: next }); - } - }} - > - - - - - - - - - { - setWordWrap(Boolean(pressed)); - }} - /> - } - > - - - - {wordWrap ? "Disable line wrapping" : "Enable line wrapping"} - - - - { - setDiffIgnoreWhitespace(Boolean(pressed)); - }} - /> - } + }} > - - - - {diffIgnoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"} - - - {testFileCount > 0 && ( + + + + + + + setHideTestFiles(Boolean(pressed))} + pressed={wordWrap} + onPressedChange={(pressed) => { + setWordWrap(Boolean(pressed)); + }} /> } > - + - {hideTestFiles - ? `Show ${testFileCount} hidden test ${testFileCount === 1 ? "file" : "files"}` - : "Hide test files"} + {wordWrap ? "Disable line wrapping" : "Enable line wrapping"} - )} - {diffFileKeys.length > 0 && ( setFileTreeOpen(Boolean(pressed))} + pressed={diffIgnoreWhitespace} + onPressedChange={(pressed) => { + setDiffIgnoreWhitespace(Boolean(pressed)); + }} /> } > - + - {fileTreeOpen ? "Hide file tree" : "Show file tree"} + {diffIgnoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"} - )} + {testFileCount > 0 && ( + + setHideTestFiles(Boolean(pressed))} + /> + } + > + + + + {hideTestFiles + ? `Show ${testFileCount} hidden test ${testFileCount === 1 ? "file" : "files"}` + : "Hide test files"} + + + )} + {diffFileKeys.length > 0 && ( + + setFileTreeOpen(Boolean(pressed))} + /> + } + > + + + + {fileTreeOpen ? "Hide file tree" : "Show file tree"} + + + )} +
); diff --git a/apps/web/src/components/DiffPanelShell.tsx b/apps/web/src/components/DiffPanelShell.tsx index 456f70516b23b..38e0097d03aad 100644 --- a/apps/web/src/components/DiffPanelShell.tsx +++ b/apps/web/src/components/DiffPanelShell.tsx @@ -10,7 +10,7 @@ export type DiffPanelMode = "inline" | "sheet" | "sidebar" | "embedded"; function getDiffPanelHeaderRowClassName(mode: DiffPanelMode) { const shouldUseDragRegion = isElectron && mode !== "sheet" && mode !== "embedded"; return cn( - "flex items-center justify-between gap-2", + "@container/diff-toolbar flex items-center justify-between gap-2", mode === "embedded" ? "px-2" : "px-4", shouldUseDragRegion ? "drag-region h-[var(--workspace-topbar-height)] border-b border-border wco:pr-(--workspace-native-controls-inset)" diff --git a/apps/web/src/components/diffs/DiffOptionsMenu.tsx b/apps/web/src/components/diffs/DiffOptionsMenu.tsx new file mode 100644 index 0000000000000..d3e69fee29618 --- /dev/null +++ b/apps/web/src/components/diffs/DiffOptionsMenu.tsx @@ -0,0 +1,97 @@ +import { Settings2Icon } from "lucide-react"; + +import { Button } from "../ui/button"; +import { + Menu, + MenuCheckboxItem, + MenuItem, + MenuPopup, + MenuRadioGroup, + MenuRadioItem, + MenuSeparator, + MenuTrigger, +} from "../ui/menu"; +import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; + +export function DiffOptionsMenu({ + wordWrap, + onWordWrapChange, + ignoreWhitespace, + onIgnoreWhitespaceChange, + diffLayout, + onDiffLayoutChange, + files, + testFiles, +}: { + wordWrap: boolean; + onWordWrapChange: (value: boolean) => void; + ignoreWhitespace: boolean; + onIgnoreWhitespaceChange: (value: boolean) => void; + diffLayout: "stacked" | "split"; + onDiffLayoutChange: (value: "stacked" | "split") => void; + files: { + collapsed: boolean; + onToggleCollapsed: () => void; + treeOpen: boolean; + onTreeOpenChange: (value: boolean) => void; + } | null; + testFiles: { hidden: boolean; onHiddenChange: (value: boolean) => void } | null; +}) { + return ( +
+ + + } + aria-label="Diff options" + /> + } + > + + + Diff options + + + + Wrap lines + + + Hide whitespace changes + + + { + if (value === "stacked" || value === "split") onDiffLayoutChange(value); + }} + > + + Stacked + + + Split + + + {files || testFiles ? : null} + {files ? ( + + {files.collapsed ? "Expand all files" : "Collapse all files"} + + ) : null} + {testFiles ? ( + + Hide test files + + ) : null} + {files ? ( + + Show file tree + + ) : null} + + +
+ ); +} diff --git a/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx b/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx index a456c84bf5320..78724f350ead8 100644 --- a/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx +++ b/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx @@ -25,7 +25,6 @@ import { MessageSquareOffIcon, PilcrowIcon, Rows3Icon, - Settings2Icon, TextWrapIcon, TriangleAlertIcon, } from "lucide-react"; @@ -81,6 +80,7 @@ import { pullRequestEnvironment } from "~/state/pullRequests"; import { useEnvironmentQuery } from "~/state/query"; import { useAtomCommand } from "~/state/use-atom-command"; +import { DiffOptionsMenu } from "../diffs/DiffOptionsMenu"; import { DiffPanelLoadingState } from "../DiffPanelShell"; import { useDiffWorkerPool } from "../DiffWorkerPoolProvider"; import { DiffCommentAnnotation } from "../diffs/DiffCommentAnnotation"; @@ -96,12 +96,10 @@ import { Checkbox } from "../ui/checkbox"; import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible"; import { DropdownMenu, - DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, - DropdownMenuSeparator, DropdownMenuTrigger, } from "../ui/menu"; import { toastManager } from "../ui/toast"; @@ -1094,7 +1092,7 @@ function PullRequestCodeTab({ setSelectedLines(null); }; const toolbar = ( -
+
{orderedCommits.length > 0 ? ( @@ -1235,53 +1233,33 @@ function PullRequestCodeTab({
- - - } - className="@lg:hidden" - aria-label="Diff options" - /> - } - > - - - Diff options - - - changeIgnoreWhitespace(checked)} - > - Hide whitespace changes - - setWordWrap(checked)} - > - Wrap lines - - - { - if (value === "stacked" || value === "split") { - updateClientSettings({ diffLayout: value }); + updateClientSettings({ diffLayout: value })} + files={ + fileKeys.length > 0 + ? { + collapsed: allFilesCollapsed, + onToggleCollapsed: toggleAllFiles, + treeOpen: fileTreeOpen, + onTreeOpenChange: setFileTreeOpen, } - }} - > - - Stacked - - - Split - - - - -
+ : null + } + testFiles={ + testFileCount > 0 + ? { + hidden: hideTestFiles, + onHiddenChange: setHideTestFiles, + } + : null + } + /> +
-
- {fileKeys.length > 0 ? ( - - - } - > - - - - {allFilesCollapsed ? "Expand all files" : "Collapse all files"} - - - ) : null} - {testFileCount > 0 ? ( - - setHideTestFiles(Boolean(pressed))} - /> - } - > - - - - {hideTestFiles ? "Show test files" : "Hide test files"} - - - ) : null} - {fileKeys.length > 0 ? ( - - setFileTreeOpen(Boolean(pressed))} + {fileKeys.length > 0 ? ( + + + } + > + - } - > - - - - {fileTreeOpen ? "Hide file tree" : "Show file tree"} - - - ) : null} + + + {allFilesCollapsed ? "Expand all files" : "Collapse all files"} + + + ) : null} + {testFileCount > 0 ? ( + + setHideTestFiles(Boolean(pressed))} + /> + } + > + + + + {hideTestFiles ? "Show test files" : "Hide test files"} + + + ) : null} + {fileKeys.length > 0 ? ( + + setFileTreeOpen(Boolean(pressed))} + /> + } + > + + + + {fileTreeOpen ? "Hide file tree" : "Show file tree"} + + + ) : null} +
);