All blocks

Kanban board

Sortable columns with drag-and-drop cards. Uses dnd-kit.

Preview

Applied

2
JD

Jane Doe

Counselor, age 21

AC

Alex Chen

Lifeguard, age 19

Interview

1
PP

Priya Patel

Art specialist, age 22

Ready to hire

2
SO

Sam O'Brien

Counselor, age 20

MS

Maya Silva

Waterfront, age 23

Placed

1
TR

Tomás Ruiz

Counselor, age 21

Code

"use client"

// Thin demo wiring over the library's Kanban primitives.
// Copy this whole file, rewrite columns/seed/ParticipantCardBody for your
// data shape, and adapt the drag handlers to persist to your backend.

import { useState } from "react"
import type {
  DragEndEvent,
  DragOverEvent,
  DragStartEvent,
} from "@dnd-kit/core"
import { arrayMove } from "@dnd-kit/sortable"
import {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  CountryFlag,
  KanbanBoard,
  KanbanCard,
  KanbanCardHandle,
  KanbanColumn,
} from "@smaller-earth/ui"

type Column = { id: string; title: string }
type Participant = {
  id: string
  columnId: string
  name: string
  detail: string
  countryCode: string
  initials: string
}

const columns: Column[] = [
  { id: "applied", title: "Applied" },
  { id: "interview", title: "Interview" },
  { id: "ready", title: "Ready to hire" },
  { id: "placed", title: "Placed" },
]

const seed: Participant[] = [
  { id: "p1", columnId: "applied", name: "Jane Doe", detail: "Counselor, age 21", countryCode: "gb", initials: "JD" },
  { id: "p2", columnId: "applied", name: "Alex Chen", detail: "Lifeguard, age 19", countryCode: "us", initials: "AC" },
  { id: "p3", columnId: "interview", name: "Priya Patel", detail: "Art specialist, age 22", countryCode: "in", initials: "PP" },
  { id: "p4", columnId: "ready", name: "Sam O'Brien", detail: "Counselor, age 20", countryCode: "ie", initials: "SO" },
]

function ParticipantCardBody({ p }: { p: Participant }) {
  return (
    <div className="flex items-center gap-3 px-3 py-2">
      <Avatar size="sm">
        <AvatarFallback>{p.initials}</AvatarFallback>
        <AvatarBadge className="overflow-hidden">
          <CountryFlag code={p.countryCode} shape="circle" className="h-full w-full" />
        </AvatarBadge>
      </Avatar>
      <div className="min-w-0 flex-1">
        <p className="truncate text-sm font-medium text-card-foreground">{p.name}</p>
        <p className="truncate text-xs text-muted-foreground">{p.detail}</p>
      </div>
      <KanbanCardHandle />
    </div>
  )
}

export function KanbanBoardDemo() {
  const [items, setItems] = useState<Participant[]>(seed)
  const [activeId, setActiveId] = useState<string | null>(null)
  const activeItem = items.find((i) => i.id === activeId) ?? null

  function findColumnId(id: string) {
    return items.find((i) => i.id === id)?.columnId
  }

  function handleDragStart(event: DragStartEvent) {
    setActiveId(String(event.active.id))
  }

  function handleDragOver(event: DragOverEvent) {
    const { active, over } = event
    if (!over) return
    const aId = String(active.id)
    const oId = String(over.id)
    if (aId === oId) return
    const activeCol = findColumnId(aId)
    const overCol = columns.some((c) => c.id === oId) ? oId : findColumnId(oId)
    if (!activeCol || !overCol || activeCol === overCol) return
    setItems((prev) => prev.map((i) => (i.id === aId ? { ...i, columnId: overCol } : i)))
  }

  function handleDragEnd(event: DragEndEvent) {
    const { active, over } = event
    setActiveId(null)
    if (!over) return
    const aId = String(active.id)
    const oId = String(over.id)
    if (aId === oId) return
    setItems((prev) => {
      const aIdx = prev.findIndex((i) => i.id === aId)
      const oIdx = prev.findIndex((i) => i.id === oId)
      if (aIdx === -1 || oIdx === -1) return prev
      return arrayMove(prev, aIdx, oIdx)
    })
  }

  return (
    <KanbanBoard
      onDragStart={handleDragStart}
      onDragOver={handleDragOver}
      onDragEnd={handleDragEnd}
      overlay={
        activeItem ? (
          <KanbanCard id={activeItem.id} overlay>
            <ParticipantCardBody p={activeItem} />
          </KanbanCard>
        ) : null
      }
    >
      {columns.map((col) => {
        const colItems = items.filter((i) => i.columnId === col.id)
        return (
          <KanbanColumn
            key={col.id}
            id={col.id}
            title={col.title}
            itemIds={colItems.map((i) => i.id)}
          >
            {colItems.map((p) => (
              <KanbanCard key={p.id} id={p.id}>
                <ParticipantCardBody p={p} />
              </KanbanCard>
            ))}
          </KanbanColumn>
        )
      })}
    </KanbanBoard>
  )
}