APOLLO VISION LABS

Hotspots

Un marqueur sur un élément qui ouvre une courte explication et peut lancer une visite, ce qui le retire pour de bon, et quand il ne dessine plus rien du tout.

Un hotspot est un petit marqueur épinglé sur un élément, en dehors de toute visite. Cliquer dessus ouvre une bulle avec un titre, un texte, et, en option, un bouton qui lance une visite. Ouvrir la bulle marque le hotspot comme vu, pour de bon : un hotspot vu ne dessine plus aucun marqueur, ni sur cette page ni à la visite suivante, jusqu’à ce que reset() soit appelé.

Hotspot et ResolvedHotspot

interface Hotspot {
  id: string;
  target: string;
  title?: string;
  titleKey?: string;
  body?: string;
  bodyKey?: string;
  tourId?: string;
  placement?: 'top' | 'bottom' | 'left' | 'right';
}
  • target : la clé logique portée par l’attribut data-guide de l’élément, comparée de la même façon que le target d’une étape de visite.
  • title / body : du texte littéral. titleKey / bodyKey passent par la prop translate du provider, exactement comme une étape de visite.
  • tourId : la visite lancée quand le bouton « Show me » de la bulle est cliqué. Omettez-le et la bulle n’affiche qu’un bouton Fermer.
  • placement : remplace le placement par défaut de la bulle, pour ce hotspot uniquement.

Deux hotspots partageant un id font échouer HotspotProvider au rendu, immédiatement, tout comme un identifiant de visite dupliqué.

useHotspots() renvoie chaque hotspot résolu en ResolvedHotspot, avec son texte déjà traduit et son état vu attaché :

interface ResolvedHotspot {
  id: string;
  target: string;
  title: string;
  body: string;
  seen: boolean;
  tourId?: string;
  placement?: 'top' | 'bottom' | 'left' | 'right';
}

HotspotProvider et useHotspots

import {
  HotspotProvider,
  createBrowserStorage,
  type Hotspot,
} from '@apollovisionlabs/guide-core';
import { Hotspots } from '@apollovisionlabs/guide-mui';

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

const hotspots: Hotspot[] = [
  { id: 'share', target: 'project.share', title: 'Share a project', tourId: 'sharing' },
];

export function App() {
  return (
    <HotspotProvider hotspots={hotspots} storage={storage}>
      <AppRoutes />
      <Hotspots />
    </HotspotProvider>
  );
}
Prop Type Défaut Description
hotspots Hotspot[] aucun Les hotspots disponibles dans cet arbre. Les identifiants doivent être uniques.
children ReactNode aucun Votre application.
storage GuideStorage aucun Persiste les hotspots déjà ouverts, sous la seule clé hotspots:seen. Voir Persistance.
translate (key: string) => string aucun Résout titleKey / bodyKey.
onEvent (event: GuideEvent) => void aucun Appelé pour hotspot:show et hotspot:open. Voir « Événements » ci-dessous.

useHotspots() renvoie :

interface UseHotspotsResult {
  hotspots: ResolvedHotspot[];
  restored: boolean;
  open: (hotspotId: string) => void;
  startTour: (hotspotId: string) => void;
  reset: () => void;
  notifyShown: (hotspotId: string) => void;
}
  • hotspots : tous les hotspots, vus et non vus, chacun avec son propre seen. Un rendu a besoin aussi de ceux déjà vus, pour garder un marqueur monté pendant que sa propre bulle se ferme ; filtrer sur les non vus tient en une ligne au point d’appel si c’est tout ce qu’il vous faut.
  • restored : si la lecture initiale depuis le stockage s’est stabilisée. true immédiatement quand aucune prop storage n’a été fournie, puisqu’il n’y a alors rien à attendre, et true une fois que la lecture se résout ou échoue dans les autres cas, y compris une lecture en échec. Un rendu devrait attendre cette valeur avant de dessiner le moindre marqueur, sinon un hotspot déjà vu en stockage peut clignoter à l’écran une fois avant que la restauration n’arrive.
  • open(hotspotId) : marque le hotspot comme vu et émet hotspot:open. Le rappeler sur un hotspot déjà vu ne fait plus rien. Écrit un avertissement dans la console et ne fait rien pour un identifiant qui ne désigne aucun hotspot.
  • startTour(hotspotId) : lance le tourId du hotspot sur le GuideProvider le plus proche. Ne fait rien si le hotspot n’a pas de tourId. Avertit une fois si l’arbre n’a pas de GuideProvider, et avertit une fois si la visite échoue à démarrer.
  • reset() : efface tous les hotspots vus d’un coup, ramenant tous les marqueurs. Il n’existe pas d’annulation par hotspot.
  • notifyShown(hotspotId) : appelé par un rendu une fois qu’un marqueur est effectivement dessiné à l’écran. Déclenche hotspot:show, et est dédupliqué par hotspot et par montage, si bien qu’un défilement qui remesure la cible n’annonce pas une seconde impression. Hotspots l’appelle déjà pour vous ; ne l’écrivez vous-même que si vous dessinez vos propres marqueurs par-dessus useHotspots.

