Merge pull request #459 from msabramo/resizable-left-sidebar

Resizable left sidebar
This commit is contained in:
Ola Hungerford 2025-06-04 19:58:59 -07:00 • committed by GitHub
commit a2d2cf6f3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 114 additions and 28 deletions

View file

@ -30,7 +30,10 @@ import React, {
useState, useState,
} from "react"; } from "react";
import { useConnection } from "./lib/hooks/useConnection"; import { useConnection } from "./lib/hooks/useConnection";
import { useDraggablePane } from "./lib/hooks/useDraggablePane"; import {
useDraggablePane,
useDraggableSidebar,
} from "./lib/hooks/useDraggablePane";
import { StdErrNotification } from "./lib/notificationTypes"; import { StdErrNotification } from "./lib/notificationTypes";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@ -154,6 +157,11 @@ const App = () => {
const progressTokenRef = useRef(0); const progressTokenRef = useRef(0);
const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300); const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300);
const {
width: sidebarWidth,
isDragging: isSidebarDragging,
handleDragStart: handleSidebarDragStart,
} = useDraggableSidebar(320);
const { const {
connectionStatus, connectionStatus,
@ -607,32 +615,58 @@ const App = () => {
return ( return (
<div className="flex h-screen bg-background"> <div className="flex h-screen bg-background">
<Sidebar <div
connectionStatus={connectionStatus} style={{
transportType={transportType} width: sidebarWidth,
setTransportType={setTransportType} minWidth: 200,
command={command} maxWidth: 600,
setCommand={setCommand} transition: isSidebarDragging ? "none" : "width 0.15s",
args={args} }}
setArgs={setArgs} className="bg-card border-r border-border flex flex-col h-full relative"
sseUrl={sseUrl} >
setSseUrl={setSseUrl} <Sidebar
env={env} connectionStatus={connectionStatus}
setEnv={setEnv} transportType={transportType}
config={config} setTransportType={setTransportType}
setConfig={setConfig} command={command}
bearerToken={bearerToken} setCommand={setCommand}
setBearerToken={setBearerToken} args={args}
headerName={headerName} setArgs={setArgs}
setHeaderName={setHeaderName} sseUrl={sseUrl}
onConnect={connectMcpServer} setSseUrl={setSseUrl}
onDisconnect={disconnectMcpServer} env={env}
stdErrNotifications={stdErrNotifications} setEnv={setEnv}
logLevel={logLevel} config={config}
sendLogLevelRequest={sendLogLevelRequest} setConfig={setConfig}
loggingSupported={!!serverCapabilities?.logging || false} bearerToken={bearerToken}
clearStdErrNotifications={clearStdErrNotifications} setBearerToken={setBearerToken}
/> headerName={headerName}
setHeaderName={setHeaderName}
onConnect={connectMcpServer}
onDisconnect={disconnectMcpServer}
stdErrNotifications={stdErrNotifications}
logLevel={logLevel}
sendLogLevelRequest={sendLogLevelRequest}
loggingSupported={!!serverCapabilities?.logging || false}
clearStdErrNotifications={clearStdErrNotifications}
/>
{/* Drag handle for resizing sidebar */}
<div
onMouseDown={handleSidebarDragStart}
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",
}}
aria-label="Resize sidebar"
data-testid="sidebar-drag-handle"
/>
</div>
<div className="flex-1 flex flex-col overflow-hidden"> <div className="flex-1 flex flex-col overflow-hidden">
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{mcpClient ? ( {mcpClient ? (

View file

@ -214,7 +214,7 @@ const Sidebar = ({
}, [generateMCPServerFile, toast, reportError]); }, [generateMCPServerFile, toast, reportError]);
return ( return (
<div className="w-80 bg-card border-r border-border flex flex-col h-full"> <div className="bg-card border-r border-border flex flex-col h-full">
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-border"> <div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-border">
<div className="flex items-center"> <div className="flex items-center">
<h1 className="ml-2 text-lg font-semibold"> <h1 className="ml-2 text-lg font-semibold">

View file

@ -51,3 +51,55 @@ export function useDraggablePane(initialHeight: number) {
handleDragStart, handleDragStart,
}; };
} }
export function useDraggableSidebar(initialWidth: number) {
const [width, setWidth] = useState(initialWidth);
const [isDragging, setIsDragging] = useState(false);
const dragStartX = useRef<number>(0);
const dragStartWidth = useRef<number>(0);
const handleDragStart = useCallback(
(e: React.MouseEvent) => {
setIsDragging(true);
dragStartX.current = e.clientX;
dragStartWidth.current = width;
document.body.style.userSelect = "none";
},
[width],
);
const handleDragMove = useCallback(
(e: MouseEvent) => {
if (!isDragging) return;
const deltaX = e.clientX - dragStartX.current;
const newWidth = Math.max(
200,
Math.min(600, dragStartWidth.current + deltaX),
);
setWidth(newWidth);
},
[isDragging],
);
const handleDragEnd = useCallback(() => {
setIsDragging(false);
document.body.style.userSelect = "";
}, []);
useEffect(() => {
if (isDragging) {
window.addEventListener("mousemove", handleDragMove);
window.addEventListener("mouseup", handleDragEnd);
return () => {
window.removeEventListener("mousemove", handleDragMove);
window.removeEventListener("mouseup", handleDragEnd);
};
}
}, [isDragging, handleDragMove, handleDragEnd]);
return {
width,
isDragging,
handleDragStart,
};
}