kanban
Minimal kanban with stacked frame
Minimal kanban with stacked frame
Загрузка превью…
src/kanban/c-kanban-4.vue
<!--
Сетевые unsplash-аватары заменены статичным 1x1 data-URI (PIXEL).
Доска в покое с фиксированным порядком карточек.
-->
<script setup lang="ts">
import { ref } from "vue"
import { Badge } from "@/components/reui/badge"
import { Frame, FrameDescription, FrameHeader, FramePanel, FrameTitle } from "@/components/reui/frame"
import { Kanban, KanbanBoard, KanbanColumn, KanbanColumnContent, KanbanItem, KanbanItemHandle, KanbanOverlay } from "@/components/reui/kanban"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Progress } from "@/components/ui/progress"
const PIXEL =
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="
interface Task {
id: string
title: string
assignee: string
assigneeAvatar: string
progress: number
}
const columnDescriptions: Record<string, string> = {
planning: "Tasks being scoped",
active: "Currently in development",
completed: "Finished and deployed",
}
const columns = ref<Record<string, Task[]>>({
planning: [
{ id: "1", title: "Research competitors", assignee: "Alex J.", assigneeAvatar: PIXEL, progress: 20 },
],
active: [
{ id: "2", title: "Build dashboard", assignee: "Sarah C.", assigneeAvatar: PIXEL, progress: 65 },
{ id: "3", title: "API integration", assignee: "David K.", assigneeAvatar: PIXEL, progress: 40 },
],
completed: [
{ id: "4", title: "Setup repository", assignee: "Emma W.", assigneeAvatar: PIXEL, progress: 100 },
],
})
</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, columnId) in columns" :key="columnId" :value="columnId">
<Frame stacked spacing="sm" class="h-full">
<FrameHeader>
<div class="flex items-center justify-between">
<FrameTitle class="capitalize">{{ columnId }}</FrameTitle>
<Badge variant="outline" size="sm">{{ tasks.length }}</Badge>
</div>
<FrameDescription>{{ columnDescriptions[columnId] }}</FrameDescription>
</FrameHeader>
<KanbanColumnContent :value="columnId" class="flex flex-col gap-2 p-0.5">
<KanbanItem v-for="task in tasks" :key="task.id" :value="task.id">
<KanbanItemHandle>
<Frame variant="ghost" spacing="xs" class="p-0">
<FramePanel class="space-y-3 p-3">
<p class="text-sm font-medium">{{ task.title }}</p>
<Progress :model-value="task.progress" class="h-1" />
<div class="flex items-center justify-between">
<div class="flex items-center gap-1.5">
<Avatar class="size-5">
<AvatarImage :src="task.assigneeAvatar" :alt="task.assignee" />
<AvatarFallback class="text-[9px]">{{ task.assignee.charAt(0) }}</AvatarFallback>
</Avatar>
<span class="text-muted-foreground text-xs">{{ task.assignee }}</span>
</div>
<span class="text-muted-foreground text-xs">{{ `${task.progress}%` }}</span>
</div>
</FramePanel>
</Frame>
</KanbanItemHandle>
</KanbanItem>
</KanbanColumnContent>
</Frame>
</KanbanColumn>
</KanbanBoard>
<KanbanOverlay class="bg-muted/10 rounded-md border-2 border-dashed" />
</Kanban>
</div>
</template>