From d0539fcd655ef2caa8b4d263ae752196116ee531 Mon Sep 17 00:00:00 2001 From: Philip Levy Date: Thu, 16 Jul 2026 14:48:44 -0400 Subject: [PATCH 1/4] Add task progress component --- src/components/Chat/TaskProgress.stories.tsx | 77 ++++++++++ src/components/Chat/TaskProgress.tsx | 139 +++++++++++++++++++ src/components/Chat/index.ts | 3 + 3 files changed, 219 insertions(+) create mode 100644 src/components/Chat/TaskProgress.stories.tsx create mode 100644 src/components/Chat/TaskProgress.tsx diff --git a/src/components/Chat/TaskProgress.stories.tsx b/src/components/Chat/TaskProgress.stories.tsx new file mode 100644 index 0000000..9644f2f --- /dev/null +++ b/src/components/Chat/TaskProgress.stories.tsx @@ -0,0 +1,77 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { TaskProgress } from './TaskProgress' +import type { Task } from './TaskProgress' + +const meta = { + title: 'Components/Chat/TaskProgress', + component: TaskProgress, + tags: ['autodocs'], + parameters: { layout: 'padded' }, +} satisfies Meta + +export default meta +type Story = StoryObj + +const defaultTasks: Task[] = [ + { id: '1', label: 'Create loan application record type', status: 'COMPLETED' }, + { id: '2', label: 'Create document record type', status: 'COMPLETED' }, + { id: '3', label: 'Create brand selection interface', status: 'ACTIVE' }, + { id: '4', label: 'Build loan application form interface', status: 'TODO' }, + { id: '5', label: 'Create document upload component', status: 'TODO' }, +] + +export const Default: Story = { + args: { + title: 'Task Progress', + tasks: defaultTasks, + defaultOpen: true, + }, +} + +export const Collapsed: Story = { + args: { + title: 'Task Progress', + tasks: defaultTasks, + defaultOpen: false, + }, +} + +export const AllCompleted: Story = { + args: { + title: 'Completed Tasks', + tasks: [ + { id: '1', label: 'Setup project structure', status: 'COMPLETED' }, + { id: '2', label: 'Install dependencies', status: 'COMPLETED' }, + { id: '3', label: 'Create components', status: 'COMPLETED' }, + { id: '4', label: 'Write documentation', status: 'COMPLETED' }, + ], + defaultOpen: true, + }, +} + +export const MostlyTodo: Story = { + args: { + title: 'Upcoming Tasks', + tasks: [ + { id: '1', label: 'Setup project', status: 'COMPLETED' }, + { id: '2', label: 'Design components', status: 'ACTIVE' }, + { id: '3', label: 'Implement authentication', status: 'TODO' }, + { id: '4', label: 'Add database integration', status: 'TODO' }, + { id: '5', label: 'Write tests', status: 'TODO' }, + { id: '6', label: 'Deploy to production', status: 'TODO' }, + ], + defaultOpen: true, + }, +} + +export const CustomTitle: Story = { + args: { + title: 'Sprint 3 Progress', + tasks: [ + { id: '1', label: 'User authentication module', status: 'COMPLETED' }, + { id: '2', label: 'Dashboard layout', status: 'COMPLETED' }, + { id: '3', label: 'API integration', status: 'ACTIVE' }, + ], + defaultOpen: true, + }, +} diff --git a/src/components/Chat/TaskProgress.tsx b/src/components/Chat/TaskProgress.tsx new file mode 100644 index 0000000..c6f4f0c --- /dev/null +++ b/src/components/Chat/TaskProgress.tsx @@ -0,0 +1,139 @@ +import * as React from 'react' +import { useState } from 'react' +import { ChevronDown, Check, Circle } from 'lucide-react' +import { mergeClasses } from '../../utils/classNames' + +type TaskStatus = "COMPLETED" | "ACTIVE" | "TODO" + +export interface Task { + /** Unique identifier for the task */ + id: string + /** Task description */ + label: string + /** Task status */ + status: TaskStatus +} + +export interface TaskProgressProps { + /** Section title displayed in the collapsible header */ + title?: string + /** Array of tasks to display */ + tasks: Task[] + /** Whether the task list starts expanded */ + defaultOpen?: boolean + /** Additional Tailwind classes for prototype-specific styling (not part of SAIL API) */ + className?: string +} + +/** + * TaskProgress Component + * Displays a collapsible list of tasks with a progress bar summarizing completion. + * Used in chat interfaces to visualize multi-step task execution state. + */ +export const TaskProgress: React.FC = ({ + title = "Task Progress", + tasks, + defaultOpen = true, + className, +}) => { + const [open, setOpen] = useState(defaultOpen) + + const completedCount = tasks.filter((t) => t.status === "COMPLETED").length + const totalCount = tasks.length + const progressValue = totalCount > 0 ? (completedCount / totalCount) * 100 : 0 + + const sailClasses = 'border border-gray-200 rounded-md overflow-hidden bg-white' + + return ( +
+ + + {open && ( +
+
    + {tasks.map((task) => ( + + ))} +
+
+ )} +
+ ) +} + +interface TaskItemProps { + task: Task +} + +const TaskItem: React.FC = ({ task }) => { + const statusIcon = () => { + if (task.status === "COMPLETED") { + return