sortable
Sortable list of items with drag-and-drop
Sortable list of items with drag-and-drop
Загрузка превью…
src/sortable/c-sortable-1.vue
<!--
Расхождение с апстримом: `IconPlaceholder` заменена инлайновым `<svg>`
(lucide-react v0.545.0 "grip-vertical"/"image"/"file-text"/"music"/
"video") — см. docs/PORTING.md §5. Диф снимает статику (список в покое,
фиксированный порядок) — перетаскивание отдельным гейтом не проверяется.
-->
<script setup lang="ts">
import { ref } from "vue"
import { Badge } from "@/components/reui/badge"
import { Sortable, SortableItem, SortableItemHandle } from "@/components/reui/sortable"
interface SortableItemData {
id: string
title: string
description: string
type: "image" | "document" | "audio" | "video"
size: string
}
const items = ref<SortableItemData[]>([
{ id: "1", title: "Product Demo", description: "Main product image", type: "image", size: "2.4 MB" },
{ id: "2", title: "Product Specification", description: "Technical details document", type: "document", size: "1.2 MB" },
{ id: "3", title: "Product Demo Video", description: "How to use the product", type: "video", size: "15.7 MB" },
{ id: "4", title: "Product Audio Guide", description: "Audio instructions", type: "audio", size: "8.3 MB" },
{ id: "5", title: "Product Specification", description: "Additional product view", type: "image", size: "3.1 MB" },
])
function getTypeColor(type: SortableItemData["type"]) {
switch (type) {
case "image":
return "primary-light"
case "document":
return "success-light"
case "audio":
return "destructive-light"
case "video":
return "info-light"
}
}
function getItemValue(item: SortableItemData) {
return item.id
}
</script>
<template>
<div class="mx-auto w-full max-w-xl space-y-8 p-6">
<Sortable
v-model:value="items"
:get-item-value="getItemValue"
strategy="vertical"
class="space-y-2"
>
<SortableItem v-for="item in items" :key="item.id" :value="item.id">
<div
class="bg-background border-border hover:bg-accent/50 rounded-md flex cursor-pointer items-center gap-3 border p-3 transition-colors"
>
<SortableItemHandle class="text-muted-foreground hover:text-foreground">
<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" class="lucide lucide-grip-vertical h-4 w-4">
<circle cx="9" cy="12" r="1" />
<circle cx="9" cy="5" r="1" />
<circle cx="9" cy="19" r="1" />
<circle cx="15" cy="12" r="1" />
<circle cx="15" cy="5" r="1" />
<circle cx="15" cy="19" r="1" />
</svg>
</SortableItemHandle>
<div class="text-muted-foreground flex items-center gap-2">
<svg v-if="item.type === 'image'" 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" class="lucide lucide-image h-4 w-4">
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
<circle cx="9" cy="9" r="2" />
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
</svg>
<svg v-else-if="item.type === 'document'" 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" class="lucide lucide-file-text h-4 w-4">
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
<path d="M10 9H8" />
<path d="M16 13H8" />
<path d="M16 17H8" />
</svg>
<svg v-else-if="item.type === 'audio'" 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" class="lucide lucide-music h-4 w-4">
<path d="M9 18V5l12-2v13" />
<circle cx="6" cy="18" r="3" />
<circle cx="18" cy="16" r="3" />
</svg>
<svg v-else 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" class="lucide lucide-video h-4 w-4">
<path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5" />
<rect x="2" y="6" width="14" height="12" rx="2" />
</svg>
</div>
<div class="min-w-0 flex-1">
<h4 class="truncate text-sm font-medium">{{ item.title }}</h4>
<p class="text-muted-foreground truncate text-xs">{{ item.description }}</p>
</div>
<div class="flex items-center gap-2">
<Badge :variant="getTypeColor(item.type)">{{ item.type }}</Badge>
<span class="text-muted-foreground text-xs">{{ item.size }}</span>
</div>
</div>
</SortableItem>
</Sortable>
</div>
</template>