Drag and Drop operation on MusicCard

This commit is contained in:
2025-11-21 01:40:53 +05:00
parent b860ea95d3
commit 133558c4ab
6 changed files with 96 additions and 4 deletions

View File

@ -0,0 +1,42 @@
<script setup lang="ts">
import { useDraggable } from '@vue-dnd-kit/core';
import { computed } from 'vue';
const { index, source } = defineProps<{
index: number;
source: any[];
}>();
const { elementRef, handleDragStart, isOvered, isDragging } = useDraggable({
data: computed(() => ({
index,
source,
})),
});
</script>
<template>
<div ref="elementRef" @pointerdown="handleDragStart" :class="{
'is-over': isOvered,
'is-dragging': isDragging,
}" class="draggable">
<slot />
</div>
</template>
<style>
.draggable {
cursor: move;
user-select: none;
touch-action: none;
}
.is-dragging {
opacity: 0.2;
}
.is-over {
padding-top: 0.5rem;
border-top: 2px solid var(--border);
}
</style>

View File

@ -2,9 +2,11 @@
import { Search } from 'lucide-vue-next' import { Search } from 'lucide-vue-next'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { InputWithIcon } from '@/components/ui/input' import { InputWithIcon } from '@/components/ui/input'
import MusicCard from '@/components/ui/musiccard/MusicCard.vue'
import { Outline } from '@/components/ui/outline' import { Outline } from '@/components/ui/outline'
import { SidebarTrigger } from '@/components/ui/sidebar' import { SidebarTrigger } from '@/components/ui/sidebar'
import MusicCard from '~/components/internal/musiccard/MusicCard.vue'
import { DnDOperations, useDroppable } from '@vue-dnd-kit/core'
import Draggable from '~/components/action/Draggable.vue'
const searchValue = ref('') const searchValue = ref('')
@ -66,6 +68,15 @@ const selectTrack = (trackId: number) => {
track.selected = track.id === trackId track.selected = track.id === trackId
}) })
} }
const { elementRef: tracksRef } = useDroppable({
data: {
source: tracks.value,
},
events: {
onDrop: DnDOperations.applyTransfer,
},
});
</script> </script>
<template> <template>
@ -88,9 +99,16 @@ const selectTrack = (trackId: number) => {
<InputWithIcon v-model="searchValue" :icon="Search" placeholder="Search..." type="search" icon-size="5" <InputWithIcon v-model="searchValue" :icon="Search" placeholder="Search..." type="search" icon-size="5"
id="user-search" class="w-full" /> id="user-search" class="w-full" />
<MusicCard v-for="track in filteredTracks" :key="track.id" :title="track.title" :author="track.author" <div ref="tracksRef" class="space-y-2">
:authorLabel="track.authorLabel" :badges="track.badges" :imageUrl="track.imageUrl" <TransitionGroup name="list" ref="tracksRef">
:date="track.date" :selected="track.selected" @click="selectTrack(track.id)" /> <Draggable v-for="(track, index) in filteredTracks" :key="track.id" :index="index"
:source="tracks">
<MusicCard :key="track.id" :title="track.title" :author="track.author"
:authorLabel="track.authorLabel" :badges="track.badges" :imageUrl="track.imageUrl"
:date="track.date" :selected="track.selected" @click="selectTrack(track.id)" />
</Draggable>
</TransitionGroup>
</div>
</div> </div>
<template #sidebar> <template #sidebar>
<Outline padding="none" class="h-full" side="left"> <Outline padding="none" class="h-full" side="left">
@ -104,3 +122,25 @@ const selectTrack = (trackId: number) => {
</NuxtLayout> </NuxtLayout>
</div> </div>
</template> </template>
<style>
.list-move {
transition: 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.list-enter-active,
.list-leave-active {
transition: 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateY(-20px);
}
.list-leave-active {
position: absolute;
pointer-events: none;
}
</style>

View File

@ -0,0 +1,6 @@
// ~/plugins/vue-dnd-kit.client.ts
import VueDnDKitPlugin from '@vue-dnd-kit/core';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueDnDKitPlugin);
});

View File

@ -11,6 +11,7 @@
"@tailwindcss/vite": "^4.1.16", "@tailwindcss/vite": "^4.1.16",
"@tanstack/vue-query": "^5.90.7", "@tanstack/vue-query": "^5.90.7",
"@vee-validate/zod": "^4.15.1", "@vee-validate/zod": "^4.15.1",
"@vue-dnd-kit/core": "^1.7.0",
"@vueuse/core": "^14.0.0", "@vueuse/core": "^14.0.0",
"axios": "^1.13.2", "axios": "^1.13.2",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
@ -611,6 +612,8 @@
"@volar/source-map": ["@volar/source-map@2.4.23", "", {}, "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q=="], "@volar/source-map": ["@volar/source-map@2.4.23", "", {}, "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q=="],
"@vue-dnd-kit/core": ["@vue-dnd-kit/core@1.7.0", "", { "peerDependencies": { "@vueuse/core": "^13.1.0", "vue": "^3.5.13" } }, "sha512-6Otpo/9Fp/cX5EiUoN6XmIPtm/mOreAXi8clr9HgzVvDHD0sfkgVOFxCUiVbH46yAzr6KBU1Q1tjSWLBqua+5g=="],
"@vue-macros/common": ["@vue-macros/common@3.1.1", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-afW2DMjgCBVs33mWRlz7YsGHzoEEupnl0DK5ZTKsgziAlLh5syc5m+GM7eqeYrgiQpwMaVxa1fk73caCvPxyAw=="], "@vue-macros/common": ["@vue-macros/common@3.1.1", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-afW2DMjgCBVs33mWRlz7YsGHzoEEupnl0DK5ZTKsgziAlLh5syc5m+GM7eqeYrgiQpwMaVxa1fk73caCvPxyAw=="],
"@vue/babel-helper-vue-transform-on": ["@vue/babel-helper-vue-transform-on@1.5.0", "", {}, "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA=="], "@vue/babel-helper-vue-transform-on": ["@vue/babel-helper-vue-transform-on@1.5.0", "", {}, "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA=="],

View File

@ -16,6 +16,7 @@
"@tailwindcss/vite": "^4.1.16", "@tailwindcss/vite": "^4.1.16",
"@tanstack/vue-query": "^5.90.7", "@tanstack/vue-query": "^5.90.7",
"@vee-validate/zod": "^4.15.1", "@vee-validate/zod": "^4.15.1",
"@vue-dnd-kit/core": "^1.7.0",
"@vueuse/core": "^14.0.0", "@vueuse/core": "^14.0.0",
"axios": "^1.13.2", "axios": "^1.13.2",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",