radix-vue / shadcn-vue

Vue port of shadcn-ui
https://www.shadcn-vue.com/
MIT License
4.55k stars 261 forks source link

[Feature]: Addition of Drag and Drop Component to the Component Library #631

Open Rukhsar opened 3 months ago

Rukhsar commented 3 months ago

Describe the feature

I want to request the addition of a Drag and Drop component to the component library. This feature will enhance the user experience by enabling users to easily move items within a list, between lists, or between different application sections. This component is essential for creating intuitive and interactive interfaces, especially for tasks involving organization, prioritization, and management of items.

Additional information

sadeghbarati commented 3 months ago

This feature is in progress in V2 of radix-vue

Here the Tree component with DnD feature https://stackblitz.com/edit/github-8f3fzs?file=src%2FTreeDND.vue

DnD in the ListBox component should be in V2 I think

genius192x commented 3 months ago

Describe the feature

I want to request the addition of a Drag and Drop component to the component library. This feature will enhance the user experience by enabling users to easily move items within a list, between lists, or between different application sections. This component is essential for creating intuitive and interactive interfaces, especially for tasks involving organization, prioritization, and management of items.

Additional information

  • [ ] I intend to submit a PR for this feature.
  • [ ] I have already implemented and/or tested this feature.

Now i am trying to do something for my pet-project with add this D&D library https://sortablejs.github.io/vue.draggable.next/#/simple , but i will try to adaptive it for Shadcn look like. In my case it will be a table with some columns, the same with the example in shadcn examples and it will be transform to kanban structure by one switcher. In kanban all tasks will be sorting by priority and you can easily drag and drop each task in the right column(hight/medium/low). Is it interesting for you?) Maybe i will show it in a few days

svenvreenen commented 3 months ago

i created one using the input component. for nuxt 3 so you might need to add additional imports. but to me it works great:

<script setup lang="ts">
import { ref } from 'vue';
import { useDropZone } from '@vueuse/core';

const dropZoneRef = ref<HTMLDivElement | null>(null);

const props = defineProps<{
  modelValue: File | null;
}>();

const emits = defineEmits<{
  (e: 'update:modelValue', payload: File | null): void;
}>();

const fileInputRef = ref<HTMLInputElement | null>(null);
const previewUrl = ref<string | null>(null);

const handleFileSelected = (file: File | null) => {
  emits('update:modelValue', file);
  if (file) {
    const reader = new FileReader();
    reader.onload = (e) => {
      previewUrl.value = e.target?.result as string;
    };
    reader.readAsDataURL(file);
  } else {
    previewUrl.value = null;
  }
};

const handleDrop = (files: File[] | null) => {
  if (files && files.length > 0) {
    handleFileSelected(files[0]);
  }
};

const { isOverDropZone } = useDropZone(dropZoneRef, handleDrop);

const triggerFileInput = () => {
  fileInputRef.value?.click();
};

const handleInputChange = (event: Event) => {
  const target = event.target as HTMLInputElement;
  const file = target.files ? target.files[0] : null;
  handleFileSelected(file);
};
</script>

<template>
  <div
    ref="dropZoneRef"
    :class="{'border-primary': isOverDropZone}"
    class="relative flex aspect-[19/6] w-full items-center justify-center rounded-md border border-dashed cursor-pointer"
    @click="triggerFileInput"
  >
    <div v-if="previewUrl" class="relative">
      <button
        class="absolute top-2 right-2 bg-white rounded-full h-6 w-6 flex items-center justify-center z-20"
        @click.stop="handleFileSelected(null)"
      >
        <font-awesome-icon :icon="['fas', 'xmark']" />
      </button>
      <img :src="previewUrl" alt="Selected Image" class="aspect-[19/6] w-full rounded-md object-cover" />
    </div>
    <div v-else>
      <font-awesome-icon :icon="['fas', 'arrow-up-from-bracket']" />
      <span class="sr-only">Upload</span>
    </div>
    <input
      ref="fileInputRef"
      type="file"
      accept="image/*"
      class="hidden"
      @change="handleInputChange"
    />
  </div>
</template>

Then in the page:

<DashboardImageDropZone v-model="coverImage" @update:modelValue="handleFileSelected" />

in script setup:

const handleFileSelected = async (file) => { if (file) { const uploadedFile = await uploadFile(file); if (uploadedFile) { coverImage.value = uploadedFile.id; // Store the file ID } console.log(uploadedFile); } };

Hope this helps for now!