storybook component fixes and improvements
- Add @storybook/addon-actions for action logging in stories - Add onTestCapability callback to ExperimentalFeaturesPanel - Use CopyButton overlay in ResourcePreviewPanel and ResultPanel - Replace includeContext checkbox with badge in SamplingRequestPanel - Add LongServerName story for ServerCard - Fix long tool name overflow with truncation and contained Paper variant - Add optional title prop to ToolListItem and ToolDetailPanel - Move tool annotations from list to detail panel form - Vertically center static text in ResourceTemplateInput - Fix InlineError blank space when no details Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4a4818e933
commit
1da76c53ef
18 changed files with 167 additions and 110 deletions
|
|
@ -7,6 +7,7 @@ const config: StorybookConfig = {
|
|||
],
|
||||
"addons": [
|
||||
"@chromatic-com/storybook",
|
||||
"@storybook/addon-actions",
|
||||
"@storybook/addon-vitest",
|
||||
"@storybook/addon-a11y",
|
||||
"@storybook/addon-docs",
|
||||
|
|
|
|||
12
clients/web/package-lock.json
generated
12
clients/web/package-lock.json
generated
|
|
@ -20,6 +20,7 @@
|
|||
"@chromatic-com/storybook": "^5.0.1",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@storybook/addon-a11y": "^10.2.19",
|
||||
"@storybook/addon-actions": "^9.0.8",
|
||||
"@storybook/addon-docs": "^10.2.19",
|
||||
"@storybook/addon-onboarding": "^10.2.19",
|
||||
"@storybook/addon-vitest": "^10.2.19",
|
||||
|
|
@ -1695,6 +1696,17 @@
|
|||
"storybook": "^10.2.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-actions": {
|
||||
"version": "9.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-9.0.8.tgz",
|
||||
"integrity": "sha512-LFePu7PPnWN0Il/uoUpmA5T0J0C7d6haJIbg0pXrjxW2MQVSYXE4S4LSUz8fOImltBDV3xAl6tLPYHFj6VcrOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/storybook"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-docs": {
|
||||
"version": "10.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.19.tgz",
|
||||
|
|
|
|||
|
|
@ -23,30 +23,31 @@
|
|||
"react-dom": "^19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^5.0.1",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@storybook/addon-a11y": "^10.2.19",
|
||||
"@storybook/addon-actions": "^9.0.8",
|
||||
"@storybook/addon-docs": "^10.2.19",
|
||||
"@storybook/addon-onboarding": "^10.2.19",
|
||||
"@storybook/addon-vitest": "^10.2.19",
|
||||
"@storybook/react-vite": "^10.2.19",
|
||||
"@types/node": "^24.12.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.0",
|
||||
"@vitest/browser-playwright": "^4.1.0",
|
||||
"@vitest/coverage-v8": "^4.1.0",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"eslint-plugin-storybook": "^10.2.19",
|
||||
"globals": "^17.4.0",
|
||||
"playwright": "^1.58.2",
|
||||
"prettier": "^3.8.1",
|
||||
"storybook": "^10.2.19",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.56.1",
|
||||
"prettier": "^3.8.1",
|
||||
"vite": "^8.0.0",
|
||||
"storybook": "^10.2.19",
|
||||
"@storybook/react-vite": "^10.2.19",
|
||||
"@chromatic-com/storybook": "^5.0.1",
|
||||
"@storybook/addon-vitest": "^10.2.19",
|
||||
"@storybook/addon-a11y": "^10.2.19",
|
||||
"@storybook/addon-docs": "^10.2.19",
|
||||
"@storybook/addon-onboarding": "^10.2.19",
|
||||
"eslint-plugin-storybook": "^10.2.19",
|
||||
"vitest": "^4.1.0",
|
||||
"playwright": "^1.58.2",
|
||||
"@vitest/browser-playwright": "^4.1.0",
|
||||
"@vitest/coverage-v8": "^4.1.0"
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const meta: Meta<typeof ExperimentalFeaturesPanel> = {
|
|||
onRemoveHeader: fn(),
|
||||
onHeaderChange: fn(),
|
||||
onCopyResponse: fn(),
|
||||
onTestCapability: fn(),
|
||||
clientCapabilities: [
|
||||
{ name: "experimental/customSampling", enabled: false },
|
||||
{ name: "experimental/batchRequests", enabled: true },
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export interface ExperimentalFeaturesPanelProps {
|
|||
onRemoveHeader: (index: number) => void;
|
||||
onHeaderChange: (index: number, key: string, value: string) => void;
|
||||
onCopyResponse: () => void;
|
||||
onTestCapability: (name: string) => void;
|
||||
}
|
||||
|
||||
export function ExperimentalFeaturesPanel({
|
||||
|
|
@ -67,6 +68,7 @@ export function ExperimentalFeaturesPanel({
|
|||
onRemoveHeader,
|
||||
onHeaderChange,
|
||||
onCopyResponse,
|
||||
onTestCapability,
|
||||
}: ExperimentalFeaturesPanelProps) {
|
||||
return (
|
||||
<Stack gap="md">
|
||||
|
|
@ -94,7 +96,11 @@ export function ExperimentalFeaturesPanel({
|
|||
</Text>
|
||||
)}
|
||||
<Group>
|
||||
<Button size="xs" variant="light">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={() => onTestCapability(cap.name)}
|
||||
>
|
||||
Test →
|
||||
</Button>
|
||||
</Group>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ const meta: Meta<typeof ResourcePreviewPanel> = {
|
|||
title: "Molecules/ResourcePreviewPanel",
|
||||
component: ResourcePreviewPanel,
|
||||
args: {
|
||||
onCopy: fn(),
|
||||
onSubscribe: fn(),
|
||||
onUnsubscribe: fn(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ export interface ResourcePreviewPanelProps {
|
|||
content: string;
|
||||
lastUpdated?: string;
|
||||
isSubscribed: boolean;
|
||||
onCopy: () => void;
|
||||
onSubscribe: () => void;
|
||||
onUnsubscribe: () => void;
|
||||
}
|
||||
|
|
@ -26,7 +25,6 @@ export function ResourcePreviewPanel({
|
|||
content,
|
||||
lastUpdated,
|
||||
isSubscribed,
|
||||
onCopy,
|
||||
onSubscribe,
|
||||
onUnsubscribe,
|
||||
}: ResourcePreviewPanelProps) {
|
||||
|
|
@ -61,11 +59,9 @@ export function ResourcePreviewPanel({
|
|||
type={resolveContentType(mimeType)}
|
||||
content={content}
|
||||
mimeType={mimeType}
|
||||
copyable
|
||||
/>
|
||||
<Group>
|
||||
<Button variant="light" size="sm" onClick={onCopy}>
|
||||
Copy
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
size="sm"
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export function ResourceTemplateInput({
|
|||
<Group gap="xs" align="flex-end">
|
||||
{parts.map((part, index) =>
|
||||
part.type === "static" ? (
|
||||
<Text key={index} size="sm">
|
||||
<Text key={index} size="sm" pb={6}>
|
||||
{part.value}
|
||||
</Text>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ const meta: Meta<typeof ResultPanel> = {
|
|||
title: "Molecules/ResultPanel",
|
||||
component: ResultPanel,
|
||||
args: {
|
||||
onCopy: fn(),
|
||||
onClear: fn(),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ export interface ResultContentItem {
|
|||
|
||||
export interface ResultPanelProps {
|
||||
content: ResultContentItem[];
|
||||
onCopy: () => void;
|
||||
onClear: () => void;
|
||||
}
|
||||
|
||||
export function ResultPanel({ content, onCopy, onClear }: ResultPanelProps) {
|
||||
export function ResultPanel({ content, onClear }: ResultPanelProps) {
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={4}>Results</Title>
|
||||
|
|
@ -29,6 +28,7 @@ export function ResultPanel({ content, onCopy, onClear }: ResultPanelProps) {
|
|||
key={index}
|
||||
type={item.type === "text" ? "text" : "json"}
|
||||
content={item.text}
|
||||
copyable
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -47,9 +47,6 @@ export function ResultPanel({ content, onCopy, onClear }: ResultPanelProps) {
|
|||
</>
|
||||
)}
|
||||
<Group>
|
||||
<Button variant="light" size="sm" onClick={onCopy}>
|
||||
Copy
|
||||
</Button>
|
||||
<Button variant="light" size="sm" onClick={onClear}>
|
||||
Clear
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Group,
|
||||
Paper,
|
||||
|
|
@ -134,11 +133,10 @@ export function SamplingRequestPanel({
|
|||
</Text>
|
||||
|
||||
{includeContext && (
|
||||
<Checkbox
|
||||
label={`Include Context: ${includeContext}`}
|
||||
checked
|
||||
readOnly
|
||||
/>
|
||||
<Group gap="xs">
|
||||
<Text size="sm">Include Context:</Text>
|
||||
<Badge>{includeContext}</Badge>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{tools && tools.length > 0 && (
|
||||
|
|
|
|||
|
|
@ -89,6 +89,19 @@ export const WithClientFeatures: Story = {
|
|||
},
|
||||
};
|
||||
|
||||
export const LongServerName: Story = {
|
||||
args: {
|
||||
name: "my-organization-super-long-experimental-mcp-server-with-many-features-v2",
|
||||
version: "1.0.0-beta.42",
|
||||
transport: "stdio",
|
||||
connectionMode: "Subprocess",
|
||||
command:
|
||||
"npx -y @my-organization/super-long-experimental-mcp-server-with-many-features-v2",
|
||||
status: "connected",
|
||||
canTestClientFeatures: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const HttpDirect: Story = {
|
||||
args: {
|
||||
name: "Remote API Server",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Button, Divider, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { Button, Divider, Group, Stack, Text } from "@mantine/core";
|
||||
import { AnnotationBadge } from "../../atoms/AnnotationBadge/AnnotationBadge";
|
||||
import { ProgressDisplay } from "../../atoms/ProgressDisplay/ProgressDisplay";
|
||||
import { SchemaForm } from "../SchemaForm/SchemaForm";
|
||||
|
|
@ -14,6 +14,7 @@ export interface ToolAnnotations {
|
|||
|
||||
export interface ToolDetailPanelProps {
|
||||
name: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
annotations?: ToolAnnotations;
|
||||
schema: JsonSchema;
|
||||
|
|
@ -27,6 +28,7 @@ export interface ToolDetailPanelProps {
|
|||
|
||||
export function ToolDetailPanel({
|
||||
name,
|
||||
title,
|
||||
description,
|
||||
annotations,
|
||||
schema,
|
||||
|
|
@ -46,8 +48,39 @@ export function ToolDetailPanel({
|
|||
annotations.hints);
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Title order={4}>Tool: {name}</Title>
|
||||
<Stack gap="md" miw={0}>
|
||||
<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 && (
|
||||
<AnnotationBadge
|
||||
label={annotations.audience}
|
||||
variant="audience"
|
||||
/>
|
||||
)}
|
||||
{annotations.readOnly && (
|
||||
<AnnotationBadge label="read-only" variant="readOnly" />
|
||||
)}
|
||||
{annotations.destructive && (
|
||||
<AnnotationBadge label="destructive" variant="destructive" />
|
||||
)}
|
||||
{annotations.longRunning && (
|
||||
<AnnotationBadge label="long-run" variant="longRun" />
|
||||
)}
|
||||
{annotations.hints && (
|
||||
<Text size="xs" c="dimmed" fs="italic">
|
||||
{annotations.hints}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{description && (
|
||||
<Text size="sm" c="dimmed">
|
||||
|
|
@ -55,34 +88,6 @@ export function ToolDetailPanel({
|
|||
</Text>
|
||||
)}
|
||||
|
||||
{hasAnnotations && (
|
||||
<>
|
||||
<Text size="sm">Annotations:</Text>
|
||||
<Group gap="xs">
|
||||
{annotations.audience && (
|
||||
<AnnotationBadge
|
||||
label={annotations.audience}
|
||||
variant="audience"
|
||||
/>
|
||||
)}
|
||||
{annotations.readOnly && (
|
||||
<AnnotationBadge label="read-only" variant="readOnly" />
|
||||
)}
|
||||
{annotations.destructive && (
|
||||
<AnnotationBadge label="destructive" variant="destructive" />
|
||||
)}
|
||||
{annotations.longRunning && (
|
||||
<AnnotationBadge label="long-run" variant="longRun" />
|
||||
)}
|
||||
{annotations.hints && (
|
||||
<Text size="xs" c="dimmed" fs="italic">
|
||||
{annotations.hints}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
<SchemaForm
|
||||
|
|
|
|||
|
|
@ -27,27 +27,11 @@ export const Selected: Story = {
|
|||
},
|
||||
};
|
||||
|
||||
export const WithAnnotations: Story = {
|
||||
export const WithTitle: Story = {
|
||||
args: {
|
||||
name: "get_weather",
|
||||
title: "Get Weather",
|
||||
selected: false,
|
||||
annotations: [
|
||||
{ label: "user", variant: "audience" },
|
||||
{ label: "read-only", variant: "readOnly" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const MultipleAnnotations: Story = {
|
||||
args: {
|
||||
name: "update_database",
|
||||
selected: false,
|
||||
annotations: [
|
||||
{ label: "user", variant: "audience" },
|
||||
{ label: "read-only", variant: "readOnly" },
|
||||
{ label: "destructive", variant: "destructive" },
|
||||
{ label: "long-run", variant: "longRun" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,15 @@
|
|||
import { Group, Stack, Text, UnstyledButton } from "@mantine/core";
|
||||
import { AnnotationBadge } from "../../atoms/AnnotationBadge/AnnotationBadge";
|
||||
import type { AnnotationVariant } from "../../atoms/AnnotationBadge/AnnotationBadge";
|
||||
|
||||
export interface ToolAnnotation {
|
||||
label: string;
|
||||
variant?: AnnotationVariant;
|
||||
}
|
||||
import { Stack, Text, UnstyledButton } from "@mantine/core";
|
||||
|
||||
export interface ToolListItemProps {
|
||||
name: string;
|
||||
annotations?: ToolAnnotation[];
|
||||
title?: string;
|
||||
selected: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export function ToolListItem({
|
||||
name,
|
||||
annotations,
|
||||
title,
|
||||
selected,
|
||||
onClick,
|
||||
}: ToolListItemProps) {
|
||||
|
|
@ -28,18 +21,14 @@ export function ToolListItem({
|
|||
style={{ borderRadius: "var(--mantine-radius-md)" }}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text fw={500}>{name}</Text>
|
||||
{annotations && annotations.length > 0 && (
|
||||
<Group gap="xs">
|
||||
{annotations.map((annotation) => (
|
||||
<AnnotationBadge
|
||||
key={annotation.label}
|
||||
label={annotation.label}
|
||||
variant={annotation.variant}
|
||||
/>
|
||||
))}
|
||||
</Group>
|
||||
<Stack gap={2}>
|
||||
<Text fw={500} truncate>
|
||||
{title ?? name}
|
||||
</Text>
|
||||
{title && (
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{name}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</UnstyledButton>
|
||||
|
|
|
|||
|
|
@ -21,23 +21,26 @@ export default meta;
|
|||
type Story = StoryObj<typeof ToolsScreen>;
|
||||
|
||||
const sampleTools: ToolListItemProps[] = [
|
||||
{ name: "send_message", selected: false, onClick: fn() },
|
||||
{
|
||||
name: "send_message",
|
||||
title: "Send Message",
|
||||
selected: false,
|
||||
onClick: fn(),
|
||||
},
|
||||
{
|
||||
name: "create_record",
|
||||
annotations: [{ label: "read-only" }],
|
||||
title: "Create Record",
|
||||
selected: false,
|
||||
onClick: fn(),
|
||||
},
|
||||
{
|
||||
name: "delete_records",
|
||||
annotations: [{ label: "destructive", variant: "destructive" }],
|
||||
selected: false,
|
||||
onClick: fn(),
|
||||
},
|
||||
{ name: "list_users", selected: false, onClick: fn() },
|
||||
{
|
||||
name: "batch_process",
|
||||
annotations: [{ label: "long-run", variant: "longRun" }],
|
||||
selected: false,
|
||||
onClick: fn(),
|
||||
},
|
||||
|
|
@ -45,6 +48,7 @@ const sampleTools: ToolListItemProps[] = [
|
|||
|
||||
const selectedToolData: ToolDetailPanelProps = {
|
||||
name: "create_record",
|
||||
title: "Create Record",
|
||||
description: "Creates a new record with the given parameters",
|
||||
schema: {
|
||||
type: "object",
|
||||
|
|
@ -79,7 +83,6 @@ const resultData: ResultPanelProps = {
|
|||
),
|
||||
},
|
||||
],
|
||||
onCopy: fn(),
|
||||
onClear: fn(),
|
||||
};
|
||||
|
||||
|
|
@ -111,6 +114,51 @@ export const WithResult: Story = {
|
|||
},
|
||||
};
|
||||
|
||||
export const LongToolName: Story = {
|
||||
args: {
|
||||
tools: [
|
||||
...sampleTools,
|
||||
{
|
||||
name: "organization_internal_database_complex_multi_table_join_query_with_aggregation_and_filtering",
|
||||
selected: true,
|
||||
onClick: fn(),
|
||||
},
|
||||
],
|
||||
selectedTool: {
|
||||
name: "organization_internal_database_complex_multi_table_join_query_with_aggregation_and_filtering",
|
||||
annotations: {
|
||||
readOnly: true,
|
||||
longRunning: true,
|
||||
},
|
||||
description:
|
||||
"Executes a complex multi-table join query across the organization's internal database with support for aggregation functions, nested filtering, and pagination of large result sets.",
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
primary_table_name: {
|
||||
type: "string",
|
||||
description: "The main table to query from",
|
||||
},
|
||||
join_configuration: {
|
||||
type: "string",
|
||||
description: "JSON configuration for table joins",
|
||||
},
|
||||
aggregation_functions: {
|
||||
type: "string",
|
||||
description: "Comma-separated list of aggregation functions to apply",
|
||||
},
|
||||
},
|
||||
required: ["primary_table_name"],
|
||||
},
|
||||
formValues: {},
|
||||
isExecuting: false,
|
||||
onFormChange: fn(),
|
||||
onExecute: fn(),
|
||||
onCancel: fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const WithListChanged: Story = {
|
||||
args: {
|
||||
tools: sampleTools,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export function ToolsScreen({
|
|||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={5}>
|
||||
<Paper withBorder p="md">
|
||||
<Paper withBorder p="md" variant="contained">
|
||||
{selectedTool ? (
|
||||
<ToolDetailPanel {...selectedTool} />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ export const ThemePaper = Paper.extend({
|
|||
},
|
||||
};
|
||||
}
|
||||
if (props.variant === "contained") {
|
||||
return {
|
||||
root: {
|
||||
overflow: "hidden",
|
||||
minWidth: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
return { root: {} };
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue