sortable

Sortable list of items with grid layout

Sortable list of items with grid layout

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

src/sortable/c-sortable-2.vue

<!--
  Расхождение с апстримом: `IconPlaceholder` заменена инлайновым `<svg>`
  (lucide-react v0.545.0 "grip-vertical") — см. 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"
import { cn } from "@/lib/utils"

interface GridItem {
  id: string
  title: string
  description: string
  type: "image" | "document" | "audio" | "video" | "featured"
  size: string
  priority: "high" | "medium" | "low"
}

const items = ref<GridItem[]>([
  { id: "1", title: "Hero Image", description: "Main banner image", type: "image", size: "2.4 MB", priority: "high" },
  { id: "2", title: "Product Specs", description: "Technical documentation", type: "document", size: "1.2 MB", priority: "medium" },
  { id: "3", title: "Demo Video", description: "Product demonstration", type: "video", size: "15.7 MB", priority: "high" },
  { id: "4", title: "Audio Guide", description: "Voice instructions", type: "audio", size: "8.3 MB", priority: "low" },
  { id: "5", title: "Gallery Photo 1", description: "Product view 1", type: "image", size: "3.1 MB", priority: "medium" },
  { id: "6", title: "Gallery Photo 2", description: "Product view 2", type: "image", size: "2.8 MB", priority: "medium" },
  { id: "7", title: "User Manual", description: "Installation guide", type: "document", size: "4.2 MB", priority: "high" },
  { id: "8", title: "Background Music", description: "Ambient soundtrack", type: "audio", size: "12.1 MB", priority: "low" },
  { id: "9", title: "Feature Highlight", description: "Key product features", type: "featured", size: "N/A", priority: "high" },
])

function getTypeColor(type: GridItem["type"]) {
  switch (type) {
    case "image":
      return "primary-light"
    case "document":
      return "success-light"
    case "audio":
      return "destructive-light"
    case "video":
      return "info-light"
    case "featured":
      return "warning-light"
  }
}

function getItemSize(type: GridItem["type"]) {
  switch (type) {
    case "featured":
      return "col-span-2 row-span-2"
    default:
      return "col-span-1 row-span-1"
  }
}

function getItemValue(item: GridItem) {
  return item.id
}
</script>

<template>
  <div class="mx-auto w-full max-w-2xl space-y-6 p-4">
    <Sortable
      v-model:value="items"
      :get-item-value="getItemValue"
      strategy="grid"
      class="grid auto-rows-fr grid-cols-3 gap-3"
    >
      <SortableItem v-for="item in items" :key="item.id" :value="item.id">
        <div
          :class="
            cn(
              'group bg-background border-border hover:bg-accent/50 rounded-md relative cursor-pointer border p-3 transition-colors',
              getItemSize(item.type),
              'flex min-h-[100px] flex-col'
            )
          "
        >
          <SortableItemHandle class="text-muted-foreground hover:text-foreground absolute end-1.5 top-2.5 z-10 opacity-0 transition-opacity group-hover:opacity-100">
            <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-3.5 w-3.5">
              <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="min-w-0 flex-1">
            <h4 class="truncate text-sm font-medium">{{ item.title }}</h4>
            <p class="text-muted-foreground mt-0.5 truncate text-xs">{{ item.description }}</p>
          </div>

          <div class="mt-2 flex items-center justify-between">
            <Badge :variant="getTypeColor(item.type)" size="sm">{{ item.type }}</Badge>
            <span v-if="item.type !== 'featured'" 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-2.json

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

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