Introduction
Hey everyone, we will create an awesome Kanban board using dnd-kit
in this post.
We will use the next.js starter with tailwindcss as a code starter for our Kanban board.
prerequisite -
containers
: a container is a board of Kanban board that will contain the Items or tasks of the Kanban boardItems
: these are the items that will be dragged between the containers or board of Kanban.
so, let's get started with our app
In your terminal run these commands and install tailwindcss -
npx create-next-app my-project
cd my-project
Install dnd-kit
once we have our nextjs starter with tailwindcss ready we are going to install the following packages.
@dnd-kit/core
: this is the core library for handling our sorting and dragging.@dndkit-sortable
: this package is the extension to thednd-kit
which will help us in sorting thecontainers
anditems
uuid
: this library is going to handle our unique ID generation.
okay so now we have all the packages that we need installed in our app.
Layout setup-
Now, let's set up our base setup where we will be adding our Kanban board.
pages/index.tsx
-
import { useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
// DnD
import {
DndContext,
DragEndEvent,
DragMoveEvent,
DragOverlay,
DragStartEvent,
KeyboardSensor,
PointerSensor,
UniqueIdentifier,
closestCorners,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
SortableContext,
arrayMove,
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable';
export default function Home() {
return (
<div className="mx-auto max-w-7xl py-10">
<div className="flex items-center justify-between gap-y-2">
<h1 className="text-gray-800 text-3xl font-bold">Dnd-kit Guide</h1>
</div>
<div className="mt-10">
<div className="grid grid-cols-3 gap-6">
</div>
</div>
</div>
);
}
After adding our base layout we will have something like this on the screen -
State and types setup
Okay, so the next thing that we need to do is to add all the states that we need in the app and also define the type for our containers
DnDType (Container / Board Type)
:
import { UniqueIdentifier } from '@dnd-kit/core';
type DNDType = {
id: UniqueIdentifier; // Unique Id for Containers
title: string;
items: {
id: UniqueIdentifier; // Unique Id for Items
title: string;
}[];
};
we have the type of our container
which is going to hold all of our items of the kanban board.
now let's define the react states that we need in the app.
const [containers, setContainers] = useState<DNDType[]>([]);
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
const [currentContainerId, setCurrentContainerId] =
useState<UniqueIdentifier>();
const [containerName, setContainerName] = useState('');
const [itemName, setItemName] = useState('');
const [showAddContainerModal, setShowAddContainerModal] = useState(false);
const [showAddItemModal, setShowAddItemModal] = useState(false);
containers
- This state will keep track of all the containers that we are going to add to the Kanban board with items inside it.activeId
- It is going to be used to track the current selected or dragging element Id, which we will use later when we are renderingDragOverlay
currentContainerId
- We are going to use this container ID when we are adding a new Item to a container.itemName
&containerName
- these states will be used in our controlled Input which will give us the name of the container and item that we are going to create.showAddContainerModal
&showAddItemModal
- these states will be used to render the modal of container and item.
DndContext Setup -
Okay so now let's add DndContext to our app which will hold all of our SortableContext.
DndContext
: In order for your Droppable and Draggable components to interact with each other, you'll need to make sure that the part of your React tree that uses them is nested within a parent <DndContext>
component. The <DndContext>
provider makes use of the React Context API to share data between draggable and droppable components and hooks.
SortableContext
: The SortableContext
provides information via context that is consumed by the useSortable
hook
So now that we know what the use of these react components let's add those to our app.
import { useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
// DnD
import {
DndContext,
DragEndEvent,
DragMoveEvent,
DragOverlay,
DragStartEvent,
KeyboardSensor,
PointerSensor,
UniqueIdentifier,
closestCorners,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
SortableContext,
arrayMove,
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable';
import Container from '@/components/Container';
import Items from '@/components/Item';
export default function Home() {
type DNDType = {
id: UniqueIdentifier; // Unique Id for Containers
title: string;
items: {
id: UniqueIdentifier; // Unique Id for Items
title: string;
}[];
};
const [containers, setContainers] = useState<DNDType[]>([]);
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
const [currentContainerId, setCurrentContainerId] =
useState<UniqueIdentifier>();
const [containerName, setContainerName] = useState('');
const [itemName, setItemName] = useState('');
const [showAddContainerModal, setShowAddContainerModal] = useState(false);
// Dnd Handlers
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);
const handleDragStart = (event: DragStartEvent) => {};
const handleDragMove = (event: DragMoveEvent) => {};
const handleDragEnd = (event: DragEndEvent) => {};
return (
<div className="mx-auto max-w-7xl py-10">
<div className="flex items-center justify-between gap-y-2">
<h1 className="text-gray-800 text-3xl font-bold">Dnd-kit Guide</h1>
</div>
<div className="mt-10">
<div className="grid grid-cols-3 gap-6">
<DndContext
sensors={sensors}
collisionDetection={closestCorners}
onDragStart={handleDragStart}
onDragMove={handleDragMove}
onDragEnd={handleDragEnd}
>
<SortableContext items={containers.map((i) => i.id)}>
{containers.map((container) => (
<Container
id={container.id}
title={container.title}
key={container.id}
onAddItem={() => {}}
>
<SortableContext items={container.items.map((i) => i.id)}>
<div className="flex items-start flex-col gap-y-4">
{container.items.map((i) => (
<Items title={i.title} id={i.id} key={i.id} />
))}
</div>
</SortableContext>
</Container>
))}
</SortableContext>
</DndContext>
</div>
</div>
</div>
);
}
Container -
We are going to useSortable
hook, which will give us attributes
, setNodeRef
, listerners
, transform
, transition
, isDragging
which we will use these properties in our component to make the component behave like a draggable and droppable element.
import React from 'react';
import ContainerProps from './container.type';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import clsx from 'clsx';
import { Button } from '../Button';
const Container = ({
id,
children,
title,
description,
onAddItem,
}: ContainerProps) => {
const {
attributes,
setNodeRef,
listeners,
transform,
transition,
isDragging,
} = useSortable({
id: id,
data: {
type: 'container',
},
});
return (
<div
{...attributes}
ref={setNodeRef}
style={{
transition,
transform: CSS.Translate.toString(transform),
}}
className={clsx(
'w-full h-full p-4 bg-gray-50 rounded-xl flex flex-col gap-y-4',
isDragging && 'opacity-50',
)}
>
<div className="flex items-center justify-between">
<div className="flex flex-col gap-y-1">
<h1 className="text-gray-800 text-xl">{title}</h1>
<p className="text-gray-400 text-sm">{description}</p>
</div>
<button
className="border p-2 text-xs rounded-xl shadow-lg hover:shadow-xl"
{...listeners}
>
Drag Handle
</button>
</div>
{children}
<Button variant="ghost" onClick={onAddItem}>
Add Item
</Button>
</div>
);
};
export default Container;
Item-
It will be similar to a container that we will be using useSortable
because we are going to drag and drop these in the container or board.
import { UniqueIdentifier } from '@dnd-kit/core';
import { useSortable } from '@dnd-kit/sortable';
import React from 'react';
import { CSS } from '@dnd-kit/utilities';
import clsx from 'clsx';
type ItemsType = {
id: UniqueIdentifier;
title: string;
};
const Items = ({ id, title }: ItemsType) => {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id: id,
data: {
type: 'item',
},
});
return (
<div
ref={setNodeRef}
{...attributes}
style={{
transition,
transform: CSS.Translate.toString(transform),
}}
className={clsx(
'px-2 py-4 bg-white shadow-md rounded-xl w-full border border-transparent hover:border-gray-200 cursor-pointer',
isDragging && 'opacity-50',
)}
>
<div className="flex items-center justify-between">
{title}
<button
className="border p-2 text-xs rounded-xl shadow-lg hover:shadow-xl"
{...listeners}
>
Drag Handle
</button>
</div>
</div>
);
};
export default Items;
Ok so now we have all the things that we need ready for sorting,
let's implement the DndContext
handlers to bring our kanban board to life.
Handle Drag Start-
In this function, we are going to extract the active element and then set the active ID state.
function handleDragStart(event: DragStartEvent) {
const { active } = event;
const { id } = active;
setActiveId(id);
}
Handle Drag Move -
In this, we are going to extract the active and over
item
from the event.there are going to be two types of scenarios we need to handle
Sorting between Items - In this, we are going to handle the sorting between the items and check both the
active
andover
element are items and then swap those items to their respective positions.Sorting between active Item and over container - when we have the active element to be an Item we are dragging that over an empty container.
const handleDragMove = (event: DragMoveEvent) => {
const { active, over } = event;
// Handle Items Sorting
if (
active.id.toString().includes('item') &&
over?.id.toString().includes('item') &&
active &&
over &&
active.id !== over.id
) {
// Find the active container and over container
const activeContainer = findValueOfItems(active.id, 'item');
const overContainer = findValueOfItems(over.id, 'item');
// If the active or over container is not found, return
if (!activeContainer || !overContainer) return;
// Find the index of the active and over container
const activeContainerIndex = containers.findIndex(
(container) => container.id === activeContainer.id,
);
const overContainerIndex = containers.findIndex(
(container) => container.id === overContainer.id,
);
// Find the index of the active and over item
const activeitemIndex = activeContainer.items.findIndex(
(item) => item.id === active.id,
);
const overitemIndex = overContainer.items.findIndex(
(item) => item.id === over.id,
);
// In the same container
if (activeContainerIndex === overContainerIndex) {
let newItems = [...containers];
newItems[activeContainerIndex].items = arrayMove(
newItems[activeContainerIndex].items,
activeitemIndex,
overitemIndex,
);
setContainers(newItems);
} else {
// In different containers
let newItems = [...containers];
const [removeditem] = newItems[activeContainerIndex].items.splice(
activeitemIndex,
1,
);
newItems[overContainerIndex].items.splice(
overitemIndex,
0,
removeditem,
);
setContainers(newItems);
}
}
// Handling Item Drop Into a Container
if (
active.id.toString().includes('item') &&
over?.id.toString().includes('container') &&
active &&
over &&
active.id !== over.id
) {
// Find the active and over container
const activeContainer = findValueOfItems(active.id, 'item');
const overContainer = findValueOfItems(over.id, 'container');
// If the active or over container is not found, return
if (!activeContainer || !overContainer) return;
// Find the index of the active and over container
const activeContainerIndex = containers.findIndex(
(container) => container.id === activeContainer.id,
);
const overContainerIndex = containers.findIndex(
(container) => container.id === overContainer.id,
);
// Find the index of the active and over item
const activeitemIndex = activeContainer.items.findIndex(
(item) => item.id === active.id,
);
// Remove the active item from the active container and add it to the over container
let newItems = [...containers];
const [removeditem] = newItems[activeContainerIndex].items.splice(
activeitemIndex,
1,
);
newItems[overContainerIndex].items.push(removeditem);
setContainers(newItems);
}
};
Handle Drag End -
this function is going to be similar to handleDragMove
but in this function, we are also going to handle the scenarios in which we are sorting the containers
.
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
// Handling Container Sorting
if (
active.id.toString().includes('container') &&
over?.id.toString().includes('container') &&
active &&
over &&
active.id !== over.id
) {
// Find the index of the active and over container
const activeContainerIndex = containers.findIndex(
(container) => container.id === active.id,
);
const overContainerIndex = containers.findIndex(
(container) => container.id === over.id,
);
// Swap the active and over container
let newItems = [...containers];
newItems = arrayMove(newItems, activeContainerIndex, overContainerIndex);
setContainers(newItems);
}
// Handling item Sorting
if (
active.id.toString().includes('item') &&
over?.id.toString().includes('item') &&
active &&
over &&
active.id !== over.id
) {
// Find the active and over container
const activeContainer = findValueOfItems(active.id, 'item');
const overContainer = findValueOfItems(over.id, 'item');
// If the active or over container is not found, return
if (!activeContainer || !overContainer) return;
// Find the index of the active and over container
const activeContainerIndex = containers.findIndex(
(container) => container.id === activeContainer.id,
);
const overContainerIndex = containers.findIndex(
(container) => container.id === overContainer.id,
);
// Find the index of the active and over item
const activeitemIndex = activeContainer.items.findIndex(
(item) => item.id === active.id,
);
const overitemIndex = overContainer.items.findIndex(
(item) => item.id === over.id,
);
// In the same container
if (activeContainerIndex === overContainerIndex) {
let newItems = [...containers];
newItems[activeContainerIndex].items = arrayMove(
newItems[activeContainerIndex].items,
activeitemIndex,
overitemIndex,
);
setContainers(newItems);
} else {
// In different containers
let newItems = [...containers];
const [removeditem] = newItems[activeContainerIndex].items.splice(
activeitemIndex,
1,
);
newItems[overContainerIndex].items.splice(
overitemIndex,
0,
removeditem,
);
setContainers(newItems);
}
}
// Handling item dropping into Container
if (
active.id.toString().includes('item') &&
over?.id.toString().includes('container') &&
active &&
over &&
active.id !== over.id
) {
// Find the active and over container
const activeContainer = findValueOfItems(active.id, 'item');
const overContainer = findValueOfItems(over.id, 'container');
// If the active or over container is not found, return
if (!activeContainer || !overContainer) return;
// Find the index of the active and over container
const activeContainerIndex = containers.findIndex(
(container) => container.id === activeContainer.id,
);
const overContainerIndex = containers.findIndex(
(container) => container.id === overContainer.id,
);
// Find the index of the active and over item
const activeitemIndex = activeContainer.items.findIndex(
(item) => item.id === active.id,
);
let newItems = [...containers];
const [removeditem] = newItems[activeContainerIndex].items.splice(
activeitemIndex,
1,
);
newItems[overContainerIndex].items.push(removeditem);
setContainers(newItems);
}
setActiveId(null);
}
Now let's add some dummy content to our containers
state and see if we can sort items
and containers
.
const [containers, setContainers] = useState<DNDType[]>([
{
id: `container-${uuidv4()}`,
title: 'Container 1',
items: [
{
id: `item-${uuidv4()}`,
title: 'Item 1',
},
],
},
{
id: `container-${uuidv4()}`,
title: 'Container 2',
items: [
{
id: `item-${uuidv4()}`,
title: 'Item 2',
},
],
},
]);
Now, we can sort the items
and containers
.
Add Container Modal -
Now that we have core functionality working for our Kanban board let's add the ability to add a container
.
First, we are going to add a button to add a container
When we click that button we are going to open a modal and let the user enter the name for the container and then append that container to our container's state.
const onAddContainer = () => {
if (!containerName) return;
const id = `container-${uuidv4()}`;
setContainers([
...containers,
{
id,
title: containerName,
items: [],
},
]);
setContainerName('');
setShowAddContainerModal(false);
};
Add Item Modal
After adding the functionality of creating a container, we can similarly add the functionality to create an item in a particular container using currentContainerId
state.
const onAddItem = () => {
if (!itemName) return;
const id = `item-${uuidv4()}`;
const container = containers.find((item) => item.id === currentContainerId);
if (!container) return;
container.items.push({
id,
title: itemName,
});
setContainers([...containers]);
setItemName('');
setShowAddItemModal(false);
};
Drag Overlay
At last, we need to add a drag overlay so that we can render a preview of the element we are dragging wither container
or items
.
<DragOverlay adjustScale={false}>
{/* Drag Overlay For item Item */}
{activeId && activeId.toString().includes('item') && (
<Items id={activeId} title={findItemTitle(activeId)} />
)}
{/* Drag Overlay For Container */}
{activeId && activeId.toString().includes('container') && (
<Container id={activeId} title={findContainerTitle(activeId)}>
{findContainerItems(activeId).map((i) => (
<Items key={i.id} title={i.title} id={i.id} />
))}
</Container>
)}
</DragOverlay>
Final Code -
import { useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
// DnD
import {
DndContext,
DragEndEvent,
DragMoveEvent,
DragOverlay,
DragStartEvent,
KeyboardSensor,
PointerSensor,
UniqueIdentifier,
closestCorners,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
SortableContext,
arrayMove,
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable';
import { Inter } from 'next/font/google';
// Components
import Container from '@/components/Container';
import Items from '@/components/Item';
import Modal from '@/components/Modal';
import Input from '@/components/Input';
import { Button } from '@/components/Button';
const inter = Inter({ subsets: ['latin'] });
type DNDType = {
id: UniqueIdentifier;
title: string;
items: {
id: UniqueIdentifier;
title: string;
}[];
};
export default function Home() {
const [containers, setContainers] = useState<DNDType[]>([]);
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
const [currentContainerId, setCurrentContainerId] =
useState<UniqueIdentifier>();
const [containerName, setContainerName] = useState('');
const [itemName, setItemName] = useState('');
const [showAddContainerModal, setShowAddContainerModal] = useState(false);
const [showAddItemModal, setShowAddItemModal] = useState(false);
const onAddContainer = () => {
if (!containerName) return;
const id = `container-${uuidv4()}`;
setContainers([
...containers,
{
id,
title: containerName,
items: [],
},
]);
setContainerName('');
setShowAddContainerModal(false);
};
const onAddItem = () => {
if (!itemName) return;
const id = `item-${uuidv4()}`;
const container = containers.find((item) => item.id === currentContainerId);
if (!container) return;
container.items.push({
id,
title: itemName,
});
setContainers([...containers]);
setItemName('');
setShowAddItemModal(false);
};
// Find the value of the items
function findValueOfItems(id: UniqueIdentifier | undefined, type: string) {
if (type === 'container') {
return containers.find((item) => item.id === id);
}
if (type === 'item') {
return containers.find((container) =>
container.items.find((item) => item.id === id),
);
}
}
const findItemTitle = (id: UniqueIdentifier | undefined) => {
const container = findValueOfItems(id, 'item');
if (!container) return '';
const item = container.items.find((item) => item.id === id);
if (!item) return '';
return item.title;
};
const findContainerTitle = (id: UniqueIdentifier | undefined) => {
const container = findValueOfItems(id, 'container');
if (!container) return '';
return container.title;
};
const findContainerItems = (id: UniqueIdentifier | undefined) => {
const container = findValueOfItems(id, 'container');
if (!container) return [];
return container.items;
};
// DND Handlers
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);
function handleDragStart(event: DragStartEvent) {
const { active } = event;
const { id } = active;
setActiveId(id);
}
const handleDragMove = (event: DragMoveEvent) => {
const { active, over } = event;
// Handle Items Sorting
if (
active.id.toString().includes('item') &&
over?.id.toString().includes('item') &&
active &&
over &&
active.id !== over.id
) {
// Find the active container and over container
const activeContainer = findValueOfItems(active.id, 'item');
const overContainer = findValueOfItems(over.id, 'item');
// If the active or over container is not found, return
if (!activeContainer || !overContainer) return;
// Find the index of the active and over container
const activeContainerIndex = containers.findIndex(
(container) => container.id === activeContainer.id,
);
const overContainerIndex = containers.findIndex(
(container) => container.id === overContainer.id,
);
// Find the index of the active and over item
const activeitemIndex = activeContainer.items.findIndex(
(item) => item.id === active.id,
);
const overitemIndex = overContainer.items.findIndex(
(item) => item.id === over.id,
);
// In the same container
if (activeContainerIndex === overContainerIndex) {
let newItems = [...containers];
newItems[activeContainerIndex].items = arrayMove(
newItems[activeContainerIndex].items,
activeitemIndex,
overitemIndex,
);
setContainers(newItems);
} else {
// In different containers
let newItems = [...containers];
const [removeditem] = newItems[activeContainerIndex].items.splice(
activeitemIndex,
1,
);
newItems[overContainerIndex].items.splice(
overitemIndex,
0,
removeditem,
);
setContainers(newItems);
}
}
// Handling Item Drop Into a Container
if (
active.id.toString().includes('item') &&
over?.id.toString().includes('container') &&
active &&
over &&
active.id !== over.id
) {
// Find the active and over container
const activeContainer = findValueOfItems(active.id, 'item');
const overContainer = findValueOfItems(over.id, 'container');
// If the active or over container is not found, return
if (!activeContainer || !overContainer) return;
// Find the index of the active and over container
const activeContainerIndex = containers.findIndex(
(container) => container.id === activeContainer.id,
);
const overContainerIndex = containers.findIndex(
(container) => container.id === overContainer.id,
);
// Find the index of the active and over item
const activeitemIndex = activeContainer.items.findIndex(
(item) => item.id === active.id,
);
// Remove the active item from the active container and add it to the over container
let newItems = [...containers];
const [removeditem] = newItems[activeContainerIndex].items.splice(
activeitemIndex,
1,
);
newItems[overContainerIndex].items.push(removeditem);
setContainers(newItems);
}
};
// This is the function that handles the sorting of the containers and items when the user is done dragging.
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
// Handling Container Sorting
if (
active.id.toString().includes('container') &&
over?.id.toString().includes('container') &&
active &&
over &&
active.id !== over.id
) {
// Find the index of the active and over container
const activeContainerIndex = containers.findIndex(
(container) => container.id === active.id,
);
const overContainerIndex = containers.findIndex(
(container) => container.id === over.id,
);
// Swap the active and over container
let newItems = [...containers];
newItems = arrayMove(newItems, activeContainerIndex, overContainerIndex);
setContainers(newItems);
}
// Handling item Sorting
if (
active.id.toString().includes('item') &&
over?.id.toString().includes('item') &&
active &&
over &&
active.id !== over.id
) {
// Find the active and over container
const activeContainer = findValueOfItems(active.id, 'item');
const overContainer = findValueOfItems(over.id, 'item');
// If the active or over container is not found, return
if (!activeContainer || !overContainer) return;
// Find the index of the active and over container
const activeContainerIndex = containers.findIndex(
(container) => container.id === activeContainer.id,
);
const overContainerIndex = containers.findIndex(
(container) => container.id === overContainer.id,
);
// Find the index of the active and over item
const activeitemIndex = activeContainer.items.findIndex(
(item) => item.id === active.id,
);
const overitemIndex = overContainer.items.findIndex(
(item) => item.id === over.id,
);
// In the same container
if (activeContainerIndex === overContainerIndex) {
let newItems = [...containers];
newItems[activeContainerIndex].items = arrayMove(
newItems[activeContainerIndex].items,
activeitemIndex,
overitemIndex,
);
setContainers(newItems);
} else {
// In different containers
let newItems = [...containers];
const [removeditem] = newItems[activeContainerIndex].items.splice(
activeitemIndex,
1,
);
newItems[overContainerIndex].items.splice(
overitemIndex,
0,
removeditem,
);
setContainers(newItems);
}
}
// Handling item dropping into Container
if (
active.id.toString().includes('item') &&
over?.id.toString().includes('container') &&
active &&
over &&
active.id !== over.id
) {
// Find the active and over container
const activeContainer = findValueOfItems(active.id, 'item');
const overContainer = findValueOfItems(over.id, 'container');
// If the active or over container is not found, return
if (!activeContainer || !overContainer) return;
// Find the index of the active and over container
const activeContainerIndex = containers.findIndex(
(container) => container.id === activeContainer.id,
);
const overContainerIndex = containers.findIndex(
(container) => container.id === overContainer.id,
);
// Find the index of the active and over item
const activeitemIndex = activeContainer.items.findIndex(
(item) => item.id === active.id,
);
let newItems = [...containers];
const [removeditem] = newItems[activeContainerIndex].items.splice(
activeitemIndex,
1,
);
newItems[overContainerIndex].items.push(removeditem);
setContainers(newItems);
}
setActiveId(null);
}
return (
<div className="mx-auto max-w-7xl py-10">
{/* Add Container Modal */}
<Modal
showModal={showAddContainerModal}
setShowModal={setShowAddContainerModal}
>
<div className="flex flex-col w-full items-start gap-y-4">
<h1 className="text-gray-800 text-3xl font-bold">Add Container</h1>
<Input
type="text"
placeholder="Container Title"
name="containername"
value={containerName}
onChange={(e) => setContainerName(e.target.value)}
/>
<Button onClick={onAddContainer}>Add container</Button>
</div>
</Modal>
{/* Add Item Modal */}
<Modal showModal={showAddItemModal} setShowModal={setShowAddItemModal}>
<div className="flex flex-col w-full items-start gap-y-4">
<h1 className="text-gray-800 text-3xl font-bold">Add Item</h1>
<Input
type="text"
placeholder="Item Title"
name="itemname"
value={itemName}
onChange={(e) => setItemName(e.target.value)}
/>
<Button onClick={onAddItem}>Add Item</Button>
</div>
</Modal>
<div className="flex items-center justify-between gap-y-2">
<h1 className="text-gray-800 text-3xl font-bold">Dnd-kit Guide</h1>
<Button onClick={() => setShowAddContainerModal(true)}>
Add Container
</Button>
</div>
<div className="mt-10">
<div className="grid grid-cols-3 gap-6">
<DndContext
sensors={sensors}
collisionDetection={closestCorners}
onDragStart={handleDragStart}
onDragMove={handleDragMove}
onDragEnd={handleDragEnd}
>
<SortableContext items={containers.map((i) => i.id)}>
{containers.map((container) => (
<Container
id={container.id}
title={container.title}
key={container.id}
onAddItem={() => {
setShowAddItemModal(true);
setCurrentContainerId(container.id);
}}
>
<SortableContext items={container.items.map((i) => i.id)}>
<div className="flex items-start flex-col gap-y-4">
{container.items.map((i) => (
<Items title={i.title} id={i.id} key={i.id} />
))}
</div>
</SortableContext>
</Container>
))}
</SortableContext>
<DragOverlay adjustScale={false}>
{/* Drag Overlay For item Item */}
{activeId && activeId.toString().includes('item') && (
<Items id={activeId} title={findItemTitle(activeId)} />
)}
{/* Drag Overlay For Container */}
{activeId && activeId.toString().includes('container') && (
<Container id={activeId} title={findContainerTitle(activeId)}>
{findContainerItems(activeId).map((i) => (
<Items key={i.id} title={i.title} id={i.id} />
))}
</Container>
)}
</DragOverlay>
</DndContext>
</div>
</div>
</div>
);
}
Github - https://github.com/chetanverma16/dndkit-guide
Conclusion
That's all I have for you! Hopefully, you learned something new.
If you enjoyed this article, give it a ❤️ so others can find it too.
For more such content, stay in touch on Twitter
Contact Me: