Skip to content

Commit 81e019d

Browse files
authored
zoom issue: replace resizable with a simple (#1097)
## Description Replaced the `react-resizable-panels` library with a custom resize handle implementation for the context panel. This change removes the dependency on the external library and implements a simpler, more controlled resizing mechanism. It helps to keep work area a const width, preventing any issues with `fitView`. ## Type of Change - [x] Bugfix - [x] Improvement - [x] Cleanup/Refactor ## Checklist - [x] I have tested this does not break current pipelines / runs functionality - [ ] I have tested the changes on staging ## Test Instructions [Screen Recording 2025-10-15 at 12.16.34 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/user-attachments/thumbnails/b9d4c2b3-0bdb-4264-931e-b6a9dcb1fac7.mov" />](https://app.graphite.dev/user-attachments/video/b9d4c2b3-0bdb-4264-931e-b6a9dcb1fac7.mov) 1. Open the Pipeline Editor and verify that the context panel can be resized by dragging the handle 2. Confirm that the resize handle has proper constraints (min-width: 200px, max-width: 600px) 3. Check that the Pipeline Run page also has the same resizing functionality 4. Verify that the UI appearance and behavior remains consistent with the previous implementation
1 parent 0961a84 commit 81e019d

File tree

8 files changed

+165
-136
lines changed

8 files changed

+165
-136
lines changed

package-lock.json

Lines changed: 0 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@
8787
"react-dom": "^19.2.0",
8888
"react-error-boundary": "^6.0.0",
8989
"react-icons": "^5.5.0",
90-
"react-resizable-panels": "^3.0.6",
9190
"react-scan": "^0.4.3",
9291
"react-toastify": "^11.0.5",
9392
"tailwind-merge": "^3.3.1",

src/components/Editor/PipelineEditor.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ import {
1010
FlowSidebar,
1111
} from "@/components/shared/ReactFlow";
1212
import { UndoRedo } from "@/components/shared/UndoRedo";
13-
import {
14-
ResizableHandle,
15-
ResizablePanel,
16-
ResizablePanelGroup,
17-
} from "@/components/ui/resizable";
13+
import { BlockStack, InlineStack } from "@/components/ui/layout";
1814
import { Spinner } from "@/components/ui/spinner";
1915
import { AutoSaveProvider } from "@/providers/AutoSaveProvider";
2016
import { ComponentLibraryProvider } from "@/providers/ComponentLibraryProvider";
@@ -70,8 +66,9 @@ const PipelineEditor = () => {
7066
<ForcedSearchProvider>
7167
<ComponentLibraryProvider>
7268
<FlowSidebar />
73-
<ResizablePanelGroup direction="horizontal">
74-
<ResizablePanel>
69+
70+
<InlineStack className="w-full h-full" align="start">
71+
<BlockStack className="flex-1 h-full">
7572
<div className="reactflow-wrapper relative">
7673
<FlowCanvas {...flowConfig}>
7774
<MiniMap position="bottom-left" pannable />
@@ -88,10 +85,9 @@ const PipelineEditor = () => {
8885
<UndoRedo />
8986
</div>
9087
</div>
91-
</ResizablePanel>
92-
<ResizableHandle />
88+
</BlockStack>
9389
<CollapsibleContextPanel />
94-
</ResizablePanelGroup>
90+
</InlineStack>
9591
</ComponentLibraryProvider>
9692
</ForcedSearchProvider>
9793
</ContextPanelProvider>

src/components/PipelineRun/PipelineRunPage.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ import { Background, MiniMap, type ReactFlowProps } from "@xyflow/react";
22
import { useCallback, useState } from "react";
33

44
import { FlowCanvas, FlowControls } from "@/components/shared/ReactFlow";
5-
import {
6-
ResizableHandle,
7-
ResizablePanel,
8-
ResizablePanelGroup,
9-
} from "@/components/ui/resizable";
5+
import { BlockStack, InlineStack } from "@/components/ui/layout";
106
import { ComponentLibraryProvider } from "@/providers/ComponentLibraryProvider";
117
import { ContextPanelProvider } from "@/providers/ContextPanelProvider";
128

@@ -39,9 +35,9 @@ const PipelineRunPage = ({ pipelineRunId }: { pipelineRunId: string }) => {
3935
<RootExecutionStatusProvider pipelineRunId={pipelineRunId}>
4036
<ContextPanelProvider defaultContent={<RunDetails />}>
4137
<ComponentLibraryProvider>
42-
<ResizablePanelGroup direction="horizontal">
43-
<ResizablePanel>
44-
<div className="reactflow-wrapper h-full w-full">
38+
<InlineStack className="w-full h-full" align="start">
39+
<BlockStack className="flex-1 h-full">
40+
<div className="reactflow-wrapper relative">
4541
<FlowCanvas {...flowConfig} readOnly>
4642
<MiniMap position="bottom-left" pannable />
4743
<FlowControls
@@ -53,10 +49,9 @@ const PipelineRunPage = ({ pipelineRunId }: { pipelineRunId: string }) => {
5349
<Background gap={GRID_SIZE} className="bg-slate-50!" />
5450
</FlowCanvas>
5551
</div>
56-
</ResizablePanel>
57-
<ResizableHandle />
52+
</BlockStack>
5853
<CollapsibleContextPanel />
59-
</ResizablePanelGroup>
54+
</InlineStack>
6055
</ComponentLibraryProvider>
6156
</ContextPanelProvider>
6257
</RootExecutionStatusProvider>
Lines changed: 44 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,60 @@
1-
import { PanelRightClose, PanelRightOpen } from "lucide-react";
2-
import { useRef, useState } from "react";
3-
import type { ImperativePanelHandle } from "react-resizable-panels";
1+
import { useState } from "react";
42

53
import { Button } from "@/components/ui/button";
6-
import { ResizablePanel } from "@/components/ui/resizable";
4+
import {
5+
Collapsible,
6+
CollapsibleContent,
7+
CollapsibleTrigger,
8+
} from "@/components/ui/collapsible";
9+
import { Icon } from "@/components/ui/icon";
10+
import { VerticalResizeHandle } from "@/components/ui/resize-handle";
711
import { cn } from "@/lib/utils";
812

913
import { ContextPanel } from "./ContextPanel";
1014

15+
const MIN_WIDTH = 200;
16+
const MAX_WIDTH = 600;
17+
const DEFAULT_WIDTH = 400;
18+
19+
interface CollapsibleContextPanelProps {
20+
defaultOpen?: boolean;
21+
}
22+
1123
export function CollapsibleContextPanel({
12-
defaultSize = 30,
13-
minSize = 15,
14-
maxSize = 50,
15-
collapsedSize = 3,
16-
}: {
17-
defaultSize?: number;
18-
minSize?: number;
19-
maxSize?: number;
20-
collapsedSize?: number;
21-
}) {
22-
const resizablePanelRef = useRef<ImperativePanelHandle>(null);
23-
const [collapsed, setCollapsed] = useState(false);
24+
defaultOpen = true,
25+
}: CollapsibleContextPanelProps = {}) {
26+
const [open, setOpen] = useState(defaultOpen);
2427

2528
return (
26-
<ResizablePanel
27-
ref={resizablePanelRef}
28-
collapsible
29-
defaultSize={defaultSize}
30-
minSize={minSize}
31-
maxSize={maxSize}
32-
collapsedSize={collapsedSize}
33-
onCollapse={() => setCollapsed(true)}
34-
onExpand={() => setCollapsed(false)}
35-
>
36-
{collapsed && (
37-
<div className="relative">
38-
<Button
39-
className="absolute top-2 right-2"
40-
variant="ghost"
41-
onClick={() => {
42-
resizablePanelRef.current?.expand();
43-
resizablePanelRef.current?.resize(30);
44-
}}
45-
>
46-
<PanelRightOpen className="h-6 w-6" />
47-
</Button>
48-
</div>
49-
)}
50-
<div className={cn("h-full relative", collapsed && "hidden")}>
29+
<Collapsible open={open} onOpenChange={setOpen} className="h-full">
30+
<CollapsibleTrigger asChild>
5131
<Button
52-
className="absolute top-2 right-2"
5332
variant="ghost"
54-
onClick={() => {
55-
resizablePanelRef.current?.collapse();
56-
}}
33+
size="icon"
34+
className={
35+
"absolute top-[95px] z-0 transition-all duration-300 bg-white rounded-r-md shadow-md p-0.5 pr-1 -translate-x-8"
36+
}
37+
aria-label={open ? "Collapse context panel" : "Expand context panel"}
5738
>
58-
<PanelRightClose className="h-6 w-6" />
39+
<Icon name={open ? "PanelRightClose" : "PanelRightOpen"} />
5940
</Button>
60-
<ContextPanel />
41+
</CollapsibleTrigger>
42+
<div
43+
className={cn("relative h-full flex", !open && "!w-0")}
44+
style={{ width: `${DEFAULT_WIDTH}px` }}
45+
>
46+
{open && (
47+
<VerticalResizeHandle
48+
side="left"
49+
minWidth={MIN_WIDTH}
50+
maxWidth={MAX_WIDTH}
51+
/>
52+
)}
53+
54+
<CollapsibleContent className="flex-1 h-full overflow-hidden">
55+
<ContextPanel />
56+
</CollapsibleContent>
6157
</div>
62-
</ResizablePanel>
58+
</Collapsible>
6359
);
6460
}

src/components/shared/ReactFlow/FlowCanvas/SubgraphBreadcrumbs/SubgraphBreadcrumbs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const SubgraphBreadcrumbs = () => {
2626
align="space-between"
2727
blockAlign="center"
2828
gap="0"
29-
className="px-4 py-2 bg-gray-50 border-b w-full"
29+
className="px-4 py-2 bg-gray-50 border-b w-full z-1"
3030
>
3131
<Breadcrumb>
3232
<BreadcrumbList>

src/components/ui/resizable.tsx

Lines changed: 0 additions & 54 deletions
This file was deleted.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { useCallback, useRef } from "react";
2+
3+
import { Icon } from "@/components/ui/icon";
4+
import { cn } from "@/lib/utils";
5+
6+
interface ResizeHandleProps {
7+
minWidth?: number;
8+
maxWidth?: number;
9+
side?: "left" | "right";
10+
}
11+
12+
export const VerticalResizeHandle = ({
13+
minWidth = 200,
14+
maxWidth = 600,
15+
side = "left",
16+
}: ResizeHandleProps) => {
17+
const parentElementRef = useRef<HTMLElement | null>(null);
18+
const resizingRef = useRef<{ startX: number; startWidth: number } | null>(
19+
null,
20+
);
21+
22+
const captureParentElement = useCallback((element: HTMLElement | null) => {
23+
parentElementRef.current = element?.parentElement ?? null;
24+
}, []);
25+
26+
const handleMouseMove = useCallback(
27+
(e: MouseEvent) => {
28+
if (!resizingRef.current || !parentElementRef.current) return;
29+
30+
const deltaX = e.clientX - resizingRef.current.startX;
31+
32+
let newWidth: number;
33+
if (side === "left") {
34+
newWidth = resizingRef.current.startWidth - deltaX;
35+
} else {
36+
newWidth = resizingRef.current.startWidth + deltaX;
37+
}
38+
39+
const constrainedWidth = Math.max(minWidth, Math.min(maxWidth, newWidth));
40+
41+
parentElementRef.current.style.width = `${constrainedWidth}px`;
42+
},
43+
[minWidth, maxWidth, side],
44+
);
45+
46+
const handleMouseUp = useCallback(() => {
47+
resizingRef.current = null;
48+
49+
document.removeEventListener("mousemove", handleMouseMove);
50+
document.removeEventListener("mouseup", handleMouseUp);
51+
}, [handleMouseMove]);
52+
53+
const handleMouseDown = useCallback(
54+
(e: React.MouseEvent) => {
55+
e.preventDefault();
56+
e.stopPropagation();
57+
58+
if (!parentElementRef.current) return;
59+
60+
resizingRef.current = {
61+
startX: e.clientX,
62+
startWidth: parentElementRef.current.offsetWidth,
63+
};
64+
65+
document.addEventListener("mousemove", handleMouseMove);
66+
document.addEventListener("mouseup", handleMouseUp);
67+
},
68+
[handleMouseMove, handleMouseUp],
69+
);
70+
71+
return (
72+
<div
73+
className={cn(
74+
"absolute top-0 h-full cursor-col-resize z-10 group",
75+
side === "left" ? "left-0" : "right-0",
76+
"w-2 -mx-0.5",
77+
)}
78+
ref={captureParentElement}
79+
onMouseDown={handleMouseDown}
80+
>
81+
{/* Thin visible line with shadow */}
82+
<div
83+
className={cn(
84+
"absolute top-0 h-full w-px bg-border transition-all pointer-events-none",
85+
"group-hover:bg-primary/30",
86+
87+
side === "left"
88+
? "left-1/2 -translate-x-1/2 shadow-[2px_0_8px_rgba(0,0,0,0.08)]"
89+
: "right-1/2 translate-x-1/2 shadow-[-2px_0_8px_rgba(0,0,0,0.08)]",
90+
)}
91+
/>
92+
93+
<Icon
94+
name="GripVertical"
95+
className={cn(
96+
"absolute top-1/2 -translate-y-1/2 pointer-events-none",
97+
"bg-background/90 backdrop-blur-sm rounded-xs p-1",
98+
"text-muted-foreground opacity-0 group-hover:opacity-100",
99+
"transition-opacity duration-200",
100+
101+
side === "left"
102+
? "left-1/2 -translate-x-1/2"
103+
: "right-1/2 translate-x-1/2",
104+
)}
105+
/>
106+
</div>
107+
);
108+
};

0 commit comments

Comments
 (0)