Skip to content

Commit a2d2cf6

Browse files
authored
Merge pull request #459 from msabramo/resizable-left-sidebar
Resizable left sidebar
2 parents 798bc1b + 13d3159 commit a2d2cf6

File tree

3 files changed

+114
-28
lines changed

3 files changed

+114
-28
lines changed

client/src/App.tsx

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import React, {
3030
useState,
3131
} from "react";
3232
import { useConnection } from "./lib/hooks/useConnection";
33-
import { useDraggablePane } from "./lib/hooks/useDraggablePane";
33+
import {
34+
useDraggablePane,
35+
useDraggableSidebar,
36+
} from "./lib/hooks/useDraggablePane";
3437
import { StdErrNotification } from "./lib/notificationTypes";
3538

3639
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -154,6 +157,11 @@ const App = () => {
154157
const progressTokenRef = useRef(0);
155158

156159
const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300);
160+
const {
161+
width: sidebarWidth,
162+
isDragging: isSidebarDragging,
163+
handleDragStart: handleSidebarDragStart,
164+
} = useDraggableSidebar(320);
157165

158166
const {
159167
connectionStatus,
@@ -607,32 +615,58 @@ const App = () => {
607615

608616
return (
609617
<div className="flex h-screen bg-background">
610-
<Sidebar
611-
connectionStatus={connectionStatus}
612-
transportType={transportType}
613-
setTransportType={setTransportType}
614-
command={command}
615-
setCommand={setCommand}
616-
args={args}
617-
setArgs={setArgs}
618-
sseUrl={sseUrl}
619-
setSseUrl={setSseUrl}
620-
env={env}
621-
setEnv={setEnv}
622-
config={config}
623-
setConfig={setConfig}
624-
bearerToken={bearerToken}
625-
setBearerToken={setBearerToken}
626-
headerName={headerName}
627-
setHeaderName={setHeaderName}
628-
onConnect={connectMcpServer}
629-
onDisconnect={disconnectMcpServer}
630-
stdErrNotifications={stdErrNotifications}
631-
logLevel={logLevel}
632-
sendLogLevelRequest={sendLogLevelRequest}
633-
loggingSupported={!!serverCapabilities?.logging || false}
634-
clearStdErrNotifications={clearStdErrNotifications}
635-
/>
618+
<div
619+
style={{
620+
width: sidebarWidth,
621+
minWidth: 200,
622+
maxWidth: 600,
623+
transition: isSidebarDragging ? "none" : "width 0.15s",
624+
}}
625+
className="bg-card border-r border-border flex flex-col h-full relative"
626+
>
627+
<Sidebar
628+
connectionStatus={connectionStatus}
629+
transportType={transportType}
630+
setTransportType={setTransportType}
631+
command={command}
632+
setCommand={setCommand}
633+
args={args}
634+
setArgs={setArgs}
635+
sseUrl={sseUrl}
636+
setSseUrl={setSseUrl}
637+
env={env}
638+
setEnv={setEnv}
639+
config={config}
640+
setConfig={setConfig}
641+
bearerToken={bearerToken}
642+
setBearerToken={setBearerToken}
643+
headerName={headerName}
644+
setHeaderName={setHeaderName}
645+
onConnect={connectMcpServer}
646+
onDisconnect={disconnectMcpServer}
647+
stdErrNotifications={stdErrNotifications}
648+
logLevel={logLevel}
649+
sendLogLevelRequest={sendLogLevelRequest}
650+
loggingSupported={!!serverCapabilities?.logging || false}
651+
clearStdErrNotifications={clearStdErrNotifications}
652+
/>
653+
{/* Drag handle for resizing sidebar */}
654+
<div
655+
onMouseDown={handleSidebarDragStart}
656+
style={{
657+
cursor: "col-resize",
658+
position: "absolute",
659+
top: 0,
660+
right: 0,
661+
width: 6,
662+
height: "100%",
663+
zIndex: 10,
664+
background: isSidebarDragging ? "rgba(0,0,0,0.08)" : "transparent",
665+
}}
666+
aria-label="Resize sidebar"
667+
data-testid="sidebar-drag-handle"
668+
/>
669+
</div>
636670
<div className="flex-1 flex flex-col overflow-hidden">
637671
<div className="flex-1 overflow-auto">
638672
{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-border">
219219
<div className="flex items-center">
220220
<h1 className="ml-2 text-lg font-semibold">

client/src/lib/hooks/useDraggablePane.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,55 @@ 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(
76+
200,
77+
Math.min(600, dragStartWidth.current + deltaX),
78+
);
79+
setWidth(newWidth);
80+
},
81+
[isDragging],
82+
);
83+
84+
const handleDragEnd = useCallback(() => {
85+
setIsDragging(false);
86+
document.body.style.userSelect = "";
87+
}, []);
88+
89+
useEffect(() => {
90+
if (isDragging) {
91+
window.addEventListener("mousemove", handleDragMove);
92+
window.addEventListener("mouseup", handleDragEnd);
93+
return () => {
94+
window.removeEventListener("mousemove", handleDragMove);
95+
window.removeEventListener("mouseup", handleDragEnd);
96+
};
97+
}
98+
}, [isDragging, handleDragMove, handleDragEnd]);
99+
100+
return {
101+
width,
102+
isDragging,
103+
handleDragStart,
104+
};
105+
}

0 commit comments

Comments
 (0)