{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "kanban-board-shadcnui",
  "type": "registry:block",
  "title": "Kanban Board",
  "description": "Interactive Kanban board with drag-and-drop, glassmorphism styling, and task management features",
  "registryDependencies": [
    "button"
  ],
  "dependencies": [
    "framer-motion",
    "react"
  ],
  "files": [
    {
      "path": "@uitripled/react-shadcn/src/components/components/kanban/kanban-board.tsx",
      "content": "\"use client\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  closestCorners,\n  defaultDropAnimationSideEffects,\n  DndContext,\n  DragEndEvent,\n  DragOverEvent,\n  DragOverlay,\n  DragStartEvent,\n  DropAnimation,\n  KeyboardSensor,\n  PointerSensor,\n  useSensor,\n  useSensors,\n} from \"@dnd-kit/core\";\nimport {\n  arrayMove,\n  horizontalListSortingStrategy,\n  SortableContext,\n  sortableKeyboardCoordinates,\n  useSortable,\n  verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport {\n  Clock,\n  Filter,\n  MessageSquare,\n  MoreHorizontal,\n  Paperclip,\n  Plus,\n  Search,\n} from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\n\n// --- Types ---\n\ntype Id = string | number;\n\ntype Column = {\n  id: Id;\n  title: string;\n};\n\ntype Task = {\n  id: Id;\n  columnId: Id;\n  content: string;\n  priority: \"low\" | \"medium\" | \"high\";\n  tags: string[];\n  comments: number;\n  attachments: number;\n  dueDate?: string;\n  assignees: string[];\n};\n\n// --- Mock Data ---\n\nconst initialColumns: Column[] = [\n  { id: \"todo\", title: \"To Do\" },\n  { id: \"in-progress\", title: \"In Progress\" },\n  { id: \"review\", title: \"Review\" },\n  { id: \"done\", title: \"Done\" },\n];\n\nconst initialTasks: Task[] = [\n  {\n    id: \"1\",\n    columnId: \"todo\",\n    content: \"Design new landing page\",\n    priority: \"high\",\n    tags: [\"Design\", \"Marketing\"],\n    comments: 3,\n    attachments: 2,\n    dueDate: \"Tomorrow\",\n    assignees: [\"/avatars/01.png\", \"/avatars/02.png\"],\n  },\n  {\n    id: \"2\",\n    columnId: \"todo\",\n    content: \"Competitor analysis\",\n    priority: \"medium\",\n    tags: [\"Research\"],\n    comments: 1,\n    attachments: 0,\n    dueDate: \"Next Week\",\n    assignees: [\"/avatars/03.png\"],\n  },\n  {\n    id: \"3\",\n    columnId: \"in-progress\",\n    content: \"Implement authentication flow\",\n    priority: \"high\",\n    tags: [\"Dev\", \"Backend\"],\n    comments: 5,\n    attachments: 1,\n    dueDate: \"Today\",\n    assignees: [\"/avatars/04.png\", \"/avatars/05.png\"],\n  },\n  {\n    id: \"4\",\n    columnId: \"in-progress\",\n    content: \"Fix navigation bug on mobile\",\n    priority: \"low\",\n    tags: [\"Bug\", \"Mobile\"],\n    comments: 0,\n    attachments: 0,\n    assignees: [\"/avatars/01.png\"],\n  },\n  {\n    id: \"5\",\n    columnId: \"review\",\n    content: \"Update documentation\",\n    priority: \"medium\",\n    tags: [\"Docs\"],\n    comments: 2,\n    attachments: 1,\n    dueDate: \"Yesterday\",\n    assignees: [\"/avatars/02.png\"],\n  },\n  {\n    id: \"6\",\n    columnId: \"done\",\n    content: \"Release v1.0.0\",\n    priority: \"high\",\n    tags: [\"Release\"],\n    comments: 12,\n    attachments: 4,\n    dueDate: \"Last Week\",\n    assignees: [\"/avatars/01.png\", \"/avatars/02.png\", \"/avatars/03.png\"],\n  },\n];\n\n// --- Components ---\n\nexport function KanbanBoard() {\n  const [columns, setColumns] = useState<Column[]>(initialColumns);\n  const [tasks, setTasks] = useState<Task[]>(initialTasks);\n  const [activeColumn, setActiveColumn] = useState<Column | null>(null);\n  const [activeTask, setActiveTask] = useState<Task | null>(null);\n  const [searchQuery, setSearchQuery] = useState(\"\");\n\n  const sensors = useSensors(\n    useSensor(PointerSensor, {\n      activationConstraint: {\n        distance: 3, // 3px movement required before drag starts\n      },\n    }),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates,\n    })\n  );\n\n  const columnsId = useMemo(() => columns.map((col) => col.id), [columns]);\n\n  const filteredTasks = useMemo(() => {\n    if (!searchQuery) return tasks;\n    return tasks.filter((task) =>\n      task.content.toLowerCase().includes(searchQuery.toLowerCase())\n    );\n  }, [tasks, searchQuery]);\n\n  function onDragStart(event: DragStartEvent) {\n    if (event.active.data.current?.type === \"Column\") {\n      setActiveColumn(event.active.data.current.column);\n      return;\n    }\n\n    if (event.active.data.current?.type === \"Task\") {\n      setActiveTask(event.active.data.current.task);\n      return;\n    }\n  }\n\n  function onDragOver(event: DragOverEvent) {\n    const { active, over } = event;\n    if (!over) return;\n\n    const activeId = active.id;\n    const overId = over.id;\n\n    if (activeId === overId) return;\n\n    const isActiveTask = active.data.current?.type === \"Task\";\n    const isOverTask = over.data.current?.type === \"Task\";\n\n    if (!isActiveTask) return;\n\n    // Im dropping a Task over another Task\n    if (isActiveTask && isOverTask) {\n      setTasks((tasks) => {\n        const activeIndex = tasks.findIndex((t) => t.id === activeId);\n        const overIndex = tasks.findIndex((t) => t.id === overId);\n\n        if (tasks[activeIndex].columnId !== tasks[overIndex].columnId) {\n          tasks[activeIndex].columnId = tasks[overIndex].columnId;\n        }\n\n        return arrayMove(tasks, activeIndex, overIndex);\n      });\n    }\n\n    const isOverColumn = over.data.current?.type === \"Column\";\n\n    // Im dropping a Task over a column\n    if (isActiveTask && isOverColumn) {\n      setTasks((tasks) => {\n        const activeIndex = tasks.findIndex((t) => t.id === activeId);\n\n        tasks[activeIndex].columnId = overId;\n        console.log(\"DROPPING TASK OVER COLUMN\", { activeIndex });\n        return arrayMove(tasks, activeIndex, activeIndex);\n      });\n    }\n  }\n\n  function onDragEnd(event: DragEndEvent) {\n    setActiveColumn(null);\n    setActiveTask(null);\n\n    const { active, over } = event;\n    if (!over) return;\n\n    const activeId = active.id;\n    const overId = over.id;\n\n    if (activeId === overId) return;\n\n    const isActiveColumn = active.data.current?.type === \"Column\";\n    if (isActiveColumn) {\n      setColumns((columns) => {\n        const activeIndex = columns.findIndex((col) => col.id === activeId);\n        const overIndex = columns.findIndex((col) => col.id === overId);\n        return arrayMove(columns, activeIndex, overIndex);\n      });\n    }\n  }\n\n  const dropAnimation: DropAnimation = {\n    sideEffects: defaultDropAnimationSideEffects({\n      styles: {\n        active: {\n          opacity: \"0.5\",\n        },\n      },\n    }),\n  };\n\n  return (\n    <div className=\"relative flex h-full min-h-screen w-full flex-col gap-6 overflow-hidden bg-background p-6\">\n      {/* Glassmorphism background blobs */}\n      <div className=\"absolute inset-0 -z-10 pointer-events-none\">\n        <div className=\"absolute left-1/2 top-0 h-[520px] w-[520px] -translate-x-1/2 rounded-full bg-foreground/[0.035] blur-[140px]\" />\n        <div className=\"absolute bottom-0 right-0 h-[360px] w-[360px] rounded-full bg-foreground/[0.025] blur-[120px]\" />\n        <div className=\"absolute top-1/2 left-1/4 h-[400px] w-[400px] rounded-full bg-primary/[0.02] blur-[150px]\" />\n      </div>\n      {/* Header */}\n      <div className=\"relative flex flex-col gap-4 rounded-2xl border border-border/40 bg-background/60 p-6 backdrop-blur-xl md:flex-row md:items-center md:justify-between\">\n        {/* Gradient overlay */}\n        <div className=\"absolute inset-0 rounded-2xl bg-gradient-to-br from-foreground/[0.04] via-transparent to-transparent opacity-60\" />\n        <div className=\"relative z-10\">\n          <h1 className=\"text-3xl font-semibold tracking-tight text-foreground\">\n            Product Roadmap\n          </h1>\n          <p className=\"text-foreground/60\">\n            Manage tasks, track progress, and collaborate with your team.\n          </p>\n        </div>\n        <div className=\"flex items-center gap-2\">\n          <div className=\"relative\">\n            <Search className=\"absolute left-2.5 top-2.5 h-4 w-4 text-foreground/40\" />\n            <Input\n              placeholder=\"Search tasks...\"\n              className=\"w-[200px] pl-9 bg-background/60 border-border/50 backdrop-blur-md focus:bg-background/80 focus:border-border/70 transition-all\"\n              value={searchQuery}\n              onChange={(e) => setSearchQuery(e.target.value)}\n            />\n          </div>\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"bg-background/60 border-border/50 backdrop-blur-md hover:bg-background/80\"\n          >\n            <Filter className=\"h-4 w-4 text-foreground/70\" />\n          </Button>\n          <Button className=\"gap-2 bg-primary text-primary-foreground hover:bg-primary/90 shadow-lg shadow-primary/20\">\n            <Plus className=\"h-4 w-4\" />\n            New Task\n          </Button>\n        </div>\n      </div>\n\n      {/* Board */}\n      <DndContext\n        sensors={sensors}\n        collisionDetection={closestCorners}\n        onDragStart={onDragStart}\n        onDragOver={onDragOver}\n        onDragEnd={onDragEnd}\n      >\n        <div className=\"flex h-full gap-6 overflow-x-auto pb-4\">\n          <SortableContext\n            items={columnsId}\n            strategy={horizontalListSortingStrategy}\n          >\n            {columns.map((col) => (\n              <BoardColumn\n                key={col.id}\n                column={col}\n                tasks={filteredTasks.filter((task) => task.columnId === col.id)}\n              />\n            ))}\n          </SortableContext>\n        </div>\n\n        {/* Drag Overlay */}\n        <DragOverlay dropAnimation={dropAnimation}>\n          {activeColumn && (\n            <BoardColumn\n              column={activeColumn}\n              tasks={tasks.filter((task) => task.columnId === activeColumn.id)}\n              isOverlay\n            />\n          )}\n          {activeTask && <TaskCard task={activeTask} isOverlay />}\n        </DragOverlay>\n      </DndContext>\n    </div>\n  );\n}\n\ninterface BoardColumnProps {\n  column: Column;\n  tasks: Task[];\n  isOverlay?: boolean;\n}\n\nfunction BoardColumn({ column, tasks, isOverlay }: BoardColumnProps) {\n  const {\n    setNodeRef,\n    attributes,\n    listeners,\n    transform,\n    transition,\n    isDragging,\n  } = useSortable({\n    id: column.id,\n    data: {\n      type: \"Column\",\n      column,\n    },\n  });\n\n  const style = {\n    transition,\n    transform: CSS.Translate.toString(transform),\n  };\n\n  const tasksIds = useMemo(() => tasks.map((task) => task.id), [tasks]);\n\n  return (\n    <div\n      ref={setNodeRef}\n      style={style}\n      className={cn(\n        \"group/column relative flex h-full w-[350px] min-w-[350px] flex-col overflow-hidden rounded-2xl border border-border/40 bg-background/50 backdrop-blur-xl shadow-lg\",\n        isDragging && \"opacity-50\",\n        isOverlay &&\n          \"rotate-2 scale-105 shadow-2xl cursor-grabbing bg-background/70\"\n      )}\n    >\n      {/* Gradient overlay for column */}\n      <div className=\"absolute inset-0 bg-gradient-to-br from-foreground/[0.03] via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover/column:opacity-100\" />\n\n      {/* Column Header */}\n      <div\n        {...attributes}\n        {...listeners}\n        className=\"relative z-10 flex items-center justify-between border-b border-border/30 bg-background/30 p-4 backdrop-blur-sm cursor-grab active:cursor-grabbing\"\n      >\n        <div className=\"flex items-center gap-2\">\n          <div className=\"flex h-6 w-6 items-center justify-center rounded-full bg-primary/20 text-xs font-bold text-primary shadow-sm shadow-primary/20 backdrop-blur-sm\">\n            {tasks.length}\n          </div>\n          <h3 className=\"font-semibold text-foreground\">{column.title}</h3>\n        </div>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"h-8 w-8 text-foreground/40 hover:text-foreground hover:bg-background/50\"\n        >\n          <MoreHorizontal className=\"h-4 w-4\" />\n        </Button>\n      </div>\n\n      {/* Column Content */}\n      <div className=\"relative z-10 flex flex-1 flex-col gap-3 p-3\">\n        <SortableContext\n          items={tasksIds}\n          strategy={verticalListSortingStrategy}\n        >\n          {tasks.map((task) => (\n            <TaskCard key={task.id} task={task} />\n          ))}\n        </SortableContext>\n        <Button\n          variant=\"ghost\"\n          className=\"w-full justify-start gap-2 border border-dashed border-border/30 text-foreground/60 hover:text-foreground hover:bg-background/60 hover:border-border/50 backdrop-blur-sm\"\n        >\n          <Plus className=\"h-4 w-4\" />\n          Add Task\n        </Button>\n      </div>\n    </div>\n  );\n}\n\ninterface TaskCardProps {\n  task: Task;\n  isOverlay?: boolean;\n}\n\nfunction TaskCard({ task, isOverlay }: TaskCardProps) {\n  const {\n    setNodeRef,\n    attributes,\n    listeners,\n    transform,\n    transition,\n    isDragging,\n  } = useSortable({\n    id: task.id,\n    data: {\n      type: \"Task\",\n      task,\n    },\n  });\n\n  const style = {\n    transition,\n    transform: CSS.Translate.toString(transform),\n  };\n\n  const priorityColors = {\n    low: \"bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20\",\n    medium:\n      \"bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20\",\n    high: \"bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20\",\n  };\n\n  return (\n    <div\n      ref={setNodeRef}\n      style={style}\n      {...attributes}\n      {...listeners}\n      className={cn(\n        \"group relative flex cursor-grab flex-col gap-3 overflow-hidden rounded-xl border border-border/40 bg-background/70 p-4 shadow-lg backdrop-blur-xl transition-all hover:border-border/60 hover:shadow-xl hover:-translate-y-1 active:cursor-grabbing\",\n        isDragging && \"opacity-30\",\n        isOverlay &&\n          \"rotate-2 scale-105 shadow-2xl cursor-grabbing opacity-100 bg-background/90 backdrop-blur-xl z-50\"\n      )}\n    >\n      {/* Gradient overlay for card */}\n      <div className=\"absolute inset-0 bg-gradient-to-br from-foreground/[0.02] via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100\" />\n\n      {/* Header / Tags */}\n      <div className=\"relative z-10 flex items-start justify-between\">\n        <div className=\"flex flex-wrap gap-1.5\">\n          <Badge\n            variant=\"outline\"\n            className={cn(\n              \"border px-1.5 py-0.5 text-[10px] uppercase tracking-wider backdrop-blur-sm\",\n              priorityColors[task.priority]\n            )}\n          >\n            {task.priority}\n          </Badge>\n          {task.tags.map((tag) => (\n            <Badge\n              key={tag}\n              variant=\"secondary\"\n              className=\"bg-secondary/50 text-secondary-foreground/80 px-1.5 py-0.5 text-[10px] backdrop-blur-sm\"\n            >\n              {tag}\n            </Badge>\n          ))}\n        </div>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"h-6 w-6 opacity-0 transition-opacity group-hover:opacity-100\"\n        >\n          <MoreHorizontal className=\"h-3 w-3\" />\n        </Button>\n      </div>\n\n      {/* Content */}\n      <p className=\"relative z-10 text-sm font-medium text-foreground leading-relaxed\">\n        {task.content}\n      </p>\n\n      {/* Footer */}\n      <div className=\"relative z-10 flex items-center justify-between pt-1\">\n        <div className=\"flex items-center gap-3 text-xs text-foreground/50\">\n          {task.dueDate && (\n            <div\n              className={cn(\n                \"flex items-center gap-1\",\n                task.priority === \"high\" && \"text-red-500/80\"\n              )}\n            >\n              <Clock className=\"h-3 w-3\" />\n              <span>{task.dueDate}</span>\n            </div>\n          )}\n          {(task.comments > 0 || task.attachments > 0) && (\n            <div className=\"flex items-center gap-2\">\n              {task.comments > 0 && (\n                <div className=\"flex items-center gap-1\">\n                  <MessageSquare className=\"h-3 w-3\" />\n                  <span>{task.comments}</span>\n                </div>\n              )}\n              {task.attachments > 0 && (\n                <div className=\"flex items-center gap-1\">\n                  <Paperclip className=\"h-3 w-3\" />\n                  <span>{task.attachments}</span>\n                </div>\n              )}\n            </div>\n          )}\n        </div>\n\n        {/* Assignees */}\n        {task.assignees.length > 0 && (\n          <div className=\"flex -space-x-2\">\n            {task.assignees.map((avatar, i) => (\n              <Avatar key={i} className=\"h-6 w-6 border-2 border-background\">\n                <AvatarImage src={avatar} />\n                <AvatarFallback className=\"text-[8px] bg-primary/10 text-primary\">\n                  U{i + 1}\n                </AvatarFallback>\n              </Avatar>\n            ))}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:block",
      "target": "components/uitripled/kanban-board-shadcnui.tsx"
    }
  ]
}