타입스크립트 예제가 없으면 내가 만들어주지
yarn add @dnd-kit/core
yarn add @dnd-kit/sortable
yarn add @dnd-kit/utilities
import {
closestCorners,
DndContext,
DragEndEvent,
DragOverEvent,
DragOverlay,
DragStartEvent,
KeyboardSensor,
PointerSensor,
UniqueIdentifier,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
import styled from '@emotion/styled';
import { useState } from 'react';
import SortableContainer from './Components/SortableContainer';
import SortableItem from './Components/SortableItem';
const SortableIndx = () => {
const [activeId, setActiveId] = useState<UniqueIdentifier>();
const [items, setItems] = useState<{
[key: string]: string[];
}>({
root: ['1', '2', '3', '4', ' 5', '6', '7', '8', '9'],
});
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);
const findContainer = (id: UniqueIdentifier) => {
if (id in items) {
return id;
}
return Object.keys(items).find((key: string) => items[key].includes(id.toString()));
};
const handleDragStart = (event: DragStartEvent) => {
const { active } = event;
const { id } = active;
setActiveId(id);
};
const handleDragOver = (event: DragOverEvent) => {
const { active, over } = event;
const id = active.id.toString();
const overId = over?.id;
if (!overId) return;
// 컨테이너 찾기
const activeContainer = findContainer(id);
const overContainer = findContainer(over?.id);
if (!activeContainer || !overContainer || activeContainer === overContainer) {
return;
}
setItems((prev) => {
const activeItems = prev[activeContainer];
const overItems = prev[overContainer];
// 각 아이템의 인덱스 찾기
const activeIndex = activeItems.indexOf(id);
const overIndex = overItems.indexOf(overId.toString());
let newIndex;
if (overId in prev) {
newIndex = overItems.length + 1;
} else {
const activeTop = active.rect.current.translated?.top ?? 0;
const isBelowLastItem =
over && overIndex === overItems.length - 1 && activeTop > over.rect.top + over.rect.height;
const modifier = isBelowLastItem ? 1 : 0;
newIndex = overIndex >= 0 ? overIndex + modifier : overItems.length + 1;
}
return {
...prev,
[activeContainer]: [...prev[activeContainer].filter((item) => item !== active.id)],
[overContainer]: [
...prev[overContainer].slice(0, newIndex),
items[activeContainer][activeIndex],
...prev[overContainer].slice(newIndex, prev[overContainer].length),
],
};
});
};
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
const id = active.id.toString();
const overId = over?.id;
if (!overId) return;
// 컨테이너 찾기
const activeContainer = findContainer(id);
const overContainer = findContainer(overId);
if (!activeContainer || !overContainer || activeContainer !== overContainer) {
return;
}
const activeIndex = items[activeContainer].indexOf(id);
const overIndex = items[overContainer].indexOf(overId.toString());
if (activeIndex !== overIndex) {
setItems((items) => ({
...items,
[overContainer]: arrayMove(items[overContainer], activeIndex, overIndex),
}));
}
setActiveId(undefined);
};
return (
<Wrapper>
<DndContext
sensors={sensors}
collisionDetection={closestCorners}
onDragStart={handleDragStart}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
>
<SortableContainer id='root' items={items.root} />
<DragOverlay>{activeId ? <SortableItem id={activeId} /> : null}</DragOverlay>
</DndContext>
</Wrapper>
);
};
export default SortableIndx;
const Wrapper = styled.div`
dispaly: flex;
flexdirection: 'row';
`;
import { useDroppable } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import styled from '@emotion/styled';
import SortableItem from './SortableItem';
interface ISortableContainer {
id: string;
items: any;
}
const SortableContainer = ({ id, items }: ISortableContainer) => {
const { setNodeRef } = useDroppable({ id });
return (
<SortableContext id={id} items={items} strategy={verticalListSortingStrategy}>
<SortableContainerWrapper ref={setNodeRef}>
{items.map((id: string) => (
<SortableItem key={id} id={id} />
))}
</SortableContainerWrapper>
</SortableContext>
);
};
export default SortableContainer;
const SortableContainerWrapper = styled.div`
background: "#dadada",
padding: 10,
margin: 10,
flex: 1
`;
import React from 'react';
import { useSortable } from '@dnd-kit/sortable';
import styled from '@emotion/styled';
import { CSS } from '@dnd-kit/utilities';
import { UniqueIdentifier } from '@dnd-kit/core';
interface IItem {
id: UniqueIdentifier;
}
const Item = ({ id }: IItem) => {
return <ItemWrapper>{id}</ItemWrapper>;
};
const SortableItem = ({ id }: { id: UniqueIdentifier }) => {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
const SortableItemWrapper = styled.div`
transform: CSS.Transform.toString(${transform}), ${transition};
`;
return (
<SortableItemWrapper ref={setNodeRef} {...attributes} {...listeners}>
<Item id={id} />
</SortableItemWrapper>
);
};
export default SortableItem;
const ItemWrapper = styled.div`
width: "100%",
height: 50,
display: "flex",
alignItems: "center",
justifyContent: "center",
border: "1px solid black",
margin: "10px 0",
background: "white"
}
`;