Drag and drop d'une liste avec la librairie dnd-kit et Next.js

Mise en ligne : dimanche 28 janvier 2024

image de Drag and drop d'une liste avec la librairie dnd-kit et Next.js}

J'ai préparé un projet Next.js avec tout le frontend déjà en place. Voici le lien vers le projet de départ : projet github

Installons les dépendances et lançons le projet :

pnpm i pnpm dev

Notre page contient une liste de langages de programmation.

Pour le moment, la seule fonctionnalité présente est la possibilité d'ajouter un nouveau langage à notre liste.

Commençons par installer les dépendances nécessaires :

pnpm i @dnd-kit/core @dnd-kit/utilities @dnd-kit/sortable @dnd-kit/modifiers

1. Ajout du <DndContext /> pour englober la logique du drag and drop

On englobe donc notre languages.map dans le <DndContext />.

page.tsx
<DndContext> {languages.map((language) => ( <LanguageView key={language.id} language={language} /> ))} </DndContext>

2. Ajout du <SortableContext />

On ajoute le <SortableContext /> dans le <DndContext />, avec la seule props qui est obligatoire, items, dans laquelle ont met notre tableau (languages) Votre code devrait ressembler à ceci :

page.tsx
<DndContext> <SortableContext items={languages}> {languages.map((language) => ( <LanguageView key={language.id} language={language} /> ))} </SortableContext> </DndContext>

3. Ajout du useSortable dans le composant LanguageView

C'est l'élément central du fonctionnement de dnd-kit/sortable pour le drag and drop sur les listes.

C'est lui, qui sous le capot, fera tout le nécessaire pour que notre drag and drop se réalise comme par magie, avec très peu de code, et beaucoup de possibilités de personnalisation.

Importons le hook, ainsi que CSS, qui sera nécessaire pour les styles (transform) :

language-view.tsx
import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities';

Ajoutons le hook, et mettons en place le style :

language-view.tsx
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: language.id }); const style = { transform: CSS.Transform.toString(transform), transition, };

Pour finir, modifions le return de notre LanguageView, afin de prendre en compte ce que nous fournit useSortable.

En particulier, nous avons le { ... listeners } qui gérera pour nous les événements liés au drag en drop.

language-view.tsx
return ( <div ref={setNodeRef} style={style} {...attributes} {...listeners} className={cn( 'flex justify-between items-center shadow-xl rounded-lg p-4 z-0 bg-white h-16' )}> <span>{language.name}</span> </div> );

Nous pouvons constater que nous avons 3 problèmes :

  • la liste n'est pas mise à jour après le drag en drop.
  • les éléments peuvent être bougés dans tous les sens (y compris vers la droite et / ou vers la gauche), ce n'est pas forcément que ce l'on veut.
  • optionnel : nous aimerions manipuler notre élément à partir d'un endroit de notre vue (un bouton Grap) et non dans son ensemble.

4. Mise à jour de la liste après le drag and drop

Pour se faire, DndContext possèdent des props qui gèrent tout le cycle de vie du drag and drop :

  • onDragStart
  • onDragCancel
  • onDragOver
  • onDrapMove
  • onDragEnd

Nous allons nous intéresser à onDragEnd, qui répondra exactement à nos attentes, car elle renverra un état quand le drag and drop est terminé. Ajoutons donc cette ligne à notre DndContext :

page.tsx
onDragEnd={onDragEnd}

Créons ensuite la fonction qui gérera le comportement voulu à la fin du drag and drop.

page.tsx
const onDragEnd = (event: DragEndEvent) => { const { active, over } = event; if (active.id === over?.id) return; setLanguages((languages) => { const oldIndex = languages.findIndex( (language) => language.id === active.id ); const newIndex = languages.findIndex( (language) => language.id === over?.id ); return arrayMove(languages, oldIndex, newIndex); }); };

Nous destructurons event en récupérant active, qui correspondant à l'élément actuel, et over qui est l'élément sur lequel s'est terminé le drag and drop.

Si l'élément n'a pas bougé (un simple click par exemple, ou un léger mouvement), on return directement et on ne fait rien.

Sinon, on va utiliser une fonction utlitaire fourni par @dnd-kit/sortable, arrayMove, qui va nous permettre très facilement d'inverser nos deux éléments. Désormais, les éléments sont bien mis à jour, quand le drag and drop est terminé et que l'élément a réellement changé de place.

5. Empêcher les déplacements horizontaux

Pour se faire, nous allons ajouter un modifier, celui-ci nous permettra de gérer cet use case automatiquement. Nous l'importons :

page.tsx
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';

Nous l'utilisons ensuite dans notre DndContext, dans la props modifiers, prévue à cette effet.

page.tsx
modifiers={[restrictToVerticalAxis]}

Désormais, notre drag and drop fonctionne uniquement de manière verticale, et ne va plus dans tous les sens.

6. Ajout d'un bouton de Grip

Pour terminer, nous allons définir un bouton sur notre LanguageView, et c'est désormais sur ce bouton qu'il faudra se placer pour que le drag and drop commence.

language-view.tsx
<div ref={setNodeRef} style={style} className={cn( 'flex justify-between items-center shadow-xl rounded-lg p-4 z-0 bg-white h-16' )}> <span>{language.name}</span> <Button variant={'outline'} {...listeners}> <svg viewBox="0 0 20 20" width="12"> <path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z"></path> </svg> </Button> </div>

Nous supprimons { ... listerners } et { ... attributes } de la div principale. Nous ajoutons le { ... listeners } sur notre nouveau bouton, et le tour est joué, c'est maintenant lui qui déclenchera le drag and drop. Nous n'avons pas besoin du { ... attributes }, car c'est un bouton, et il a donc déjà tous les attributs d'affichage dont nous avons besoin. Notre projet et terminé. La liste est totalement fonctionnelle, avec les fonctionnalités dont nous avions besoin. Nous avons pu voir que dnd-kit permet de facilement mettre en place le drag and drop, et qu'il est également facilement configurable.

Voici un lien vers la documentation officielle, si vous désirez aller plus loin :

Documentation officielle de dnd-kit