Ouvrir retire un hotspot, pour de bon

open(hotspotId), que le marqueur fourni appelle quand on clique dessus, est la seule chose qui passe seen à true. Une fois qu’il a tourné pour un hotspot, le seen de ce ResolvedHotspot vaut true partout où useHotspots() est lu, et, avec storage configuré, le reste après rechargement, car il est écrit sous hotspots:seen. Le composant Hotspots ne dessine rien pour un hotspot vu. Le seul retour en arrière est reset(), et il emporte tous les hotspots avec lui : il n’y a pas de moyen de faire redevenir non vu un seul hotspot.

Le composant MUI Hotspots

import { Hotspots } from '@apollovisionlabs/guide-mui';

<Hotspots labels={{ startTour: 'Show me', close: 'Close' }} placement="bottom" />;
Prop Type Défaut Description
labels Partial<HotspotLabels> voir ci-dessous Le nom accessible du marqueur et le texte des boutons de la bulle.
placement 'top' | 'bottom' | 'left' | 'right' 'bottom' Placement par défaut de la bulle ; le placement propre au hotspot l’emporte.
zIndex number theme.zIndex.drawer + 1 Niveau d’empilement du marqueur ; la bulle se place un niveau au-dessus. Voir « Limites » ci-dessous pour les cas où cela compte.

HotspotLabels.marker est une fonction

interface HotspotLabels {
  marker: (title: string) => string;
  startTour: string;
  close: string;
}

Défauts : marker: (title) => \Show what is new: ${title}`, startTour: ‘Show me’, close: ‘Close’`.

marker construit le nom accessible du bouton marqueur, et c’est une fonction plutôt qu’un gabarit de chaîne parce que la place du titre dans la phrase n’est pas la même d’une langue à l’autre : une traduction peut avoir besoin de mettre le titre en premier, de l’entourer autrement, ou de laisser tomber la formule d’introduction. startTour et close sont de simples chaînes, comme le reste des labels de GuideTour et Checklist.

Événements

Événement Charge utile Quand
hotspot:show { hotspotId } Le marqueur d’un hotspot est effectivement dessiné à l’écran. Émis une fois par hotspot et par montage.
hotspot:open { hotspotId } La bulle du hotspot s’ouvre, ce qui le marque aussi comme vu. Pas réémis pour une bulle déjà ouverte ; un second clic sur un marqueur ouvert se contente de la fermer.

Les hotspots s’effacent devant une visite en cours

Tant qu’une visite est running ou paused, Hotspots ne dessine aucun marqueur, pas seulement ceux qui entrent en collision avec la cible de l’étape en cours. Un marqueur est position: fixed sur le coin supérieur droit de sa cible, donc quand une étape de visite pointe sur ce même élément, le marqueur peut intercepter le clic censé faire avancer une étape advanceOn: 'click', rester à pulser inutilement par-dessus le spot d’une étape non interactive, ou garder le focus clavier au-dessus de l’étape qu’une visite vient tout juste de lancer depuis sa propre bulle. Plutôt que de résoudre chacune de ces collisions au cas par cas, aucun marqueur de hotspot ne se dessine tant qu’une visite est en cours.

paused compte comme en cours : une visite en pause attend sa cible, elle n’est pas terminée. C’est une mise en sourdine, pas un retrait. Rien ici ne touche seen, donc les marqueurs reviennent exactement comme avant, non vus compris, dès que la visite s’arrête ou se termine, et hotspot:show se redéclenche pour eux puisque sa déduplication se fait par montage, pas globalement.

Limites

  • Un hotspot dans une boîte de dialogue modale est couvert. Le zIndex par défaut du marqueur est theme.zIndex.drawer + 1, choisi pour se placer au-dessus d’une barre d’application ou d’un tiroir, où se trouve souvent la cible d’un hotspot, et en dessous du spot d’une visite en cours, à theme.zIndex.modal. Une boîte de dialogue se rend elle aussi au-dessus du niveau du tiroir, donc un hotspot dont la cible vit à l’intérieur d’une boîte de dialogue est couvert par elle, sauf à relever zIndex au-dessus.
  • Avec storage configuré, aucun marqueur ne se dessine tant que la lecture initiale ne s’est pas stabilisée. Hotspots attend restored avant de dessiner quoi que ce soit, donc avec un stockage lent le premier rendu n’affiche aucun marqueur, et ils apparaissent une fois la lecture résolue.
  • Une visite en pause sur une cible qui ne se monte jamais met tous les hotspots en sourdine aussi longtemps qu’elle reste en pause. Sous la politique de cible absente wait, une visite en pause ne dessine déjà rien elle-même ; les hotspots s’effaçant devant elle aussi, la page peut se retrouver sans aucune affordance visible, Échap restant la seule sortie.

Pour aller plus loin