Mise en ligne : dimanche 28 janvier 2024
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
On englobe donc notre languages.map
dans le <DndContext />.
page.tsx<DndContext>
{languages.map((language) => (
<LanguageView
key={language.id}
language={language}
/>
))}
</DndContext>
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>
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.tsximport { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
Ajoutons le hook, et mettons en place le style :
language-view.tsxconst { 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.tsxreturn (
<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 :
Pour se faire, DndContext possèdent des props qui gèrent tout le cycle de vie du drag and drop :
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.tsxonDragEnd={onDragEnd}
Créons ensuite la fonction qui gérera le comportement voulu à la fin du drag and drop.
page.tsxconst 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.
Pour se faire, nous allons ajouter un modifier
, celui-ci nous permettra de gérer cet use case automatiquement.
Nous l'importons :
page.tsximport { restrictToVerticalAxis } from '@dnd-kit/modifiers';
Nous l'utilisons ensuite dans notre DndContext, dans la props modifiers
, prévue à cette effet.
page.tsxmodifiers={[restrictToVerticalAxis]}
Désormais, notre drag and drop fonctionne uniquement de manière verticale, et ne va plus dans tous les sens.
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 :