From d53c3d338fbf042b2f4a46ab3e36659e027abd58 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Tue, 17 Mar 2026 10:47:52 -0400 Subject: [PATCH] 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) --- .../ConnectedLayout.stories.tsx | 53 ++++++++++++ .../ConnectedLayout/ConnectedLayout.tsx | 83 +++++++++++++++++++ .../layouts/HomeLayout/HomeLayout.stories.tsx | 45 ++++++++++ .../layouts/HomeLayout/HomeLayout.tsx | 30 +++++++ 4 files changed, 211 insertions(+) create mode 100644 clients/web/src/components/layouts/ConnectedLayout/ConnectedLayout.stories.tsx create mode 100644 clients/web/src/components/layouts/ConnectedLayout/ConnectedLayout.tsx create mode 100644 clients/web/src/components/layouts/HomeLayout/HomeLayout.stories.tsx create mode 100644 clients/web/src/components/layouts/HomeLayout/HomeLayout.tsx diff --git a/clients/web/src/components/layouts/ConnectedLayout/ConnectedLayout.stories.tsx b/clients/web/src/components/layouts/ConnectedLayout/ConnectedLayout.stories.tsx new file mode 100644 index 00000000..b20e0a08 --- /dev/null +++ b/clients/web/src/components/layouts/ConnectedLayout/ConnectedLayout.stories.tsx @@ -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 = { + 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; + +export const ToolsActive: Story = { + args: { + activeTab: 'Tools', + children: Tools screen content, + }, +}; + +export const ResourcesActive: Story = { + args: { + activeTab: 'Resources', + children: Resources screen content, + }, +}; + +export const LimitedTabs: Story = { + args: { + availableTabs: ['Tools', 'Resources', 'Prompts'], + children: Tools screen content, + }, +}; + +export const LongServerName: Story = { + args: { + serverName: 'my-very-long-server-name-that-might-overflow', + children: Tools screen content, + }, +}; diff --git a/clients/web/src/components/layouts/ConnectedLayout/ConnectedLayout.tsx b/clients/web/src/components/layouts/ConnectedLayout/ConnectedLayout.tsx new file mode 100644 index 00000000..366682a3 --- /dev/null +++ b/clients/web/src/components/layouts/ConnectedLayout/ConnectedLayout.tsx @@ -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 ( + + + + + {serverName} + + + + value && onTabChange(value)} + variant="default" + > + + {availableTabs.map((tab) => ( + + {tab} + + ))} + + + + + + {colorScheme === 'dark' ? '\u2600' : '\u263E'} + + + + + + + {children} + + ); +} diff --git a/clients/web/src/components/layouts/HomeLayout/HomeLayout.stories.tsx b/clients/web/src/components/layouts/HomeLayout/HomeLayout.stories.tsx new file mode 100644 index 00000000..1467e949 --- /dev/null +++ b/clients/web/src/components/layouts/HomeLayout/HomeLayout.stories.tsx @@ -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 = { + title: 'Layouts/HomeLayout', + component: HomeLayout, + parameters: { layout: 'fullscreen' }, + args: { + onToggleTheme: fn(), + }, +}; + +export default meta; +type Story = StoryObj; + +export const Light: Story = { + args: { + children: Page content goes here, + }, +}; + +export const WithContent: Story = { + args: { + children: ( + + + + Local Dev Server + npx @modelcontextprotocol/server-filesystem + + + Database Tools + python -m mcp_server_sqlite + + + Remote API Server + https://api.example.com/mcp + + + + ), + }, +}; diff --git a/clients/web/src/components/layouts/HomeLayout/HomeLayout.tsx b/clients/web/src/components/layouts/HomeLayout/HomeLayout.tsx new file mode 100644 index 00000000..b7ed919d --- /dev/null +++ b/clients/web/src/components/layouts/HomeLayout/HomeLayout.tsx @@ -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 ( + + + + MCP Inspector + + {colorScheme === 'dark' ? '\u2600' : '\u263E'} + + + + {children} + + ); +}