feat: Add HomeLayout and ConnectedLayout components with Storybook stories
- HomeLayout: AppShell with header (title + theme toggle) for disconnected state - ConnectedLayout: AppShell with server name, status indicator, tab navigation, theme toggle, and disconnect button for connected state All 6 phases of the Storybook component plan are now complete: 12 atoms, 25 molecules, 7 organisms, 2 layouts = 46 components total Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
019e10429d
commit
d53c3d338f
4 changed files with 211 additions and 0 deletions
|
|
@ -0,0 +1,53 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { fn } from 'storybook/test';
|
||||
import { Text } from '@mantine/core';
|
||||
import { ConnectedLayout } from './ConnectedLayout';
|
||||
|
||||
const allTabs = ['Tools', 'Resources', 'Prompts', 'Logs', 'Tasks', 'History'];
|
||||
|
||||
const meta: Meta<typeof ConnectedLayout> = {
|
||||
title: 'Layouts/ConnectedLayout',
|
||||
component: ConnectedLayout,
|
||||
parameters: { layout: 'fullscreen' },
|
||||
args: {
|
||||
serverName: 'my-mcp-server',
|
||||
status: 'connected',
|
||||
latencyMs: 23,
|
||||
availableTabs: allTabs,
|
||||
activeTab: 'Tools',
|
||||
onTabChange: fn(),
|
||||
onDisconnect: fn(),
|
||||
onToggleTheme: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ConnectedLayout>;
|
||||
|
||||
export const ToolsActive: Story = {
|
||||
args: {
|
||||
activeTab: 'Tools',
|
||||
children: <Text>Tools screen content</Text>,
|
||||
},
|
||||
};
|
||||
|
||||
export const ResourcesActive: Story = {
|
||||
args: {
|
||||
activeTab: 'Resources',
|
||||
children: <Text>Resources screen content</Text>,
|
||||
},
|
||||
};
|
||||
|
||||
export const LimitedTabs: Story = {
|
||||
args: {
|
||||
availableTabs: ['Tools', 'Resources', 'Prompts'],
|
||||
children: <Text>Tools screen content</Text>,
|
||||
},
|
||||
};
|
||||
|
||||
export const LongServerName: Story = {
|
||||
args: {
|
||||
serverName: 'my-very-long-server-name-that-might-overflow',
|
||||
children: <Text>Tools screen content</Text>,
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import type { ReactNode } from 'react';
|
||||
import { ActionIcon, AppShell, Button, Group, Tabs, Text } from '@mantine/core';
|
||||
import { useComputedColorScheme } from '@mantine/core';
|
||||
import { StatusIndicator } from '../../atoms/StatusIndicator/StatusIndicator';
|
||||
|
||||
export interface ConnectedLayoutProps {
|
||||
serverName: string;
|
||||
status: 'connected' | 'connecting' | 'failed';
|
||||
latencyMs?: number;
|
||||
activeTab: string;
|
||||
availableTabs: string[];
|
||||
onTabChange: (tab: string) => void;
|
||||
onDisconnect: () => void;
|
||||
onToggleTheme: () => void;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const ServerName = Text.withProps({
|
||||
fw: 600,
|
||||
size: 'lg',
|
||||
});
|
||||
|
||||
export function ConnectedLayout({
|
||||
serverName,
|
||||
status,
|
||||
latencyMs,
|
||||
activeTab,
|
||||
availableTabs,
|
||||
onTabChange,
|
||||
onDisconnect,
|
||||
onToggleTheme,
|
||||
children,
|
||||
}: ConnectedLayoutProps) {
|
||||
const colorScheme = useComputedColorScheme();
|
||||
|
||||
return (
|
||||
<AppShell header={{ height: 60 }} padding="md">
|
||||
<AppShell.Header>
|
||||
<Group h="100%" px="md" justify="space-between" wrap="nowrap">
|
||||
<Group gap="md" wrap="nowrap">
|
||||
<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 gap="sm">
|
||||
<ActionIcon
|
||||
variant="default"
|
||||
size="lg"
|
||||
aria-label="Toggle color scheme"
|
||||
onClick={onToggleTheme}
|
||||
>
|
||||
{colorScheme === 'dark' ? '\u2600' : '\u263E'}
|
||||
</ActionIcon>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={onDisconnect}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
</AppShell.Header>
|
||||
|
||||
<AppShell.Main>{children}</AppShell.Main>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { fn } from 'storybook/test';
|
||||
import { Container, Paper, Stack, Text } from '@mantine/core';
|
||||
import { HomeLayout } from './HomeLayout';
|
||||
|
||||
const meta: Meta<typeof HomeLayout> = {
|
||||
title: 'Layouts/HomeLayout',
|
||||
component: HomeLayout,
|
||||
parameters: { layout: 'fullscreen' },
|
||||
args: {
|
||||
onToggleTheme: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof HomeLayout>;
|
||||
|
||||
export const Light: Story = {
|
||||
args: {
|
||||
children: <Text>Page content goes here</Text>,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithContent: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<Container>
|
||||
<Stack>
|
||||
<Paper p="md" shadow="xs">
|
||||
<Text fw={500}>Local Dev Server</Text>
|
||||
<Text size="sm" c="dimmed">npx @modelcontextprotocol/server-filesystem</Text>
|
||||
</Paper>
|
||||
<Paper p="md" shadow="xs">
|
||||
<Text fw={500}>Database Tools</Text>
|
||||
<Text size="sm" c="dimmed">python -m mcp_server_sqlite</Text>
|
||||
</Paper>
|
||||
<Paper p="md" shadow="xs">
|
||||
<Text fw={500}>Remote API Server</Text>
|
||||
<Text size="sm" c="dimmed">https://api.example.com/mcp</Text>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Container>
|
||||
),
|
||||
},
|
||||
};
|
||||
30
clients/web/src/components/layouts/HomeLayout/HomeLayout.tsx
Normal file
30
clients/web/src/components/layouts/HomeLayout/HomeLayout.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import type { ReactNode } from 'react';
|
||||
import { ActionIcon, AppShell, Group, Title, useComputedColorScheme } from '@mantine/core';
|
||||
|
||||
export interface HomeLayoutProps {
|
||||
children: ReactNode;
|
||||
onToggleTheme: () => void;
|
||||
}
|
||||
|
||||
export function HomeLayout({ children, onToggleTheme }: HomeLayoutProps) {
|
||||
const colorScheme = useComputedColorScheme();
|
||||
|
||||
return (
|
||||
<AppShell header={{ height: 60 }} padding="md">
|
||||
<AppShell.Header>
|
||||
<Group h="100%" px="md" justify="space-between">
|
||||
<Title order={2}>MCP Inspector</Title>
|
||||
<ActionIcon
|
||||
variant="default"
|
||||
size="lg"
|
||||
aria-label="Toggle color scheme"
|
||||
onClick={onToggleTheme}
|
||||
>
|
||||
{colorScheme === 'dark' ? '\u2600' : '\u263E'}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</AppShell.Header>
|
||||
<AppShell.Main>{children}</AppShell.Main>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue