A flexible, reusable React drag and drop component library with TypeScript support. Perfect for building Kanban boards, sortable lists, and any drag-and-drop interface.
- 🎯 Generic & Flexible - Works with any data type extending
DragDropItem
- 🔄 Cross-Container Transfers - Drag items between different containers
- 🎨 Customizable Styling - Full control over appearance and animations
- 📱 TypeScript Support - Complete type safety and IntelliSense
- 🚀 Performance Optimized - Efficient state management with React Context
- 🎪 Visual Feedback - Beautiful drop indicators and hover effects
- 🔧 Easy Integration - Simple HOC pattern with minimal setup
npm install @avinash_ghosh/react-generic-dragdrop
yarn add @avinash_ghosh/react-generic-dragdrop
import React, { useState } from 'react';
import { DragDropProvider, DragDropContainer, DragDropItem } from '@avinash_ghosh/react-generic-dragdrop';
// Define your item type
interface TaskItem extends DragDropItem {
title: string;
description: string;
}
function App() {
const [items, setItems] = useState<TaskItem[]>([
{ id: '1', title: 'Task 1', description: 'First task' },
{ id: '2', title: 'Task 2', description: 'Second task' },
]);
// Custom render function
const renderItem = (item: TaskItem, index: number, isDragging: boolean) => (
<div style={{
padding: '12px',
backgroundColor: isDragging ? '#f0f0f0' : 'white',
border: '1px solid #ddd',
borderRadius: '4px',
opacity: isDragging ? 0.8 : 1,
}}>
<h4>{item.title}</h4>
<p>{item.description}</p>
</div>
);
return (
<DragDropProvider>
<DragDropContainer
id="my-container"
items={items}
renderItem={renderItem}
onReorder={setItems}
title="My Tasks"
containerStyles={{
backgroundColor: '#f8f9fa',
padding: '16px',
borderRadius: '8px',
}}
/>
</DragDropProvider>
);
}
import React, { useState } from 'react';
import { DragDropProvider, DragDropContainer, DragDropItem } from '@avinash_ghosh/react-generic-dragdrop';
interface Task extends DragDropItem {
title: string;
description: string;
priority: 'low' | 'medium' | 'high';
assignee: string;
}
function KanbanBoard() {
const [todoItems, setTodoItems] = useState<Task[]>([
{ id: '1', title: 'Design Homepage', description: 'Create wireframes', priority: 'high', assignee: 'John' },
{ id: '2', title: 'Setup Database', description: 'Configure PostgreSQL', priority: 'medium', assignee: 'Jane' },
]);
const [inProgressItems, setInProgressItems] = useState<Task[]>([
{ id: '3', title: 'Implement Auth', description: 'JWT authentication', priority: 'high', assignee: 'Bob' },
]);
const [doneItems, setDoneItems] = useState<Task[]>([]);
const renderTask = (task: Task, index: number, isDragging: boolean) => {
const priorityColors = {
high: '#ff4444',
medium: '#ffaa00',
low: '#44aa44'
};
return (
<div style={{
padding: '12px',
backgroundColor: 'white',
borderRadius: '8px',
border: '2px solid transparent',
borderColor: isDragging ? '#007bff' : 'transparent',
boxShadow: isDragging ? '0 8px 16px rgba(0,0,0,0.2)' : '0 2px 4px rgba(0,0,0,0.1)',
transform: isDragging ? 'rotate(2deg) scale(1.02)' : 'none',
transition: 'all 0.2s ease',
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span style={{ fontWeight: 'bold' }}>{task.title}</span>
<span style={{
padding: '2px 6px',
borderRadius: '12px',
backgroundColor: priorityColors[task.priority],
color: 'white',
fontSize: '10px',
textTransform: 'uppercase'
}}>
{task.priority}
</span>
</div>
<p style={{ margin: '8px 0', color: '#666' }}>{task.description}</p>
<div style={{ fontSize: '12px', color: '#888' }}>Assigned to: {task.assignee}</div>
</div>
);
};
const handleMoveItem = (item: Task, fromContainerId: string) => {
// Remove from source
if (fromContainerId === 'todo') setTodoItems(prev => prev.filter(i => i.id !== item.id));
if (fromContainerId === 'inprogress') setInProgressItems(prev => prev.filter(i => i.id !== item.id));
if (fromContainerId === 'done') setDoneItems(prev => prev.filter(i => i.id !== item.id));
};
const handleReceiveItem = (item: Task, fromContainerId: string, toIndex: number, toContainerId: string) => {
// Add to destination
if (toContainerId === 'todo') {
setTodoItems(prev => {
const newItems = [...prev];
newItems.splice(toIndex, 0, item);
return newItems;
});
}
if (toContainerId === 'inprogress') {
setInProgressItems(prev => {
const newItems = [...prev];
newItems.splice(toIndex, 0, item);
return newItems;
});
}
if (toContainerId === 'done') {
setDoneItems(prev => {
const newItems = [...prev];
newItems.splice(toIndex, 0, item);
return newItems;
});
}
};
return (
<DragDropProvider>
<div style={{ display: 'flex', gap: '20px', padding: '20px' }}>
<DragDropContainer
id="todo"
items={todoItems}
renderItem={renderTask}
onReorder={setTodoItems}
onMoveItemOut={handleMoveItem}
onReceiveItemFromOutside={(item, fromId, toIndex) => handleReceiveItem(item, fromId, toIndex, 'todo')}
title="📋 To Do"
containerStyles={{
backgroundColor: '#f8f9fa',
border: '2px dashed #dee2e6',
borderRadius: '12px',
padding: '16px',
minHeight: '400px',
width: '300px'
}}
gap="12px"
emptyMessage="Drop tasks here"
/>
<DragDropContainer
id="inprogress"
items={inProgressItems}
renderItem={renderTask}
onReorder={setInProgressItems}
onMoveItemOut={handleMoveItem}
onReceiveItemFromOutside={(item, fromId, toIndex) => handleReceiveItem(item, fromId, toIndex, 'inprogress')}
title="🚀 In Progress"
containerStyles={{
backgroundColor: '#fff3cd',
border: '2px dashed #ffeaa7',
borderRadius: '12px',
padding: '16px',
minHeight: '400px',
width: '300px'
}}
gap="12px"
emptyMessage="Drop tasks here"
/>
<DragDropContainer
id="done"
items={doneItems}
renderItem={renderTask}
onReorder={setDoneItems}
onMoveItemOut={handleMoveItem}
onReceiveItemFromOutside={(item, fromId, toIndex) => handleReceiveItem(item, fromId, toIndex, 'done')}
title="✅ Done"
containerStyles={{
backgroundColor: '#d4edda',
border: '2px dashed #c3e6cb',
borderRadius: '12px',
padding: '16px',
minHeight: '400px',
width: '300px'
}}
gap="12px"
emptyMessage="Drop completed tasks here"
/>
</div>
</DragDropProvider>
);
}
Context provider that manages global drag state. Must wrap all DragDropContainer
components.
<DragDropProvider>
{/* Your drag drop containers */}
</DragDropProvider>
Generic container component for drag and drop functionality.
Prop | Type | Required | Description |
---|---|---|---|
id |
string |
✅ | Unique identifier for the container |
items |
T[] |
✅ | Array of items to display |
renderItem |
(item: T, index: number, isDragging: boolean) => ReactNode |
✅ | Function to render each item |
onReorder |
(items: T[]) => void |
❌ | Called when items are reordered within the same container |
onMoveItemOut |
(item: T, fromContainerId: string) => void |
❌ | Called when an item is moved out of this container |
onReceiveItemFromOutside |
(item: T, fromContainerId: string, toIndex: number) => void |
❌ | Called when an item is moved into this container |
containerStyles |
ContainerStyles |
❌ | Custom styles for the container |
gap |
string |
❌ | Gap between items (default: '8px') |
className |
string |
❌ | CSS class name for the container |
title |
string |
❌ | Title displayed at the top of the container |
emptyMessage |
string |
❌ | Message shown when container is empty |
disabled |
boolean |
❌ | Disable drag and drop functionality |
Base interface that all draggable items must extend:
interface DragDropItem {
id: string;
[key: string]: any; // Allow additional properties
}
Styling options for containers:
interface ContainerStyles {
backgroundColor?: string;
border?: string;
borderRadius?: string;
padding?: string;
minHeight?: string;
gap?: string;
[key: string]: any; // Allow any CSS properties
}
The component provides default styling but is fully customizable:
<DragDropContainer
containerStyles={{
backgroundColor: '#f8f9fa',
border: '2px dashed #dee2e6',
borderRadius: '12px',
padding: '20px',
minHeight: '300px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
}}
/>
Control item appearance through your renderItem
function:
const renderItem = (item, index, isDragging) => (
<div style={{
padding: '12px',
backgroundColor: isDragging ? '#e3f2fd' : 'white',
border: `2px solid ${isDragging ? '#2196f3' : '#e0e0e0'}`,
borderRadius: '8px',
transform: isDragging ? 'rotate(2deg) scale(1.05)' : 'none',
opacity: isDragging ? 0.8 : 1,
transition: 'all 0.2s ease',
cursor: 'grab',
}}>
{/* Your item content */}
</div>
);
npm run build
npm test
npm run build:watch
MIT © [Your Name]
Contributions are welcome! Please feel free to submit a Pull Request.
If you find a bug or have a feature request, please open an issue on GitHub.
- Initial release
- Generic drag and drop container
- Cross-container transfers
- TypeScript support
- Customizable styling