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>

Установка

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

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

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