sortable
Sortable playlist with frame
Sortable playlist with frame
Загрузка превью…
src/sortable/c-sortable-4.vue
<!--
Расхождение с апстримом: `IconPlaceholder` заменена инлайновым `<svg>`
(lucide-react v0.545.0 "list-music"/"grip-vertical"/"music") — см.
docs/PORTING.md §5.
-->
<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 { Sortable, SortableItem, SortableItemHandle } from "@/components/reui/sortable"
interface Track {
id: string
title: string
artist: string
album: string
duration: string
plays: string
active?: boolean
}
const tracks = ref<Track[]>([
{ id: "1", title: "Midnight City", artist: "M83", album: "Hurry Up, We're Dreaming", duration: "4:03", plays: "1.2B", active: true },
{ id: "2", title: "Digital Love", artist: "Daft Punk", album: "Discovery", duration: "4:58", plays: "845M" },
{ id: "3", title: "Starlight", artist: "Muse", album: "Black Holes", duration: "3:59", plays: "720M" },
{ id: "4", title: "Take On Me", artist: "a-ha", album: "Hunting High and Low", duration: "3:48", plays: "1.8B" },
{ id: "5", title: "Blue Monday", artist: "New Order", album: "Power, Corruption", duration: "7:29", plays: "530M" },
])
function getItemValue(item: Track) {
return item.id
}
</script>
<template>
<div class="mx-auto w-full max-w-md">
<Frame spacing="xs">
<FrameHeader>
<div class="flex items-center justify-between">
<div>
<FrameTitle>Queue</FrameTitle>
<FrameDescription>{{ tracks.length }} tracks</FrameDescription>
</div>
<Badge variant="outline" size="sm"><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-list-music size-3">
<path d="M16 5H3" />
<path d="M11 12H3" />
<path d="M11 19H3" />
<path d="M21 16V5" />
<circle cx="18" cy="16" r="3" />
</svg>Playlist</Badge>
</div>
</FrameHeader>
<Sortable
v-model:value="tracks"
:get-item-value="getItemValue"
strategy="vertical"
class="space-y-0.5"
>
<SortableItem v-for="track in tracks" :key="track.id" :value="track.id">
<FramePanel class="p-0!">
<div class="group flex items-center gap-3 px-3 py-2.5">
<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 size-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="bg-muted flex size-9 shrink-0 items-center justify-center rounded-md">
<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-music text-muted-foreground size-4">
<path d="M9 18V5l12-2v13" />
<circle cx="6" cy="18" r="3" />
<circle cx="18" cy="16" r="3" />
</svg>
</div>
<div class="min-w-0 flex-1">
<p :class="`truncate text-sm font-medium ${track.active ? 'text-primary' : ''}`">{{ track.title }}<Badge v-if="track.active" variant="primary-light" size="xs" class="ms-1.5 align-middle">Playing</Badge></p>
<p class="text-muted-foreground truncate text-xs">{{ track.artist }} · {{ track.album }}</p>
</div>
<div class="flex items-center gap-3">
<span class="text-muted-foreground hidden text-xs tabular-nums sm:inline">{{ track.plays }}</span>
<span class="text-muted-foreground text-xs tabular-nums">{{ track.duration }}</span>
</div>
</div>
</FramePanel>
</SortableItem>
</Sortable>
</Frame>
</div>
</template>