Skip to content

Commit bff09e0

Browse files
committed
Resizable left sidebar
Make left sidebar resizable so long URLs can be viewed in their entirety. Fixes #457 Vibe-coded with Cursor
1 parent 7b1299c commit bff09e0

File tree

3 files changed

+90
-28
lines changed

3 files changed

+90
-28
lines changed

client/src/App.tsx

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import React, {
2828
useState,
2929
} from "react";
3030
import { useConnection } from "./lib/hooks/useConnection";
31-
import { useDraggablePane } from "./lib/hooks/useDraggablePane";
31+
import { useDraggablePane, useDraggableSidebar } from "./lib/hooks/useDraggablePane";
3232
import { StdErrNotification } from "./lib/notificationTypes";
3333

3434
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -163,6 +163,7 @@ const App = () => {
163163
const progressTokenRef = useRef(0);
164164

165165
const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300);
166+
const { width: sidebarWidth, isDragging: isSidebarDragging, handleDragStart: handleSidebarDragStart } = useDraggableSidebar(320);
166167

167168
const {
168169
connectionStatus,
@@ -562,32 +563,44 @@ const App = () => {
562563

563564
return (
564565
<div className="flex h-screen bg-background">
565-
<Sidebar
566-
connectionStatus={connectionStatus}
567-
transportType={transportType}
568-
setTransportType={setTransportType}
569-
command={command}
570-
setCommand={setCommand}
571-
args={args}
572-
setArgs={setArgs}
573-
sseUrl={sseUrl}
574-
setSseUrl={setSseUrl}
575-
env={env}
576-
setEnv={setEnv}
577-
config={config}
578-
setConfig={setConfig}
579-
bearerToken={bearerToken}
580-
setBearerToken={setBearerToken}
581-
headerName={headerName}
582-
setHeaderName={setHeaderName}
583-
onConnect={connectMcpServer}
584-
onDisconnect={disconnectMcpServer}
585-
stdErrNotifications={stdErrNotifications}
586-
logLevel={logLevel}
587-
sendLogLevelRequest={sendLogLevelRequest}
588-
loggingSupported={!!serverCapabilities?.logging || false}
589-
clearStdErrNotifications={clearStdErrNotifications}
590-
/>
566+
<div
567+
style={{ width: sidebarWidth, minWidth: 200, maxWidth: 600, transition: isSidebarDragging ? 'none' : 'width 0.15s' }}
568+
className="bg-card border-r border-border flex flex-col h-full relative"
569+
>
570+
<Sidebar
571+
connectionStatus={connectionStatus}
572+
transportType={transportType}
573+
setTransportType={setTransportType}
574+
command={command}
575+
setCommand={setCommand}
576+
args={args}
577+
setArgs={setArgs}
578+
sseUrl={sseUrl}
579+
setSseUrl={setSseUrl}
580+
env={env}
581+
setEnv={setEnv}
582+
config={config}
583+
setConfig={setConfig}
584+
bearerToken={bearerToken}
585+
setBearerToken={setBearerToken}
586+
headerName={headerName}
587+
setHeaderName={setHeaderName}
588+
onConnect={connectMcpServer}
589+
onDisconnect={disconnectMcpServer}
590+
stdErrNotifications={stdErrNotifications}
591+
logLevel={logLevel}
592+
sendLogLevelRequest={sendLogLevelRequest}
593+
loggingSupported={!!serverCapabilities?.logging || false}
594+
clearStdErrNotifications={clearStdErrNotifications}
595+
/>
596+
{/* Drag handle for resizing sidebar */}
597+
<div
598+
onMouseDown={handleSidebarDragStart}
599+
style={{ cursor: 'col-resize', position: 'absolute', top: 0, right: 0, width: 6, height: '100%', zIndex: 10, background: isSidebarDragging ? 'rgba(0,0,0,0.08)' : 'transparent' }}
600+
aria-label="Resize sidebar"
601+
data-testid="sidebar-drag-handle"
602+
/>
603+
</div>
591604
<div className="flex-1 flex flex-col overflow-hidden">
592605
<div className="flex-1 overflow-auto">
593606
{mcpClient ? (

client/src/components/Sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ const Sidebar = ({
214214
}, [generateMCPServerFile, toast, reportError]);
215215

216216
return (
217-
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
217+
<div className="bg-card border-r border-border flex flex-col h-full">
218218
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800">
219219
<div className="flex items-center">
220220
<h1 className="ml-2 text-lg font-semibold">

client/src/lib/hooks/useDraggablePane.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,52 @@ export function useDraggablePane(initialHeight: number) {
5151
handleDragStart,
5252
};
5353
}
54+
55+
export function useDraggableSidebar(initialWidth: number) {
56+
const [width, setWidth] = useState(initialWidth);
57+
const [isDragging, setIsDragging] = useState(false);
58+
const dragStartX = useRef<number>(0);
59+
const dragStartWidth = useRef<number>(0);
60+
61+
const handleDragStart = useCallback(
62+
(e: React.MouseEvent) => {
63+
setIsDragging(true);
64+
dragStartX.current = e.clientX;
65+
dragStartWidth.current = width;
66+
document.body.style.userSelect = "none";
67+
},
68+
[width],
69+
);
70+
71+
const handleDragMove = useCallback(
72+
(e: MouseEvent) => {
73+
if (!isDragging) return;
74+
const deltaX = e.clientX - dragStartX.current;
75+
const newWidth = Math.max(200, Math.min(600, dragStartWidth.current + deltaX));
76+
setWidth(newWidth);
77+
},
78+
[isDragging],
79+
);
80+
81+
const handleDragEnd = useCallback(() => {
82+
setIsDragging(false);
83+
document.body.style.userSelect = "";
84+
}, []);
85+
86+
useEffect(() => {
87+
if (isDragging) {
88+
window.addEventListener("mousemove", handleDragMove);
89+
window.addEventListener("mouseup", handleDragEnd);
90+
return () => {
91+
window.removeEventListener("mousemove", handleDragMove);
92+
window.removeEventListener("mouseup", handleDragEnd);
93+
};
94+
}
95+
}, [isDragging, handleDragMove, handleDragEnd]);
96+
97+
return {
98+
width,
99+
isDragging,
100+
handleDragStart,
101+
};
102+
}

0 commit comments

Comments
 (0)