From db0b76e9eeb6294caa44f2cc83b6f0358042ed09 Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 9 Jun 2026 17:03:11 +0000 Subject: [PATCH] Stabilize file viewer browser pane Co-Authored-By: Paperclip --- .../components/FileViewerSheet.copy.test.tsx | 7 + ui/src/components/FileViewerSheet.tsx | 64 ++++- .../components/WorkspaceFileBrowser.test.tsx | 42 ++- ui/src/components/WorkspaceFileBrowser.tsx | 264 ++++++------------ 4 files changed, 186 insertions(+), 191 deletions(-) diff --git a/ui/src/components/FileViewerSheet.copy.test.tsx b/ui/src/components/FileViewerSheet.copy.test.tsx index 3ef5f8e6e94..6dc4873bbd8 100644 --- a/ui/src/components/FileViewerSheet.copy.test.tsx +++ b/ui/src/components/FileViewerSheet.copy.test.tsx @@ -143,4 +143,11 @@ describe("FileViewerSheet copy actions", () => { expect(writeText).toHaveBeenCalledWith(window.location.href); expect(document.body.textContent).toContain("Copied link"); }); + + it("renders a keyboard-addressable file tree resize separator", () => { + renderSheet(); + const separator = document.body.querySelector('[role="separator"][aria-label="Resize file tree"]'); + expect(separator).not.toBeNull(); + expect(separator?.getAttribute("aria-valuenow")).toBe("288"); + }); }); diff --git a/ui/src/components/FileViewerSheet.tsx b/ui/src/components/FileViewerSheet.tsx index d5eb97a2faa..59c1a9eaef6 100644 --- a/ui/src/components/FileViewerSheet.tsx +++ b/ui/src/components/FileViewerSheet.tsx @@ -4,6 +4,8 @@ import { useMemo, useRef, useState, + type KeyboardEvent as ReactKeyboardEvent, + type PointerEvent as ReactPointerEvent, type ReactNode, } from "react"; import { useQuery, type UseQueryResult } from "@tanstack/react-query"; @@ -49,6 +51,8 @@ import type { const FILE_VIEWER_LABELLED_BY_ID = "paperclip-file-viewer-title"; const FILE_VIEWER_DESCRIBED_BY_ID = "paperclip-file-viewer-description"; +const MIN_FILE_TREE_WIDTH = 220; +const MAX_FILE_TREE_WIDTH = 520; interface FileViewerErrorShape { status: number; @@ -430,7 +434,9 @@ export function FileViewerSheet({ const [copyingField, setCopyingField] = useState<"content" | "link" | null>(null); const [copyFeedback, setCopyFeedback] = useState(""); const [announcement, setAnnouncement] = useState(""); + const [fileTreeWidth, setFileTreeWidth] = useState(288); const copyFeedbackTimerRef = useRef(null); + const resizeCleanupRef = useRef<(() => void) | null>(null); useEffect(() => { if (!state) { @@ -485,6 +491,7 @@ export function FileViewerSheet({ useEffect(() => () => { if (copyFeedbackTimerRef.current) window.clearTimeout(copyFeedbackTimerRef.current); + resizeCleanupRef.current?.(); }, []); const handleOpenChange = useCallback( @@ -573,6 +580,37 @@ export function FileViewerSheet({ if (canPreview) void contentQuery.refetch(); }, [canPreview, contentQuery, resolveQuery]); + const handleResizeStart = useCallback((event: ReactPointerEvent) => { + event.preventDefault(); + resizeCleanupRef.current?.(); + const startX = event.clientX; + const startWidth = fileTreeWidth; + const handlePointerMove = (moveEvent: PointerEvent) => { + const nextWidth = Math.min( + MAX_FILE_TREE_WIDTH, + Math.max(MIN_FILE_TREE_WIDTH, startWidth + moveEvent.clientX - startX), + ); + setFileTreeWidth(nextWidth); + }; + const cleanup = () => { + document.removeEventListener("pointermove", handlePointerMove); + document.removeEventListener("pointerup", cleanup); + resizeCleanupRef.current = null; + }; + resizeCleanupRef.current = cleanup; + document.addEventListener("pointermove", handlePointerMove); + document.addEventListener("pointerup", cleanup, { once: true }); + }, [fileTreeWidth]); + + const handleResizeKeyDown = useCallback((event: ReactKeyboardEvent) => { + if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return; + event.preventDefault(); + setFileTreeWidth((current) => { + const delta = event.key === "ArrowLeft" ? -24 : 24; + return Math.min(MAX_FILE_TREE_WIDTH, Math.max(MIN_FILE_TREE_WIDTH, current + delta)); + }); + }, []); + const title = state ? basename(state.path) : "Browse workspace"; const description = state ? middleTruncatePath(state.path) @@ -692,19 +730,39 @@ export function FileViewerSheet({ {state ? (
-