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 =
| "audience"
@ -17,11 +17,11 @@ const variantMap: Record<
AnnotationVariant,
{ color: string; variant: string }
> = {
audience: { color: "gray", variant: "outline" },
readOnly: { color: "dark", variant: "filled" },
destructive: { color: "red", variant: "outline" },
longRun: { color: "yellow", variant: "outline" },
priority: { color: "orange", variant: "light" },
audience: { color: "blue", variant: "outline" },
readOnly: { color: "green", variant: "filled" },
destructive: { color: "red", variant: "filled" },
longRun: { color: "yellow", variant: "filled" },
priority: { color: "orange", variant: "filled" },
default: { color: "gray", variant: "light" },
};
@ -29,10 +29,13 @@ export function AnnotationBadge({
label,
variant = "default",
}: AnnotationBadgeProps) {
const colorScheme = useComputedColorScheme();
const { color, variant: badgeVariant } = variantMap[variant];
const textColor =
(variant === "priority" || variant === "destructive") && colorScheme === "dark" ? "black" : undefined;
return (
<Badge color={color} variant={badgeVariant}>
<Badge color={color} variant={badgeVariant} c={textColor}>
{label}
</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 { Text } from "@mantine/core";
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"];
@ -27,14 +30,153 @@ type Story = StoryObj<typeof ConnectedLayout>;
export const ToolsActive: Story = {
args: {
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 = {
args: {
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 = {
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>,
},
};

View file

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

View file

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

View file

@ -5,9 +5,7 @@ import { PromptMessagesDisplay } from "./PromptMessagesDisplay";
const meta: Meta<typeof PromptMessagesDisplay> = {
title: "Molecules/PromptMessagesDisplay",
component: PromptMessagesDisplay,
args: {
onCopy: fn(),
},
args: {},
};
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";
export interface PromptMessage {
@ -10,12 +10,10 @@ export interface PromptMessage {
export interface PromptMessagesDisplayProps {
messages: PromptMessage[];
onCopy: () => void;
}
export function PromptMessagesDisplay({
messages,
onCopy,
}: PromptMessagesDisplayProps) {
return (
<Stack gap="md">
@ -34,9 +32,6 @@ export function PromptMessagesDisplay({
/>
))
)}
<Button variant="light" size="sm" onClick={onCopy}>
Copy
</Button>
</Stack>
);
}

View file

@ -1,5 +1,4 @@
import { Group, Stack, Text, UnstyledButton } from "@mantine/core";
import { AnnotationBadge } from "../../atoms/AnnotationBadge/AnnotationBadge";
import { Text, UnstyledButton } from "@mantine/core";
export interface ResourceAnnotations {
audience?: string;
@ -14,15 +13,8 @@ export interface ResourceListItemProps {
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({
name,
annotations,
selected,
onClick,
}: ResourceListItemProps) {
@ -34,25 +26,7 @@ export function ResourceListItem({
style={{ borderRadius: "var(--mantine-radius-md)" }}
onClick={onClick}
>
<Stack gap="xs">
<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>
<Text fw={500}>{name}</Text>
</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 { CopyButton } from "../../atoms/CopyButton/CopyButton";
import { SubscribeButton } from "../../atoms/SubscribeButton/SubscribeButton";
export interface ResourcePreviewPanelProps {
uri: string;
@ -12,6 +15,12 @@ export interface ResourcePreviewPanelProps {
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" {
if (mimeType === "application/json") return "json";
if (mimeType.startsWith("image/")) return "image";
@ -30,51 +39,53 @@ export function ResourcePreviewPanel({
}: ResourcePreviewPanelProps) {
return (
<Stack gap="md">
<Title order={4}>Content Preview</Title>
<Text size="sm">
URI:{" "}
<Text span c="blue">
{uri}
</Text>
</Text>
<Text size="sm" c="dimmed">
MIME: {mimeType}
</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>
)}
<Group justify="space-between" wrap="nowrap">
<Title order={4}>Resource</Title>
<Group gap="xs" wrap="nowrap">
<Text size="sm" c="blue" truncate="end">
{uri}
</Text>
<CopyButton value={uri} />
</Group>
</Group>
<ContentViewer
type={resolveContentType(mimeType)}
content={content}
mimeType={mimeType}
copyable
/>
<Group>
<Button
variant="light"
size="sm"
onClick={isSubscribed ? onUnsubscribe : onSubscribe}
>
{isSubscribed ? "Unsubscribe" : "Subscribe"}
</Button>
</Group>
{lastUpdated && (
<Text size="xs" c="dimmed" ta="right">
Last updated: {lastUpdated}
<Group justify="space-between" wrap="nowrap">
{lastUpdated ? (
<Text size="xs" c="dimmed">
Last updated: {lastUpdated}
</Text>
) : (
<span />
)}
<Text size="sm" c="dimmed">
{mimeType}
</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>
);
}

View file

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

View file

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

View file

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

View file

@ -76,7 +76,6 @@ export const WithResult: Story = {
content: "Hola, como estas?",
},
],
onCopy: fn(),
},
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 { PromptMessagesDisplay } from "../../molecules/PromptMessagesDisplay/PromptMessagesDisplay";
import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator";
@ -19,9 +19,10 @@ export function PromptsScreen({
onRefreshList,
}: PromptsScreenProps) {
return (
<Grid>
<Container size="xl" py="xl">
<Grid align="stretch">
<Grid.Col span={5}>
<Paper withBorder p="md">
<Card withBorder padding="lg" h="100%">
<Stack gap="md">
<ListChangedIndicator
visible={listChanged}
@ -29,10 +30,10 @@ export function PromptsScreen({
/>
<PromptArgumentsForm {...promptForm} />
</Stack>
</Paper>
</Card>
</Grid.Col>
<Grid.Col span={7}>
<Paper withBorder p="md">
<Card withBorder padding="lg" h="100%">
{messages ? (
<PromptMessagesDisplay {...messages} />
) : (
@ -40,8 +41,9 @@ export function PromptsScreen({
Select a prompt and click Get Prompt to see messages
</Text>
)}
</Paper>
</Card>
</Grid.Col>
</Grid>
</Container>
);
}

View file

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

View file

@ -4,7 +4,6 @@ import {
SimpleGrid,
Stack,
Text,
Title,
} from "@mantine/core";
import { ServerCard } from "../../molecules/ServerCard/ServerCard";
import { AddServerMenu } from "../../molecules/AddServerMenu/AddServerMenu";
@ -26,8 +25,7 @@ export function ServerListScreen({
return (
<Container size="xl" py="xl">
<Stack gap="lg">
<Group justify="space-between">
<Title order={1}>MCP Inspector</Title>
<Group justify="flex-end">
<AddServerMenu
onAddManually={onAddManually}
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 type { TaskCardProps } from "../../molecules/TaskCard/TaskCard";
@ -16,11 +16,11 @@ export function TasksScreen({
onClearHistory,
}: TasksScreenProps) {
return (
<Container size="xl" py="xl">
<Stack gap="lg">
<Group justify="space-between">
<Title order={2}>Tasks</Title>
<Button variant="light" onClick={onRefresh}>
Refresh
<Group justify="flex-end">
<Button variant="dark" onClick={onRefresh}>
Refresh Tasks
</Button>
</Group>
@ -37,9 +37,11 @@ export function TasksScreen({
<Group justify="space-between">
<Title order={4}>Completed Tasks ({completedTasks.length})</Title>
<Button variant="light" size="xs" onClick={onClearHistory}>
Clear History
</Button>
{completedTasks.length > 0 && (
<Button variant="light" size="xs" onClick={onClearHistory}>
Clear History
</Button>
)}
</Group>
{completedTasks.length === 0 ? (
<Text c="dimmed">No completed tasks</Text>
@ -51,5 +53,6 @@ export function TasksScreen({
</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 { ToolListItem } from "../../molecules/ToolListItem/ToolListItem";
import { ToolDetailPanel } from "../../molecules/ToolDetailPanel/ToolDetailPanel";
@ -35,10 +35,12 @@ export function ToolsScreen({
: tools;
return (
<Grid>
<Container size="xl" py="xl">
<Grid align="stretch">
<Grid.Col span={3}>
<Paper withBorder p="md">
<Card withBorder padding="lg" h="100%">
<Stack gap="sm">
<Title order={4}>Tools</Title>
<ListChangedIndicator
visible={listChanged}
onRefresh={onRefreshList}
@ -56,11 +58,11 @@ export function ToolsScreen({
/>
))}
</Stack>
</Paper>
</Card>
</Grid.Col>
<Grid.Col span={5}>
<Paper withBorder p="md" variant="contained">
<Card withBorder padding="lg" h="100%">
{selectedTool ? (
<ToolDetailPanel {...selectedTool} />
) : (
@ -68,11 +70,11 @@ export function ToolsScreen({
Select a tool to view details
</Text>
)}
</Paper>
</Card>
</Grid.Col>
<Grid.Col span={4}>
<Paper withBorder p="md">
<Card withBorder padding="lg" h="100%">
{result ? (
<ResultPanel {...result} />
) : (
@ -80,8 +82,9 @@ export function ToolsScreen({
Results will appear here
</Text>
)}
</Paper>
</Card>
</Grid.Col>
</Grid>
</Container>
);
}

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