어드민에서 관리자가 드래그를 여러 컴포넌트를 오고가며 순서가 굉장히 유동적으로 바뀌는 기능일 작업하게 되었다.
dnd를 쓸까 했지만, dnd-kit에 원하는 공식예제가 있는 걸 보고 사용하기로 했다.
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",

여기서 드래그앤 드롭 이용했고,
https://docs.dndkit.com/presets/sortable
공식문서에 가면 Item, Context를 이용한 간단한 예시가 있다.
리스트 아이템을 드래그로 정렬하는 기능을 구현한 예제
관리자가 여러 개의 하위 아이템을 추가/삭제하고, 유동적으로 순서를 변경할 수 있도록 설계되었다.
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const onDragEnd = (event) => {
const { active, over } = event;
if (!over) return; // 드래그 도중 놓칠 경우 예외 처리
if (active.id !== over.id) {
setItems((items) => {
const oldIndex = items.findIndex((item) => item.id === active.id);
const newIndex = items.findIndex((item) => item.id === over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
};
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={onDragEnd}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items.map((component) => (
<SortableItem id={component.id} key={component.id}>
{renderComponent(component.childList, component.category)}
</SortableItem>
))}
</SortableContext>
</DndContext>
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
return (
<div ref={setNodeRef} {...attributes} {...listeners}>
<Icon.Group />
</div>
)
Component.tsx
import {
DndContext,
KeyboardSensor,
PointerSensor,
closestCenter,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
SortableContext,
arrayMove,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import React, { useState } from 'react';
import { SortableItem } from '../../common/SortableItem';
import { Divider } from './child/divider/Divider';
export const Component = () => {
const [items, setItems] = useState([]);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);
const onDragEnd = (event) => {
const { active, over } = event;
if (!over) return;
if (active.id !== over.id) {
setItems((items) => {
const oldIndex = items.findIndex((item) => item.id === active.id);
const newIndex = items.findIndex((item) => item.id === over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
};
const renderComponent = (
category: 'div' | 'divider',
componentId: number,
) => {
return {
div: <div />,
divider: <Divider />,
}[category];
};
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={onDragEnd}>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
<>
{items.map((component) => {
return (
<SortableItem id={component.id} key={component.id}>
<React.Fragment key={component.id}>
{renderComponent(component.childList, component.category)}
</React.Fragment>
</SortableItem>
);
})}
</>
</SortableContext>
</DndContext>
);
};
SortableItem.tsx
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Icon } from 'images/icon';
import { PropsWithChildren } from 'react';
interface Props extends PropsWithChildren {
id: string | number;
}
export function SortableItem(props: Props) {
const { id, children } = props;
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
cursor: 'grab',
width: '100%',
display: 'flex',
gap: '8px',
};
return (
<div style={style}>
/* 클릭 할 아이콘 */
<div ref={setNodeRef} {...attributes} {...listeners}>
<Icon.Group />
</div>
{children}
</div>
);
}
위와 같이 적용하면 내가 원하는 아이콘을 잡고 적용되어있는 하위 컴포넌트들을 동작시킬 수 있다!
중간에 삭제한 로직은 많지만 원하는 내용이 많아 커스텀해야 할 부분이 많다면
이 라이브러리를 써보는것도 좋을 것 같다
