Resizable left sidebar

Make left sidebar resizable so long URLs can be viewed in their
entirety.

Fixes https://github.com/modelcontextprotocol/inspector/issues/457

Vibe-coded with Cursor
This commit is contained in:
Marc Abramowitz 2025-05-27 17:28:04 -07:00
commit bff09e0409
3 changed files with 90 additions and 28 deletions

View file

@ -28,7 +28,7 @@ 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";
@ -163,6 +163,7 @@ 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,
@ -562,32 +563,44 @@ const App = () => {
return ( return (
<div className="flex h-screen bg-background"> <div className="flex h-screen bg-background">
<Sidebar <div
connectionStatus={connectionStatus} style={{ width: sidebarWidth, minWidth: 200, maxWidth: 600, transition: isSidebarDragging ? 'none' : 'width 0.15s' }}
transportType={transportType} className="bg-card border-r border-border flex flex-col h-full relative"
setTransportType={setTransportType} >
command={command} <Sidebar
setCommand={setCommand} connectionStatus={connectionStatus}
args={args} transportType={transportType}
setArgs={setArgs} setTransportType={setTransportType}
sseUrl={sseUrl} command={command}
setSseUrl={setSseUrl} setCommand={setCommand}
env={env} args={args}
setEnv={setEnv} setArgs={setArgs}
config={config} sseUrl={sseUrl}
setConfig={setConfig} setSseUrl={setSseUrl}
bearerToken={bearerToken} env={env}
setBearerToken={setBearerToken} setEnv={setEnv}
headerName={headerName} config={config}
setHeaderName={setHeaderName} setConfig={setConfig}
onConnect={connectMcpServer} bearerToken={bearerToken}
onDisconnect={disconnectMcpServer} setBearerToken={setBearerToken}
stdErrNotifications={stdErrNotifications} headerName={headerName}
logLevel={logLevel} setHeaderName={setHeaderName}
sendLogLevelRequest={sendLogLevelRequest} onConnect={connectMcpServer}
loggingSupported={!!serverCapabilities?.logging || false} onDisconnect={disconnectMcpServer}
clearStdErrNotifications={clearStdErrNotifications} 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-gray-800"> <div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800">
<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,52 @@ 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,
};
}