Components
Loading preview...
Here is Kanban Board component
@arihantcodes_1f7b8c4d
npx shadcn@latest add https://21st.dev/r/arihantcodes_1f7b8c4d/kanban-board'use client';
import { useState } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Calendar, GripVertical, MessageCircle, Paperclip, Plus } from 'lucide-react';
interface Task {
id: string;
title: string;
description?: string;
priority?: 'low' | 'medium' | 'high';
assignee?: {
name: string;
avatar: string;
};
tags?: string[];
dueDate?: string;
attachments?: number;
comments?: number;
}
interface Column {
id: string;
title: string;
tasks: Task[];
color?: string;
}
const sampleData: Column[] = [
{
id: 'todo',
title: 'To Do',
color: '#8B7355',
tasks: [
{
id: '1',
title: 'Design System Audit',
description: 'Review and update component library',
priority: 'high',
assignee: { name: 'Sarah Chen', avatar: '/headshot/Lummi Doodle 02.png' },
tags: ['Design', 'System'],
dueDate: '2024-01-15',
attachments: 3,
comments: 7,
},
{
id: '2',
title: 'User Research Analysis',
description: 'Analyze feedback from recent user interviews',
priority: 'medium',
assignee: { name: 'Alex Rivera', avatar: '/headshot/Lummi Doodle 04.png' },
tags: ['Research', 'UX'],
dueDate: '2024-01-18',
comments: 4,
},
],
},
{
id: 'progress',
title: 'In Progress',
color: '#6B8E23',
tasks: [
{
id: '3',
title: 'Mobile App Redesign',
description: 'Implementing new navigation patterns',
priority: 'high',
assignee: { name: 'Jordan Kim', avatar: '/headshot/Lummi Doodle 06.png' },
tags: ['Mobile', 'UI'],
attachments: 8,
comments: 12,
},
],
},
{
id: 'review',
title: 'Review',
color: '#CD853F',
tasks: [
{
id: '4',
title: 'API Documentation',
description: 'Complete developer documentation',
priority: 'medium',
assignee: { name: 'Maya Patel', avatar: '/headshot/Lummi Doodle 09.png' },
tags: ['Documentation', 'API'],
dueDate: '2024-01-20',
comments: 2,
},
],
},
{
id: 'done',
title: 'Done',
color: '#556B2F',
tasks: [
{
id: '5',
title: 'Landing Page Optimization',
description: 'Improved conversion rate by 23%',
priority: 'low',
assignee: { name: 'Chris Wong', avatar: '/headshot/Lummi Doodle 10.png' },
tags: ['Marketing', 'Web'],
attachments: 2,
comments: 8,
},
],
},
];
export default function KanbanBoard() {
const [columns, setColumns] = useState<Column[]>(sampleData);
const handleDragStart = (e: React.DragEvent, task: Task, columnId: string) => {
e.dataTransfer.setData('text/plain', JSON.stringify({ task, sourceColumnId: columnId }));
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
};
const handleDrop = (e: React.DragEvent, targetColumnId: string) => {
e.preventDefault();
const data = JSON.parse(e.dataTransfer.getData('text/plain'));
const { task, sourceColumnId } = data;
if (sourceColumnId === targetColumnId) return;
setColumns((prev) =>
prev.map((col) => {
if (col.id === sourceColumnId) {
return { ...col, tasks: col.tasks.filter((t) => t.id !== task.id) };
}
if (col.id === targetColumnId) {
return { ...col, tasks: [...col.tasks, task] };
}
return col;
}),
);
};
return (
<div className="">
<div className="mb-8 text-center">
<h1 className="text-4xl font-light text-neutral-900 dark:text-neutral-100 mb-2">
Kanban Board
</h1>
<p className="text-neutral-700 dark:text-neutral-300">Drag and drop task management</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{columns.map((column) => (
<div
key={column.id}
className="bg-white/20 dark:bg-neutral-900/20 backdrop-blur-xl rounded-3xl p-5 border border-border dark:border-neutral-700/50"
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(e, column.id)}
>
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<div className="w-4 h-4 rounded-full " style={{ backgroundColor: column.color }} />
<h3 className="font-semibold text-neutral-900 dark:text-neutral-100">
{column.title}
</h3>
<Badge className="bg-neutral-100/80 dark:bg-neutral-800/80 text-neutral-800 dark:text-neutral-200 border-neutral-200/50 dark:border-neutral-600/50">
{column.tasks.length}
</Badge>
</div>
<button className="p-1 rounded-full bg-white/30 dark:bg-neutral-800/30 hover:bg-white/50 dark:hover:bg-neutral-700/50 transition-colors">
<Plus className="w-4 h-4 text-neutral-700 dark:text-neutral-300" />
</button>
</div>
<div className="space-y-4">
{column.tasks.map((task) => (
<Card
key={task.id}
className="cursor-move transition-all duration-300 border bg-white/60 dark:bg-neutral-800/60 backdrop-blur-sm hover:bg-white/70 dark:hover:bg-neutral-700/70"
draggable
onDragStart={(e) => handleDragStart(e, task, column.id)}
>
<CardContent className="p-5">
<div className="space-y-4">
<div className="flex items-start justify-between">
<h4 className="font-semibold text-neutral-900 dark:text-neutral-100 leading-tight">
{task.title}
</h4>
<GripVertical className="w-5 h-5 text-neutral-500 dark:text-neutral-400 cursor-move" />
</div>
{task.description && (
<p className="text-sm text-neutral-700 dark:text-neutral-300 leading-relaxed">
{task.description}
</p>
)}
{task.tags && (
<div className="flex flex-wrap gap-2">
{task.tags.map((tag) => (
<Badge
key={tag}
className="text-xs bg-neutral-100/60 dark:bg-neutral-700/60 text-neutral-800 dark:text-neutral-200 border-neutral-200/50 dark:border-neutral-600/50 backdrop-blur-sm"
>
{tag}
</Badge>
))}
</div>
)}
<div className="flex items-center justify-between pt-2 border-t border-neutral-200/30 dark:border-neutral-700/30">
<div className="flex items-center gap-4 text-neutral-600 dark:text-neutral-400">
{task.dueDate && (
<div className="flex items-center gap-1">
<Calendar className="w-4 h-4" />
<span className="text-xs font-medium">Jan 15</span>
</div>
)}
{task.comments && (
<div className="flex items-center gap-1">
<MessageCircle className="w-4 h-4" />
<span className="text-xs font-medium">{task.comments}</span>
</div>
)}
{task.attachments && (
<div className="flex items-center gap-1">
<Paperclip className="w-4 h-4" />
<span className="text-xs font-medium">{task.attachments}</span>
</div>
)}
</div>
{task.assignee && (
<Avatar className="w-8 h-8 ring-2 ring-white/50 dark:ring-neutral-700/50">
<AvatarImage src={task.assignee.avatar} />
<AvatarFallback className="bg-neutral-200 dark:bg-neutral-700 text-neutral-800 dark:text-neutral-200 font-medium">
{task.assignee.name
.split(' ')
.map((n) => n[0])
.join('')}
</AvatarFallback>
</Avatar>
)}
</div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
))}
</div>
</div>
);
}