Привет, Хабр! Недавно я представил вам Vue DnD Kit — библиотеку для создания интерфейсов с перетаскиванием в Vue 3. Сегодня хочу рассказать о новом пакете vue-dnd-kit/components, который значительно упрощает разработку сложных drag & drop интерфейсов.

Важно: Эта публикация — краткая новость о выходе пакета компонентов. Для полноценных примеров и подробной документации рекомендую перейти на официальный сайт документации.

⚠️ Статус проекта: Пакет находится в активной разработке (бета-версия). API может изменяться между минорными версиями. Не рекомендуется для продакшена до версии 1.0.0.

Что такое vue-dnd-kit/components?

vue-dnd-kit/components — это пакет готовых компонентов, построенных поверх основного пакета vue-dnd-kit/core. Он предоставляет готовые решения для типовых сценариев использования drag & drop, таких как:

  • ? Сортируемые таблицы с перетаскиванием строк и столбцов

  • ? Канбан-доски для управления задачами

  • ? Древовидные структуры с неограниченной вложенностью

  • ? Интерактивные дашборды с перетаскиваемыми виджетами

Ключевые особенности

1. CLI для быстрой установки компонентов

Одна из самых крутых фич — встроенный CLI, который работает по принципу shadcn/ui:

# Посмотреть доступные компоненты
pnpm dlx @vue-dnd-kit/components list

# Добавить компонент в проект
pnpm dlx @vue-dnd-kit/components add Table

# Добавить компонент в конкретную папку
pnpm dlx @vue-dnd-kit/components add Kanban --dir src/shared/components

Важно: CLI клонирует компоненты прямо в ваш проект, давая полный контроль над кодом и стилизацией. Вы можете свободно модифицировать, кастомизировать и адаптировать компоненты под свои нужды — это не зависимость, а ваш собственный код!

2. Минимальная стилизация

Все компоненты поставляются с минимальной стилизацией, что позволяет легко интегрировать их в любой дизайн-систему:

<template>
  <Table
    :rows="users"
    :columns="columns"
  >
    <template #caption>Список пользователей</template>
    <template #default="props">
      <TableRow
        v-for="(user, index) in users"
        :key="user.id"
        :row="user"
        :row-index="index"
        v-bind="props"
      />
    </template>
  </Table>
</template>

<style>
  /* Ваши собственные стили */
  .vue-dnd-table {
    @apply w-full border-collapse;
  }

  .vue-dnd-table-row {
    @apply hover:bg-gray-50 transition-colors;
  }
</style>

3. Гибкая система слотов

Каждый компонент предоставляет множество слотов для кастомизации:

<template>
  <Kanban
    :columns="columns"
    v-slot="{ columns }"
  >
    <KanbanColumn
      v-for="(column, index) in columns"
      :key="column.id"
      :column="column"
      :columns="columns"
      :column-index="index"
      :body-source="column.tasks"
    >
      <!-- Кастомный заголовок колонки -->
      <template #header>
        <div class="flex items-center gap-2">
          <span class="font-semibold">{{ column.title }}</span>
          <span class="text-sm text-gray-500">({{ column.tasks.length }})</span>
        </div>
      </template>

      <!-- Кастомные карточки задач -->
      <KanbanItem
        v-for="(task, taskIndex) in column.tasks"
        :key="task.id"
        :item="task"
        :items="column.tasks"
        :item-index="taskIndex"
      >
        <div class="p-3 bg-white rounded-lg shadow-sm">
          <h4 class="font-medium">{{ task.title }}</h4>
          <p class="text-sm text-gray-600 mt-1">{{ task.description }}</p>
        </div>
      </KanbanItem>

      <!-- Кастомный футер колонки -->
      <template #footer>
        <button class="w-full p-2 text-gray-500 hover:bg-gray-50 rounded">
          + Добавить задачу
        </button>
      </template>
    </KanbanColumn>
  </Kanban>
</template>

Доступные компоненты

? Table — Сортируемые таблицы

Полнофункциональная таблица с возможностью перетаскивания строк и столбцов:

<script setup lang="ts">
  import { ref } from 'vue';
  import { Table, TableRow, type ITableColumn } from './components/Table';

  interface IUser {
    id: number;
    name: string;
    email: string;
    role: string;
  }

  const users = ref<IUser[]>([
    { id: 1, name: 'Иван Петров', email: 'ivan@example.com', role: 'Админ' },
    {
      id: 2,
      name: 'Мария Сидорова',
      email: 'maria@example.com',
      role: 'Менеджер',
    },
    {
      id: 3,
      name: 'Алексей Козлов',
      email: 'alex@example.com',
      role: 'Разработчик',
    },
  ]);

  const columns = ref<ITableColumn<IUser>[]>([
    { label: 'ID', key: 'id' },
    { label: 'Имя', key: 'name' },
    { label: 'Email', key: 'email' },
    { label: 'Роль', key: 'role' },
  ]);
</script>

