Tableau kanban
Trois colonnes, des cartes qu'on fait glisser de l'une à l'autre, à la souris, au doigt ou au clavier. Le bon exercice pour rendre le glisser-déposer accessible à tout le monde.
- Utiliser l'API Drag and Drop du navigateur
- Offrir une alternative au clavier et au tactile
- Annoncer un déplacement aux lecteurs d'écran
Pourquoi un tableau kanban ? À faire, en cours, fait : c'est la façon la plus simple de suivre un projet, et le glisser-déposer rend la chose naturelle.
Le navigateur fournit une API de glisser-déposer avec dragstart, dragover et drop. Mais elle ne marche pas au doigt sur téléphone, et pas du tout au clavier. Un kanban qui ne marche qu'à la souris exclut beaucoup de monde.
On ajoute donc deux autres chemins : de petites flèches sur chaque carte, et au clavier, Entrée pour saisir une carte puis les flèches pour la déplacer. Chaque déplacement est annoncé à voix haute par une zone aria-live.
Pourquoi en JavaScript simple ? Toutes les méthodes appellent la même fonction deplacer. Les données restent un simple tableau de cartes.
LES DONNÉES
Une carte sait dans quelle colonne elle est. Les colonnes ne stockent rien.
const COLONNES = ['afaire', 'encours', 'fait']; let cartes = [{ id: 1, texte: 'Maquette', col: 'afaire' }];UNE SEULE FONCTION POUR DÉPLACER
Souris, doigt ou clavier : tout passe par ici.
function deplacer(id, sens) { const carte = cartes.find(c => c.id === id); const i = COLONNES.indexOf(carte.col) + sens; if (i < 0 || i >= COLONNES.length) return; carte.col = COLONNES[i]; annoncer(`${carte.texte} déplacée dans ${COLONNES[i]}`); rendre(id); // et on remet le focus sur la carte }LA SOURIS : DRAG AND DROP
tableau.addEventListener('dragstart', e => { e.dataTransfer.setData('text/plain', e.target.closest('.carte').dataset.id); }); tableau.addEventListener('dragover', e => { if (e.target.closest('.colonne')) e.preventDefault(); // sans ça, drop ne se déclenche pas }); tableau.addEventListener('drop', e => { const id = Number(e.dataTransfer.getData('text/plain')); const vers = COLONNES.indexOf(e.target.closest('.colonne').dataset.col); const de = COLONNES.indexOf(cartes.find(c => c.id === id).col); deplacer(id, vers - de); });LE CLAVIER
Entrée saisit la carte, les flèches la déplacent, Entrée la pose.
let saisie = null; tableau.addEventListener('keydown', e => { const id = Number(e.target.closest('.carte')?.dataset.id); if (!id) return; if (e.key === 'Enter') saisie = saisie === id ? null : id; if (saisie === id && e.key === 'ArrowRight') deplacer(id, 1); if (saisie === id && e.key === 'ArrowLeft') deplacer(id, -1); });ANNONCER
Une zone aria-live lit le message à voix haute dès qu'il change.
const annoncer = msg => (document.getElementById('statut').textContent = msg);
PIÈGE 1 : Oublier preventDefault sur dragover
Mauvais : écouter seulement drop. L'événement ne se déclenche jamais, et la carte revient à sa place.
Correct : appeler e.preventDefault() dans dragover sur les zones qui acceptent le dépôt.
PIÈGE 2 : Un kanban réservé à la souris
Mauvais : compter uniquement sur le glisser-déposer natif. Il ne marche pas au doigt sur la plupart des téléphones, ni au clavier.
Correct : des boutons de déplacement visibles et une gestion du clavier, qui appellent la même fonction.
PIÈGE 3 : Perdre le focus après un déplacement
Mauvais : redessiner le tableau sans rien faire d'autre. Au clavier, on se retrouve tout en haut de la page.
Correct : après le rendu, remettre le focus sur la carte déplacée, dans sa nouvelle colonne.
- Réordonner les cartes dans une même colonne, en calculant la position sous le pointeur.
- Une limite par colonne, comme trois cartes maximum « en cours ».
- Des étiquettes de couleur et une date par carte.
- Pour un vrai produit, une bibliothèque comme dnd-kit gère le tactile, le défilement automatique et l'accessibilité.
- Sauvegardez le tableau dans localStorage, comme la liste de tâches qui se souvient.
Les cartes sont un useState tableau, et deplacer renvoie un nouveau tableau avec map.
Chaque colonne filtre les cartes au rendu, avec une key stable par carte.
Le focus après déplacement se fait dans un useEffect, grâce à une ref vers la carte.
Chargement des commentaires…