Drag and Drop operation on MusicCard
This commit is contained in:
42
app/components/action/Draggable.vue
Normal file
42
app/components/action/Draggable.vue
Normal 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>
|
||||
@ -2,9 +2,11 @@
|
||||
import { Search } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { InputWithIcon } from '@/components/ui/input'
|
||||
import MusicCard from '@/components/ui/musiccard/MusicCard.vue'
|
||||
import { Outline } from '@/components/ui/outline'
|
||||
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('')
|
||||
|
||||
@ -66,6 +68,15 @@ const selectTrack = (trackId: number) => {
|
||||
track.selected = track.id === trackId
|
||||
})
|
||||
}
|
||||
|
||||
const { elementRef: tracksRef } = useDroppable({
|
||||
data: {
|
||||
source: tracks.value,
|
||||
},
|
||||
events: {
|
||||
onDrop: DnDOperations.applyTransfer,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -88,9 +99,16 @@ const selectTrack = (trackId: number) => {
|
||||
<InputWithIcon v-model="searchValue" :icon="Search" placeholder="Search..." type="search" icon-size="5"
|
||||
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">
|
||||
<TransitionGroup name="list" ref="tracksRef">
|
||||
<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>
|
||||
<template #sidebar>
|
||||
<Outline padding="none" class="h-full" side="left">
|
||||
@ -104,3 +122,25 @@ const selectTrack = (trackId: number) => {
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
6
app/plugins/vue-dnd-kit.client.ts
Normal file
6
app/plugins/vue-dnd-kit.client.ts
Normal 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);
|
||||
});
|
||||
3
bun.lock
3
bun.lock
@ -11,6 +11,7 @@
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@tanstack/vue-query": "^5.90.7",
|
||||
"@vee-validate/zod": "^4.15.1",
|
||||
"@vue-dnd-kit/core": "^1.7.0",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"axios": "^1.13.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@ -611,6 +612,8 @@
|
||||
|
||||
"@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/babel-helper-vue-transform-on": ["@vue/babel-helper-vue-transform-on@1.5.0", "", {}, "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA=="],
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@tanstack/vue-query": "^5.90.7",
|
||||
"@vee-validate/zod": "^4.15.1",
|
||||
"@vue-dnd-kit/core": "^1.7.0",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"axios": "^1.13.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
Reference in New Issue
Block a user