Refactor ServerCard with active server logic, rename StatusIndicator, and polish UI panels
- Rename StatusIndicator to ServerStatusIndicator across all files - ServerCard: add activeServer/onSetActiveServer props for coordinated connection flow — when a card's toggle is switched on, it sets itself as the active server, shows "Connecting" status, and dims all other cards (opacity + pointerEvents none + inert for full keyboard blocking) - ServerListScreen: manage activeServer state internally, pass to cards; switch to full-width layout with 3-column grid and scrollable card area - ServerCard: move "Test Client Features" dropdown above ContentViewer with min-height row for consistent card heights - ToolResultPanel: move Clear button to top-right header row - ToolDetailPanel: right-align Execute Tool button, size md, not full-width - ListChangedIndicator: bump Refresh button to size sm - ToolsActive story: expand to 30 tools with titles - UnconnectedView stories: all servers disconnected, remove Connecting story Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ad2f3b7efd
commit
8e73bc6ffb
5 changed files with 295 additions and 117 deletions
|
|
@ -7,6 +7,7 @@ const meta: Meta<typeof ServerCard> = {
|
|||
component: ServerCard,
|
||||
args: {
|
||||
onToggleConnection: fn(),
|
||||
onSetConnectingServer: fn(),
|
||||
onServerInfo: fn(),
|
||||
onSettings: fn(),
|
||||
onEdit: fn(),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect } from "react";
|
||||
import { Badge, Button, Card, Group, Menu, Stack, Text } from "@mantine/core";
|
||||
import { StatusIndicator } from "../../elements/StatusIndicator/StatusIndicator";
|
||||
import { ServerStatusIndicator } from "../../elements/ServerStatusIndicator/ServerStatusIndicator";
|
||||
import { TransportBadge } from "../../elements/TransportBadge/TransportBadge";
|
||||
import { ConnectionToggle } from "../../elements/ConnectionToggle/ConnectionToggle";
|
||||
import { ContentViewer } from "../../elements/ContentViewer/ContentViewer";
|
||||
|
|
@ -15,6 +16,8 @@ export interface ServerCardProps {
|
|||
retryCount?: number;
|
||||
error?: { message: string; details?: string };
|
||||
canTestClientFeatures: boolean;
|
||||
activeServer?: string;
|
||||
onSetActiveServer?: (name: string | undefined) => void;
|
||||
onToggleConnection: (connect: boolean) => void;
|
||||
onServerInfo: () => void;
|
||||
onSettings: () => void;
|
||||
|
|
@ -71,6 +74,8 @@ export function ServerCard({
|
|||
retryCount,
|
||||
error,
|
||||
canTestClientFeatures,
|
||||
activeServer,
|
||||
onSetActiveServer,
|
||||
onToggleConnection,
|
||||
onServerInfo,
|
||||
onSettings,
|
||||
|
|
@ -83,11 +88,35 @@ export function ServerCard({
|
|||
onConfigureRoots,
|
||||
compact = false,
|
||||
}: ServerCardProps) {
|
||||
const isThisConnecting = activeServer === name;
|
||||
const isDimmed = activeServer !== undefined && activeServer !== name;
|
||||
const isConnected = status === "connected";
|
||||
const isConnecting = status === "connecting";
|
||||
const isConnecting = status === "connecting" || isThisConnecting;
|
||||
const displayStatus = isThisConnecting ? "connecting" : status;
|
||||
|
||||
useEffect(() => {
|
||||
if (isThisConnecting) {
|
||||
onToggleConnection(true);
|
||||
}
|
||||
}, [isThisConnecting, onToggleConnection]);
|
||||
|
||||
function handleToggle(connect: boolean) {
|
||||
if (connect && !activeServer) {
|
||||
onSetActiveServer?.(name);
|
||||
} else if (!connect && activeServer && activeServer === name) {
|
||||
onSetActiveServer?.(undefined);
|
||||
onToggleConnection(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card withBorder padding="lg">
|
||||
<Card
|
||||
withBorder
|
||||
padding="lg"
|
||||
opacity={isDimmed ? 0.4 : 1}
|
||||
style={isDimmed ? { pointerEvents: "none" } : undefined}
|
||||
{...(isDimmed ? { "aria-disabled": true, inert: "" } : {})}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" wrap="wrap">
|
||||
<HeaderLeft>
|
||||
|
|
@ -95,21 +124,47 @@ export function ServerCard({
|
|||
{version && <Badge variant="outline">{version}</Badge>}
|
||||
</HeaderLeft>
|
||||
<HeaderRight>
|
||||
<StatusIndicator status={status} retryCount={retryCount} />
|
||||
<ServerStatusIndicator
|
||||
status={displayStatus}
|
||||
retryCount={retryCount}
|
||||
/>
|
||||
<ConnectionToggle
|
||||
checked={isConnected}
|
||||
loading={isConnecting}
|
||||
disabled={false}
|
||||
onChange={onToggleConnection}
|
||||
disabled={isDimmed}
|
||||
onChange={handleToggle}
|
||||
/>
|
||||
</HeaderRight>
|
||||
</Group>
|
||||
|
||||
{!compact && (
|
||||
<>
|
||||
<Group gap="sm">
|
||||
<TransportBadge transport={transport} />
|
||||
<ModeText>{connectionMode}</ModeText>
|
||||
<Group justify="space-between" mih={30}>
|
||||
<Group gap="sm">
|
||||
<TransportBadge transport={transport} />
|
||||
<ModeText>{connectionMode}</ModeText>
|
||||
</Group>
|
||||
{canTestClientFeatures && (
|
||||
<Menu>
|
||||
<Menu.Target>
|
||||
<SubtleButton>Test Client Features ▾</SubtleButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item onClick={onTestSampling}>
|
||||
Simulate Sampling Request
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={onTestElicitationForm}>
|
||||
Simulate Elicitation (Form)
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={onTestElicitationUrl}>
|
||||
Simulate Elicitation (URL)
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={onConfigureRoots}>
|
||||
Configure Roots
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<ContentViewer type="text" content={command} copyable />
|
||||
|
|
@ -131,27 +186,6 @@ export function ServerCard({
|
|||
<ActionsRow>
|
||||
<SubtleButton onClick={onServerInfo}>Server Info</SubtleButton>
|
||||
<SubtleButton onClick={onSettings}>Settings</SubtleButton>
|
||||
{canTestClientFeatures && (
|
||||
<Menu>
|
||||
<Menu.Target>
|
||||
<SubtleButton>Test Client Features ▾</SubtleButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item onClick={onTestSampling}>
|
||||
Simulate Sampling Request
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={onTestElicitationForm}>
|
||||
Simulate Elicitation (Form)
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={onTestElicitationUrl}>
|
||||
Simulate Elicitation (URL)
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={onConfigureRoots}>
|
||||
Configure Roots
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
)}
|
||||
</ActionsRow>
|
||||
</Group>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -115,16 +115,16 @@ export function ToolDetailPanel({
|
|||
/>
|
||||
)}
|
||||
|
||||
<Group>
|
||||
<Group justify="flex-end">
|
||||
{isExecuting && <CancelButton onClick={onCancel}>Cancel</CancelButton>}
|
||||
<Button
|
||||
fullWidth
|
||||
size="md"
|
||||
onClick={onExecute}
|
||||
disabled={isExecuting}
|
||||
loading={isExecuting}
|
||||
>
|
||||
Execute Tool
|
||||
</Button>
|
||||
{isExecuting && <CancelButton onClick={onCancel}>Cancel</CancelButton>}
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ function resolveMediaType(mimeType?: string): "image" | "audio" {
|
|||
export function ToolResultPanel({ content, onClear }: ToolResultPanelProps) {
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={4}>Results</Title>
|
||||
<Group justify="space-between">
|
||||
<Title order={4}>Results</Title>
|
||||
<ClearButton onClick={onClear}>Clear</ClearButton>
|
||||
</Group>
|
||||
{content.length === 0 ? (
|
||||
<Text c="dimmed">No results yet</Text>
|
||||
) : (
|
||||
|
|
@ -59,9 +62,6 @@ export function ToolResultPanel({ content, onClear }: ToolResultPanelProps) {
|
|||
})}
|
||||
</>
|
||||
)}
|
||||
<Group>
|
||||
<ClearButton onClick={onClear}>Clear</ClearButton>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from "@mantine/core";
|
||||
import { useComputedColorScheme } from "@mantine/core";
|
||||
import { MdLightMode, MdDarkMode } from "react-icons/md";
|
||||
import { StatusIndicator } from "../../elements/StatusIndicator/StatusIndicator";
|
||||
import { ServerStatusIndicator } from "../../elements/ServerStatusIndicator/ServerStatusIndicator";
|
||||
import mcpLogo from "../../../theme/assets/MCP.svg";
|
||||
import mcpLogoDark from "../../../theme/assets/MCP-dark.svg";
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ export function ViewHeader(props: ViewHeaderProps) {
|
|||
</CenterSection>
|
||||
|
||||
<RightSection>
|
||||
<StatusIndicator status={props.status} latencyMs={props.latencyMs} />
|
||||
<ServerStatusIndicator status={props.status} latencyMs={props.latencyMs} />
|
||||
<DisconnectButton onClick={props.onDisconnect}>
|
||||
Disconnect
|
||||
</DisconnectButton>
|
||||
|
|
|
|||
Loading…
Reference in a new issue