kanban

Kanban board persisted to a backend

Persist moves with onValueCommit, optimistic updates, and Sonner rollback

Загрузка превью…

src/kanban/c-kanban-6.vue

<!--
  IconPlaceholder (иконка "grip-vertical") заменена инлайновым `<svg>`.
  Доска в покое с фиксированным порядком карточек — drag-and-drop не
  проверяется дифом (docs/PORTING.md, kanban).

  Упрощение (не влияет на статичный рендер, тот же приём, что и в
  `c-autocomplete-12.vue`): оригинал вызывает `onValueCommit` только по
  завершении реального перетаскивания (симулированный бэкенд + `sonner`
  toast с оптимистичным rollback) — на статичной доске без интеракции
  коллбэк никогда не срабатывает, поэтому `toast`/`persistBoard` не
  подключены.
-->
<script setup lang="ts">
import { ref } from "vue"
import { Badge } from "@/components/reui/badge"
import { Kanban, KanbanBoard, KanbanColumn, KanbanColumnContent, KanbanColumnHandle, KanbanItem, KanbanItemHandle, KanbanOverlay } from "@/components/reui/kanban"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader } from "@/components/ui/card"

interface Task {
  id: string
  title: string
  priority: "low" | "medium" | "high"
}

const columnTitles: Record<string, string> = {
  todo: "To Do",
  inProgress: "In Progress",
  done: "Done",
}

function priorityVariant(priority: Task["priority"]) {
  if (priority === "high") return "destructive-light"
  if (priority === "medium") return "primary-light"
  return "warning-light"
}

const columns = ref<Record<string, Task[]>>({
  todo: [
    { id: "1", title: "Add authentication", priority: "high" },
    { id: "2", title: "Create API endpoints", priority: "medium" },
    { id: "3", title: "Write documentation", priority: "low" },
  ],
  inProgress: [
    { id: "4", title: "Design system updates", priority: "high" },
    { id: "5", title: "Implement dark mode", priority: "medium" },
  ],
  done: [{ id: "6", title: "Setup project", priority: "low" }],
})
</script>

<template>
  <div>
    <Kanban v-model:value="columns" :get-item-value="(item: Task) => item.id">
      <KanbanBoard class="grid auto-rows-fr grid-cols-3">
        <KanbanColumn v-for="(tasks, columnValue) in columns" :key="columnValue" :value="columnValue">
          <Card class="mb-2.5">
            <CardHeader class="flex items-center justify-between">
              <div class="flex items-center gap-2.5">
                <span class="text-sm font-semibold">{{ columnTitles[columnValue] }}</span>
                <Badge variant="outline">{{ tasks.length }}</Badge>
              </div>
              <KanbanColumnHandle as-child>
                <Button size="icon-xs" variant="ghost">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    width="24"
                    height="24"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    aria-hidden="true"
                  ><circle cx="9" cy="5" r="1" /><circle cx="9" cy="12" r="1" /><circle cx="9" cy="19" r="1" /><circle cx="15" cy="5" r="1" /><circle cx="15" cy="12" r="1" /><circle cx="15" cy="19" r="1" /></svg>
                </Button>
              </KanbanColumnHandle>
            </CardHeader>
            <CardContent>
              <KanbanColumnContent :value="columnValue" class="flex flex-col gap-2.5">
                <KanbanItem v-for="task in tasks" :key="task.id" :value="task.id">
                  <KanbanItemHandle>
                    <Card>
                      <CardContent class="flex items-center justify-between gap-2">
                        <span class="line-clamp-1 text-sm font-medium">{{ task.title }}</span>
                        <Badge :variant="priorityVariant(task.priority)" class="pointer-events-none h-5 shrink-0 rounded-sm px-1.5 text-xs capitalize">
                          {{ task.priority }}
                        </Badge>
                      </CardContent>
                    </Card>
                  </KanbanItemHandle>
                </KanbanItem>
              </KanbanColumnContent>
            </CardContent>
          </Card>
        </KanbanColumn>
      </KanbanBoard>
      <KanbanOverlay class="bg-muted/10 rounded-md border-2 border-dashed" />
    </Kanban>
  </div>
</template>

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-kanban-6.json

Зависимости реестра

Источник: порт из ReUI (Keenthemes, MIT)