add mobile support
This commit is contained in:
parent
616410aa36
commit
f6c8b2a299
@ -12,6 +12,26 @@
|
|||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-toggle {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-toggle:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-brand {
|
.header-brand {
|
||||||
@ -29,11 +49,13 @@
|
|||||||
.header-brand h1 {
|
.header-brand h1 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-status {
|
.header-status {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
@ -61,6 +83,7 @@
|
|||||||
height: 8px;
|
height: 8px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: currentColor;
|
background: currentColor;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge.connected .status-dot {
|
.status-badge.connected .status-dot {
|
||||||
@ -82,6 +105,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-overlay {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@ -97,9 +125,47 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive - Tablet */
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.content-panels {
|
.content-panels {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Responsive - Mobile */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header {
|
||||||
|
padding: 10px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-toggle {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-brand h1 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-overlay {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panels {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useState, useCallback } from 'react'
|
import { useState, useCallback } from 'react'
|
||||||
|
import { Menu, X } from 'lucide-react'
|
||||||
import { Sidebar } from './components/Sidebar'
|
import { Sidebar } from './components/Sidebar'
|
||||||
import { ToolsPanel } from './components/ToolsPanel'
|
import { ToolsPanel } from './components/ToolsPanel'
|
||||||
import { ResultsPane } from './components/ResultsPane'
|
import { ResultsPane } from './components/ResultsPane'
|
||||||
@ -50,6 +51,7 @@ function App() {
|
|||||||
const [toolResult, setToolResult] = useState<ToolResult | null>(null)
|
const [toolResult, setToolResult] = useState<ToolResult | null>(null)
|
||||||
const [isExecuting, setIsExecuting] = useState(false)
|
const [isExecuting, setIsExecuting] = useState(false)
|
||||||
const [lastExecution, setLastExecution] = useState<{ toolName: string; params: Record<string, unknown> } | null>(null)
|
const [lastExecution, setLastExecution] = useState<{ toolName: string; params: Record<string, unknown> } | null>(null)
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||||
|
|
||||||
const handleConnect = useCallback(async () => {
|
const handleConnect = useCallback(async () => {
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
@ -93,6 +95,13 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<header className="header">
|
<header className="header">
|
||||||
|
<button
|
||||||
|
className="mobile-menu-toggle"
|
||||||
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||||
|
aria-label={sidebarOpen ? 'Close menu' : 'Open menu'}
|
||||||
|
>
|
||||||
|
{sidebarOpen ? <X size={20} /> : <Menu size={20} />}
|
||||||
|
</button>
|
||||||
<div className="header-brand" onClick={() => window.location.reload()}>
|
<div className="header-brand" onClick={() => window.location.reload()}>
|
||||||
<h1>MCP UI Inspector</h1>
|
<h1>MCP UI Inspector</h1>
|
||||||
</div>
|
</div>
|
||||||
@ -100,18 +109,24 @@ function App() {
|
|||||||
{isConnected ? (
|
{isConnected ? (
|
||||||
<span className="status-badge connected">
|
<span className="status-badge connected">
|
||||||
<span className="status-dot"></span>
|
<span className="status-dot"></span>
|
||||||
Connected
|
<span className="status-text">Connected</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="status-badge disconnected">
|
<span className="status-badge disconnected">
|
||||||
<span className="status-dot"></span>
|
<span className="status-dot"></span>
|
||||||
Disconnected
|
<span className="status-text">Disconnected</span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="main-layout">
|
<div className="main-layout">
|
||||||
|
{sidebarOpen && (
|
||||||
|
<div
|
||||||
|
className="sidebar-overlay"
|
||||||
|
onClick={() => setSidebarOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Sidebar
|
<Sidebar
|
||||||
serverUrl={serverUrl}
|
serverUrl={serverUrl}
|
||||||
onServerUrlChange={setServerUrl}
|
onServerUrlChange={setServerUrl}
|
||||||
@ -121,6 +136,8 @@ function App() {
|
|||||||
isStateless={isStateless}
|
isStateless={isStateless}
|
||||||
onConnect={handleConnect}
|
onConnect={handleConnect}
|
||||||
error={error}
|
error={error}
|
||||||
|
isOpen={sidebarOpen}
|
||||||
|
onClose={() => setSidebarOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<main className="content">
|
<main className="content">
|
||||||
|
|||||||
@ -59,3 +59,17 @@
|
|||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.btn {
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost {
|
||||||
|
min-height: 36px;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 400px;
|
min-width: 300px;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,3 +193,53 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile styles */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.results-pane {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-pane .panel-header {
|
||||||
|
padding: 0 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-tabs {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-actions {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-loading,
|
||||||
|
.results-empty,
|
||||||
|
.results-error {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-error pre {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-output {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-output pre {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -8,6 +8,25 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile sidebar */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 100;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
width: 85%;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.open {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-section {
|
.sidebar-section {
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,8 @@ interface SidebarProps {
|
|||||||
isStateless: boolean
|
isStateless: boolean
|
||||||
onConnect: () => void
|
onConnect: () => void
|
||||||
error: string | null
|
error: string | null
|
||||||
|
isOpen?: boolean
|
||||||
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Sidebar({
|
export function Sidebar({
|
||||||
@ -21,10 +23,18 @@ export function Sidebar({
|
|||||||
sessionId,
|
sessionId,
|
||||||
isStateless,
|
isStateless,
|
||||||
onConnect,
|
onConnect,
|
||||||
error
|
error,
|
||||||
|
isOpen,
|
||||||
|
onClose
|
||||||
}: SidebarProps) {
|
}: SidebarProps) {
|
||||||
|
const handleConnect = () => {
|
||||||
|
onConnect()
|
||||||
|
// Close sidebar on mobile after connecting
|
||||||
|
if (onClose) onClose()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="sidebar">
|
<aside className={`sidebar ${isOpen ? 'open' : ''}`}>
|
||||||
<div className="sidebar-section">
|
<div className="sidebar-section">
|
||||||
<div className="sidebar-section-header">
|
<div className="sidebar-section-header">
|
||||||
<Server size={16} />
|
<Server size={16} />
|
||||||
@ -46,7 +56,7 @@ export function Sidebar({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant={isConnected ? 'default' : 'primary'}
|
variant={isConnected ? 'default' : 'primary'}
|
||||||
onClick={onConnect}
|
onClick={handleConnect}
|
||||||
loading={isConnecting}
|
loading={isConnecting}
|
||||||
loadingText="Connecting..."
|
loadingText="Connecting..."
|
||||||
icon={isConnected ? <PlugZap size={16} /> : <Plug size={16} />}
|
icon={isConnected ? <PlugZap size={16} /> : <Plug size={16} />}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-right: 1px solid var(--border-color);
|
border-right: 1px solid var(--border-color);
|
||||||
min-width: 400px;
|
min-width: 300px;
|
||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,3 +285,55 @@
|
|||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile styles */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.tools-panel {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-layout {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-list {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-height: 200px;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-detail {
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-detail-header {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-params {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-actions {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-field input,
|
||||||
|
.param-field select {
|
||||||
|
font-size: 16px; /* Prevents zoom on iOS */
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-editor {
|
||||||
|
font-size: 14px;
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -85,3 +85,28 @@ code,
|
|||||||
background: var(--accent-blue);
|
background: var(--accent-blue);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Touch-friendly improvements */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
min-height: 44px; /* Minimum touch target size */
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-size: 16px; /* Prevents zoom on iOS */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safe area insets for notched devices */
|
||||||
|
@supports (padding: env(safe-area-inset-bottom)) {
|
||||||
|
body {
|
||||||
|
padding-left: env(safe-area-inset-left);
|
||||||
|
padding-right: env(safe-area-inset-right);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user