refine UI layout, theming, and component consistency across screens

- Add MCP logo (light/dark variants) to ConnectedLayout header
- Replace Tabs with SegmentedControl in header, split into 1/3 sections
- Wrap all screens in Container for consistent margins
- Use Card instead of Paper for panel backgrounds across all screens
- Add SubscribeButton atom, CopyButton for resource URI
- Move annotations from ResourceListItem to ResourcePreviewPanel
- Improve AnnotationBadge colors for dark mode visibility
- Add Tools, Resources, and Prompts headers to list panels
- Add ConnectedLayout stories for Tools, Resources, and Prompts tabs
- Remove unused onCopy prop and copy button from PromptMessagesDisplay

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
cliffhall 2026-03-23 16:56:53 -04:00
commit f674ce0c22
22 changed files with 357 additions and 157 deletions

View file

@ -1,4 +1,4 @@
import { Badge } from "@mantine/core"; import { Badge, useComputedColorScheme } from "@mantine/core";
export type AnnotationVariant = export type AnnotationVariant =
| "audience" | "audience"
@ -17,11 +17,11 @@ const variantMap: Record<
AnnotationVariant, AnnotationVariant,
{ color: string; variant: string } { color: string; variant: string }
> = { > = {
audience: { color: "gray", variant: "outline" }, audience: { color: "blue", variant: "outline" },
readOnly: { color: "dark", variant: "filled" }, readOnly: { color: "green", variant: "filled" },
destructive: { color: "red", variant: "outline" }, destructive: { color: "red", variant: "filled" },
longRun: { color: "yellow", variant: "outline" }, longRun: { color: "yellow", variant: "filled" },
priority: { color: "orange", variant: "light" }, priority: { color: "orange", variant: "filled" },
default: { color: "gray", variant: "light" }, default: { color: "gray", variant: "light" },
}; };
@ -29,10 +29,13 @@ export function AnnotationBadge({
label, label,
variant = "default", variant = "default",
}: AnnotationBadgeProps) { }: AnnotationBadgeProps) {
const colorScheme = useComputedColorScheme();
const { color, variant: badgeVariant } = variantMap[variant]; const { color, variant: badgeVariant } = variantMap[variant];
const textColor =
(variant === "priority" || variant === "destructive") && colorScheme === "dark" ? "black" : undefined;
return ( return (
<Badge color={color} variant={badgeVariant}> <Badge color={color} variant={badgeVariant} c={textColor}>
{label} {label}
</Badge> </Badge>
); );

View file

@ -0,0 +1,26 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { fn } from "storybook/test";
import { SubscribeButton } from "./SubscribeButton";
const meta: Meta<typeof SubscribeButton> = {
title: "Atoms/SubscribeButton",
component: SubscribeButton,
args: {
onToggle: fn(),
},
};
export default meta;
type Story = StoryObj<typeof SubscribeButton>;
export const Subscribed: Story = {
args: {
subscribed: true,
},
};
export const Unsubscribed: Story = {
args: {
subscribed: false,
},
};

View file

@ -0,0 +1,17 @@
import { Button } from "@mantine/core";
export interface SubscribeButtonProps {
subscribed: boolean;
onToggle: () => void;
}
export function SubscribeButton({
subscribed,
onToggle,
}: SubscribeButtonProps) {
return (
<Button variant="outline" size="sm" onClick={onToggle}>
{subscribed ? "Unsubscribe" : "Subscribe"}
</Button>
);
}

View file

@ -2,6 +2,9 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import { Text } from "@mantine/core"; import { Text } from "@mantine/core";
import { ConnectedLayout } from "./ConnectedLayout"; import { ConnectedLayout } from "./ConnectedLayout";
import { ToolsScreen } from "../../organisms/ToolsScreen/ToolsScreen";
import { ResourcesScreen } from "../../organisms/ResourcesScreen/ResourcesScreen";
import { PromptsScreen } from "../../organisms/PromptsScreen/PromptsScreen";
const allTabs = ["Tools", "Resources", "Prompts", "Logs", "Tasks", "History"]; const allTabs = ["Tools", "Resources", "Prompts", "Logs", "Tasks", "History"];
@ -27,14 +30,153 @@ type Story = StoryObj<typeof ConnectedLayout>;
export const ToolsActive: Story = { export const ToolsActive: Story = {
args: { args: {
activeTab: "Tools", activeTab: "Tools",
children: <Text>Tools screen content</Text>, children: (
<ToolsScreen
tools={[
{ name: "send_message", title: "Send Message", selected: false, onClick: fn() },
{ name: "create_record", title: "Create Record", selected: true, onClick: fn() },
{ name: "delete_records", selected: false, onClick: fn() },
{ name: "list_users", selected: false, onClick: fn() },
{ name: "batch_process", selected: false, onClick: fn() },
]}
selectedTool={{
name: "create_record",
title: "Create Record",
description: "Creates a new record with the given parameters",
schema: {
type: "object",
properties: {
title: { type: "string", description: "Record title" },
count: { type: "number", description: "Number of items" },
enabled: { type: "boolean", description: "Whether the record is active" },
},
required: ["title"],
},
formValues: {},
isExecuting: false,
onFormChange: fn(),
onExecute: fn(),
onCancel: fn(),
}}
result={{
content: [
{
type: "text",
text: JSON.stringify(
{ id: 42, title: "New Record", count: 5, enabled: true, createdAt: "2026-03-17T12:00:00Z" },
null,
2,
),
},
],
onClear: fn(),
}}
searchText=""
listChanged={false}
onSearchChange={fn()}
onRefreshList={fn()}
onSelectTool={fn()}
/>
),
}, },
}; };
export const ResourcesActive: Story = { export const ResourcesActive: Story = {
args: { args: {
activeTab: "Resources", activeTab: "Resources",
children: <Text>Resources screen content</Text>, children: (
<ResourcesScreen
resources={[
{
name: "config.json",
uri: "file:///config.json",
annotations: { audience: "developer", priority: 0.8 },
selected: true,
onClick: fn(),
},
{
name: "README.md",
uri: "file:///README.md",
selected: false,
onClick: fn(),
},
{
name: "schema.sql",
uri: "file:///schema.sql",
annotations: { priority: 0.5 },
selected: false,
onClick: fn(),
},
]}
templates={[
{
template: "file:///users/{userId}/profile",
variables: { userId: "" },
onVariableChange: fn(),
onSubmit: fn(),
},
]}
subscriptions={[
{ name: "config.json", lastUpdated: "2026-03-17T10:30:00Z" },
]}
selectedResource={{
uri: "file:///config.json",
mimeType: "application/json",
annotations: { audience: "developer", priority: 0.8 },
content: JSON.stringify(
{ name: "my-project", version: "1.0.0", settings: { debug: true, logLevel: "info" } },
null,
2,
),
lastUpdated: "2026-03-17T10:30:00Z",
isSubscribed: true,
onSubscribe: fn(),
onUnsubscribe: fn(),
}}
listChanged={false}
searchText=""
onSearchChange={fn()}
onRefreshList={fn()}
onSelectResource={fn()}
/>
),
},
};
export const PromptsActive: Story = {
args: {
activeTab: "Prompts",
children: (
<PromptsScreen
promptForm={{
prompts: [
{ name: "summarize", description: "Summarize a document" },
{ name: "translate", description: "Translate text to another language" },
{ name: "code-review", description: "Review code for issues" },
],
selectedPrompt: "translate",
arguments: [
{ name: "text", required: true, description: "The text to translate" },
{ name: "targetLanguage", required: true, description: "Target language code" },
],
argumentValues: {
text: "Hello, how are you?",
targetLanguage: "es",
},
onSelectPrompt: fn(),
onArgumentChange: fn(),
onGetPrompt: fn(),
}}
messages={{
messages: [
{ role: "user", content: 'Translate the following text to Spanish: "Hello, how are you?"' },
{ role: "assistant", content: "Hola, como estas?" },
],
}}
listChanged={false}
onRefreshList={fn()}
/>
),
}, },
}; };
@ -47,7 +189,7 @@ export const LimitedTabs: Story = {
export const LongServerName: Story = { export const LongServerName: Story = {
args: { args: {
serverName: "my-very-long-server-name-that-might-overflow", serverName: "my-very-long-server-name-that-might-overflow-is",
children: <Text>Tools screen content</Text>, children: <Text>Tools screen content</Text>,
}, },
}; };

View file

@ -1,7 +1,9 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { ActionIcon, AppShell, Button, Group, Tabs, Text } from "@mantine/core"; import { ActionIcon, AppShell, Button, Group, Image, SegmentedControl, Text } from "@mantine/core";
import { useComputedColorScheme } from "@mantine/core"; import { useComputedColorScheme } from "@mantine/core";
import { StatusIndicator } from "../../atoms/StatusIndicator/StatusIndicator"; import { StatusIndicator } from "../../atoms/StatusIndicator/StatusIndicator";
import mcpLogo from "../../../theme/assets/MCP.svg";
import mcpLogoDark from "../../../theme/assets/MCP-dark.svg";
export interface ConnectedLayoutProps { export interface ConnectedLayoutProps {
serverName: string; serverName: string;
@ -18,6 +20,8 @@ export interface ConnectedLayoutProps {
const ServerName = Text.withProps({ const ServerName = Text.withProps({
fw: 600, fw: 600,
size: "lg", size: "lg",
truncate: "end",
maw: "calc(100% - 40px)",
}); });
export function ConnectedLayout({ export function ConnectedLayout({
@ -36,35 +40,23 @@ export function ConnectedLayout({
return ( return (
<AppShell header={{ height: 60 }} padding="md"> <AppShell header={{ height: 60 }} padding="md">
<AppShell.Header> <AppShell.Header>
<Group h="100%" px="md" justify="space-between" wrap="nowrap"> <Group h="100%" px="md" wrap="nowrap" gap={0}>
<Group gap="md" wrap="nowrap"> <Group gap="md" wrap="nowrap" w="33.33%" miw={0}>
<Image src={colorScheme === "dark" ? mcpLogoDark : mcpLogo} alt="MCP" w={28} h={28} fit="contain" />
<ServerName>{serverName}</ServerName> <ServerName>{serverName}</ServerName>
<StatusIndicator status={status} latencyMs={latencyMs} />
</Group> </Group>
<Tabs <Group w="33.33%" justify="center">
value={activeTab} <SegmentedControl
onChange={(value) => value && onTabChange(value)} value={activeTab}
variant="default" onChange={onTabChange}
> data={availableTabs}
<Tabs.List> size="sm"
{availableTabs.map((tab) => ( />
<Tabs.Tab key={tab} value={tab}> </Group>
{tab}
</Tabs.Tab>
))}
</Tabs.List>
</Tabs>
<Group gap="sm"> <Group gap="sm" w="33.33%" justify="flex-end">
<ActionIcon <StatusIndicator status={status} latencyMs={latencyMs} />
variant="default"
size="lg"
aria-label="Toggle color scheme"
onClick={onToggleTheme}
>
{colorScheme === "dark" ? "\u2600" : "\u263E"}
</ActionIcon>
<Button <Button
variant="outline" variant="outline"
color="red" color="red"
@ -73,6 +65,14 @@ export function ConnectedLayout({
> >
Disconnect Disconnect
</Button> </Button>
<ActionIcon
variant="default"
size="lg"
aria-label="Toggle color scheme"
onClick={onToggleTheme}
>
{colorScheme === "dark" ? "\u2600" : "\u263E"}
</ActionIcon>
</Group> </Group>
</Group> </Group>
</AppShell.Header> </AppShell.Header>

View file

@ -34,7 +34,7 @@ export function PromptArgumentsForm({
return ( return (
<Stack gap="md"> <Stack gap="md">
<Title order={4}>Select Prompt</Title> <Title order={4}>Prompts</Title>
<Select <Select
data={prompts.map((p) => ({ value: p.name, label: p.name }))} data={prompts.map((p) => ({ value: p.name, label: p.name }))}
value={selectedPrompt ?? null} value={selectedPrompt ?? null}

View file

@ -5,9 +5,7 @@ import { PromptMessagesDisplay } from "./PromptMessagesDisplay";
const meta: Meta<typeof PromptMessagesDisplay> = { const meta: Meta<typeof PromptMessagesDisplay> = {
title: "Molecules/PromptMessagesDisplay", title: "Molecules/PromptMessagesDisplay",
component: PromptMessagesDisplay, component: PromptMessagesDisplay,
args: { args: {},
onCopy: fn(),
},
}; };
export default meta; export default meta;

View file

@ -1,4 +1,4 @@
import { Button, Stack, Text, Title } from "@mantine/core"; import { Stack, Text, Title } from "@mantine/core";
import { MessageBubble } from "../../atoms/MessageBubble/MessageBubble"; import { MessageBubble } from "../../atoms/MessageBubble/MessageBubble";
export interface PromptMessage { export interface PromptMessage {
@ -10,12 +10,10 @@ export interface PromptMessage {
export interface PromptMessagesDisplayProps { export interface PromptMessagesDisplayProps {
messages: PromptMessage[]; messages: PromptMessage[];
onCopy: () => void;
} }
export function PromptMessagesDisplay({ export function PromptMessagesDisplay({
messages, messages,
onCopy,
}: PromptMessagesDisplayProps) { }: PromptMessagesDisplayProps) {
return ( return (
<Stack gap="md"> <Stack gap="md">
@ -34,9 +32,6 @@ export function PromptMessagesDisplay({
/> />
)) ))
)} )}
<Button variant="light" size="sm" onClick={onCopy}>
Copy
</Button>
</Stack> </Stack>
); );
} }

View file

@ -1,5 +1,4 @@
import { Group, Stack, Text, UnstyledButton } from "@mantine/core"; import { Text, UnstyledButton } from "@mantine/core";
import { AnnotationBadge } from "../../atoms/AnnotationBadge/AnnotationBadge";
export interface ResourceAnnotations { export interface ResourceAnnotations {
audience?: string; audience?: string;
@ -14,15 +13,8 @@ export interface ResourceListItemProps {
onClick: () => void; onClick: () => void;
} }
function priorityLabel(priority: number): string {
if (priority >= 0.7) return "priority: high";
if (priority >= 0.4) return "priority: medium";
return "priority: low";
}
export function ResourceListItem({ export function ResourceListItem({
name, name,
annotations,
selected, selected,
onClick, onClick,
}: ResourceListItemProps) { }: ResourceListItemProps) {
@ -34,25 +26,7 @@ export function ResourceListItem({
style={{ borderRadius: "var(--mantine-radius-md)" }} style={{ borderRadius: "var(--mantine-radius-md)" }}
onClick={onClick} onClick={onClick}
> >
<Stack gap="xs"> <Text fw={500}>{name}</Text>
<Text fw={500}>{name}</Text>
{annotations && (
<Group gap="xs">
{annotations.audience && (
<AnnotationBadge
label={annotations.audience}
variant="audience"
/>
)}
{annotations.priority !== undefined && (
<AnnotationBadge
label={priorityLabel(annotations.priority)}
variant="priority"
/>
)}
</Group>
)}
</Stack>
</UnstyledButton> </UnstyledButton>
); );
} }

View file

@ -1,5 +1,8 @@
import { Button, Group, Stack, Text, Title } from "@mantine/core"; import { Group, Stack, Text, Title } from "@mantine/core";
import { AnnotationBadge } from "../../atoms/AnnotationBadge/AnnotationBadge";
import { ContentViewer } from "../../atoms/ContentViewer/ContentViewer"; import { ContentViewer } from "../../atoms/ContentViewer/ContentViewer";
import { CopyButton } from "../../atoms/CopyButton/CopyButton";
import { SubscribeButton } from "../../atoms/SubscribeButton/SubscribeButton";
export interface ResourcePreviewPanelProps { export interface ResourcePreviewPanelProps {
uri: string; uri: string;
@ -12,6 +15,12 @@ export interface ResourcePreviewPanelProps {
onUnsubscribe: () => void; onUnsubscribe: () => void;
} }
function priorityLabel(priority: number): string {
if (priority >= 0.7) return "priority: high";
if (priority >= 0.4) return "priority: medium";
return "priority: low";
}
function resolveContentType(mimeType: string): "json" | "image" | "text" { function resolveContentType(mimeType: string): "json" | "image" | "text" {
if (mimeType === "application/json") return "json"; if (mimeType === "application/json") return "json";
if (mimeType.startsWith("image/")) return "image"; if (mimeType.startsWith("image/")) return "image";
@ -30,51 +39,53 @@ export function ResourcePreviewPanel({
}: ResourcePreviewPanelProps) { }: ResourcePreviewPanelProps) {
return ( return (
<Stack gap="md"> <Stack gap="md">
<Title order={4}>Content Preview</Title> <Group justify="space-between" wrap="nowrap">
<Text size="sm"> <Title order={4}>Resource</Title>
URI:{" "} <Group gap="xs" wrap="nowrap">
<Text span c="blue"> <Text size="sm" c="blue" truncate="end">
{uri} {uri}
</Text> </Text>
</Text> <CopyButton value={uri} />
<Text size="sm" c="dimmed"> </Group>
MIME: {mimeType} </Group>
</Text>
{annotations && (
<Stack gap="xs">
<Text size="sm">Annotations:</Text>
{annotations.audience && (
<Text size="sm" c="dimmed">
Audience: {annotations.audience}
</Text>
)}
{annotations.priority !== undefined && (
<Text size="sm" c="dimmed">
Priority: {annotations.priority}
</Text>
)}
</Stack>
)}
<ContentViewer <ContentViewer
type={resolveContentType(mimeType)} type={resolveContentType(mimeType)}
content={content} content={content}
mimeType={mimeType} mimeType={mimeType}
copyable copyable
/> />
<Group> <Group justify="space-between" wrap="nowrap">
<Button {lastUpdated ? (
variant="light" <Text size="xs" c="dimmed">
size="sm" Last updated: {lastUpdated}
onClick={isSubscribed ? onUnsubscribe : onSubscribe} </Text>
> ) : (
{isSubscribed ? "Unsubscribe" : "Subscribe"} <span />
</Button> )}
</Group> <Text size="sm" c="dimmed">
{lastUpdated && ( {mimeType}
<Text size="xs" c="dimmed" ta="right">
Last updated: {lastUpdated}
</Text> </Text>
)} </Group>
<Group justify="space-between">
<Group gap="xs">
{annotations?.audience && (
<AnnotationBadge
label={annotations.audience}
variant="audience"
/>
)}
{annotations?.priority !== undefined && (
<AnnotationBadge
label={priorityLabel(annotations.priority)}
variant="priority"
/>
)}
</Group>
<SubscribeButton
subscribed={isSubscribed}
onToggle={isSubscribed ? onUnsubscribe : onSubscribe}
/>
</Group>
</Stack> </Stack>
); );
} }

View file

@ -55,7 +55,6 @@ export function ResourceTemplateInput({
<TextInput <TextInput
key={part.value} key={part.value}
size="sm" size="sm"
label={part.value}
placeholder={part.value} placeholder={part.value}
value={variables[part.value] ?? ""} value={variables[part.value] ?? ""}
onChange={(e) => onChange={(e) =>

View file

@ -52,11 +52,6 @@ export function ToolDetailPanel({
<Text fw={700} size="lg" truncate="end"> <Text fw={700} size="lg" truncate="end">
{title ?? name} {title ?? name}
</Text> </Text>
{title && (
<Text size="sm" c="dimmed" truncate="end">
{name}
</Text>
)}
{hasAnnotations && ( {hasAnnotations && (
<Group gap="xs"> <Group gap="xs">
{annotations.audience && ( {annotations.audience && (

View file

@ -1,6 +1,7 @@
import { import {
Button, Button,
Checkbox, Checkbox,
Container,
Grid, Grid,
Group, Group,
Paper, Paper,
@ -30,6 +31,7 @@ export function LoggingScreen({
onCopyAll, onCopyAll,
}: LoggingScreenProps) { }: LoggingScreenProps) {
return ( return (
<Container size="xl" py="xl">
<Grid align="stretch"> <Grid align="stretch">
<Grid.Col span={3}> <Grid.Col span={3}>
<Paper withBorder p="md" h="100%"> <Paper withBorder p="md" h="100%">
@ -69,5 +71,6 @@ export function LoggingScreen({
</Paper> </Paper>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</Container>
); );
} }

View file

@ -76,7 +76,6 @@ export const WithResult: Story = {
content: "Hola, como estas?", content: "Hola, como estas?",
}, },
], ],
onCopy: fn(),
}, },
listChanged: false, listChanged: false,
}, },

View file

@ -1,4 +1,4 @@
import { Grid, Paper, Stack, Text } from "@mantine/core"; import { Card, Container, Grid, Stack, Text } from "@mantine/core";
import { PromptArgumentsForm } from "../../molecules/PromptArgumentsForm/PromptArgumentsForm"; import { PromptArgumentsForm } from "../../molecules/PromptArgumentsForm/PromptArgumentsForm";
import { PromptMessagesDisplay } from "../../molecules/PromptMessagesDisplay/PromptMessagesDisplay"; import { PromptMessagesDisplay } from "../../molecules/PromptMessagesDisplay/PromptMessagesDisplay";
import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator"; import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator";
@ -19,9 +19,10 @@ export function PromptsScreen({
onRefreshList, onRefreshList,
}: PromptsScreenProps) { }: PromptsScreenProps) {
return ( return (
<Grid> <Container size="xl" py="xl">
<Grid align="stretch">
<Grid.Col span={5}> <Grid.Col span={5}>
<Paper withBorder p="md"> <Card withBorder padding="lg" h="100%">
<Stack gap="md"> <Stack gap="md">
<ListChangedIndicator <ListChangedIndicator
visible={listChanged} visible={listChanged}
@ -29,10 +30,10 @@ export function PromptsScreen({
/> />
<PromptArgumentsForm {...promptForm} /> <PromptArgumentsForm {...promptForm} />
</Stack> </Stack>
</Paper> </Card>
</Grid.Col> </Grid.Col>
<Grid.Col span={7}> <Grid.Col span={7}>
<Paper withBorder p="md"> <Card withBorder padding="lg" h="100%">
{messages ? ( {messages ? (
<PromptMessagesDisplay {...messages} /> <PromptMessagesDisplay {...messages} />
) : ( ) : (
@ -40,8 +41,9 @@ export function PromptsScreen({
Select a prompt and click Get Prompt to see messages Select a prompt and click Get Prompt to see messages
</Text> </Text>
)} )}
</Paper> </Card>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</Container>
); );
} }

View file

@ -75,7 +75,6 @@ const selectedResourceProps: ResourcePreviewPanelProps = {
), ),
lastUpdated: "2026-03-17T10:30:00Z", lastUpdated: "2026-03-17T10:30:00Z",
isSubscribed: true, isSubscribed: true,
onCopy: fn(),
onSubscribe: fn(), onSubscribe: fn(),
onUnsubscribe: fn(), onUnsubscribe: fn(),
}; };

View file

@ -1,4 +1,4 @@
import { Accordion, Grid, Paper, Stack, Text, TextInput } from "@mantine/core"; import { Accordion, Card, Container, Grid, Stack, Text, TextInput, Title } from "@mantine/core";
import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator"; import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator";
import { ResourceListItem } from "../../molecules/ResourceListItem/ResourceListItem"; import { ResourceListItem } from "../../molecules/ResourceListItem/ResourceListItem";
import { ResourcePreviewPanel } from "../../molecules/ResourcePreviewPanel/ResourcePreviewPanel"; import { ResourcePreviewPanel } from "../../molecules/ResourcePreviewPanel/ResourcePreviewPanel";
@ -38,11 +38,19 @@ export function ResourcesScreen({
r.name.toLowerCase().includes(searchText.toLowerCase()), r.name.toLowerCase().includes(searchText.toLowerCase()),
); );
const openSections = [
...(filteredResources.length > 0 ? ["resources"] : []),
...(templates.length > 0 ? ["templates"] : []),
...(subscriptions.length > 0 ? ["subscriptions"] : []),
];
return ( return (
<Grid> <Container size="xl" py="xl">
<Grid align="stretch">
<Grid.Col span={4}> <Grid.Col span={4}>
<Paper withBorder p="md"> <Card withBorder padding="lg" h="100%">
<Stack gap="sm"> <Stack gap="sm">
<Title order={4}>Resources</Title>
<ListChangedIndicator <ListChangedIndicator
visible={listChanged} visible={listChanged}
onRefresh={onRefreshList} onRefresh={onRefreshList}
@ -54,11 +62,11 @@ export function ResourcesScreen({
/> />
<Accordion <Accordion
multiple multiple
defaultValue={["resources", "templates", "subscriptions"]} defaultValue={openSections}
> >
<Accordion.Item value="resources"> <Accordion.Item value="resources">
<Accordion.Control> <Accordion.Control disabled={filteredResources.length === 0}>
Resources ({filteredResources.length}) URIs ({filteredResources.length})
</Accordion.Control> </Accordion.Control>
<Accordion.Panel> <Accordion.Panel>
<Stack gap="xs"> <Stack gap="xs">
@ -70,7 +78,7 @@ export function ResourcesScreen({
</Accordion.Item> </Accordion.Item>
<Accordion.Item value="templates"> <Accordion.Item value="templates">
<Accordion.Control> <Accordion.Control disabled={templates.length === 0}>
Templates ({templates.length}) Templates ({templates.length})
</Accordion.Control> </Accordion.Control>
<Accordion.Panel> <Accordion.Panel>
@ -86,7 +94,7 @@ export function ResourcesScreen({
</Accordion.Item> </Accordion.Item>
<Accordion.Item value="subscriptions"> <Accordion.Item value="subscriptions">
<Accordion.Control> <Accordion.Control disabled={subscriptions.length === 0}>
Subscriptions ({subscriptions.length}) Subscriptions ({subscriptions.length})
</Accordion.Control> </Accordion.Control>
<Accordion.Panel> <Accordion.Panel>
@ -107,11 +115,11 @@ export function ResourcesScreen({
</Accordion.Item> </Accordion.Item>
</Accordion> </Accordion>
</Stack> </Stack>
</Paper> </Card>
</Grid.Col> </Grid.Col>
<Grid.Col span={8}> <Grid.Col span={8}>
<Paper withBorder p="md"> <Card withBorder padding="lg">
{selectedResource ? ( {selectedResource ? (
<ResourcePreviewPanel {...selectedResource} /> <ResourcePreviewPanel {...selectedResource} />
) : ( ) : (
@ -119,8 +127,9 @@ export function ResourcesScreen({
Select a resource to preview Select a resource to preview
</Text> </Text>
)} )}
</Paper> </Card>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</Container>
); );
} }

View file

@ -4,7 +4,6 @@ import {
SimpleGrid, SimpleGrid,
Stack, Stack,
Text, Text,
Title,
} from "@mantine/core"; } from "@mantine/core";
import { ServerCard } from "../../molecules/ServerCard/ServerCard"; import { ServerCard } from "../../molecules/ServerCard/ServerCard";
import { AddServerMenu } from "../../molecules/AddServerMenu/AddServerMenu"; import { AddServerMenu } from "../../molecules/AddServerMenu/AddServerMenu";
@ -26,8 +25,7 @@ export function ServerListScreen({
return ( return (
<Container size="xl" py="xl"> <Container size="xl" py="xl">
<Stack gap="lg"> <Stack gap="lg">
<Group justify="space-between"> <Group justify="flex-end">
<Title order={1}>MCP Inspector</Title>
<AddServerMenu <AddServerMenu
onAddManually={onAddManually} onAddManually={onAddManually}
onImportConfig={onImportConfig} onImportConfig={onImportConfig}

View file

@ -1,4 +1,4 @@
import { Button, Group, Stack, Text, Title } from "@mantine/core"; import { Button, Container, Group, Stack, Text, Title } from "@mantine/core";
import { TaskCard } from "../../molecules/TaskCard/TaskCard"; import { TaskCard } from "../../molecules/TaskCard/TaskCard";
import type { TaskCardProps } from "../../molecules/TaskCard/TaskCard"; import type { TaskCardProps } from "../../molecules/TaskCard/TaskCard";
@ -16,11 +16,11 @@ export function TasksScreen({
onClearHistory, onClearHistory,
}: TasksScreenProps) { }: TasksScreenProps) {
return ( return (
<Container size="xl" py="xl">
<Stack gap="lg"> <Stack gap="lg">
<Group justify="space-between"> <Group justify="flex-end">
<Title order={2}>Tasks</Title> <Button variant="dark" onClick={onRefresh}>
<Button variant="light" onClick={onRefresh}> Refresh Tasks
Refresh
</Button> </Button>
</Group> </Group>
@ -37,9 +37,11 @@ export function TasksScreen({
<Group justify="space-between"> <Group justify="space-between">
<Title order={4}>Completed Tasks ({completedTasks.length})</Title> <Title order={4}>Completed Tasks ({completedTasks.length})</Title>
<Button variant="light" size="xs" onClick={onClearHistory}> {completedTasks.length > 0 && (
Clear History <Button variant="light" size="xs" onClick={onClearHistory}>
</Button> Clear History
</Button>
)}
</Group> </Group>
{completedTasks.length === 0 ? ( {completedTasks.length === 0 ? (
<Text c="dimmed">No completed tasks</Text> <Text c="dimmed">No completed tasks</Text>
@ -51,5 +53,6 @@ export function TasksScreen({
</Stack> </Stack>
)} )}
</Stack> </Stack>
</Container>
); );
} }

View file

@ -1,4 +1,4 @@
import { Grid, Paper, Stack, Text, TextInput } from "@mantine/core"; import { Card, Container, Grid, Stack, Text, TextInput, Title } from "@mantine/core";
import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator"; import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator";
import { ToolListItem } from "../../molecules/ToolListItem/ToolListItem"; import { ToolListItem } from "../../molecules/ToolListItem/ToolListItem";
import { ToolDetailPanel } from "../../molecules/ToolDetailPanel/ToolDetailPanel"; import { ToolDetailPanel } from "../../molecules/ToolDetailPanel/ToolDetailPanel";
@ -35,10 +35,12 @@ export function ToolsScreen({
: tools; : tools;
return ( return (
<Grid> <Container size="xl" py="xl">
<Grid align="stretch">
<Grid.Col span={3}> <Grid.Col span={3}>
<Paper withBorder p="md"> <Card withBorder padding="lg" h="100%">
<Stack gap="sm"> <Stack gap="sm">
<Title order={4}>Tools</Title>
<ListChangedIndicator <ListChangedIndicator
visible={listChanged} visible={listChanged}
onRefresh={onRefreshList} onRefresh={onRefreshList}
@ -56,11 +58,11 @@ export function ToolsScreen({
/> />
))} ))}
</Stack> </Stack>
</Paper> </Card>
</Grid.Col> </Grid.Col>
<Grid.Col span={5}> <Grid.Col span={5}>
<Paper withBorder p="md" variant="contained"> <Card withBorder padding="lg" h="100%">
{selectedTool ? ( {selectedTool ? (
<ToolDetailPanel {...selectedTool} /> <ToolDetailPanel {...selectedTool} />
) : ( ) : (
@ -68,11 +70,11 @@ export function ToolsScreen({
Select a tool to view details Select a tool to view details
</Text> </Text>
)} )}
</Paper> </Card>
</Grid.Col> </Grid.Col>
<Grid.Col span={4}> <Grid.Col span={4}>
<Paper withBorder p="md"> <Card withBorder padding="lg" h="100%">
{result ? ( {result ? (
<ResultPanel {...result} /> <ResultPanel {...result} />
) : ( ) : (
@ -80,8 +82,9 @@ export function ToolsScreen({
Results will appear here Results will appear here
</Text> </Text>
)} )}
</Paper> </Card>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</Container>
); );
} }

Some files were not shown because too many files have changed in this diff Show more