Refactor renamed the component groups to something less obtuse:

Elements -> Groups -> Screens -> Views
This commit is contained in:
cliffhall 2026-03-28 12:58:47 -04:00
commit dd46acba87
2 changed files with 151 additions and 144 deletions

View file

@ -0,0 +1,94 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { fn } from "storybook/test";
import { TaskCard } from "./TaskCard";
const meta: Meta<typeof TaskCard> = {
title: "Groups/TaskCard",
component: TaskCard,
args: {
onViewDetails: fn(),
onViewResult: fn(),
onCancel: fn(),
onDismiss: fn(),
},
};
export default meta;
type Story = StoryObj<typeof TaskCard>;
export const Running: Story = {
args: {
taskId: "task-abc-123",
status: "running",
method: "tools/call",
target: "generate_report",
progress: 80,
progressDescription: "Generating report section 4 of 5...",
startedAt: "2026-03-17T10:30:00Z",
elapsed: "2m 15s",
},
};
export const RunningWithProgress: Story = {
args: {
taskId: "task-def-456",
status: "running",
method: "tools/call",
target: "analyze_data",
progress: 45,
progressDescription: "Analyzing dataset...",
startedAt: "2026-03-17T10:32:00Z",
elapsed: "30s",
},
};
export const Waiting: Story = {
args: {
taskId: "task-ghi-789",
status: "waiting",
method: "resources/read",
target: "file:///data/report.csv",
progress: 0,
startedAt: "2026-03-17T10:35:00Z",
},
};
export const Completed: Story = {
args: {
taskId: "task-jkl-012",
status: "completed",
method: "tools/call",
target: "generate_summary",
progress: 100,
startedAt: "2026-03-17T10:20:00Z",
completedAt: "2026-03-17T10:25:00Z",
elapsed: "5m 00s",
},
};
export const Failed: Story = {
args: {
taskId: "task-mno-345",
status: "failed",
method: "tools/call",
target: "fetch_remote_data",
progress: 45,
startedAt: "2026-03-17T10:28:00Z",
completedAt: "2026-03-17T10:29:30Z",
elapsed: "1m 30s",
error: "Connection refused: upstream server not responding after 3 retries",
},
};
export const Cancelled: Story = {
args: {
taskId: "task-pqr-678",
status: "cancelled",
method: "tools/call",
target: "long_running_analysis",
progress: 30,
startedAt: "2026-03-17T10:15:00Z",
completedAt: "2026-03-17T10:18:00Z",
elapsed: "3m 00s",
},
};

View file

@ -0,0 +1,159 @@
import { Badge, Button, Card, Group, Stack, Text } from "@mantine/core";
import { ProgressDisplay } from "../../elements/ProgressDisplay/ProgressDisplay";
export type TaskStatus =
| "waiting"
| "running"
| "completed"
| "failed"
| "cancelled";
export interface TaskCardProps {
taskId: string;
status: TaskStatus;
method: string;
target?: string;
progress?: number;
progressDescription?: string;
startedAt?: string;
completedAt?: string;
elapsed?: string;
error?: string;
onViewDetails: () => void;
onViewResult: () => void;
onCancel: () => void;
onDismiss: () => void;
}
const STATUS_COLORS: Record<TaskStatus, string> = {
waiting: "gray",
running: "blue",
completed: "green",
failed: "red",
cancelled: "yellow",
};
const TaskContainer = Card.withProps({
withBorder: true,
padding: "md",
});
const TaskIdText = Text.withProps({
size: "sm",
ff: "monospace",
});
const DetailText = Text.withProps({
size: "sm",
c: "dimmed",
});
const ValueSpan = Text.withProps({
component: "span",
fw: 600,
});
const ProgressNote = Text.withProps({
size: "sm",
c: "dimmed",
fs: "italic",
});
const SubtleButton = Button.withProps({
variant: "subtle",
size: "xs",
});
const CancelButton = Button.withProps({
variant: "subtle",
color: "red",
size: "xs",
});
function formatTaskId(taskId: string): string {
return `Task: ${taskId}`;
}
function formatTimeline(
startedAt: string,
completedAt?: string,
elapsed?: string,
): string {
let result = `Started: ${startedAt}`;
if (completedAt) result += ` | Completed: ${completedAt}`;
if (elapsed) result += ` | Elapsed: ${elapsed}`;
return result;
}
export function TaskCard({
taskId,
status,
method,
target,
progress,
progressDescription,
startedAt,
completedAt,
elapsed,
error,
onViewDetails,
onViewResult,
onCancel,
onDismiss,
}: TaskCardProps) {
const isActive = status === "waiting" || status === "running";
return (
<TaskContainer>
<Stack gap="xs">
<Group justify="space-between">
<Group>
<TaskIdText>{formatTaskId(taskId)}</TaskIdText>
<Badge color={STATUS_COLORS[status]}>{status}</Badge>
</Group>
{progress !== undefined && <ProgressDisplay progress={progress} />}
</Group>
<DetailText>
Method: <ValueSpan>{method}</ValueSpan>
</DetailText>
{target && (
<DetailText>
Tool/Resource: <ValueSpan>{target}</ValueSpan>
</DetailText>
)}
{startedAt && (
<DetailText>
{formatTimeline(startedAt, completedAt, elapsed)}
</DetailText>
)}
{progressDescription && (
<ProgressNote>{progressDescription}</ProgressNote>
)}
{error && (
<Text size="sm" c="red">
{error}
</Text>
)}
<Group justify="flex-end">
{isActive ? (
<>
<SubtleButton onClick={onViewDetails}>View Details</SubtleButton>
<CancelButton onClick={onCancel}>Cancel</CancelButton>
</>
) : (
<>
<SubtleButton onClick={onViewResult}>View Result</SubtleButton>
<SubtleButton onClick={onDismiss}>Dismiss</SubtleButton>
</>
)}
</Group>
</Stack>
</TaskContainer>
);
}