fix(FN-XXX): improve git manager diff layout
This commit is contained in:
5
.changeset/improve-git-manager-diff-pane.md
Normal file
5
.changeset/improve-git-manager-diff-pane.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Improve the Git Manager diff layout and file path truncation in dashboard modals.
|
||||
@@ -220,7 +220,7 @@ export function CommitDiffTab({ commitSha, mergeDetails }: CommitDiffTabProps) {
|
||||
{getStatusLabel(file.status)}
|
||||
</span>
|
||||
<span className="changes-file-path" title={file.path}>
|
||||
{file.path}
|
||||
<bdo dir="ltr">{file.path}</bdo>
|
||||
</span>
|
||||
<span
|
||||
className="changes-file-stat"
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { Task } from "@fusion/core";
|
||||
import { getErrorMessage } from "@fusion/core";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import { useConfirm } from "../hooks/useConfirm";
|
||||
import { truncateMiddle } from "../utils/truncatePath";
|
||||
import { getPathBasename } from "../utils/pathDisplay";
|
||||
import { useModalResizePersist } from "../hooks/useModalResizePersist";
|
||||
import { useOverlayDismiss } from "../hooks/useOverlayDismiss";
|
||||
@@ -1054,6 +1053,8 @@ function ChangesPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="gm-changes-split">
|
||||
<div className="gm-changes-lists">
|
||||
{/* Unstaged Changes */}
|
||||
<div className="gm-file-section">
|
||||
<div className="gm-file-section-header">
|
||||
@@ -1116,7 +1117,7 @@ function ChangesPanel({
|
||||
/>
|
||||
</label>
|
||||
<FileStatusIcon status={f.status} />
|
||||
<span className="gm-file-name" title={f.file}>{truncateMiddle(f.file, 40)}</span>
|
||||
<span className="gm-file-name" title={f.file}><bdo dir="ltr">{f.file}</bdo></span>
|
||||
<FileStatusBadge status={f.status} />
|
||||
<button
|
||||
className="gm-icon-btn"
|
||||
@@ -1188,7 +1189,7 @@ function ChangesPanel({
|
||||
/>
|
||||
</label>
|
||||
<FileStatusIcon status={f.status} />
|
||||
<span className="gm-file-name" title={f.file}>{truncateMiddle(f.file, 40)}</span>
|
||||
<span className="gm-file-name" title={f.file}><bdo dir="ltr">{f.file}</bdo></span>
|
||||
<FileStatusBadge status={f.status} />
|
||||
<button
|
||||
className="gm-icon-btn"
|
||||
@@ -1207,33 +1208,43 @@ function ChangesPanel({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Diff Viewer */}
|
||||
{(selectedDiffTarget || loadingChangeDiff || changeDiff || changeDiffError) && (
|
||||
<div className="gm-diff-section">
|
||||
{selectedDiffTarget && (
|
||||
<div className="gm-diff-target">
|
||||
<FileDiff size={14} />
|
||||
<span>{selectedDiffTarget.staged ? "Staged" : "Unstaged"} diff: </span>
|
||||
<code>{selectedDiffTarget.file}</code>
|
||||
</div>
|
||||
)}
|
||||
{loadingChangeDiff && (
|
||||
<div className="gm-diff-loading">
|
||||
<Loader2 size={16} className="spin" />
|
||||
Loading diff...
|
||||
</div>
|
||||
)}
|
||||
{changeDiffError && !loadingChangeDiff && (
|
||||
<div className="gm-diff-error">{changeDiffError}</div>
|
||||
)}
|
||||
{changeDiff && !loadingChangeDiff && (
|
||||
<div className="gm-diff-viewer">
|
||||
{changeDiff.stat && <pre className="gm-diff-stat">{changeDiff.stat}</pre>}
|
||||
<pre className="gm-diff-patch">{changeDiff.patch}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Diff Viewer (right pane on desktop, stacked below on mobile) */}
|
||||
<div className="gm-changes-diff">
|
||||
{(selectedDiffTarget || loadingChangeDiff || changeDiff || changeDiffError) ? (
|
||||
<div className="gm-diff-section">
|
||||
{selectedDiffTarget && (
|
||||
<div className="gm-diff-target">
|
||||
<FileDiff size={14} />
|
||||
<span>{selectedDiffTarget.staged ? "Staged" : "Unstaged"} diff: </span>
|
||||
<code>{selectedDiffTarget.file}</code>
|
||||
</div>
|
||||
)}
|
||||
{loadingChangeDiff && (
|
||||
<div className="gm-diff-loading">
|
||||
<Loader2 size={16} className="spin" />
|
||||
Loading diff...
|
||||
</div>
|
||||
)}
|
||||
{changeDiffError && !loadingChangeDiff && (
|
||||
<div className="gm-diff-error">{changeDiffError}</div>
|
||||
)}
|
||||
{changeDiff && !loadingChangeDiff && (
|
||||
<div className="gm-diff-viewer">
|
||||
{changeDiff.stat && <pre className="gm-diff-stat">{changeDiff.stat}</pre>}
|
||||
<pre className="gm-diff-patch">{changeDiff.patch}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="gm-diff-empty">
|
||||
<FileDiff size={20} />
|
||||
<span>Select a file to view its diff</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* /gm-changes-split */}
|
||||
|
||||
{/* Commit Form */}
|
||||
<form className="gm-commit-form" onSubmit={onCommit}>
|
||||
|
||||
@@ -2035,6 +2035,86 @@
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
/* ── Split-pane: file lists on left, diff viewer on right ─────────────────
|
||||
* Lazygit-style layout so the file lists stay visible while reading a diff.
|
||||
* `.gm-content` (the panel scroll container) keeps overflow-y: auto, and the
|
||||
* left column uses `position: sticky` so it pins to the top as the right
|
||||
* column scrolls. Mobile (≤720px) collapses to a single column with the
|
||||
* lists rendered first and the diff below — sticky disabled because there's
|
||||
* no horizontal room for a second pane and a sticky list would eat the
|
||||
* already-tight viewport.
|
||||
*/
|
||||
.gm-changes-split {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(280px, 360px) 1fr;
|
||||
gap: var(--space-md);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.gm-changes-lists {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
align-self: start;
|
||||
/* Cap the sticky column so its internal lists never push past the viewport
|
||||
* — without this, two long lists at max-height 200px each + headers can
|
||||
* exceed the available scroll-port height and the column visually clips. */
|
||||
max-height: calc(100dvh - var(--overlay-padding-top, 10vh) - 280px);
|
||||
overflow-y: auto;
|
||||
/* Don't let the sticky scrollbar overlap the diff column. */
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.gm-changes-diff {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
/* Allow children (long file paths, code lines) to shrink below intrinsic
|
||||
* width — required for `overflow: hidden` / `word-break` to work in the
|
||||
* diff viewer. Without this the right column would push the layout wider
|
||||
* than the modal. */
|
||||
min-width: 0;
|
||||
/* Match the lists' top so empty/loading/diff states align with the list
|
||||
* column visually. */
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/* Empty placeholder shown when no file is selected. Gives the right pane
|
||||
* structure on first open instead of a blank gap. */
|
||||
.gm-diff-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--text-dim);
|
||||
font-size: 13px;
|
||||
min-height: 140px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.gm-changes-split {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.gm-changes-lists {
|
||||
position: static;
|
||||
max-height: none;
|
||||
overflow-y: visible;
|
||||
padding-right: 0;
|
||||
}
|
||||
.gm-diff-empty {
|
||||
/* Don't take a full screen of empty state on mobile — collapse to a
|
||||
* compact hint when no file is selected. */
|
||||
min-height: 0;
|
||||
padding: var(--space-md);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── File Section ── */
|
||||
|
||||
.gm-file-section {
|
||||
@@ -2118,11 +2198,18 @@
|
||||
accent-color: var(--todo);
|
||||
}
|
||||
|
||||
/* Show as much of the path as fits, truncating the LEFT side (parent dirs)
|
||||
* with an ellipsis so the filename — the most identifying part — stays
|
||||
* visible. `direction: rtl` flips the truncation side; the JSX wraps the
|
||||
* value in `<bdo dir="ltr">` so the displayed text still reads naturally
|
||||
* left-to-right. */
|
||||
.gm-file-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
direction: rtl;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--text);
|
||||
@@ -3555,6 +3642,8 @@
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
direction: rtl;
|
||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
|
||||
@@ -1087,11 +1087,16 @@
|
||||
margin-left: calc(16px + var(--space-sm)); /* align with text after icon + gap */
|
||||
}
|
||||
|
||||
/* Truncate parent dirs from the LEFT so the filename stays visible. JSX
|
||||
* wraps the path in `<bdo dir="ltr">` to preserve readable left-to-right
|
||||
* display while `direction: rtl` flips the ellipsis to the start. */
|
||||
.changes-file-path {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
direction: rtl;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user