Le moteur
Dérouler un tuto : un état qui survit aux changements de page et au rechargement sans écart d’hydratation, un fournisseur qui fait avancer les étapes de page en page, et un voile percé qui suit une cible qui bouge, ne laisse agir qu’elle, et reconnaît l’action attendue — au clavier comme à la souris.
Précédemment. Un tuto est une donnée : des étapes qui désignent chacune un élément par son nom data-tour, et disent ce qui les fait passer.
Le moteur tient en trois pièces, chacune avec une responsabilité :
Pièce | Fichier | Responsabilité |
|---|---|---|
Le store | tour.store.ts | Quel tuto, quelle étape : un état qui survit à un changement de page et à un rechargement. |
Le fournisseur | TourProvider.tsx | Monté sur toutes les pages : lit l’état, fait passer les étapes de page, affiche l’overlay ou le lanceur. |
L’overlay | TourOverlay.tsx | Une étape à l’écran : trouver la cible, assombrir le reste, reconnaître l’action, la bulle. |
Showing entries1to3entries
Un état qui traverse les pages 🧭#
Un tuto commence sur une page et finit sur une autre. Un useState dans un composant disparaît quand la page change ; un état global en mémoire, quand la page se recharge. L’état du tuto vit donc dans le sessionStorage du navigateur : propre à l’onglet (deux onglets, deux tutos), effacé à sa fermeture.
tour.store.ts
Clique une étape pour mettre en évidence la zone correspondante dans le fichier complet.
// ⚠ Fichier introuvable : apps/web/src/features/tours/tour.store.ts// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.Un état qui doit survivre au-delà d’un composant n’a pas sa place dans un useState recopié depuis le stockage par un effet : c’est une source extérieure, que useSyncExternalStore lit directement. Le composant n’a rien à synchroniser, et le serveur a sa propre réponse (partie « Qualité », chapitre « Le typecheck et le lint »).
Le fournisseur, sur toutes les pages#
TourProvider est monté une fois, dans les fournisseurs de l’application : il enveloppe toutes les pages, et continue d’exister quand la page change.
TourProvider.tsx
Clique une étape pour mettre en évidence la zone correspondante dans le fichier complet.
// ⚠ Fichier introuvable : apps/web/src/features/tours/TourProvider.tsx// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.L’overlay d’une étape#
Suivre une cible qui bouge#
La cible d’une étape peut ne pas encore exister (la page charge ses données), bouger (on fait défiler, on redimensionne la fenêtre), ou changer de valeur (on tape). L’overlay ne s’abonne pas à chacun de ces événements : il regarde la cible à chaque image, par requestAnimationFrame.
TourOverlay.tsx — le suivi de la cible
Clique une étape pour mettre en évidence la zone correspondante dans le fichier complet.
// ⚠ Fichier introuvable : apps/web/src/features/tours/TourOverlay.tsx// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.Une cible n’est retenue que si elle a une position à l’écran. Un même nom peut en effet désigner deux éléments, dont un seul s’affiche selon la largeur de l’écran — la barre du header sur un ordinateur, le bouton du menu sur un téléphone (chapitre 1) :
Un élément masqué par display: none (la classe hidden, ou xl:hidden sur grand écran) n’a aucun rectangle : getClientRects() est vide. L’ancienne version prenait le premier élément du document, la barre masquée sur un téléphone, et perçait le voile sur un rectangle de taille nulle, en haut à gauche.
Un voile percé, et des clics bloqués#
Le voile est un SVG plein écran, percé par un masque à l’emplacement de la cible : la cible reste nette, le reste s’assombrit. Mais un voile dessiné ne bloque rien — le SVG laisse passer les clics (pointer-events-none). Ce sont quatre panneaux transparents, posés autour du trou, qui captent les clics partout ailleurs : un clic à côté ne déclenche rien, sauf sur la cible.
Pour une étape d’explication (next), un cinquième panneau couvre la cible elle-même : on la montre, on ne la manipule pas. Sans lui, un clic sur le bouton « Créer » pendant l’étape qui explique la date enverrait le formulaire, et le tuto serait perdu.
Couche | z-index | Rôle |
|---|---|---|
Le voile (SVG masqué) | 10000 | Assombrir tout sauf la cible ; ne capte rien. |
Les bloqueurs | 10000 | Capter les clics hors de la cible (et sur elle, pour une explication). |
Le liseré | 10001 | Souligner la cible ; ne capte rien. |
La bulle | 10002 | Le titre, l’explication, « Quitter », « Suivant ». |
Showing entries1to4entries
Reconnaître l’action#
Le clic est écouté sur tout le document, en phase de capture (true) : avant que l’élément cliqué ne le reçoive, et quoi qu’il en fasse — même un composant qui arrête la propagation ne le cache pas au tuto. L’étape passe ensuite, au tour suivant de la boucle d’événements (setTimeout(…, 0)) : l’élément a d’abord traité son clic (ouvert le formulaire, suivi le lien).
Étape | Ce qui la fait passer | Qui le constate |
|---|---|---|
click | Un clic sur la cible. | L’overlay, en capture sur le document. |
input | « Suivant », actif dès que validate accepte la valeur. | L’overlay, qui relit la valeur à chaque image. |
route | L’arrivée sur une page qui correspond. | Le fournisseur, qui survit au changement de page. |
next | « Suivant » (ou « Terminer » à la dernière étape). | La bulle. |
Showing entries1to4entries
Au clavier#
Échap quitte à tout moment. Et le curseur clavier ne sort pas de l’étape : s’il arrive ailleurs (Tab vers un lien caché sous le voile), il est ramené sur la cible ou la bulle. Pour une étape d’explication, il n’entre même pas dans la cible : le panneau posé dessus arrête la souris, mais Tab jusqu’au bouton « Envoyer » montré, puis Entrée, l’aurait cliqué. Les bloqueurs arrêtent la souris ; ce garde arrête le clavier.
Reste Entrée dans un champ à remplir : dans un <form>, elle envoie le formulaire. Sur la page du mot de passe oublié, l’adresse tapée rend le bouton actif, et Entrée partait avec le lien de réinitialisation. Pendant toute étape qui n’attend ni clic ni page, l’overlay écoute donc l’évènement submit en capture sur le document et l’annule : l’évènement n’atteint jamais React. Les étapes click et route le laissent passer — l’enregistrement d’un post, qui attend justement la page de l’article créé, en a besoin. Avec le curseur placé sur la bonne cible à chaque étape (plus haut), le tuto se suit entièrement sans souris (partie « Interface transverse », chapitre « L’accessibilité »).
Une étape perdue#
Une cible introuvable après huit secondes — un composant renommé sans la garde, une page en erreur — ne laisse pas l’utilisateur devant un voile muet. (Une étape facultative, elle, s’efface bien avant : son absence est prévue.) La bulle dit « Élément introuvable », n’offre plus que « Quitter », et le voile couvre toute la page : on ne peut ni avancer, ni agir à l’aveugle.
Récapitulatif#
Question | Réponse du projet |
|---|---|
Où vit l’état du tuto ? | Dans le sessionStorage de l’onglet, lu par un store externe (useSyncExternalStore) ; le serveur répond « aucun tuto ». |
Comment une même action ne fait-elle pas sauter deux étapes ? | next(fromIndex) ne passe que si l’étape en cours est encore fromIndex. |
Qui fait passer une étape de page ? | Le fournisseur, monté sur toutes les pages et qui survit à la navigation. |
Comment l’overlay suit-il la cible ? | À chaque image (requestAnimationFrame), sans rendu tant qu’elle ne bouge pas. |
Comment seuls les clics sur la cible passent-ils ? | Quatre bloqueurs transparents autour du trou ; un cinquième sur la cible pour une explication. |
Et une cible qui peut manquer ? | L’étape se déclare optional : absente depuis une seconde et demie, elle passe d’elle-même. |
Et un nom porté par deux éléments ? | Le premier visible est retenu (getClientRects non vide). |
Et au clavier ? | Échap quitte ; le curseur va sur la cible ou la bulle, et n’en sort pas ; pour une explication, il reste dans la bulle. |
Et Entrée dans un champ ? | Le submit est annulé en capture pendant toute étape qui n’attend ni clic ni page. |
Showing entries1to9entries
À suivre#
Le moteur déroule un tuto une fois lancé. Reste à le proposer au bon visiteur, au bon endroit — et à vérifier, de bout en bout, qu’il se suit vraiment.
Chapitre suivant : lancer, vérifier, écrire. Le bouton « ? » et le bouton des fiches, le tuto suivi par un test de bout en bout (et ce qu’il a trouvé), et la recette d’un nouveau tuto.