From 8637ff96fac732258a0232a16e42ab526d7421aa Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 22 Jun 2026 02:26:25 -0700 Subject: [PATCH] feat(dashboard): Files view two-pane when expanded, single-panel stack in dock DockFilesView uses a container query: narrow (dock) keeps the single-panel tree<->viewer navigation stack; wide (pop-out expand modal) shows the file tree and viewer side by side with an empty 'Select a file' state. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../app/components/DockFilesView.css | 83 ++++++++++++++++++- .../app/components/DockFilesView.tsx | 64 +++++++++----- 2 files changed, 123 insertions(+), 24 deletions(-) diff --git a/packages/dashboard/app/components/DockFilesView.css b/packages/dashboard/app/components/DockFilesView.css index 57115e8863..11c4012815 100644 --- a/packages/dashboard/app/components/DockFilesView.css +++ b/packages/dashboard/app/components/DockFilesView.css @@ -2,18 +2,52 @@ FNXC:RightDockFiles 2026-06-22-00:00: The inline Files viewer fills the right-dock body and scrolls internally so the read-only FileEditor never overflows the dock. The header is a compact bar: BACK on the left, a truncating file name in the middle, POP-OUT on the right. + +FNXC:Files 2026-06-22-00:00: +Responsive single-panel vs two-pane layout driven entirely by a CSS container query. +The root is a query container (container-type: inline-size, container-name: dock-files). Both the tree pane (.dock-files-view__tree) and the viewer pane (.dock-files-view__viewer) are always rendered in the DOM; CSS decides visibility per container width. +- NARROW (default, in the dock): single-panel stack. The tree fills the root. When a file is selected (root [data-selected="true"]) the viewer pane covers the stack and the tree is hidden; BACK returns to the tree. +- WIDE (>=720px, the RightDockExpandModal pop-out): two-pane side-by-side. Tree pinned left (clamped width, scrollable), viewer flex:1 on the right (scrollable). Both always visible regardless of data-selected; BACK is hidden because the tree never disappears. */ .dock-files-view { display: flex; flex-direction: column; min-height: 0; height: 100%; + /* FNXC:Files — establish the query container so child panes can respond to the dock vs expand-modal width. */ + container-type: inline-size; + container-name: dock-files; } -.dock-files-view--viewer { +/* FNXC:Files — NARROW default: tree fills the root as the single panel. */ +.dock-files-view__tree { + display: flex; + flex-direction: column; + min-height: 0; + flex: 1 1 auto; overflow: hidden; } +/* +FNXC:Files — NARROW default: viewer is the stacked second panel. +Hidden until a file is selected; when selected it overlays the tree as the single visible panel (the tree is hidden below). +*/ +.dock-files-view__viewer { + display: none; + flex-direction: column; + min-height: 0; + flex: 1 1 auto; + overflow: hidden; +} + +.dock-files-view[data-selected="true"] .dock-files-view__tree { + display: none; +} + +.dock-files-view[data-selected="true"] .dock-files-view__viewer { + display: flex; +} + .dock-files-viewer__header { display: flex; align-items: center; @@ -61,3 +95,50 @@ The header is a compact bar: BACK on the left, a truncating file name in the mid .dock-files-viewer__status--error { color: var(--danger, var(--text)); } + +/* FNXC:Files — empty-state placeholder shown in the wide right pane until a file is selected. */ +.dock-files-viewer__empty { + display: flex; + align-items: center; + justify-content: center; + flex: 1 1 auto; + text-align: center; +} + +/* +FNXC:Files 2026-06-22-00:00: +WIDE container (>=720px): two-pane side-by-side. Activated when DockFilesView is rendered in the wide RightDockExpandModal. +Both panes are always visible; data-selected no longer toggles visibility here. +*/ +@container dock-files (min-width: 720px) { + .dock-files-view { + flex-direction: row; + } + + /* Tree pinned left: clamped, scrollable, with a divider against the viewer. */ + .dock-files-view__tree { + display: flex; + flex: 0 0 clamp(220px, 32%, 360px); + min-width: 0; + overflow: auto; + border-right: 1px solid var(--border); + } + + /* Viewer fills the remaining width; always visible (empty-state until a file is selected). */ + .dock-files-view__viewer, + .dock-files-view[data-selected="true"] .dock-files-view__viewer { + display: flex; + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + } + + .dock-files-view[data-selected="true"] .dock-files-view__tree { + display: flex; + } + + /* BACK is meaningless when the tree is always visible. */ + .dock-files-view__viewer .dock-files-viewer__back { + display: none; + } +} diff --git a/packages/dashboard/app/components/DockFilesView.tsx b/packages/dashboard/app/components/DockFilesView.tsx index 56cb6eaa95..353339d043 100644 --- a/packages/dashboard/app/components/DockFilesView.tsx +++ b/packages/dashboard/app/components/DockFilesView.tsx @@ -19,6 +19,11 @@ FNXC:RightDockFiles 2026-06-22-00:00: The right-dock Files tool opens a clicked file INLINE inside the dock as a read-only viewer instead of immediately launching the resizable/movable FileBrowserModal. Clicking a file in the tree sets local `selectedFile` (it does NOT call `openFile`); the inline viewer reuses the read-only `FileEditor` so markdown previews and syntax highlighting match the rest of the app. The viewer header carries a BACK button (clears `selectedFile`, returning to the tree) and a POP-OUT button that calls `openFile(path, { workspace: "project" })` to escalate to the existing resizable/movable modal. This preserves the modal path; it is now opt-in via pop-out rather than the default click behavior. + +FNXC:Files 2026-06-22-00:00: +Responsive layout via CSS container query. The root is a query container (container-type: inline-size, container-name: dock-files). BOTH the tree pane and the viewer pane are always rendered in the DOM; CSS decides what is visible per container width. +- NARROW (dock, default): single-panel stack. Tree shows alone; selecting a file reveals the viewer pane which overlays the stack, and the BACK button returns to the tree. This preserves the prior navigation-stack UX. +- WIDE (>=720px, the RightDockExpandModal pop-out): two-pane side-by-side. Left pane = tree (always visible, clamped width, scrollable). Right pane = viewer (flex:1, scrollable) showing an empty-state until a file is selected. Selecting a file updates the right pane without hiding the tree, so the BACK button is hidden when wide. */ export function DockFilesView({ projectId, openFile }: DockFilesViewProps) { const { t } = useTranslation("app"); @@ -66,11 +71,36 @@ export function DockFilesView({ projectId, openFile }: DockFilesViewProps) { if (selectedFile) openFile?.(selectedFile, { workspace: "project" }); }, [openFile, selectedFile]); - if (selectedFile) { - const fileName = selectedFile.split("/").pop() || selectedFile; - return ( -
+ const fileName = selectedFile ? selectedFile.split("/").pop() || selectedFile : ""; + + // FNXC:Files 2026-06-22-00:00: + // `data-selected` on the root lets the container query distinguish "no file selected" (narrow: viewer pane hidden so only the tree shows) from "file selected" (narrow: viewer pane covers the stack). When wide both panes are always visible regardless of this flag. + return ( +
+ {/* FNXC:Files — left pane: tree. Always in the DOM; CSS hides it only in the narrow single-panel stack when a file is selected. */} +
+ setSelectedFile(path)} + onNavigate={setPath} + loading={loading} + error={error} + onRetry={refresh} + workspace="project" + onRefresh={refresh} + projectId={projectId} + /> +
+ + {/* FNXC:Files — right pane: viewer. Always in the DOM; CSS shows it side-by-side when wide, or as the single-panel stack when narrow + a file is selected. */} +
+ {/* FNXC:Files — BACK only matters in the narrow stack (returns to the tree); CSS hides it when wide since the tree is always visible. */} - {fileName} + {fileName}
- {contentLoading ? ( + {!selectedFile ? ( +
+ {t("fileViewer.selectAFile", "Select a file")} +
+ ) : contentLoading ? (
{t("common.loading", "Loading...")}
) : contentError ? (
{contentError}
@@ -103,23 +138,6 @@ export function DockFilesView({ projectId, openFile }: DockFilesViewProps) { )}
- ); - } - - return ( -
- setSelectedFile(path)} - onNavigate={setPath} - loading={loading} - error={error} - onRetry={refresh} - workspace="project" - onRefresh={refresh} - projectId={projectId} - />
); }