APOLLO VISION LABS

Démarrer

Installer les deux paquets, lancer une première visite, puis brancher le provider sur une vraie application.

guide accompagne un nouvel utilisateur dans une interface, une étape à la fois. La logique tient dans un paquet, le rendu Material UI dans un second.

Deux paquets, et pourquoi

npm install @apollovisionlabs/guide-core @apollovisionlabs/guide-mui @mui/material @emotion/react @emotion/styled

@apollovisionlabs/guide-core contient la machine à états, la résolution des cibles, la correspondance de routes, la persistance et les primitives d’accessibilité. Il n’affiche rien et ne dépend que de React.

@apollovisionlabs/guide-mui est un rendu de ce moteur : un projecteur qui assombrit la page et une bulle d’étape, en Material UI. Sa seule dépendance de production est le core.

Si vous dessinez votre propre bulle, installez le core seul et lisez l’étape courante avec useGuideStep(). Tout ce qui suit suppose la couche Material UI, qui est le chemin le plus court vers une visite qui tourne.

Le plus petit exemple qui fonctionne

import { GuideProvider, useTour, type Tour } from '@apollovisionlabs/guide-core';
import { GuideTour } from '@apollovisionlabs/guide-mui';

const welcomeTour: Tour = {
  id: 'welcome',
  steps: [
    {
      target: 'nav.projects',
      title: 'Vos projets sont ici',
      body: 'Tout ce que vous créez est rangé dans un projet.',
      placement: 'bottom',
    },
  ],
};

function StartButton() {
  const tour = useTour('welcome');
  return <button onClick={() => tour.start()}>Lancer la visite</button>;
}

export function App() {
  return (
    <GuideProvider tours={[welcomeTour]}>
      <nav>
        <a href="/projects" data-guide="nav.projects">
          Projets
        </a>
      </nav>
      <StartButton />
      <GuideTour />
    </GuideProvider>
  );
}

GuideProvider porte l’état, GuideTour l’affiche. Les deux lisent le thème Material UI : gardez-les à l’intérieur de votre ThemeProvider si vous en avez un.

Déclarez la visite au niveau du module, comme ci-dessus. Un objet visite reconstruit dans un corps de composant est un objet neuf à chaque rendu, et le délai de cible absente s’appuie sur l’identité des objets étape : recréez-les à chaque rendu et ce minuteur n’atteint jamais son échéance. Le symptôme est une visite bloquée indéfiniment sur une étape, avec onMissingTarget qui semble ignoré. Si une visite doit vraiment être construite à l’exécution, construisez-la une fois dans un useMemo aux dépendances stables.

Comment une étape trouve sa cible

target est une clé logique, pas un sélecteur CSS. Le moteur cherche [data-guide="<clé>"] : c’est l’élément mis en avant qui déclare sa participation.

<button data-guide="projects.create">Nouveau projet</button>

Un sélecteur CSS lierait la visite à un balisage qui change pour des raisons étrangères : une classe renommée par une refonte graphique, un conteneur ajouté par un remaniement de mise en page, un nom de classe généré par une bibliothèque de style. Un attribut data-guide est un contrat, visible dans le source de l’élément, qu’un relecteur voit qu’il est sur le point de casser.

Donnez un espace de noms aux clés, du type nav.projects ou projects.create. Elles apparaissent dans les événements step:show et target:missing, donc dans vos journaux.

L’élément n’a pas besoin d’exister tout de suite. Le moteur observe le DOM avec un MutationObserver pendant targetTimeoutMs (5000 ms par défaut), puis applique la politique de cible absente décrite dans Visites.

Brancher une vraie application

Cinq props transforment l’exemple minimal en quelque chose de livrable.

import { useLocation, useNavigate } from 'react-router';
import { useTranslation } from 'react-i18next';
import { GuideProvider, createBrowserStorage } from '@apollovisionlabs/guide-core';
import { GuideTour } from '@apollovisionlabs/guide-mui';
import { productTour } from './tours';

const storage = createBrowserStorage('my-app');

export function App() {
  const navigate = useNavigate();
  const location = useLocation();
  const { t } = useTranslation();

  return (
    <GuideProvider
      tours={[productTour]}
      navigate={(path) => navigate(path)}
      location={location.pathname}
      storage={storage}
      translate={(key) => t(key)}
      onEvent={(event) => console.info('[guide]', event)}
    >
      <AppRoutes />
      <GuideTour />
    </GuideProvider>
  );
}

navigate est appelée quand une étape vit sur une autre page. location est le chemin courant : c’est ainsi que le moteur sait s’il y est déjà. Sans location, toutes les étapes sont considérées comme étant sur la bonne page et plus rien ne navigue.

storage conserve la progression. Deux implémentations sont fournies : createMemoryStorage() pour les tests, createBrowserStorage(namespace) pour localStorage. Aucune ne parle à un serveur, et les paquets ne font aucun appel réseau. Sur un poste partagé, localStorage appartient au profil du navigateur et non à la personne connectée : la visite terminée par la première la supprime pour la seconde. Quand plusieurs comptes se partagent une machine, implémentez GuideStorage contre votre propre API.

translate résout titleKey et bodyKey. Elle prend une clé et rend une chaîne, ce qui tient en une ligne avec n’importe quelle bibliothèque de traduction. Une clé sans translate affiche la clé brute plutôt que de planter : c’est le symptôme à reconnaître. Les libellés des boutons de la bulle sont séparés et se remplacent par labels sur GuideTour :

<GuideTour
  labels={{
    next: t('common.next'),
    previous: t('common.back'),
    finish: t('common.finish'),
    close: t('common.close'),
  }}
/>

close est aussi le nom accessible du bouton de fermeture : le traduire n’est pas cosmétique. Checklist et ChecklistLauncher acceptent leurs propres labels de la même façon, décrits dans Checklist.

onEvent reçoit tous les GuideEvent : tour:start, tour:complete, tour:stop, step:show et target:missing. La bibliothèque n’envoie rien nulle part ; ce que vous enregistrez et le consentement que cela demande relèvent de votre application.

Versions requises

React 19 pour le core. React 19, Material UI 7 ou 9 et Emotion 11 pour la couche Material UI. Les deux paquets portent une directive 'use client' sur chaque fichier émis : les importer ne casse pas un build serveur. GuideProvider utilise malgré tout état et contexte, donc le composant qui le rend est un composant client. GuideTour rend null tant qu’il n’est pas monté dans le navigateur, ce qui garde la visite hors du HTML rendu côté serveur.