Add comprehensive development roadmap via GitHub Issues
Created 10 detailed GitHub issues covering: - Project activation and management UI (#1-2) - Worker node coordination and visualization (#3-4) - Automated GitHub repository scanning (#5) - Intelligent model-to-issue matching (#6) - Multi-model task execution system (#7) - N8N workflow integration (#8) - Hive-Bzzz P2P bridge (#9) - Peer assistance protocol (#10) Each issue includes detailed specifications, acceptance criteria, technical implementation notes, and dependency mapping. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
566
frontend/src/components/ui/DataTable.stories.tsx
Normal file
566
frontend/src/components/ui/DataTable.stories.tsx
Normal file
@@ -0,0 +1,566 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import DataTable, { Column } from './DataTable';
|
||||
import { Badge } from './badge';
|
||||
import { Button } from './button';
|
||||
|
||||
/**
|
||||
* DataTable component for Hive UI
|
||||
*
|
||||
* A powerful and flexible data table component with sorting, filtering, searching, and pagination.
|
||||
* Perfect for displaying agent lists, task queues, and workflow executions.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'UI Components/DataTable',
|
||||
component: DataTable,
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
The DataTable component is a comprehensive solution for displaying tabular data in the Hive application.
|
||||
It provides powerful features for data manipulation and user interaction.
|
||||
|
||||
## Features
|
||||
- **Sorting**: Click column headers to sort data ascending/descending
|
||||
- **Filtering**: Column-specific filters with text, select, and numeric options
|
||||
- **Searching**: Global search across all visible columns
|
||||
- **Pagination**: Built-in pagination with configurable page sizes
|
||||
- **Custom Rendering**: Custom cell renderers for complex content
|
||||
- **Row Actions**: Clickable rows with custom action handlers
|
||||
- **Loading States**: Built-in loading indicator
|
||||
- **Responsive**: Horizontal scrolling on smaller screens
|
||||
|
||||
## Column Configuration
|
||||
\`\`\`tsx
|
||||
const columns: Column<Agent>[] = [
|
||||
{
|
||||
key: 'id',
|
||||
header: 'ID',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: 'w-32'
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
header: 'Status',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterType: 'select',
|
||||
filterOptions: [
|
||||
{ label: 'Available', value: 'available' },
|
||||
{ label: 'Busy', value: 'busy' },
|
||||
{ label: 'Offline', value: 'offline' }
|
||||
],
|
||||
render: (agent, value) => (
|
||||
<Badge variant={getStatusVariant(value)}>{value}</Badge>
|
||||
)
|
||||
}
|
||||
];
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
\`\`\`tsx
|
||||
import DataTable from '@/components/ui/DataTable';
|
||||
|
||||
<DataTable
|
||||
data={agents}
|
||||
columns={columns}
|
||||
searchable={true}
|
||||
pageSize={10}
|
||||
onRowClick={(agent) => navigate(\`/agents/\${agent.id}\`)}
|
||||
/>
|
||||
\`\`\`
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
data: {
|
||||
control: false,
|
||||
description: 'Array of data objects to display',
|
||||
},
|
||||
columns: {
|
||||
control: false,
|
||||
description: 'Column configuration array',
|
||||
},
|
||||
searchable: {
|
||||
control: 'boolean',
|
||||
description: 'Enable global search functionality',
|
||||
},
|
||||
searchPlaceholder: {
|
||||
control: 'text',
|
||||
description: 'Placeholder text for search input',
|
||||
},
|
||||
pageSize: {
|
||||
control: 'number',
|
||||
description: 'Number of rows per page',
|
||||
},
|
||||
loading: {
|
||||
control: 'boolean',
|
||||
description: 'Show loading state',
|
||||
},
|
||||
emptyMessage: {
|
||||
control: 'text',
|
||||
description: 'Message displayed when no data is available',
|
||||
},
|
||||
onRowClick: {
|
||||
action: 'row-clicked',
|
||||
description: 'Handler for row click events',
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof DataTable>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// Sample data for stories
|
||||
interface Agent {
|
||||
id: string;
|
||||
name: string;
|
||||
model: string;
|
||||
status: 'available' | 'busy' | 'offline';
|
||||
current_tasks: number;
|
||||
max_concurrent: number;
|
||||
specialization: string;
|
||||
last_heartbeat: string;
|
||||
utilization: number;
|
||||
}
|
||||
|
||||
const sampleAgents: Agent[] = [
|
||||
{
|
||||
id: 'walnut-codellama',
|
||||
name: 'Walnut CodeLlama',
|
||||
model: 'codellama:34b',
|
||||
status: 'available',
|
||||
current_tasks: 2,
|
||||
max_concurrent: 4,
|
||||
specialization: 'kernel_dev',
|
||||
last_heartbeat: '2024-01-15T10:30:00Z',
|
||||
utilization: 0.5,
|
||||
},
|
||||
{
|
||||
id: 'oak-gemma',
|
||||
name: 'Oak Gemma',
|
||||
model: 'gemma:7b',
|
||||
status: 'busy',
|
||||
current_tasks: 3,
|
||||
max_concurrent: 3,
|
||||
specialization: 'tester',
|
||||
last_heartbeat: '2024-01-15T10:29:45Z',
|
||||
utilization: 1.0,
|
||||
},
|
||||
{
|
||||
id: 'ironwood-llama',
|
||||
name: 'Ironwood Llama',
|
||||
model: 'llama2:13b',
|
||||
status: 'offline',
|
||||
current_tasks: 0,
|
||||
max_concurrent: 2,
|
||||
specialization: 'docs_writer',
|
||||
last_heartbeat: '2024-01-15T09:15:22Z',
|
||||
utilization: 0.0,
|
||||
},
|
||||
{
|
||||
id: 'pine-mistral',
|
||||
name: 'Pine Mistral',
|
||||
model: 'mistral:7b',
|
||||
status: 'available',
|
||||
current_tasks: 1,
|
||||
max_concurrent: 4,
|
||||
specialization: 'general_ai',
|
||||
last_heartbeat: '2024-01-15T10:29:58Z',
|
||||
utilization: 0.25,
|
||||
},
|
||||
{
|
||||
id: 'birch-phi',
|
||||
name: 'Birch Phi',
|
||||
model: 'phi:3b',
|
||||
status: 'busy',
|
||||
current_tasks: 2,
|
||||
max_concurrent: 2,
|
||||
specialization: 'profiler',
|
||||
last_heartbeat: '2024-01-15T10:30:12Z',
|
||||
utilization: 1.0,
|
||||
},
|
||||
];
|
||||
|
||||
const getStatusVariant = (status: string) => {
|
||||
switch (status) {
|
||||
case 'available': return 'success';
|
||||
case 'busy': return 'warning';
|
||||
case 'offline': return 'destructive';
|
||||
default: return 'secondary';
|
||||
}
|
||||
};
|
||||
|
||||
const agentColumns: Column<Agent>[] = [
|
||||
{
|
||||
key: 'id',
|
||||
header: 'Agent ID',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: 'w-40',
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
header: 'Name',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
header: 'Model',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterType: 'select',
|
||||
filterOptions: [
|
||||
{ label: 'CodeLlama 34B', value: 'codellama:34b' },
|
||||
{ label: 'Gemma 7B', value: 'gemma:7b' },
|
||||
{ label: 'Llama2 13B', value: 'llama2:13b' },
|
||||
{ label: 'Mistral 7B', value: 'mistral:7b' },
|
||||
{ label: 'Phi 3B', value: 'phi:3b' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
header: 'Status',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterType: 'select',
|
||||
filterOptions: [
|
||||
{ label: 'Available', value: 'available' },
|
||||
{ label: 'Busy', value: 'busy' },
|
||||
{ label: 'Offline', value: 'offline' },
|
||||
],
|
||||
render: (agent, value) => (
|
||||
<Badge variant={getStatusVariant(value) as any}>
|
||||
{value.charAt(0).toUpperCase() + value.slice(1)}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'current_tasks',
|
||||
header: 'Tasks',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterType: 'number',
|
||||
render: (agent) => `${agent.current_tasks} / ${agent.max_concurrent}`,
|
||||
},
|
||||
{
|
||||
key: 'specialization',
|
||||
header: 'Specialization',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterType: 'select',
|
||||
filterOptions: [
|
||||
{ label: 'Kernel Development', value: 'kernel_dev' },
|
||||
{ label: 'Testing', value: 'tester' },
|
||||
{ label: 'Documentation', value: 'docs_writer' },
|
||||
{ label: 'General AI', value: 'general_ai' },
|
||||
{ label: 'Profiler', value: 'profiler' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'utilization',
|
||||
header: 'Utilization',
|
||||
sortable: true,
|
||||
render: (agent) => {
|
||||
const percentage = Math.round(agent.utilization * 100);
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-16 bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${
|
||||
percentage >= 80 ? 'bg-red-500' : percentage >= 50 ? 'bg-yellow-500' : 'bg-green-500'
|
||||
}`}
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm">{percentage}%</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Basic agent data table
|
||||
*/
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
data: sampleAgents,
|
||||
columns: agentColumns,
|
||||
searchable: true,
|
||||
pageSize: 10,
|
||||
loading: false,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Loading state
|
||||
*/
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
data: [],
|
||||
columns: agentColumns,
|
||||
loading: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Empty state
|
||||
*/
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
data: [],
|
||||
columns: agentColumns,
|
||||
loading: false,
|
||||
emptyMessage: 'No agents found. Register some agents to get started.',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Small page size for pagination demo
|
||||
*/
|
||||
export const WithPagination: Story = {
|
||||
args: {
|
||||
data: [...sampleAgents, ...sampleAgents, ...sampleAgents], // 15 items
|
||||
columns: agentColumns,
|
||||
pageSize: 3,
|
||||
searchable: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple task data table
|
||||
*/
|
||||
export const TaskTable: Story = {
|
||||
render: () => {
|
||||
interface Task {
|
||||
id: string;
|
||||
type: string;
|
||||
priority: number;
|
||||
status: string;
|
||||
assigned_agent: string;
|
||||
created_at: string;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
const tasks: Task[] = [
|
||||
{
|
||||
id: 'task-001',
|
||||
type: 'code_analysis',
|
||||
priority: 1,
|
||||
status: 'in_progress',
|
||||
assigned_agent: 'walnut-codellama',
|
||||
created_at: '2024-01-15T10:00:00Z',
|
||||
progress: 75,
|
||||
},
|
||||
{
|
||||
id: 'task-002',
|
||||
type: 'testing',
|
||||
priority: 2,
|
||||
status: 'completed',
|
||||
assigned_agent: 'oak-gemma',
|
||||
created_at: '2024-01-15T09:30:00Z',
|
||||
progress: 100,
|
||||
},
|
||||
{
|
||||
id: 'task-003',
|
||||
type: 'documentation',
|
||||
priority: 3,
|
||||
status: 'pending',
|
||||
assigned_agent: null,
|
||||
created_at: '2024-01-15T10:15:00Z',
|
||||
progress: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const taskColumns: Column<Task>[] = [
|
||||
{
|
||||
key: 'id',
|
||||
header: 'Task ID',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
header: 'Type',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterType: 'select',
|
||||
filterOptions: [
|
||||
{ label: 'Code Analysis', value: 'code_analysis' },
|
||||
{ label: 'Testing', value: 'testing' },
|
||||
{ label: 'Documentation', value: 'documentation' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'priority',
|
||||
header: 'Priority',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterType: 'select',
|
||||
filterOptions: [
|
||||
{ label: 'Critical (1)', value: 1 },
|
||||
{ label: 'High (2)', value: 2 },
|
||||
{ label: 'Medium (3)', value: 3 },
|
||||
],
|
||||
render: (task) => {
|
||||
const priority = task.priority;
|
||||
const variant = priority === 1 ? 'destructive' : priority === 2 ? 'warning' : 'default';
|
||||
const label = priority === 1 ? 'Critical' : priority === 2 ? 'High' : 'Medium';
|
||||
return <Badge variant={variant as any}>{label}</Badge>;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
header: 'Status',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
filterType: 'select',
|
||||
filterOptions: [
|
||||
{ label: 'Pending', value: 'pending' },
|
||||
{ label: 'In Progress', value: 'in_progress' },
|
||||
{ label: 'Completed', value: 'completed' },
|
||||
],
|
||||
render: (task, value) => {
|
||||
const variant = value === 'completed' ? 'success' : value === 'in_progress' ? 'warning' : 'secondary';
|
||||
return <Badge variant={variant as any}>{value.replace('_', ' ')}</Badge>;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'assigned_agent',
|
||||
header: 'Agent',
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
render: (task, value) => value || <span className="text-gray-400">Unassigned</span>,
|
||||
},
|
||||
{
|
||||
key: 'progress',
|
||||
header: 'Progress',
|
||||
sortable: true,
|
||||
render: (task) => (
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-16 bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className="bg-blue-500 h-2 rounded-full"
|
||||
style={{ width: `${task.progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm">{task.progress}%</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
data={tasks}
|
||||
columns={taskColumns}
|
||||
searchable={true}
|
||||
pageSize={10}
|
||||
emptyMessage="No tasks available"
|
||||
/>
|
||||
);
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Example task management table with custom rendering and status badges',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Interactive table with row actions
|
||||
*/
|
||||
export const WithRowActions: Story = {
|
||||
render: () => {
|
||||
const handleRowClick = (agent: Agent) => {
|
||||
alert(`Clicked on agent: ${agent.name}`);
|
||||
};
|
||||
|
||||
const columnsWithActions: Column<Agent>[] = [
|
||||
...agentColumns,
|
||||
{
|
||||
key: 'actions',
|
||||
header: 'Actions',
|
||||
render: (agent) => (
|
||||
<div className="flex space-x-2" onClick={(e) => e.stopPropagation()}>
|
||||
<Button size="sm" variant="outline">
|
||||
View
|
||||
</Button>
|
||||
<Button size="sm" variant="default">
|
||||
Assign
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
data={sampleAgents}
|
||||
columns={columnsWithActions}
|
||||
searchable={true}
|
||||
onRowClick={handleRowClick}
|
||||
pageSize={5}
|
||||
/>
|
||||
);
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Table with clickable rows and action buttons in cells',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Minimal table without search and filters
|
||||
*/
|
||||
export const Minimal: Story = {
|
||||
render: () => {
|
||||
const minimalColumns: Column<Agent>[] = [
|
||||
{
|
||||
key: 'name',
|
||||
header: 'Agent Name',
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
header: 'Status',
|
||||
render: (agent, value) => (
|
||||
<Badge variant={getStatusVariant(value) as any}>
|
||||
{value.charAt(0).toUpperCase() + value.slice(1)}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'current_tasks',
|
||||
header: 'Active Tasks',
|
||||
render: (agent) => `${agent.current_tasks} / ${agent.max_concurrent}`,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
data={sampleAgents}
|
||||
columns={minimalColumns}
|
||||
searchable={false}
|
||||
pageSize={10}
|
||||
className="border-0 shadow-none"
|
||||
/>
|
||||
);
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Simplified table without search, filters, or advanced features',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
258
frontend/src/components/ui/badge.stories.tsx
Normal file
258
frontend/src/components/ui/badge.stories.tsx
Normal file
@@ -0,0 +1,258 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Badge } from './badge';
|
||||
|
||||
/**
|
||||
* Badge component for Hive UI
|
||||
*
|
||||
* A small status indicator component used to display labels, statuses, and categories.
|
||||
* Perfect for showing agent statuses, task priorities, and workflow states.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'UI Components/Badge',
|
||||
component: Badge,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
The Badge component is used to display small labels and status indicators throughout the Hive application.
|
||||
It's commonly used for showing agent statuses, task priorities, and other categorical information.
|
||||
|
||||
## Features
|
||||
- Multiple color variants for different semantic meanings
|
||||
- Consistent sizing and typography
|
||||
- Rounded pill design for modern appearance
|
||||
- Customizable through className prop
|
||||
|
||||
## Usage
|
||||
\`\`\`tsx
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
<Badge variant="success">Online</Badge>
|
||||
<Badge variant="warning">Busy</Badge>
|
||||
<Badge variant="destructive">Offline</Badge>
|
||||
\`\`\`
|
||||
|
||||
## Semantic Meanings
|
||||
- **default**: Primary information or neutral status
|
||||
- **secondary**: Less important or secondary information
|
||||
- **success**: Positive status (available, completed, healthy)
|
||||
- **warning**: Attention needed (busy, pending, degraded)
|
||||
- **destructive**: Negative status (error, failed, offline)
|
||||
- **outline**: Minimal emphasis or placeholder
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['default', 'secondary', 'destructive', 'outline', 'success', 'warning'],
|
||||
description: 'Visual variant of the badge',
|
||||
},
|
||||
className: {
|
||||
control: 'text',
|
||||
description: 'Additional CSS classes',
|
||||
},
|
||||
children: {
|
||||
control: 'text',
|
||||
description: 'Badge content',
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Badge>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* Default blue badge
|
||||
*/
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: 'Default',
|
||||
variant: 'default',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Secondary gray badge
|
||||
*/
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
children: 'Secondary',
|
||||
variant: 'secondary',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Success green badge
|
||||
*/
|
||||
export const Success: Story = {
|
||||
args: {
|
||||
children: 'Available',
|
||||
variant: 'success',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Warning yellow badge
|
||||
*/
|
||||
export const Warning: Story = {
|
||||
args: {
|
||||
children: 'Busy',
|
||||
variant: 'warning',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Destructive red badge
|
||||
*/
|
||||
export const Destructive: Story = {
|
||||
args: {
|
||||
children: 'Offline',
|
||||
variant: 'destructive',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Outline variant
|
||||
*/
|
||||
export const Outline: Story = {
|
||||
args: {
|
||||
children: 'Outline',
|
||||
variant: 'outline',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* All variants showcase
|
||||
*/
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="default">Default</Badge>
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
<Badge variant="success">Success</Badge>
|
||||
<Badge variant="warning">Warning</Badge>
|
||||
<Badge variant="destructive">Destructive</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'All available badge variants displayed together',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Agent status badges as used in Hive
|
||||
*/
|
||||
export const AgentStatuses: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="success">Available</Badge>
|
||||
<Badge variant="warning">Busy</Badge>
|
||||
<Badge variant="destructive">Offline</Badge>
|
||||
<Badge variant="secondary">Maintenance</Badge>
|
||||
<Badge variant="default">Connected</Badge>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Common agent status badges used throughout the Hive application',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Task priority badges
|
||||
*/
|
||||
export const TaskPriorities: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="destructive">Critical</Badge>
|
||||
<Badge variant="warning">High</Badge>
|
||||
<Badge variant="default">Medium</Badge>
|
||||
<Badge variant="secondary">Low</Badge>
|
||||
<Badge variant="outline">Background</Badge>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Task priority badges with semantic color coding',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Task status badges
|
||||
*/
|
||||
export const TaskStatuses: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="secondary">Pending</Badge>
|
||||
<Badge variant="warning">In Progress</Badge>
|
||||
<Badge variant="success">Completed</Badge>
|
||||
<Badge variant="destructive">Failed</Badge>
|
||||
<Badge variant="outline">Cancelled</Badge>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Task execution status badges used in task management',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Workflow status badges
|
||||
*/
|
||||
export const WorkflowStatuses: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="success">Active</Badge>
|
||||
<Badge variant="warning">Running</Badge>
|
||||
<Badge variant="secondary">Paused</Badge>
|
||||
<Badge variant="destructive">Failed</Badge>
|
||||
<Badge variant="outline">Draft</Badge>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Workflow status badges used in workflow management',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom styled badges
|
||||
*/
|
||||
export const CustomStyling: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="default" className="text-lg px-4 py-1">Large Badge</Badge>
|
||||
<Badge variant="success" className="uppercase tracking-wider">Success</Badge>
|
||||
<Badge variant="warning" className="animate-pulse">Flashing</Badge>
|
||||
<Badge variant="outline" className="border-dashed border-2">Dashed Border</Badge>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Examples of custom styling applied to badges using the className prop',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
interface BadgeProps {
|
||||
className?: string;
|
||||
variant?: 'default' | 'secondary' | 'destructive' | 'outline';
|
||||
variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'warning';
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ export const Badge: React.FC<BadgeProps> = ({
|
||||
default: 'bg-blue-600 text-white',
|
||||
secondary: 'bg-gray-100 text-gray-900',
|
||||
destructive: 'bg-red-600 text-white',
|
||||
outline: 'border border-gray-300 bg-white'
|
||||
outline: 'border border-gray-300 bg-white',
|
||||
success: 'bg-green-600 text-white',
|
||||
warning: 'bg-yellow-600 text-white'
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
241
frontend/src/components/ui/button.stories.tsx
Normal file
241
frontend/src/components/ui/button.stories.tsx
Normal file
@@ -0,0 +1,241 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Button } from './button';
|
||||
|
||||
/**
|
||||
* Button component for Hive UI
|
||||
*
|
||||
* A versatile button component with multiple variants, sizes, and states.
|
||||
* Supports all standard button functionality with consistent styling.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'UI Components/Button',
|
||||
component: Button,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
The Button component is a fundamental UI element used throughout the Hive application.
|
||||
It provides consistent styling and behavior across different contexts.
|
||||
|
||||
## Features
|
||||
- Multiple visual variants (default, destructive, outline, secondary, ghost)
|
||||
- Different sizes (small, default, large)
|
||||
- Disabled state support
|
||||
- Loading state (future enhancement)
|
||||
- Icon support (via children)
|
||||
- Full accessibility support
|
||||
|
||||
## Usage
|
||||
\`\`\`tsx
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
<Button variant="default" size="default" onClick={handleClick}>
|
||||
Click me
|
||||
</Button>
|
||||
\`\`\`
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['default', 'destructive', 'outline', 'secondary', 'ghost'],
|
||||
description: 'Visual variant of the button',
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['sm', 'default', 'lg'],
|
||||
description: 'Size of the button',
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
description: 'Whether the button is disabled',
|
||||
},
|
||||
type: {
|
||||
control: 'select',
|
||||
options: ['button', 'submit', 'reset'],
|
||||
description: 'HTML button type',
|
||||
},
|
||||
children: {
|
||||
control: 'text',
|
||||
description: 'Button content',
|
||||
},
|
||||
onClick: {
|
||||
action: 'clicked',
|
||||
description: 'Click event handler',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
onClick: () => console.log('Button clicked'),
|
||||
},
|
||||
} satisfies Meta<typeof Button>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* Default button style with primary blue color
|
||||
*/
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: 'Default Button',
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Destructive variant for dangerous actions like deletion
|
||||
*/
|
||||
export const Destructive: Story = {
|
||||
args: {
|
||||
children: 'Delete Item',
|
||||
variant: 'destructive',
|
||||
size: 'default',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Outline variant for secondary actions
|
||||
*/
|
||||
export const Outline: Story = {
|
||||
args: {
|
||||
children: 'Outline Button',
|
||||
variant: 'outline',
|
||||
size: 'default',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Secondary variant for less important actions
|
||||
*/
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
children: 'Secondary Button',
|
||||
variant: 'secondary',
|
||||
size: 'default',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Ghost variant for minimal styling
|
||||
*/
|
||||
export const Ghost: Story = {
|
||||
args: {
|
||||
children: 'Ghost Button',
|
||||
variant: 'ghost',
|
||||
size: 'default',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Small size variant
|
||||
*/
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
children: 'Small Button',
|
||||
variant: 'default',
|
||||
size: 'sm',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Large size variant
|
||||
*/
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
children: 'Large Button',
|
||||
variant: 'default',
|
||||
size: 'lg',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Disabled state
|
||||
*/
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
children: 'Disabled Button',
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* All variants showcase
|
||||
*/
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Button variant="default">Default</Button>
|
||||
<Button variant="destructive">Destructive</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Showcase of all button variants side by side',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* All sizes showcase
|
||||
*/
|
||||
export const AllSizes: Story = {
|
||||
render: () => (
|
||||
<div className="flex items-center gap-4">
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="default">Default</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Showcase of all button sizes side by side',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Common Hive use cases
|
||||
*/
|
||||
export const HiveUseCases: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-4 max-w-md">
|
||||
<div className="flex gap-2">
|
||||
<Button variant="default">Create Agent</Button>
|
||||
<Button variant="outline">View Details</Button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="default">Execute Task</Button>
|
||||
<Button variant="secondary">Cancel</Button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="default">Deploy Workflow</Button>
|
||||
<Button variant="destructive">Stop Execution</Button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline">Export Logs</Button>
|
||||
<Button variant="ghost">Refresh</Button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Common button combinations used throughout the Hive application',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
340
frontend/src/components/ui/card.stories.tsx
Normal file
340
frontend/src/components/ui/card.stories.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from './card';
|
||||
import { Button } from './button';
|
||||
import { Badge } from './badge';
|
||||
|
||||
/**
|
||||
* Card component system for Hive UI
|
||||
*
|
||||
* A flexible card component system that provides a container for content.
|
||||
* Includes header, title, description, and content sections.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'UI Components/Card',
|
||||
component: Card,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
The Card component system provides a structured way to display content in containers.
|
||||
It's composed of several sub-components that work together to create consistent layouts.
|
||||
|
||||
## Components
|
||||
- **Card**: Main container component
|
||||
- **CardHeader**: Header section for titles and descriptions
|
||||
- **CardTitle**: Primary title text
|
||||
- **CardDescription**: Subtitle or description text
|
||||
- **CardContent**: Main content area
|
||||
|
||||
## Features
|
||||
- Consistent styling across the application
|
||||
- Flexible composition with sub-components
|
||||
- Responsive design support
|
||||
- Shadow and border styling
|
||||
- Customizable through className props
|
||||
|
||||
## Usage
|
||||
\`\`\`tsx
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Card Title</CardTitle>
|
||||
<CardDescription>Card description goes here</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Card content goes here</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
\`\`\`
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
className: {
|
||||
control: 'text',
|
||||
description: 'Additional CSS classes',
|
||||
},
|
||||
children: {
|
||||
control: false,
|
||||
description: 'Card content',
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Card>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* Basic card with title and content
|
||||
*/
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Card className="w-80">
|
||||
<CardHeader>
|
||||
<CardTitle>Default Card</CardTitle>
|
||||
<CardDescription>
|
||||
This is a basic card component with a title and description.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>This is the main content area of the card where you can put any content.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* Card with only content, no header
|
||||
*/
|
||||
export const ContentOnly: Story = {
|
||||
render: () => (
|
||||
<Card className="w-80">
|
||||
<CardContent>
|
||||
<p>This card contains only content without a header section.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* Agent status card as used in Hive
|
||||
*/
|
||||
export const AgentStatusCard: Story = {
|
||||
render: () => (
|
||||
<Card className="w-96">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>walnut-codellama</CardTitle>
|
||||
<CardDescription>Code analysis specialist agent</CardDescription>
|
||||
</div>
|
||||
<Badge variant="success">Available</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Model:</span>
|
||||
<span>codellama:34b</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Active Tasks:</span>
|
||||
<span>2 / 4</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Utilization:</span>
|
||||
<span>50%</span>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-4">
|
||||
<Button size="sm" variant="outline">View Details</Button>
|
||||
<Button size="sm" variant="default">Assign Task</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Example of how cards are used to display agent information in the Hive dashboard',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Task execution card as used in Hive
|
||||
*/
|
||||
export const TaskCard: Story = {
|
||||
render: () => (
|
||||
<Card className="w-96">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Code Analysis Task</CardTitle>
|
||||
<CardDescription>task-abc123 • 5 minutes ago</CardDescription>
|
||||
</div>
|
||||
<Badge variant="warning">In Progress</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Assigned Agent:</span>
|
||||
<span>walnut-codellama</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Priority:</span>
|
||||
<span>High</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Progress:</span>
|
||||
<span>75%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2 mt-2">
|
||||
<div className="bg-blue-600 h-2 rounded-full" style={{ width: '75%' }}></div>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-4">
|
||||
<Button size="sm" variant="outline">View Logs</Button>
|
||||
<Button size="sm" variant="destructive">Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Example of how cards are used to display task information in the Hive dashboard',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Workflow card as used in Hive
|
||||
*/
|
||||
export const WorkflowCard: Story = {
|
||||
render: () => (
|
||||
<Card className="w-96">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Code Review Pipeline</CardTitle>
|
||||
<CardDescription>Automated code review and testing workflow</CardDescription>
|
||||
</div>
|
||||
<Badge variant="success">Active</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Steps:</span>
|
||||
<span>4</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Success Rate:</span>
|
||||
<span>92.5%</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Last Run:</span>
|
||||
<span>2 hours ago</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-600">Executions:</span>
|
||||
<span>25</span>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-4">
|
||||
<Button size="sm" variant="default">Execute</Button>
|
||||
<Button size="sm" variant="outline">Edit</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Example of how cards are used to display workflow information in the Hive dashboard',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* System metrics card
|
||||
*/
|
||||
export const MetricsCard: Story = {
|
||||
render: () => (
|
||||
<Card className="w-80">
|
||||
<CardHeader>
|
||||
<CardTitle>System Metrics</CardTitle>
|
||||
<CardDescription>Real-time cluster performance</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-blue-600">12</div>
|
||||
<div className="text-sm text-gray-600">Active Agents</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-green-600">8</div>
|
||||
<div className="text-sm text-gray-600">Running Tasks</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-purple-600">3</div>
|
||||
<div className="text-sm text-gray-600">Workflows</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-orange-600">98.5%</div>
|
||||
<div className="text-sm text-gray-600">Uptime</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Example of a metrics card showing system statistics',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Card grid layout example
|
||||
*/
|
||||
export const CardGrid: Story = {
|
||||
render: () => (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-w-6xl">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Agent 1</CardTitle>
|
||||
<CardDescription>Available</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Active tasks: 2/4</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Agent 2</CardTitle>
|
||||
<CardDescription>Busy</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Active tasks: 4/4</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Agent 3</CardTitle>
|
||||
<CardDescription>Available</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Active tasks: 1/4</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Agent 4</CardTitle>
|
||||
<CardDescription>Offline</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Active tasks: 0/4</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Example of cards arranged in a responsive grid layout',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
423
frontend/src/components/ui/input.stories.tsx
Normal file
423
frontend/src/components/ui/input.stories.tsx
Normal file
@@ -0,0 +1,423 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Input } from './input';
|
||||
import { Label } from './label';
|
||||
import { Button } from './button';
|
||||
|
||||
/**
|
||||
* Input component for Hive UI
|
||||
*
|
||||
* A versatile input component for forms and user input throughout the Hive application.
|
||||
* Supports various input types with consistent styling and behavior.
|
||||
*/
|
||||
const meta = {
|
||||
title: 'UI Components/Input',
|
||||
component: Input,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
The Input component provides consistent styling and behavior for form inputs across the Hive application.
|
||||
It supports all standard HTML input types with enhanced styling and focus states.
|
||||
|
||||
## Features
|
||||
- Consistent styling across all input types
|
||||
- Built-in focus and disabled states
|
||||
- File upload support with custom styling
|
||||
- Form validation integration
|
||||
- Responsive design
|
||||
- Accessibility support
|
||||
|
||||
## Usage
|
||||
\`\`\`tsx
|
||||
import { Input } from '@/components/ui/input';
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Enter agent name"
|
||||
value={agentName}
|
||||
onChange={(e) => setAgentName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
\`\`\`
|
||||
|
||||
## Input Types
|
||||
- **text**: General text input
|
||||
- **email**: Email address input with validation
|
||||
- **password**: Password input with hidden text
|
||||
- **number**: Numeric input with step controls
|
||||
- **search**: Search input with enhanced styling
|
||||
- **url**: URL input with validation
|
||||
- **tel**: Telephone number input
|
||||
- **file**: File upload input
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
type: {
|
||||
control: 'select',
|
||||
options: ['text', 'email', 'password', 'number', 'search', 'url', 'tel', 'file'],
|
||||
description: 'HTML input type',
|
||||
},
|
||||
placeholder: {
|
||||
control: 'text',
|
||||
description: 'Placeholder text',
|
||||
},
|
||||
value: {
|
||||
control: 'text',
|
||||
description: 'Input value',
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
description: 'Whether the input is disabled',
|
||||
},
|
||||
required: {
|
||||
control: 'boolean',
|
||||
description: 'Whether the input is required',
|
||||
},
|
||||
className: {
|
||||
control: 'text',
|
||||
description: 'Additional CSS classes',
|
||||
},
|
||||
onChange: {
|
||||
action: 'changed',
|
||||
description: 'Change event handler',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
onChange: (e: any) => console.log('Input changed:', e.target.value),
|
||||
},
|
||||
} satisfies Meta<typeof Input>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
/**
|
||||
* Default text input
|
||||
*/
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
type: 'text',
|
||||
placeholder: 'Enter text...',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Email input with validation
|
||||
*/
|
||||
export const Email: Story = {
|
||||
args: {
|
||||
type: 'email',
|
||||
placeholder: 'Enter email address',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Password input
|
||||
*/
|
||||
export const Password: Story = {
|
||||
args: {
|
||||
type: 'password',
|
||||
placeholder: 'Enter password',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Number input
|
||||
*/
|
||||
export const Number: Story = {
|
||||
args: {
|
||||
type: 'number',
|
||||
placeholder: 'Enter number',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Search input
|
||||
*/
|
||||
export const Search: Story = {
|
||||
args: {
|
||||
type: 'search',
|
||||
placeholder: 'Search agents...',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* File input
|
||||
*/
|
||||
export const File: Story = {
|
||||
args: {
|
||||
type: 'file',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Disabled state
|
||||
*/
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
type: 'text',
|
||||
placeholder: 'Disabled input',
|
||||
disabled: true,
|
||||
value: 'Cannot edit this value',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Required input
|
||||
*/
|
||||
export const Required: Story = {
|
||||
args: {
|
||||
type: 'text',
|
||||
placeholder: 'Required field',
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Input with label (form example)
|
||||
*/
|
||||
export const WithLabel: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="agent-name">Agent Name</Label>
|
||||
<Input
|
||||
id="agent-name"
|
||||
name="agentName"
|
||||
type="text"
|
||||
placeholder="e.g., walnut-codellama"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Input component used with a label in a form context',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Form example with multiple inputs
|
||||
*/
|
||||
export const FormExample: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4 w-80">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="agent-id">Agent ID</Label>
|
||||
<Input
|
||||
id="agent-id"
|
||||
name="agentId"
|
||||
type="text"
|
||||
placeholder="unique-agent-id"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="endpoint">Endpoint URL</Label>
|
||||
<Input
|
||||
id="endpoint"
|
||||
name="endpoint"
|
||||
type="url"
|
||||
placeholder="http://hostname:port"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="model">Model Name</Label>
|
||||
<Input
|
||||
id="model"
|
||||
name="model"
|
||||
type="text"
|
||||
placeholder="codellama:34b"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="max-concurrent">Max Concurrent Tasks</Label>
|
||||
<Input
|
||||
id="max-concurrent"
|
||||
name="maxConcurrent"
|
||||
type="number"
|
||||
placeholder="4"
|
||||
min="1"
|
||||
max="10"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button variant="default" className="flex-1">Register Agent</Button>
|
||||
<Button variant="outline" className="flex-1">Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Complete form example showing agent registration with multiple input types',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Search and filter inputs
|
||||
*/
|
||||
export const SearchAndFilter: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4 w-96">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="search-agents">Search Agents</Label>
|
||||
<Input
|
||||
id="search-agents"
|
||||
type="search"
|
||||
placeholder="Search by name, model, or status..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="min-tasks">Min Tasks</Label>
|
||||
<Input
|
||||
id="min-tasks"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="max-tasks">Max Tasks</Label>
|
||||
<Input
|
||||
id="max-tasks"
|
||||
type="number"
|
||||
placeholder="10"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button variant="default" className="w-full">Apply Filters</Button>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Input components used for search and filtering functionality',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* All input types showcase
|
||||
*/
|
||||
export const AllTypes: Story = {
|
||||
render: () => (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full max-w-4xl">
|
||||
<div className="space-y-2">
|
||||
<Label>Text Input</Label>
|
||||
<Input type="text" placeholder="Text input" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Email Input</Label>
|
||||
<Input type="email" placeholder="email@example.com" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Password Input</Label>
|
||||
<Input type="password" placeholder="Password" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Number Input</Label>
|
||||
<Input type="number" placeholder="123" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Search Input</Label>
|
||||
<Input type="search" placeholder="Search..." />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>URL Input</Label>
|
||||
<Input type="url" placeholder="https://example.com" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Tel Input</Label>
|
||||
<Input type="tel" placeholder="+1 (555) 123-4567" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>File Input</Label>
|
||||
<Input type="file" />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Showcase of all supported input types',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Validation states example
|
||||
*/
|
||||
export const ValidationStates: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4 w-80">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="valid-input">Valid Input</Label>
|
||||
<Input
|
||||
id="valid-input"
|
||||
type="text"
|
||||
value="valid-agent-name"
|
||||
className="border-green-500 focus-visible:ring-green-500"
|
||||
/>
|
||||
<p className="text-sm text-green-600">✓ Agent name is available</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="error-input">Error Input</Label>
|
||||
<Input
|
||||
id="error-input"
|
||||
type="text"
|
||||
value="invalid name!"
|
||||
className="border-red-500 focus-visible:ring-red-500"
|
||||
/>
|
||||
<p className="text-sm text-red-600">✗ Agent name contains invalid characters</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="warning-input">Warning Input</Label>
|
||||
<Input
|
||||
id="warning-input"
|
||||
type="text"
|
||||
value="existing-agent"
|
||||
className="border-yellow-500 focus-visible:ring-yellow-500"
|
||||
/>
|
||||
<p className="text-sm text-yellow-600">⚠ Similar agent name already exists</p>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Examples of input validation states with custom styling',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user