dnd-kitを使ったリストの並び替えについて
Next.js
dnd kit
フロントエンド
知識
気を付けるポイントとしては、
- SortableItem用にidを定義する必要がある
- touchActionのnoneを設定しないとモバイル操作がうまくいかない
以下実装例
ts
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
modifiers={[
restrictToVerticalAxis,
restrictToWindowEdges,
restrictToParentElement,
]}
>
<SortableContext
items={ideas.map((idea) => ({ id: idea.id }))}
strategy={verticalListSortingStrategy}
>
<div className='mt-8 w-full grow rounded-3xl bg-gray-100 px-4 py-8 shadow-md'>
<div className='item-center flex justify-center'>
<div className='w-full'>
<TextInput
id='idea_title'
type='text'
variant='noneShadow'
placeholder='アイデアを入力してください'
maxLength={100}
value={newIdeaTitle}
onChange={(event) => {
setNewIdeaTitle(event.target.value);
}}
/>
</div>
<div className='ml-4'>
<button
className='flex size-9 items-center justify-center rounded-full bg-gray-300 p-2 text-white shadow-md hover:bg-gray-500'
onClick={handleAddIdea}
>
<PlusIcon />
</button>
</div>
</div>
<ul className='mt-8'>
{ideas.map((idea) => (
<SortableItem
key={idea.sort}
id={idea.id}
ideaId={idea.ideaId}
title={idea.title}
handleCreateMemoButtonClick={handleCreateMemoButtonClick}
handleDeleteIdea={handleDeleteIdea}
/>
))}
</ul>
</div>
</SortableContext>
</DndContext>
const SortableItem = ({
id,
ideaId,
title,
handleCreateMemoButtonClick,
handleDeleteIdea,
}: SortableItemProps): JSX.Element => {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
touchAction: 'none',
};
return (
<>
<li
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
className={`my-2 flex items-center justify-between bg-white p-2 shadow
${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`}
>
<span>{title}</span>
<div className='ml-auto flex gap-4'>
<CircleButton
size='small'
variant='tertiary'
onClick={() => {
handleCreateMemoButtonClick(title);
}}
>
<PencilEditIcon className='size-4' />
</CircleButton>
<CircleButton
size='small'
variant='gray'
onClick={async () => {
await handleDeleteIdea(ideaId);
}}
>
<TrashIcon className='size-4' />
</CircleButton>
</div>
</li>
</>
);
};