<template>
  <Table
    :rows="users"
    :columns="columns"
  >
    <template #caption>Список сотрудников</template>
    <template #default="props">
      <TableRow
        v-for="(user, index) in users"
        :key="user.id"
        :row="user"
        :row-index="index"
        v-bind="props"
      />
    </template>
  </Table>
</template>

Особенности:

  • Перетаскивание строк для изменения порядка

  • Перетаскивание столбцов для изменения их позиции

  • Кастомные заголовки столбцов через слоты

  • Поддержка статусов (success, processing, failed)

  • Визуальные индикаторы при перетаскивании

? Kanban — Канбан-доски

Гибкая канбан-доска для управления задачами и проектами:

<script setup lang="ts">
  import { ref } from 'vue';
  import { Kanban, KanbanColumn, KanbanItem } from './components/Kanban';

  interface ITask {
    id: number;
    title: string;
    description: string;
    priority: 'low' | 'medium' | 'high';
  }

  interface IColumn {
    id: number;
    title: string;
    tasks: ITask[];
  }

  const columns = ref<IColumn[]>([
    {
      id: 1,
      title: 'К выполнению',
      tasks: [
        {
          id: 1,
          title: 'Изучить документацию',
          description: 'Прочитать API docs',
          priority: 'medium',
        },
        {
          id: 2,
          title: 'Написать тесты',
          description: 'Покрыть код тестами',
          priority: 'high',
        },
      ],
    },
    {
      id: 2,
      title: 'В работе',
      tasks: [
        {
          id: 3,
          title: 'Разработать UI',
          description: 'Создать компоненты',
          priority: 'high',
        },
      ],
    },
    {
      id: 3,
      title: 'Готово',
      tasks: [
        {
          id: 4,
          title: 'Настройка проекта',
          description: 'Инициализация репозитория',
          priority: 'low',
        },
      ],
    },
  ]);
</script>

<template>
  <Kanban
    :columns="columns"
    v-slot="{ columns }"
  >
    <KanbanColumn
      v-for="(column, index) in columns"
      :key="column.id"
      :column="column"
      :columns="columns"
      :column-index="index"
      :body-source="column.tasks"
    >
      <template #header>
        <div class="flex items-center justify-between">
          <span class="font-semibold">{{ column.title }}</span>
          <span class="text-sm bg-gray-100 px-2 py-1 rounded-full">
            {{ column.tasks.length }}
          </span>
        </div>
      </template>

      <KanbanItem
        v-for="(task, taskIndex) in column.tasks"
        :key="task.id"
        :item="task"
        :items="column.tasks"
        :item-index="taskIndex"
      >
        <div
          class="p-3 bg-white rounded-lg shadow-sm border-l-4"
          :class="{
            'border-red-500': task.priority === 'high',
            'border-yellow-500': task.priority === 'medium',
            'border-green-500': task.priority === 'low',
          }"
        >
          <h4 class="font-medium text-sm">{{ task.title }}</h4>
          <p class="text-xs text-gray-600 mt-1">{{ task.description }}</p>
        </div>
      </KanbanItem>
    </KanbanColumn>
  </Kanban>
</template>

Особенности:

  • Перетаскивание задач между колонками

  • Перетаскивание колонок для изменения порядка

  • Кастомные заголовки и футеры колонок

  • Гибкая система слотов для кастомизации

? Tree — Древовидные структуры

Иерархические структуры с неограниченной вложенностью:

<script setup lang="ts">
  import { ref } from 'vue';
  import { Tree } from './components/Tree';

  interface IFile {
    id: number;
    name: string;
    type: 'file' | 'folder';
    children?: IFile[];
  }

  const files = ref<IFile[]>([
    {
      id: 1,
      name: 'Проект',
      type: 'folder',
      children: [
        {
          id: 2,
          name: 'src',
          type: 'folder',
          children: [
            { id: 3, name: 'main.ts', type: 'file' },
            { id: 4, name: 'App.vue', type: 'file' },
          ],
        },
        {
          id: 5,
          name: 'docs',
          type: 'folder',
          children: [{ id: 6, name: 'README.md', type: 'file' }],
        },
      ],
    },
  ]);
</script>

<template>
  <Tree
    :data="files"
    item-key="id"
    nesting-key="children"
    v-slot="{ item }"
  >
    <div class="flex items-center gap-2 py-1">
      <span
        class="text-lg"
        :class="item.type === 'folder' ? 'text-blue-500' : 'text-gray-500'"
      >
        {{ item.type === 'folder' ? '?' : '?' }}
      </span>
      <span class="font-medium">{{ item.name }}</span>
    </div>
  </Tree>
</template>

Особенности:

  • Неограниченная вложенность

  • Разворачивание/сворачивание узлов

  • Перетаскивание элементов между уровнями

  • Визуальные индикаторы для элементов с детьми

  • Подсветка зон для сброса

? Dashboard — Интерактивные дашборды

Гибкая система виджетов с перетаскиванием:

<script setup lang="ts">
  import { ref } from 'vue';
  import { Dashboard, DashboardItem } from './components/Dashboard';
  import ChartCard from './components/Dashboard/Example/ChartCard.vue';
  import StatCard from './components/Dashboard/Example/StatCard.vue';
  import TaskList from './components/Dashboard/Example/TaskList.vue';

  interface IDashboardItem {
    id: number;
    component: Component;
  }

  const dashboard = ref<IDashboardItem[]>([
    { id: 1, component: ChartCard },
    { id: 2, component: TaskList },
    { id: 3, component: StatCard },
  ]);
</script>

<template>
  <Dashboard
    :data="dashboard"
    class="dashboard"
  >
    <TransitionGroup
      name="dashboard"
      appear
    >
      <DashboardItem
        v-for="(item, index) in dashboard"
        :key="item.id"
        :index="index"
        :source="dashboard"
        class="dashboard-item"
      >
        <component :is="item.component" />
      </DashboardItem>
    </TransitionGroup>
  </Dashboard>
</template>

<style>
  .dashboard {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto auto;
    gap: 20px;
    padding: 20px;
  }

  .dashboard-item {
    border-radius: 12px;
    background: white;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    transition: all 0.3s ease;
  }

  .dashboard-move {
    transition: all 0.3s ease;
  }
</style>

Особенности:

  • CSS Grid для адаптивной раскладки

  • Плавные анимации при перетаскивании

  • Поддержка TransitionGroup

  • Кастомные виджеты через слоты

Как начать использовать

Установка

# npm
npm install @vue-dnd-kit/components @vue-dnd-kit/core

# yarn
yarn add @vue-dnd-kit/components @vue-dnd-kit/core

# pnpm
pnpm add @vue-dnd-kit/components @vue-dnd-kit/core

Быстрый старт с CLI

# Добавить таблицу
pnpm dlx @vue-dnd-kit/components add Table

# Добавить канбан-доску
pnpm dlx @vue-dnd-kit/components add Kanban

# Добавить дерево
pnpm dlx @vue-dnd-kit/components add Tree

# Добавить дашборд
pnpm dlx @vue-dnd-kit/components add Dashboard

Подключение как плагин

import { createApp } from 'vue';
import App from './App.vue';
import VueDnDKitPlugin from '@vue-dnd-kit/core';

const app = createApp(App);
app.use(VueDnDKitPlugin);
app.mount('#app');

Преимущества перед другими решениями

1. Простота использования

  • CLI для быстрой установки компонентов

  • Интуитивный API на основе слотов

  • Минимальная конфигурация

2. Гибкость

  • Полный контроль над стилизацией

  • Кастомные слоты для любой кастомизации

  • Отсутствие привязки к конкретным UI-фреймворкам

3. Производительность

  • Оптимизированные компоненты

  • Минимальные перерисовки

  • Эффективная работа с большими списками

4. Доступность

  • Полная поддержка клавиатурной навигации

  • Совместимость со скринридерами

  • Семантическая разметка

Планы на будущее

Проект активно развивается! В разработке находятся дополнительные компоненты:

  • SortableList — простые сортируемые списки

  • FormBuilder — конструктор форм с перетаскиванием

  • Tabs — перетаскиваемые вкладки

  • FileExplorer — файловый менеджер

  • Grid — адаптивная сетка

Roadmap:

  • [x] Базовые drag & drop компоненты

  • [x] Table компонент

  • [x] Kanban доска

  • [x] Tree компонент

  • [ ] SortableList

  • [ ] FormBuilder

  • [ ] Dashboard

  • [ ] Tabs

  • [ ] FileExplorer

  • [ ] Grid

  • [ ] Тесты

  • [ ] Стабильный API (версия 1.0.0)

Заключение

vue-dnd-kit/components — это мощный инструмент для быстрой разработки сложных интерфейсов с перетаскиванием. Благодаря CLI, который работает по принципу shadcn/ui, гибкой системе слотов и минимальной стилизации, вы можете создавать профессиональные drag & drop интерфейсы за считанные минуты.

Ключевое преимущество: Компоненты клонируются в ваш проект, а не устанавливаются как зависимости. Это означает полную свободу в кастомизации и отсутствие привязки к конкретным версиям библиотеки.

Библиотека с открытым исходным кодом, активно развивается, и я буду рад любому вкладу от сообщества!

Полезные ссылки:

Буду рад вашим отзывам и предложениям в комментариях!


Теги: vue.js, drag-and-drop, dnd-kit, frontend-разработка, components, ui-library

Хабы: VueJS, Frontend-разработка

Комментарии (9)


  1. GlennMiller1991
    21.06.2025 20:17

    Не доработаны чутка примеры. На той же странице в документации рассказывается о предотвращении подобного поведения, но автор сам забыл похоже)


    1. ZiZIGY Автор
      21.06.2025 20:17

      Спасибо за полезный комментарий, я просто реально забыл xD) Спасибо)