@robertlinde/react-tour-kit 0.8.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/MIGRATION.md +205 -0
- package/README.md +401 -850
- package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.d.ts +14 -0
- package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.d.ts.map +1 -0
- package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.js +21 -0
- package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.js.map +1 -0
- package/dist/react-native-platform/hooks/use-tour-target.hook.d.ts +15 -0
- package/dist/react-native-platform/hooks/use-tour-target.hook.d.ts.map +1 -0
- package/dist/react-native-platform/hooks/use-tour-target.hook.js +28 -0
- package/dist/react-native-platform/hooks/use-tour-target.hook.js.map +1 -0
- package/dist/react-native-platform/host-store.d.ts +24 -0
- package/dist/react-native-platform/host-store.d.ts.map +1 -0
- package/dist/react-native-platform/host-store.js +63 -0
- package/dist/react-native-platform/host-store.js.map +1 -0
- package/dist/react-native-platform/insets.util.d.ts +11 -0
- package/dist/react-native-platform/insets.util.d.ts.map +1 -0
- package/dist/react-native-platform/insets.util.js +31 -0
- package/dist/react-native-platform/insets.util.js.map +1 -0
- package/dist/react-native-platform/layer/native-renderer.d.ts +7 -0
- package/dist/react-native-platform/layer/native-renderer.d.ts.map +1 -0
- package/dist/react-native-platform/layer/native-renderer.js +496 -0
- package/dist/react-native-platform/layer/native-renderer.js.map +1 -0
- package/dist/react-native-platform/layer/tour-host.d.ts +13 -0
- package/dist/react-native-platform/layer/tour-host.d.ts.map +1 -0
- package/dist/react-native-platform/layer/tour-host.js +70 -0
- package/dist/react-native-platform/layer/tour-host.js.map +1 -0
- package/dist/react-native-platform/measure.util.d.ts +20 -0
- package/dist/react-native-platform/measure.util.d.ts.map +1 -0
- package/dist/react-native-platform/measure.util.js +100 -0
- package/dist/react-native-platform/measure.util.js.map +1 -0
- package/dist/react-native-platform/native-context.d.ts +12 -0
- package/dist/react-native-platform/native-context.d.ts.map +1 -0
- package/dist/react-native-platform/native-context.js +4 -0
- package/dist/react-native-platform/native-context.js.map +1 -0
- package/dist/react-native-platform/native-environment.d.ts +14 -0
- package/dist/react-native-platform/native-environment.d.ts.map +1 -0
- package/dist/react-native-platform/native-environment.js +20 -0
- package/dist/react-native-platform/native-environment.js.map +1 -0
- package/dist/react-native-platform/native-platform.d.ts +19 -0
- package/dist/react-native-platform/native-platform.d.ts.map +1 -0
- package/dist/react-native-platform/native-platform.js +265 -0
- package/dist/react-native-platform/native-platform.js.map +1 -0
- package/dist/react-native-platform/reduced-motion.d.ts +4 -0
- package/dist/react-native-platform/reduced-motion.d.ts.map +1 -0
- package/dist/react-native-platform/reduced-motion.js +44 -0
- package/dist/react-native-platform/reduced-motion.js.map +1 -0
- package/dist/react-native-platform/registry.d.ts +18 -0
- package/dist/react-native-platform/registry.d.ts.map +1 -0
- package/dist/react-native-platform/registry.js +51 -0
- package/dist/react-native-platform/registry.js.map +1 -0
- package/dist/react-native-platform/tour-provider.d.ts +16 -3
- package/dist/react-native-platform/tour-provider.d.ts.map +1 -0
- package/dist/react-native-platform/tour-provider.js +92 -211
- package/dist/react-native-platform/tour-provider.js.map +1 -1
- package/dist/react-native.d.ts +14 -8
- package/dist/react-native.d.ts.map +1 -0
- package/dist/react-native.js +7 -6
- package/dist/react-native.js.map +1 -1
- package/dist/react-platform/dom/elements.util.d.ts +35 -0
- package/dist/react-platform/dom/elements.util.d.ts.map +1 -0
- package/dist/react-platform/dom/elements.util.js +155 -0
- package/dist/react-platform/dom/elements.util.js.map +1 -0
- package/dist/react-platform/dom/occluders.util.d.ts +13 -0
- package/dist/react-platform/dom/occluders.util.d.ts.map +1 -0
- package/dist/react-platform/dom/occluders.util.js +75 -0
- package/dist/react-platform/dom/occluders.util.js.map +1 -0
- package/dist/react-platform/dom/scroll-lock.util.d.ts +13 -0
- package/dist/react-platform/dom/scroll-lock.util.d.ts.map +1 -0
- package/dist/react-platform/dom/scroll-lock.util.js +219 -0
- package/dist/react-platform/dom/scroll-lock.util.js.map +1 -0
- package/dist/react-platform/dom/scroll.util.d.ts +44 -0
- package/dist/react-platform/dom/scroll.util.d.ts.map +1 -0
- package/dist/react-platform/dom/scroll.util.js +250 -0
- package/dist/react-platform/dom/scroll.util.js.map +1 -0
- package/dist/react-platform/dom/viewport.util.d.ts +16 -0
- package/dist/react-platform/dom/viewport.util.d.ts.map +1 -0
- package/dist/react-platform/dom/viewport.util.js +65 -0
- package/dist/react-platform/dom/viewport.util.js.map +1 -0
- package/dist/react-platform/dom/web-platform.d.ts +15 -0
- package/dist/react-platform/dom/web-platform.d.ts.map +1 -0
- package/dist/react-platform/dom/web-platform.js +169 -0
- package/dist/react-platform/dom/web-platform.js.map +1 -0
- package/dist/react-platform/layer/styles.d.ts +12 -0
- package/dist/react-platform/layer/styles.d.ts.map +1 -0
- package/dist/react-platform/layer/styles.js +285 -0
- package/dist/react-platform/layer/styles.js.map +1 -0
- package/dist/react-platform/layer/web-renderer.d.ts +4 -0
- package/dist/react-platform/layer/web-renderer.d.ts.map +1 -0
- package/dist/react-platform/layer/web-renderer.js +390 -0
- package/dist/react-platform/layer/web-renderer.js.map +1 -0
- package/dist/react-platform/tour-provider.d.ts +9 -3
- package/dist/react-platform/tour-provider.d.ts.map +1 -0
- package/dist/react-platform/tour-provider.js +28 -218
- package/dist/react-platform/tour-provider.js.map +1 -1
- package/dist/react.d.ts +10 -8
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +5 -6
- package/dist/react.js.map +1 -1
- package/dist/shared/engine/abort.util.d.ts +17 -0
- package/dist/shared/engine/abort.util.d.ts.map +1 -0
- package/dist/shared/engine/abort.util.js +72 -0
- package/dist/shared/engine/abort.util.js.map +1 -0
- package/dist/shared/engine/report.util.d.ts +12 -0
- package/dist/shared/engine/report.util.d.ts.map +1 -0
- package/dist/shared/engine/report.util.js +51 -0
- package/dist/shared/engine/report.util.js.map +1 -0
- package/dist/shared/engine/tour-adapter.type.d.ts +60 -0
- package/dist/shared/engine/tour-adapter.type.d.ts.map +1 -0
- package/dist/shared/engine/tour-adapter.type.js +2 -0
- package/dist/shared/engine/tour-adapter.type.js.map +1 -0
- package/dist/shared/engine/tour-engine.d.ts +92 -0
- package/dist/shared/engine/tour-engine.d.ts.map +1 -0
- package/dist/shared/engine/tour-engine.js +787 -0
- package/dist/shared/engine/tour-engine.js.map +1 -0
- package/dist/shared/geometry/box.util.d.ts +39 -0
- package/dist/shared/geometry/box.util.d.ts.map +1 -0
- package/dist/shared/geometry/box.util.js +146 -0
- package/dist/shared/geometry/box.util.js.map +1 -0
- package/dist/shared/geometry/compute-tooltip-layout.util.d.ts +60 -0
- package/dist/shared/geometry/compute-tooltip-layout.util.d.ts.map +1 -0
- package/dist/shared/geometry/compute-tooltip-layout.util.js +148 -0
- package/dist/shared/geometry/compute-tooltip-layout.util.js.map +1 -0
- package/dist/shared/geometry/geometry.type.d.ts +34 -0
- package/dist/shared/geometry/geometry.type.d.ts.map +1 -0
- package/dist/shared/geometry/geometry.type.js +2 -0
- package/dist/shared/geometry/geometry.type.js.map +1 -0
- package/dist/shared/geometry/reveal.util.d.ts +38 -0
- package/dist/shared/geometry/reveal.util.d.ts.map +1 -0
- package/dist/shared/geometry/reveal.util.js +135 -0
- package/dist/shared/geometry/reveal.util.js.map +1 -0
- package/dist/shared/geometry/target-visibility.util.d.ts +10 -0
- package/dist/shared/geometry/target-visibility.util.d.ts.map +1 -0
- package/dist/shared/geometry/target-visibility.util.js +29 -0
- package/dist/shared/geometry/target-visibility.util.js.map +1 -0
- package/dist/shared/platform/create-tour-adapter.d.ts +9 -0
- package/dist/shared/platform/create-tour-adapter.d.ts.map +1 -0
- package/dist/shared/platform/create-tour-adapter.js +130 -0
- package/dist/shared/platform/create-tour-adapter.js.map +1 -0
- package/dist/shared/platform/platform.type.d.ts +77 -0
- package/dist/shared/platform/platform.type.d.ts.map +1 -0
- package/dist/shared/platform/platform.type.js +2 -0
- package/dist/shared/platform/platform.type.js.map +1 -0
- package/dist/shared/platform/reveal-in-scroll-containers.d.ts +27 -0
- package/dist/shared/platform/reveal-in-scroll-containers.d.ts.map +1 -0
- package/dist/shared/platform/reveal-in-scroll-containers.js +49 -0
- package/dist/shared/platform/reveal-in-scroll-containers.js.map +1 -0
- package/dist/shared/react/tour-context.d.ts +7 -0
- package/dist/shared/react/tour-context.d.ts.map +1 -0
- package/dist/shared/react/tour-context.js +23 -0
- package/dist/shared/react/tour-context.js.map +1 -0
- package/dist/shared/react/tour-provider-core.d.ts +41 -0
- package/dist/shared/react/tour-provider-core.d.ts.map +1 -0
- package/dist/shared/react/tour-provider-core.js +24 -0
- package/dist/shared/react/tour-provider-core.js.map +1 -0
- package/dist/shared/react/tour-root.d.ts +14 -0
- package/dist/shared/react/tour-root.d.ts.map +1 -0
- package/dist/shared/react/tour-root.js +22 -0
- package/dist/shared/react/tour-root.js.map +1 -0
- package/dist/shared/react/use-isomorphic-layout-effect.hook.d.ts +4 -0
- package/dist/shared/react/use-isomorphic-layout-effect.hook.d.ts.map +1 -0
- package/dist/shared/react/use-isomorphic-layout-effect.hook.js +5 -0
- package/dist/shared/react/use-isomorphic-layout-effect.hook.js.map +1 -0
- package/dist/shared/react/use-tour-view.hook.d.ts +5 -0
- package/dist/shared/react/use-tour-view.hook.d.ts.map +1 -0
- package/dist/shared/react/use-tour-view.hook.js +14 -0
- package/dist/shared/react/use-tour-view.hook.js.map +1 -0
- package/dist/shared/react/use-tour.hook.d.ts +7 -0
- package/dist/shared/react/use-tour.hook.d.ts.map +1 -0
- package/dist/shared/react/use-tour.hook.js +10 -0
- package/dist/shared/react/use-tour.hook.js.map +1 -0
- package/dist/shared/renderer/default-tooltip.d.ts +5 -0
- package/dist/shared/renderer/default-tooltip.d.ts.map +1 -0
- package/dist/shared/renderer/default-tooltip.js +13 -0
- package/dist/shared/renderer/default-tooltip.js.map +1 -0
- package/dist/shared/renderer/motion.d.ts +29 -0
- package/dist/shared/renderer/motion.d.ts.map +1 -0
- package/dist/shared/renderer/motion.js +49 -0
- package/dist/shared/renderer/motion.js.map +1 -0
- package/dist/shared/renderer/renderer-context.d.ts +17 -0
- package/dist/shared/renderer/renderer-context.d.ts.map +1 -0
- package/dist/shared/renderer/renderer-context.js +16 -0
- package/dist/shared/renderer/renderer-context.js.map +1 -0
- package/dist/shared/renderer/renderer.type.d.ts +164 -0
- package/dist/shared/renderer/renderer.type.d.ts.map +1 -0
- package/dist/shared/renderer/renderer.type.js +2 -0
- package/dist/shared/renderer/renderer.type.js.map +1 -0
- package/dist/shared/renderer/tooltip-model.d.ts +32 -0
- package/dist/shared/renderer/tooltip-model.d.ts.map +1 -0
- package/dist/shared/renderer/tooltip-model.js +24 -0
- package/dist/shared/renderer/tooltip-model.js.map +1 -0
- package/dist/shared/renderer/tour-overlay.d.ts +24 -0
- package/dist/shared/renderer/tour-overlay.d.ts.map +1 -0
- package/dist/shared/renderer/tour-overlay.js +228 -0
- package/dist/shared/renderer/tour-overlay.js.map +1 -0
- package/dist/shared/types/tour-context.type.d.ts +21 -11
- package/dist/shared/types/tour-context.type.d.ts.map +1 -0
- package/dist/shared/types/tour-end-info.type.d.ts +19 -2
- package/dist/shared/types/tour-end-info.type.d.ts.map +1 -0
- package/dist/shared/types/tour-i18n.type.d.ts +8 -0
- package/dist/shared/types/tour-i18n.type.d.ts.map +1 -0
- package/dist/shared/types/tour-step.type.d.ts +35 -6
- package/dist/shared/types/tour-step.type.d.ts.map +1 -0
- package/dist/shared/types/tour-theme.type.d.ts +22 -2
- package/dist/shared/types/tour-theme.type.d.ts.map +1 -0
- package/dist/shared/types/tour-theme.type.js +1 -16
- package/dist/shared/types/tour-theme.type.js.map +1 -1
- package/dist/shared/types/tour-tooltip-props.type.d.ts +21 -17
- package/dist/shared/types/tour-tooltip-props.type.d.ts.map +1 -0
- package/dist/shared/utils/frame.util.d.ts +6 -0
- package/dist/shared/utils/frame.util.d.ts.map +1 -0
- package/dist/shared/utils/frame.util.js +20 -0
- package/dist/shared/utils/frame.util.js.map +1 -0
- package/dist/shared/utils/get-truncated-dots.util.d.ts +13 -7
- package/dist/shared/utils/get-truncated-dots.util.d.ts.map +1 -0
- package/dist/shared/utils/get-truncated-dots.util.js +30 -30
- package/dist/shared/utils/get-truncated-dots.util.js.map +1 -1
- package/dist/shared/utils/i18n.util.d.ts +7 -0
- package/dist/shared/utils/i18n.util.d.ts.map +1 -0
- package/dist/shared/utils/i18n.util.js +34 -0
- package/dist/shared/utils/i18n.util.js.map +1 -0
- package/dist/shared/utils/theme.util.d.ts +6 -0
- package/dist/shared/utils/theme.util.d.ts.map +1 -0
- package/dist/shared/utils/theme.util.js +31 -0
- package/dist/shared/utils/theme.util.js.map +1 -0
- package/package.json +72 -55
- package/src/react-native-platform/hooks/use-tour-scroll-view.hook.ts +24 -0
- package/src/react-native-platform/hooks/use-tour-target.hook.ts +30 -0
- package/src/react-native-platform/host-store.ts +92 -0
- package/src/react-native-platform/insets.util.ts +39 -0
- package/src/react-native-platform/layer/native-renderer.tsx +776 -0
- package/src/react-native-platform/layer/tour-host.tsx +105 -0
- package/src/react-native-platform/measure.util.ts +140 -0
- package/src/react-native-platform/native-context.ts +15 -0
- package/src/react-native-platform/native-environment.ts +33 -0
- package/src/react-native-platform/native-platform.ts +368 -0
- package/src/react-native-platform/reduced-motion.ts +47 -0
- package/src/react-native-platform/registry.ts +70 -0
- package/src/react-native-platform/tour-provider.tsx +148 -0
- package/src/react-native.ts +15 -0
- package/src/react-platform/dom/elements.util.ts +191 -0
- package/src/react-platform/dom/occluders.util.ts +88 -0
- package/src/react-platform/dom/scroll-lock.util.ts +261 -0
- package/src/react-platform/dom/scroll.util.ts +310 -0
- package/src/react-platform/dom/viewport.util.ts +74 -0
- package/src/react-platform/dom/web-platform.ts +208 -0
- package/src/react-platform/layer/styles.ts +286 -0
- package/src/react-platform/layer/web-renderer.tsx +594 -0
- package/src/react-platform/tour-provider.tsx +44 -0
- package/src/react.ts +13 -0
- package/src/shared/engine/abort.util.ts +84 -0
- package/src/shared/engine/report.util.ts +58 -0
- package/src/shared/engine/tour-adapter.type.ts +62 -0
- package/src/shared/engine/tour-engine.ts +1068 -0
- package/src/shared/geometry/box.util.ts +185 -0
- package/src/shared/geometry/compute-tooltip-layout.util.ts +238 -0
- package/src/shared/geometry/geometry.type.ts +38 -0
- package/src/shared/geometry/reveal.util.ts +190 -0
- package/src/shared/geometry/target-visibility.util.ts +44 -0
- package/src/shared/platform/create-tour-adapter.ts +151 -0
- package/src/shared/platform/platform.type.ts +81 -0
- package/src/shared/platform/reveal-in-scroll-containers.ts +78 -0
- package/src/shared/react/tour-context.ts +28 -0
- package/src/shared/react/tour-provider-core.ts +67 -0
- package/src/shared/react/tour-root.tsx +34 -0
- package/src/shared/react/use-isomorphic-layout-effect.hook.ts +7 -0
- package/src/shared/react/use-tour-view.hook.ts +17 -0
- package/src/shared/react/use-tour.hook.ts +11 -0
- package/src/shared/renderer/default-tooltip.tsx +52 -0
- package/src/shared/renderer/motion.ts +59 -0
- package/src/shared/renderer/renderer-context.ts +33 -0
- package/src/shared/renderer/renderer.type.ts +183 -0
- package/src/shared/renderer/tooltip-model.ts +44 -0
- package/src/shared/renderer/tour-overlay.tsx +453 -0
- package/src/shared/types/tour-context.type.ts +23 -0
- package/src/shared/types/tour-end-info.type.ts +32 -0
- package/src/shared/types/tour-i18n.type.ts +15 -0
- package/src/shared/types/tour-step.type.ts +40 -0
- package/src/shared/types/tour-theme.type.ts +28 -0
- package/src/shared/types/tour-tooltip-props.type.ts +27 -0
- package/src/shared/utils/frame.util.ts +21 -0
- package/src/shared/utils/get-truncated-dots.util.ts +46 -0
- package/src/shared/utils/i18n.util.ts +44 -0
- package/src/shared/utils/theme.util.ts +38 -0
- package/dist/react-native-platform/components/index.d.ts +0 -2
- package/dist/react-native-platform/components/index.js +0 -3
- package/dist/react-native-platform/components/index.js.map +0 -1
- package/dist/react-native-platform/components/tour-overlay.d.ts +0 -3
- package/dist/react-native-platform/components/tour-overlay.js +0 -26
- package/dist/react-native-platform/components/tour-overlay.js.map +0 -1
- package/dist/react-native-platform/components/tour-tooltip.d.ts +0 -3
- package/dist/react-native-platform/components/tour-tooltip.js +0 -167
- package/dist/react-native-platform/components/tour-tooltip.js.map +0 -1
- package/dist/react-native-platform/hooks/index.d.ts +0 -1
- package/dist/react-native-platform/hooks/index.js +0 -2
- package/dist/react-native-platform/hooks/index.js.map +0 -1
- package/dist/react-native-platform/hooks/use-tour-target.d.ts +0 -2
- package/dist/react-native-platform/hooks/use-tour-target.js +0 -13
- package/dist/react-native-platform/hooks/use-tour-target.js.map +0 -1
- package/dist/react-native-platform/index.d.ts +0 -7
- package/dist/react-native-platform/index.js +0 -7
- package/dist/react-native-platform/index.js.map +0 -1
- package/dist/react-native-platform/platform-adapter.d.ts +0 -2
- package/dist/react-native-platform/platform-adapter.js +0 -84
- package/dist/react-native-platform/platform-adapter.js.map +0 -1
- package/dist/react-native-platform/target-registry.d.ts +0 -10
- package/dist/react-native-platform/target-registry.js +0 -17
- package/dist/react-native-platform/target-registry.js.map +0 -1
- package/dist/react-native-platform/types/tour-provider.props.type.d.ts +0 -12
- package/dist/react-native-platform/types/tour-provider.props.type.js +0 -2
- package/dist/react-native-platform/types/tour-provider.props.type.js.map +0 -1
- package/dist/react-platform/components/index.d.ts +0 -2
- package/dist/react-platform/components/index.js +0 -3
- package/dist/react-platform/components/index.js.map +0 -1
- package/dist/react-platform/components/tour-overlay.d.ts +0 -3
- package/dist/react-platform/components/tour-overlay.js +0 -29
- package/dist/react-platform/components/tour-overlay.js.map +0 -1
- package/dist/react-platform/components/tour-tooltip.d.ts +0 -2
- package/dist/react-platform/components/tour-tooltip.js +0 -155
- package/dist/react-platform/components/tour-tooltip.js.map +0 -1
- package/dist/react-platform/index.d.ts +0 -6
- package/dist/react-platform/index.js +0 -6
- package/dist/react-platform/index.js.map +0 -1
- package/dist/react-platform/platform-adapter.d.ts +0 -2
- package/dist/react-platform/platform-adapter.js +0 -101
- package/dist/react-platform/platform-adapter.js.map +0 -1
- package/dist/react-platform/types/tour-provider.props.type.d.ts +0 -12
- package/dist/react-platform/types/tour-provider.props.type.js +0 -2
- package/dist/react-platform/types/tour-provider.props.type.js.map +0 -1
- package/dist/react-platform/utils/find-visible-element.util.d.ts +0 -1
- package/dist/react-platform/utils/find-visible-element.util.js +0 -11
- package/dist/react-platform/utils/find-visible-element.util.js.map +0 -1
- package/dist/react-platform/utils/index.d.ts +0 -1
- package/dist/react-platform/utils/index.js +0 -2
- package/dist/react-platform/utils/index.js.map +0 -1
- package/dist/shared/context/index.d.ts +0 -1
- package/dist/shared/context/index.js +0 -2
- package/dist/shared/context/index.js.map +0 -1
- package/dist/shared/context/tour-context.d.ts +0 -3
- package/dist/shared/context/tour-context.js +0 -14
- package/dist/shared/context/tour-context.js.map +0 -1
- package/dist/shared/hooks/index.d.ts +0 -1
- package/dist/shared/hooks/index.js +0 -2
- package/dist/shared/hooks/index.js.map +0 -1
- package/dist/shared/hooks/use-tour.hook.d.ts +0 -2
- package/dist/shared/hooks/use-tour.hook.js +0 -7
- package/dist/shared/hooks/use-tour.hook.js.map +0 -1
- package/dist/shared/index.d.ts +0 -5
- package/dist/shared/index.js +0 -5
- package/dist/shared/index.js.map +0 -1
- package/dist/shared/types/index.d.ts +0 -10
- package/dist/shared/types/index.js +0 -2
- package/dist/shared/types/index.js.map +0 -1
- package/dist/shared/types/platform-adapter.type.d.ts +0 -24
- package/dist/shared/types/platform-adapter.type.js +0 -2
- package/dist/shared/types/platform-adapter.type.js.map +0 -1
- package/dist/shared/types/rect.type.d.ts +0 -11
- package/dist/shared/types/rect.type.js +0 -2
- package/dist/shared/types/rect.type.js.map +0 -1
- package/dist/shared/types/tour-overlay-props.type.d.ts +0 -8
- package/dist/shared/types/tour-overlay-props.type.js +0 -2
- package/dist/shared/types/tour-overlay-props.type.js.map +0 -1
- package/dist/shared/utils/calculate-tooltip-position.util.d.ts +0 -14
- package/dist/shared/utils/calculate-tooltip-position.util.js +0 -49
- package/dist/shared/utils/calculate-tooltip-position.util.js.map +0 -1
- package/dist/shared/utils/index.d.ts +0 -3
- package/dist/shared/utils/index.js +0 -3
- package/dist/shared/utils/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,18 +1,45 @@
|
|
|
1
1
|
# react-tour-kit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
Guided tours for React and React Native that never leave the screen.
|
|
4
|
+
|
|
5
|
+
Highlight any element, explain it in a tooltip, and walk users through your app — across pages and screens, into scroll containers and modals. The tour takes care of everything around it: it waits for targets to appear, scrolls them into view (clear of sticky headers and floating buttons), keeps the tooltip on screen on every device, animates smoothly, and is accessible out of the box.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
const {startTour} = useTour();
|
|
9
|
+
|
|
10
|
+
startTour([
|
|
11
|
+
{target: '[data-tour="search"]', title: 'Search', content: 'Find anything in your workspace.'},
|
|
12
|
+
{target: '[data-tour="new"]', title: 'Create', content: 'Start a new project here.'},
|
|
13
|
+
]);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- **Simple**: one provider, one hook. Steps are plain objects.
|
|
17
|
+
- **Always in sight**: the target is scrolled into view — through nested scroll containers, horizontal lists, and past sticky headers, cookie banners and floating buttons — and the tooltip stays inside the visible screen and its safe areas, at any size, orientation and zoom.
|
|
18
|
+
- **Across pages and screens**: a step's `onBeforeStep` navigates; the tour waits for the target by itself.
|
|
19
|
+
- **Smooth**: the highlight glides from step to step, with the scroll. Nothing runs while a step is on screen.
|
|
20
|
+
- **Accessible**: a real modal dialog with focus management, keyboard navigation and screen-reader announcements on the web; screen-reader focus, roles and the back button on React Native.
|
|
21
|
+
- **Themeable and customisable**: colours, texts, a content-only custom tooltip, and CSS hooks on the web.
|
|
22
|
+
- **Small and typed**: no dependencies besides React (and React Native), written in TypeScript.
|
|
23
|
+
|
|
24
|
+
## Contents
|
|
25
|
+
|
|
26
|
+
- [Installation](#installation)
|
|
27
|
+
- [API at a glance](#api-at-a-glance)
|
|
28
|
+
- [Web](#web)
|
|
29
|
+
- [React Native](#react-native)
|
|
30
|
+
- [Steps](#steps)
|
|
31
|
+
- [Tours across pages and screens](#tours-across-pages-and-screens)
|
|
32
|
+
- [`useTour()`](#usetour)
|
|
33
|
+
- [When a tour ends](#when-a-tour-ends)
|
|
34
|
+
- [Theme](#theme)
|
|
35
|
+
- [Texts and languages](#texts-and-languages)
|
|
36
|
+
- [Custom tooltip](#custom-tooltip)
|
|
37
|
+
- [Styling with CSS (web)](#styling-with-css-web)
|
|
38
|
+
- [What the tour does for you](#what-the-tour-does-for-you)
|
|
39
|
+
- [Testing your app](#testing-your-app)
|
|
40
|
+
- [Requirements](#requirements)
|
|
41
|
+
- [Migrating from 0.x](#migrating-from-0x)
|
|
42
|
+
- [Development](#development)
|
|
16
43
|
|
|
17
44
|
## Installation
|
|
18
45
|
|
|
@@ -20,1056 +47,580 @@ Cross-platform guided tour library for React and React Native. Build interactive
|
|
|
20
47
|
npm install @robertlinde/react-tour-kit
|
|
21
48
|
```
|
|
22
49
|
|
|
23
|
-
|
|
50
|
+
There is nothing else to install: no `react-native-svg`, no stylesheet to import.
|
|
24
51
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```
|
|
52
|
+
| Platform | Import from | Targets |
|
|
53
|
+
| ------------ | ------------------------------------------ | ----------------------------------------------- |
|
|
54
|
+
| Web | `@robertlinde/react-tour-kit/react` | CSS selectors such as `'[data-tour="welcome"]'` |
|
|
55
|
+
| React Native | `@robertlinde/react-tour-kit/react-native` | ids registered with `useTourTarget` |
|
|
30
56
|
|
|
31
|
-
|
|
57
|
+
## API at a glance
|
|
32
58
|
|
|
33
|
-
|
|
59
|
+
Everything you pass to the tour, with links to the details.
|
|
34
60
|
|
|
35
|
-
|
|
61
|
+
**`<TourProvider>`**: all props are optional.
|
|
36
62
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
63
|
+
| Prop | Type | Default | Details |
|
|
64
|
+
| ----------------------- | --------------------------------------------------- | ------------- | ------------------------------------------- |
|
|
65
|
+
| `theme` | `TourTheme`: colours | blue on white | [Theme](#theme) |
|
|
66
|
+
| `i18n` | `TourI18n`: texts | English | [Texts and languages](#texts-and-languages) |
|
|
67
|
+
| `onTourEnd` | `(tourId, info) => void` | – | [When a tour ends](#when-a-tour-ends) |
|
|
68
|
+
| `closeOnOverlayClick` | `boolean`: a press on the dimmed area ends the tour | `true` | [When a tour ends](#when-a-tour-ends) |
|
|
69
|
+
| `TooltipComponent` | `ComponentType<TourTooltipProps>` | built-in | [Custom tooltip](#custom-tooltip) |
|
|
70
|
+
| `insets` (React Native) | `{top, right, bottom, left}` | safe defaults | [React Native](#react-native) |
|
|
42
71
|
|
|
43
|
-
|
|
44
|
-
cd demos/react-native
|
|
45
|
-
npm install
|
|
46
|
-
npm run ios # or npm run android
|
|
47
|
-
```
|
|
72
|
+
**`startTour(steps, tourId?, options?)`**, from [`useTour()`](#usetour):
|
|
48
73
|
|
|
49
|
-
|
|
74
|
+
| Argument | Type | Details |
|
|
75
|
+
| ------------------- | ------------------------ | --------------------------------------------------------------------------- |
|
|
76
|
+
| `steps` | `TourStep[]` | below |
|
|
77
|
+
| `tourId` | `string` | names the tour in `onTourEnd`; starting the running tour again does nothing |
|
|
78
|
+
| `options.onTourEnd` | `(tourId, info) => void` | for this tour only, before the provider's |
|
|
50
79
|
|
|
51
|
-
|
|
52
|
-
| ---------------- | ----------------------------- | --------------------------------------------- |
|
|
53
|
-
| **React (Web)** | `react-tour-kit/react` | CSS selectors (e.g., `[data-tour="welcome"]`) |
|
|
54
|
-
| **React Native** | `react-tour-kit/react-native` | `useTourTarget` hook or refs |
|
|
80
|
+
**`TourStep`**, see [Steps](#steps):
|
|
55
81
|
|
|
56
|
-
|
|
82
|
+
| Field | Type | Default |
|
|
83
|
+
| -------------- | ------------------------------------------------------------------------------ | ------------------ |
|
|
84
|
+
| `target` | web: CSS selector or element ref; React Native: `useTourTarget` id or view ref | none: centred step |
|
|
85
|
+
| `title` | `ReactNode` | required |
|
|
86
|
+
| `content` | `ReactNode` | required |
|
|
87
|
+
| `placement` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` |
|
|
88
|
+
| `onBeforeStep` | `(target, {signal}) => void \| Promise<void>` | – |
|
|
57
89
|
|
|
58
|
-
|
|
90
|
+
**React Native only**: `useTourTarget(id)` registers a view as a target, `useTourScrollView(ref?)` lets the tour scroll a list, and `<TourHost />` draws the tour in modal screens. See [React Native](#react-native).
|
|
59
91
|
|
|
60
|
-
|
|
92
|
+
## Web
|
|
61
93
|
|
|
62
|
-
|
|
94
|
+
**1. Wrap your app in `TourProvider`** — inside your router if steps navigate between pages.
|
|
63
95
|
|
|
64
96
|
```tsx
|
|
65
97
|
import {TourProvider} from '@robertlinde/react-tour-kit/react';
|
|
66
98
|
|
|
67
|
-
function App() {
|
|
99
|
+
export function App() {
|
|
68
100
|
return (
|
|
69
|
-
<
|
|
70
|
-
<
|
|
71
|
-
|
|
101
|
+
<BrowserRouter>
|
|
102
|
+
<TourProvider>
|
|
103
|
+
<Routes>{/* … */}</Routes>
|
|
104
|
+
</TourProvider>
|
|
105
|
+
</BrowserRouter>
|
|
72
106
|
);
|
|
73
107
|
}
|
|
74
108
|
```
|
|
75
109
|
|
|
76
|
-
|
|
110
|
+
**2. Mark the elements** you want to highlight. Any CSS selector works; data attributes keep the tour independent of your styling.
|
|
77
111
|
|
|
78
112
|
```tsx
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<div>
|
|
82
|
-
<header data-tour="welcome">
|
|
83
|
-
<h1>Dashboard</h1>
|
|
84
|
-
</header>
|
|
85
|
-
|
|
86
|
-
<nav data-tour="sidebar">{/* Navigation items */}</nav>
|
|
87
|
-
|
|
88
|
-
<button data-tour="settings">Settings</button>
|
|
89
|
-
</div>
|
|
90
|
-
);
|
|
91
|
-
}
|
|
113
|
+
<nav data-tour="sidebar">…</nav>
|
|
114
|
+
<button data-tour="settings">Settings</button>
|
|
92
115
|
```
|
|
93
116
|
|
|
94
|
-
|
|
117
|
+
**3. Start a tour** from anywhere inside the provider.
|
|
95
118
|
|
|
96
119
|
```tsx
|
|
97
|
-
import {
|
|
120
|
+
import {type TourStep, useTour} from '@robertlinde/react-tour-kit/react';
|
|
98
121
|
|
|
99
|
-
const
|
|
122
|
+
const onboarding: TourStep[] = [
|
|
100
123
|
{
|
|
101
|
-
target: '[data-tour="welcome"]', // CSS selector
|
|
102
124
|
title: 'Welcome!',
|
|
103
|
-
content: '
|
|
104
|
-
placement: 'bottom',
|
|
125
|
+
content: 'Let me show you around.', // no target: shown in the centre of the screen
|
|
105
126
|
},
|
|
106
127
|
{
|
|
107
128
|
target: '[data-tour="sidebar"]',
|
|
108
129
|
title: 'Navigation',
|
|
109
|
-
content: '
|
|
130
|
+
content: 'Everything you need is one click away.',
|
|
110
131
|
placement: 'right',
|
|
111
132
|
},
|
|
112
133
|
{
|
|
113
134
|
target: '[data-tour="settings"]',
|
|
114
135
|
title: 'Settings',
|
|
115
|
-
content: '
|
|
116
|
-
placement: 'left',
|
|
136
|
+
content: 'Make it yours.',
|
|
117
137
|
},
|
|
118
138
|
];
|
|
119
139
|
|
|
120
|
-
function
|
|
140
|
+
function StartButton() {
|
|
121
141
|
const {startTour} = useTour();
|
|
122
|
-
|
|
123
|
-
return <button onClick={() => startTour(tourSteps, 'onboarding')}>Start Tour</button>;
|
|
142
|
+
return <button onClick={() => startTour(onboarding, 'onboarding')}>Take the tour</button>;
|
|
124
143
|
}
|
|
125
144
|
```
|
|
126
145
|
|
|
127
|
-
|
|
146
|
+
While a tour is open, the page cannot be scrolled away from it with the wheel, touch or keyboard (a classic, always-visible scrollbar stays usable, as browsers do not let pages block it; the tour then keeps the target in view), and the tour is announced as a modal dialog. Keyboard: <kbd>→</kbd> next, <kbd>←</kbd> back (swapped in right-to-left pages), <kbd>Esc</kbd> closes. Presses, focus and keys inside the tour do not reach your page's own listeners, so a menu that closes on "click outside" stays open while a step points at it. Opening the tour (a modal dialog) does close elements shown with `popover="auto"`: for a popover a step points at, use `popover="manual"`.
|
|
128
147
|
|
|
129
|
-
|
|
130
|
-
| --------------- | ------------- |
|
|
131
|
-
| `→` Arrow Right | Next step |
|
|
132
|
-
| `←` Arrow Left | Previous step |
|
|
133
|
-
| `Escape` | Close tour |
|
|
148
|
+
### Next.js
|
|
134
149
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
## React Native
|
|
138
|
-
|
|
139
|
-
### Quick Start
|
|
140
|
-
|
|
141
|
-
#### 1. Wrap your app with TourProvider
|
|
150
|
+
The web entry is marked `'use client'`, so `TourProvider` can be rendered directly in a server component such as the root layout. It renders nothing extra on the server.
|
|
142
151
|
|
|
143
152
|
```tsx
|
|
144
|
-
|
|
153
|
+
// app/layout.tsx
|
|
154
|
+
import {TourProvider} from '@robertlinde/react-tour-kit/react';
|
|
145
155
|
|
|
146
|
-
function
|
|
156
|
+
export default function RootLayout({children}: {children: React.ReactNode}) {
|
|
147
157
|
return (
|
|
148
|
-
<
|
|
149
|
-
<
|
|
150
|
-
|
|
158
|
+
<html lang="en">
|
|
159
|
+
<body>
|
|
160
|
+
<TourProvider>{children}</TourProvider>
|
|
161
|
+
</body>
|
|
162
|
+
</html>
|
|
151
163
|
);
|
|
152
164
|
}
|
|
153
165
|
```
|
|
154
166
|
|
|
155
|
-
|
|
167
|
+
Components that call `useTour()` must be client components (`'use client'`).
|
|
168
|
+
|
|
169
|
+
## React Native
|
|
170
|
+
|
|
171
|
+
**1. Wrap your app in `TourProvider`** at the root, around your navigation container. It has to fill the window, so place it **outside** any `SafeAreaView` or padded container and pass the safe-area insets instead:
|
|
156
172
|
|
|
157
173
|
```tsx
|
|
158
|
-
import {
|
|
159
|
-
import {
|
|
174
|
+
import {NavigationContainer} from '@react-navigation/native';
|
|
175
|
+
import {SafeAreaProvider, useSafeAreaInsets} from 'react-native-safe-area-context';
|
|
176
|
+
import {TourProvider} from '@robertlinde/react-tour-kit/react-native';
|
|
160
177
|
|
|
161
|
-
function
|
|
162
|
-
const
|
|
163
|
-
|
|
178
|
+
function Root() {
|
|
179
|
+
const insets = useSafeAreaInsets();
|
|
180
|
+
return (
|
|
181
|
+
<TourProvider insets={insets}>
|
|
182
|
+
<NavigationContainer>{/* … */}</NavigationContainer>
|
|
183
|
+
</TourProvider>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
164
186
|
|
|
187
|
+
export default function App() {
|
|
165
188
|
return (
|
|
166
|
-
<
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
</TouchableOpacity>
|
|
170
|
-
|
|
171
|
-
<TouchableOpacity ref={settingsRef}>
|
|
172
|
-
<Text>Settings</Text>
|
|
173
|
-
</TouchableOpacity>
|
|
174
|
-
</View>
|
|
189
|
+
<SafeAreaProvider>
|
|
190
|
+
<Root />
|
|
191
|
+
</SafeAreaProvider>
|
|
175
192
|
);
|
|
176
193
|
}
|
|
177
194
|
```
|
|
178
195
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
```tsx
|
|
182
|
-
import {useTour, type TourStep} from '@robertlinde/react-tour-kit/react-native';
|
|
196
|
+
Without `insets`, conservative defaults keep the tooltip clear of the notch, Dynamic Island, status bar and home indicator.
|
|
183
197
|
|
|
184
|
-
|
|
185
|
-
{
|
|
186
|
-
target: 'welcome-button', // String ID from useTourTarget
|
|
187
|
-
title: 'Welcome!',
|
|
188
|
-
content: 'This is your dashboard. Let me show you around.',
|
|
189
|
-
placement: 'bottom',
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
target: 'settings-button',
|
|
193
|
-
title: 'Settings',
|
|
194
|
-
content: 'Tap here to customize your preferences.',
|
|
195
|
-
placement: 'left',
|
|
196
|
-
},
|
|
197
|
-
];
|
|
198
|
+
**2. Register the views** you want to highlight with `useTourTarget`, and use the id as the step's `target`:
|
|
198
199
|
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
```tsx
|
|
201
|
+
import {Pressable, Text} from 'react-native';
|
|
202
|
+
import {useTourTarget} from '@robertlinde/react-tour-kit/react-native';
|
|
201
203
|
|
|
204
|
+
function SettingsButton() {
|
|
205
|
+
const ref = useTourTarget('settings');
|
|
202
206
|
return (
|
|
203
|
-
<
|
|
204
|
-
<Text>
|
|
205
|
-
</
|
|
207
|
+
<Pressable ref={ref} onPress={openSettings}>
|
|
208
|
+
<Text>Settings</Text>
|
|
209
|
+
</Pressable>
|
|
206
210
|
);
|
|
207
211
|
}
|
|
208
212
|
```
|
|
209
213
|
|
|
210
|
-
|
|
214
|
+
The ref is typed for `View` and components that forward their ref to one, such as `Pressable`. For other components, pass a type argument: `useTourTarget<MyComponentRef>('id')`. On the Old Architecture, Android leaves out views that only lay out their children; if a plain `View` target is never found there, give it `collapsable={false}`.
|
|
211
215
|
|
|
212
|
-
|
|
216
|
+
**3. Let the tour scroll your lists.** For targets that can be off screen, register the scroll views around them with `useTourScrollView` — it works with `ScrollView`, `FlatList`, `SectionList` and their animated versions, vertical and horizontal, nested too:
|
|
213
217
|
|
|
214
218
|
```tsx
|
|
215
|
-
import {
|
|
216
|
-
import {
|
|
217
|
-
|
|
218
|
-
function MyComponent() {
|
|
219
|
-
const buttonRef = useRef(null);
|
|
219
|
+
import {FlatList, ScrollView} from 'react-native';
|
|
220
|
+
import {useTourScrollView} from '@robertlinde/react-tour-kit/react-native';
|
|
220
221
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
content: 'Tap here to get started.',
|
|
226
|
-
},
|
|
227
|
-
];
|
|
222
|
+
function Home() {
|
|
223
|
+
const scrollRef = useTourScrollView();
|
|
224
|
+
return <ScrollView ref={scrollRef}>…</ScrollView>;
|
|
225
|
+
}
|
|
228
226
|
|
|
229
|
-
|
|
227
|
+
function Carousel() {
|
|
228
|
+
const listRef = useTourScrollView<FlatList<Item>>();
|
|
229
|
+
return <FlatList ref={listRef} horizontal data={items} renderItem={renderItem} />;
|
|
230
230
|
}
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
| Action | Trigger |
|
|
236
|
-
| ------------- | ------------------------------------------------- |
|
|
237
|
-
| Next step | Tap "Next" button |
|
|
238
|
-
| Previous step | Tap "Back" button |
|
|
239
|
-
| Close tour | Tap close button, overlay, or Android back button |
|
|
240
|
-
|
|
241
|
-
---
|
|
242
|
-
|
|
243
|
-
## Cross-Page/Cross-Screen Tours
|
|
244
|
-
|
|
245
|
-
Tours can span multiple pages (web) or screens (React Native). The key is to:
|
|
233
|
+
If the list already has a ref, pass it in: `useTourScrollView(existingRef)`. Items of a virtualised list that are not rendered yet cannot be found; scroll to them in the step's `onBeforeStep` (for example with `scrollToIndex`).
|
|
246
234
|
|
|
247
|
-
|
|
248
|
-
2. Use `onBeforeStep` to navigate before showing a step
|
|
249
|
-
|
|
250
|
-
### Web (React Router)
|
|
235
|
+
**4. Start a tour** exactly as on the web:
|
|
251
236
|
|
|
252
237
|
```tsx
|
|
253
|
-
import {
|
|
254
|
-
import {TourProvider, useTour, type TourStep} from '@robertlinde/react-tour-kit/react';
|
|
238
|
+
import {type TourStep, useTour} from '@robertlinde/react-tour-kit/react-native';
|
|
255
239
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
<BrowserRouter>
|
|
261
|
-
<Routes>
|
|
262
|
-
<Route path="/" element={<Home />} />
|
|
263
|
-
<Route path="/settings" element={<Settings />} />
|
|
264
|
-
</Routes>
|
|
265
|
-
</BrowserRouter>
|
|
266
|
-
</TourProvider>
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Define steps with navigation
|
|
271
|
-
function Home() {
|
|
272
|
-
const navigate = useNavigate();
|
|
273
|
-
const {startTour} = useTour();
|
|
274
|
-
|
|
275
|
-
const steps: TourStep[] = [
|
|
276
|
-
{
|
|
277
|
-
target: '[data-tour="welcome"]',
|
|
278
|
-
title: 'Welcome',
|
|
279
|
-
content: 'Let me show you around.',
|
|
280
|
-
placement: 'bottom',
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
target: '[data-tour="nav-settings"]',
|
|
284
|
-
title: 'Settings Link',
|
|
285
|
-
content: "Now let's visit the settings page.",
|
|
286
|
-
placement: 'bottom',
|
|
287
|
-
// Navigate before the NEXT step shows, then poll until its target mounts
|
|
288
|
-
onBeforeStep: async (target) => {
|
|
289
|
-
navigate('/settings');
|
|
290
|
-
while (!document.querySelector(target as string)) {
|
|
291
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
target: '[data-tour="theme-toggle"]',
|
|
297
|
-
title: 'Theme Settings',
|
|
298
|
-
content: 'This element is on the Settings page!',
|
|
299
|
-
placement: 'right',
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
target: '[data-tour="welcome"]',
|
|
303
|
-
title: 'Back Home',
|
|
304
|
-
content: 'And we can navigate back.',
|
|
305
|
-
placement: 'bottom',
|
|
306
|
-
onBeforeStep: async (target) => {
|
|
307
|
-
navigate('/');
|
|
308
|
-
while (!document.querySelector(target as string)) {
|
|
309
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
},
|
|
313
|
-
];
|
|
240
|
+
const steps: TourStep[] = [
|
|
241
|
+
{target: 'profile', title: 'Your profile', content: 'Tap here to edit it.'},
|
|
242
|
+
{target: 'settings', title: 'Settings', content: 'Make it yours.', placement: 'top'},
|
|
243
|
+
];
|
|
314
244
|
|
|
315
|
-
|
|
316
|
-
|
|
245
|
+
const {startTour} = useTour();
|
|
246
|
+
startTour(steps, 'onboarding');
|
|
317
247
|
```
|
|
318
248
|
|
|
319
|
-
|
|
249
|
+
The Android back button closes the tour. Screen readers move to the tooltip and announce each step.
|
|
320
250
|
|
|
321
|
-
|
|
322
|
-
import {NavigationContainer, useNavigation} from '@react-navigation/native';
|
|
323
|
-
import {createNativeStackNavigator} from '@react-navigation/native-stack';
|
|
324
|
-
import {TourProvider, useTour, useTourTarget, type TourStep} from '@robertlinde/react-tour-kit/react-native';
|
|
251
|
+
### Modal screens
|
|
325
252
|
|
|
326
|
-
|
|
253
|
+
The tour is drawn inside your app's root view. Screens presented as native modals (react-navigation's `presentation: 'modal'`, or a React Native `<Modal>`) are drawn above that view, so to highlight something in them, add a `<TourHost />` as the last child of the modal screen:
|
|
327
254
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return (
|
|
331
|
-
<TourProvider>
|
|
332
|
-
<NavigationContainer>
|
|
333
|
-
<Stack.Navigator>
|
|
334
|
-
<Stack.Screen name="Home" component={HomeScreen} />
|
|
335
|
-
<Stack.Screen name="Settings" component={SettingsScreen} />
|
|
336
|
-
</Stack.Navigator>
|
|
337
|
-
</NavigationContainer>
|
|
338
|
-
</TourProvider>
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Define steps with navigation
|
|
343
|
-
function HomeScreen() {
|
|
344
|
-
const navigation = useNavigation();
|
|
345
|
-
const {startTour} = useTour();
|
|
346
|
-
const headerRef = useTourTarget<View>('header');
|
|
347
|
-
|
|
348
|
-
const steps: TourStep[] = [
|
|
349
|
-
{
|
|
350
|
-
target: 'header',
|
|
351
|
-
title: 'Welcome',
|
|
352
|
-
content: 'Let me show you around.',
|
|
353
|
-
placement: 'bottom',
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
target: 'settings-header',
|
|
357
|
-
title: 'Settings Screen',
|
|
358
|
-
content: 'This element is on the Settings screen!',
|
|
359
|
-
placement: 'bottom',
|
|
360
|
-
onBeforeStep: async (target) => {
|
|
361
|
-
navigation.navigate('Settings');
|
|
362
|
-
await new Promise((r) => setTimeout(r, 300)); // Wait for animation
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
target: 'header',
|
|
367
|
-
title: 'Back Home',
|
|
368
|
-
content: 'And we can navigate back.',
|
|
369
|
-
placement: 'bottom',
|
|
370
|
-
onBeforeStep: async (target) => {
|
|
371
|
-
navigation.navigate('Home');
|
|
372
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
373
|
-
},
|
|
374
|
-
},
|
|
375
|
-
];
|
|
255
|
+
```tsx
|
|
256
|
+
import {TourHost} from '@robertlinde/react-tour-kit/react-native';
|
|
376
257
|
|
|
258
|
+
function HelpScreen() {
|
|
377
259
|
return (
|
|
378
|
-
<View>
|
|
379
|
-
|
|
380
|
-
<
|
|
260
|
+
<View style={{flex: 1}}>
|
|
261
|
+
{/* … */}
|
|
262
|
+
<TourHost />
|
|
381
263
|
</View>
|
|
382
264
|
);
|
|
383
265
|
}
|
|
384
|
-
|
|
385
|
-
function SettingsScreen() {
|
|
386
|
-
const headerRef = useTourTarget<View>('settings-header');
|
|
387
|
-
|
|
388
|
-
return <View ref={headerRef}>{/* ... */}</View>;
|
|
389
|
-
}
|
|
390
266
|
```
|
|
391
267
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
1. **TourProvider placement**: Must wrap your router/navigator so tour state persists across navigation
|
|
395
|
-
2. **Target availability**: Targets must be mounted when their step is active. Use `onBeforeStep` to navigate first
|
|
396
|
-
3. **Wait for navigation**: On web, poll on the `target` argument in `onBeforeStep` (e.g. `while (!document.querySelector(target))`) instead of a fixed delay. On React Native, a short delay is usually fine because navigation animations have a known duration.
|
|
397
|
-
4. **Persistent elements**: Elements like navigation bars that exist on all pages don't need `onBeforeStep`
|
|
398
|
-
|
|
399
|
-
---
|
|
268
|
+
While the screen is open, the tour is drawn there; when it closes, the tour returns to the root. VoiceOver then ignores the rest of the modal screen while a step is shown (TalkBack can still reach it).
|
|
400
269
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
### TourProvider
|
|
404
|
-
|
|
405
|
-
The provider component that enables tour functionality throughout your app.
|
|
270
|
+
Inside a React Native `<Modal>`, the Android back button goes to the modal's `onRequestClose`, not to the tour. End the tour there first:
|
|
406
271
|
|
|
407
272
|
```tsx
|
|
408
|
-
|
|
273
|
+
const {isActive, endTour} = useTour();
|
|
409
274
|
|
|
410
|
-
<
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
onTourEnd={(tourId, info) => {
|
|
415
|
-
// Optional: callback when tour ends
|
|
416
|
-
// info.reason is one of: 'completed' | 'closed' | 'blur' | 'escape' | 'target-missing'
|
|
417
|
-
console.log(`Tour ${tourId} ended: ${info.reason} at step ${info.stepIndex + 1}/${info.totalSteps}`);
|
|
418
|
-
}}
|
|
419
|
-
>
|
|
420
|
-
{children}
|
|
421
|
-
</TourProvider>;
|
|
275
|
+
<Modal visible={isOpen} onRequestClose={() => (isActive ? endTour() : setIsOpen(false))}>
|
|
276
|
+
{/* … */}
|
|
277
|
+
<TourHost />
|
|
278
|
+
</Modal>;
|
|
422
279
|
```
|
|
423
280
|
|
|
424
|
-
|
|
281
|
+
### Android navigation bar
|
|
425
282
|
|
|
426
|
-
|
|
427
|
-
| --------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
428
|
-
| `children` | `ReactNode` | Your application content |
|
|
429
|
-
| `theme` | `TourTheme` | Theme configuration for colors (see Theming section) |
|
|
430
|
-
| `i18n` | `TourI18n` | Internationalization options for labels (see i18n section) |
|
|
431
|
-
| `TooltipComponent` | `ForwardRefExoticComponent<TourTooltipProps>` | Custom tooltip component (must use forwardRef) |
|
|
432
|
-
| `OverlayComponent` | `ComponentType<TourOverlayProps>` | Custom overlay component |
|
|
433
|
-
| `onTourEnd` | `TourEndCallback` | Callback fired when any tour ends. Receives `(tourId, info)` with the reason and step context (see [Tour End Callbacks](#tour-end-callbacks)) |
|
|
434
|
-
| `closeOnOverlayClick` | `boolean` | Whether clicking the overlay closes the tour. When `false`, users must click the ✕ button. Defaults to `true` |
|
|
283
|
+
The tour draws in your app's own window, so the system navigation bar looks exactly as it does in your app. Since Android 15 apps draw edge to edge, and with 3-button navigation Android puts a translucent contrast scrim behind the buttons. To keep the bar transparent (so the tour's dimmed overlay shows behind the buttons), turn the scrim off:
|
|
435
284
|
|
|
436
|
-
|
|
285
|
+
- **Expo**: add the [`expo-navigation-bar`](https://docs.expo.dev/versions/latest/sdk/navigation-bar/) config plugin to your app config, then rebuild the native app:
|
|
437
286
|
|
|
438
|
-
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"expo": {
|
|
290
|
+
"plugins": [["expo-navigation-bar", {"enforceContrast": false}]]
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
439
294
|
|
|
440
|
-
|
|
441
|
-
import {useTour} from '@robertlinde/react-tour-kit/react';
|
|
442
|
-
|
|
443
|
-
function MyComponent() {
|
|
444
|
-
const {
|
|
445
|
-
isActive, // boolean: is a tour currently running?
|
|
446
|
-
currentStep, // number: current step index (0-based)
|
|
447
|
-
steps, // TourStep[]: all steps in current tour
|
|
448
|
-
currentTourId, // string | null: ID of current tour
|
|
449
|
-
startTour, // (steps: TourStep[], tourId?: string, options?: StartTourOptions) => void
|
|
450
|
-
endTour, // () => void
|
|
451
|
-
nextStep, // () => void
|
|
452
|
-
prevStep, // () => void
|
|
453
|
-
goToStep, // (step: number) => void
|
|
454
|
-
} = useTour();
|
|
455
|
-
}
|
|
456
|
-
```
|
|
295
|
+
- **Bare React Native**: in `MainActivity`, after `super.onCreate(…)`:
|
|
457
296
|
|
|
458
|
-
|
|
297
|
+
```kotlin
|
|
298
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
299
|
+
window.isNavigationBarContrastEnforced = false
|
|
300
|
+
}
|
|
301
|
+
```
|
|
459
302
|
|
|
460
|
-
|
|
303
|
+
(0.x drew the tour in a React Native `<Modal>`, which opens its own window. Under edge to edge, React Native forces the contrast scrim on in that window, which showed as a white strip. 1.0 no longer uses a modal.)
|
|
461
304
|
|
|
462
|
-
|
|
305
|
+
## Steps
|
|
306
|
+
|
|
307
|
+
```ts
|
|
463
308
|
type TourStep = {
|
|
464
|
-
target
|
|
465
|
-
title:
|
|
466
|
-
content:
|
|
467
|
-
placement?: 'top' | 'bottom' | 'left' | 'right'; //
|
|
468
|
-
onBeforeStep?: (target
|
|
309
|
+
target?: TourTarget; // what to highlight; leave it out for a step in the centre of the screen
|
|
310
|
+
title: ReactNode;
|
|
311
|
+
content: ReactNode;
|
|
312
|
+
placement?: 'top' | 'bottom' | 'left' | 'right'; // preferred side, default 'bottom'
|
|
313
|
+
onBeforeStep?: (target, {signal}) => void | Promise<void>;
|
|
469
314
|
};
|
|
470
|
-
|
|
471
|
-
// Target types differ by platform:
|
|
472
|
-
// Web: CSS selector string (e.g., '[data-tour="welcome"]', '#my-button')
|
|
473
|
-
// React Native: String ID (registered via useTourTarget) or RefObject
|
|
474
|
-
type TourTarget = string | RefObject<unknown>;
|
|
475
315
|
```
|
|
476
316
|
|
|
477
|
-
|
|
317
|
+
- **`target`**: on the web a CSS selector (the first _visible_ match is used) or a ref to an element; on React Native an id registered with `useTourTarget`, or a ref to a view.
|
|
318
|
+
- **`title` and `content`** can be any React content (text, links, images).
|
|
319
|
+
- **`placement`** is a preference. If there is not enough room on that side, the tooltip moves to the opposite side, then to the roomier of the remaining sides; it is never pushed off screen.
|
|
320
|
+
- **`onBeforeStep`** runs before the step is shown — to navigate, open a menu, switch a tab, or load data. The tour waits for its promise, then for the target to appear (up to 5 seconds), so there is no need to poll or add delays. It receives the step's target and an `AbortSignal` that aborts when the step is abandoned (the user moves on or closes the tour). Make it idempotent: it runs again when the user comes back to the step. If it throws or rejects, the tour ends with the reason `'error'`.
|
|
478
321
|
|
|
479
|
-
|
|
322
|
+
**Missing targets**: if a step's target does not appear within 5 seconds, or cannot be scrolled into view, the step is skipped (with a warning in development) and the tour continues in the direction the user was going. Going back past the first step returns to the step the user came from. If no step can be shown at all, the tour ends with `'target-missing'`.
|
|
480
323
|
|
|
481
|
-
|
|
324
|
+
**Carousels and sliders**: the tour scrolls everything users can scroll, but never an element with `overflow: hidden` — what such an element shows is up to your app (carousels, sliders, off-canvas menus), and scrolling it would leave it out of step with your app's state. Bring the target into view in `onBeforeStep`, for example by moving the carousel to its slide.
|
|
482
325
|
|
|
483
|
-
|
|
484
|
-
type TourEndReason =
|
|
485
|
-
| 'completed' // User clicked Finish on the last step
|
|
486
|
-
| 'closed' // User clicked the close (✕) button, or `endTour()` was called programmatically
|
|
487
|
-
| 'blur' // User clicked/tapped the overlay outside the highlighted element
|
|
488
|
-
| 'escape' // User pressed Escape (web only)
|
|
489
|
-
| 'target-missing'; // The step's target element could not be found and the tour had to end
|
|
490
|
-
|
|
491
|
-
type TourEndInfo = {
|
|
492
|
-
reason: TourEndReason;
|
|
493
|
-
stepIndex: number; // Zero-based index of the step the user was on
|
|
494
|
-
step: TourStep; // The step object the user was on
|
|
495
|
-
totalSteps: number; // Total number of steps in the tour
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
type TourEndCallback = (tourId: string | undefined, info: TourEndInfo) => void;
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
> **Note (React Native):** the `'escape'` reason only fires on web. On Android, the hardware back button is reported as `'closed'`.
|
|
326
|
+
## Tours across pages and screens
|
|
502
327
|
|
|
503
|
-
|
|
328
|
+
Put `TourProvider` around your router or navigator, and let each step open its page in `onBeforeStep`. Making every step open its own page keeps going back working too.
|
|
504
329
|
|
|
505
|
-
|
|
330
|
+
### Web (React Router)
|
|
506
331
|
|
|
507
332
|
```tsx
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
analytics.track('tour_ended', {
|
|
511
|
-
tourId,
|
|
512
|
-
reason: info.reason,
|
|
513
|
-
lastStep: info.stepIndex + 1,
|
|
514
|
-
totalSteps: info.totalSteps,
|
|
515
|
-
});
|
|
516
|
-
}}
|
|
517
|
-
>
|
|
518
|
-
<App />
|
|
519
|
-
</TourProvider>
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
#### Per-tour callback
|
|
523
|
-
|
|
524
|
-
Pass an `onTourEnd` in the options argument to `startTour` when you need tour-specific end logic. This keeps the logic colocated with the tour that owns it, instead of dispatching on `tourId` in the provider callback.
|
|
333
|
+
import {useNavigate} from 'react-router-dom';
|
|
334
|
+
import {type TourStep, useTour} from '@robertlinde/react-tour-kit/react';
|
|
525
335
|
|
|
526
|
-
|
|
527
|
-
|
|
336
|
+
function useOnboarding() {
|
|
337
|
+
const navigate = useNavigate();
|
|
528
338
|
const {startTour} = useTour();
|
|
529
339
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
return
|
|
340
|
+
// Opens a page unless it is already open (no duplicate history entries).
|
|
341
|
+
const on = (path: string, step: TourStep): TourStep => ({
|
|
342
|
+
...step,
|
|
343
|
+
onBeforeStep() {
|
|
344
|
+
if (location.pathname !== path) {
|
|
345
|
+
navigate(path);
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
return () =>
|
|
351
|
+
startTour(
|
|
352
|
+
[
|
|
353
|
+
on('/', {target: '[data-tour="projects"]', title: 'Projects', content: 'All your work.'}),
|
|
354
|
+
on('/settings', {target: '[data-tour="theme"]', title: 'Theme', content: 'Light or dark?'}),
|
|
355
|
+
on('/', {target: '[data-tour="help"]', title: 'Help', content: 'We are here for you.'}),
|
|
356
|
+
],
|
|
357
|
+
'onboarding',
|
|
358
|
+
);
|
|
541
359
|
}
|
|
542
360
|
```
|
|
543
361
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
When both a provider-level and a per-tour callback are defined, **the per-tour callback fires first**, then the provider-level callback. Both always run on every end — the per-tour callback does not suppress the provider-level one.
|
|
362
|
+
### React Native (React Navigation)
|
|
547
363
|
|
|
548
364
|
```tsx
|
|
549
|
-
|
|
550
|
-
onTourEnd = (id, info) => console.log('provider', id, info.reason);
|
|
365
|
+
import {CommonActions, createNavigationContainerRef, StackActions} from '@react-navigation/native';
|
|
551
366
|
|
|
552
|
-
//
|
|
553
|
-
startTour(steps, 'onboarding', {
|
|
554
|
-
onTourEnd: (id, info) => console.log('tour', id, info.reason),
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// On end, logs in order:
|
|
558
|
-
// 'tour' 'onboarding' 'completed'
|
|
559
|
-
// 'provider' 'onboarding' 'completed'
|
|
560
|
-
```
|
|
367
|
+
export const navigation = createNavigationContainerRef<Screens>(); // <NavigationContainer ref={navigation}>
|
|
561
368
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
```tsx
|
|
567
|
-
onTourEnd={(tourId, info) => {
|
|
568
|
-
switch (info.reason) {
|
|
569
|
-
case 'completed':
|
|
570
|
-
markComplete(tourId);
|
|
571
|
-
break;
|
|
572
|
-
case 'blur':
|
|
573
|
-
case 'closed':
|
|
574
|
-
case 'escape':
|
|
575
|
-
trackDismissal(tourId, info.reason, info.stepIndex);
|
|
576
|
-
break;
|
|
577
|
-
case 'target-missing':
|
|
578
|
-
reportBug(`Tour ${tourId} broke at step ${info.stepIndex}: ${info.step.target}`);
|
|
579
|
-
break;
|
|
369
|
+
/** Shows a screen: goes back to it when it is already open, otherwise opens it. */
|
|
370
|
+
function show(screen: keyof Screens) {
|
|
371
|
+
if (!navigation.isReady() || navigation.getCurrentRoute()?.name === screen) {
|
|
372
|
+
return;
|
|
580
373
|
}
|
|
581
|
-
}}
|
|
582
|
-
```
|
|
583
374
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
Customize the look of the default tooltip and overlay without building custom components.
|
|
587
|
-
|
|
588
|
-
### Theme Options
|
|
589
|
-
|
|
590
|
-
```tsx
|
|
591
|
-
import {TourProvider, type TourTheme} from '@robertlinde/react-tour-kit/react';
|
|
592
|
-
|
|
593
|
-
const theme: TourTheme = {
|
|
594
|
-
primaryColor: '#10b981', // Buttons, step badge, highlight border (default: '#3b82f6')
|
|
595
|
-
tooltipBackground: '#ffffff', // Tooltip background color (default: '#ffffff')
|
|
596
|
-
titleColor: '#1f2937', // Title text color (default: '#1f2937')
|
|
597
|
-
contentColor: '#4b5563', // Body text color (default: '#4b5563')
|
|
598
|
-
overlayColor: 'rgba(0, 0, 0, 0.5)', // Semi-transparent overlay (default: 'rgba(0, 0, 0, 0.5)')
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
function App() {
|
|
602
|
-
return (
|
|
603
|
-
<TourProvider theme={theme}>
|
|
604
|
-
<YourApp />
|
|
605
|
-
</TourProvider>
|
|
606
|
-
);
|
|
375
|
+
const isOpen = navigation.getRootState()?.routes.some((route) => route.name === screen) ?? false;
|
|
376
|
+
navigation.dispatch(isOpen ? StackActions.popTo(screen) : CommonActions.navigate(screen));
|
|
607
377
|
}
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
### Theme Examples
|
|
611
378
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
titleColor: '#f9fafb',
|
|
619
|
-
contentColor: '#d1d5db',
|
|
620
|
-
overlayColor: 'rgba(0, 0, 0, 0.7)',
|
|
621
|
-
};
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
**Brand colors:**
|
|
379
|
+
const on = (screen: keyof Screens, step: TourStep): TourStep => ({
|
|
380
|
+
...step,
|
|
381
|
+
onBeforeStep() {
|
|
382
|
+
show(screen);
|
|
383
|
+
},
|
|
384
|
+
});
|
|
625
385
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
// Other values use defaults
|
|
386
|
+
const steps = [
|
|
387
|
+
on('Home', {target: 'projects', title: 'Projects', content: 'All your work.'}),
|
|
388
|
+
on('Settings', {target: 'theme', title: 'Theme', content: 'Light or dark?'}),
|
|
389
|
+
];
|
|
631
390
|
```
|
|
632
391
|
|
|
633
|
-
|
|
392
|
+
The [demos](#development) contain complete, working examples of both.
|
|
634
393
|
|
|
635
|
-
|
|
394
|
+
## `useTour()`
|
|
636
395
|
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
</TourProvider>
|
|
650
|
-
);
|
|
651
|
-
}
|
|
396
|
+
```ts
|
|
397
|
+
const {
|
|
398
|
+
isActive, // boolean: a tour is running
|
|
399
|
+
currentStep, // number: index of the step on screen
|
|
400
|
+
steps, // readonly TourStep[]: the steps of the running tour
|
|
401
|
+
currentTourId, // string | undefined
|
|
402
|
+
startTour, // (steps, tourId?, options?) => void
|
|
403
|
+
endTour, // () => void — ends with the reason 'closed'
|
|
404
|
+
nextStep, // () => void — completes the tour on the last step
|
|
405
|
+
prevStep, // () => void
|
|
406
|
+
goToStep, // (index: number) => void
|
|
407
|
+
} = useTour();
|
|
652
408
|
```
|
|
653
409
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
## Internationalization (i18n)
|
|
657
|
-
|
|
658
|
-
Customize all text labels and templates to support different languages or match your app's terminology.
|
|
659
|
-
|
|
660
|
-
### i18n Options
|
|
410
|
+
The functions never change identity, so they are safe in effect dependencies. Starting the tour that is already running (same `tourId`, or the same steps array without an id) does nothing, which makes "start on mount" effects safe in Strict Mode. Starting a different tour ends the current one with `'closed'`.
|
|
661
411
|
|
|
662
412
|
```tsx
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
stepCounterTemplate: '{current} / {total}', // Step counter format (use {current} and {total} placeholders)
|
|
670
|
-
closeAriaLabel: 'Close tour', // Accessibility label for close button (web only)
|
|
671
|
-
dotAriaLabelTemplate: 'Go to step {step}', // Accessibility label template for step dots (web only)
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
function App() {
|
|
675
|
-
return (
|
|
676
|
-
<TourProvider i18n={i18n}>
|
|
677
|
-
<YourApp />
|
|
678
|
-
</TourProvider>
|
|
679
|
-
);
|
|
680
|
-
}
|
|
413
|
+
// Show the onboarding once.
|
|
414
|
+
useEffect(() => {
|
|
415
|
+
if (!user.hasSeenOnboarding) {
|
|
416
|
+
startTour(onboarding, 'onboarding');
|
|
417
|
+
}
|
|
418
|
+
}, [user.hasSeenOnboarding, startTour]);
|
|
681
419
|
```
|
|
682
420
|
|
|
683
|
-
|
|
421
|
+
## When a tour ends
|
|
684
422
|
|
|
685
|
-
|
|
423
|
+
Pass `onTourEnd` to the provider (for every tour, for example analytics) and/or to `startTour` (for one tour). Both run exactly once per tour: the tour's own callback first, then the provider's.
|
|
686
424
|
|
|
687
425
|
```tsx
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
closeAriaLabel: 'Cerrar tour',
|
|
694
|
-
dotAriaLabelTemplate: 'Ir al paso {step}',
|
|
695
|
-
};
|
|
426
|
+
<TourProvider
|
|
427
|
+
onTourEnd={(tourId, info) => {
|
|
428
|
+
analytics.track('tour_ended', {tourId, reason: info.reason, step: info.stepIndex + 1, of: info.totalSteps});
|
|
429
|
+
}}
|
|
430
|
+
>
|
|
696
431
|
```
|
|
697
432
|
|
|
698
|
-
**German:**
|
|
699
|
-
|
|
700
433
|
```tsx
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
};
|
|
434
|
+
startTour(onboarding, 'onboarding', {
|
|
435
|
+
onTourEnd(tourId, info) {
|
|
436
|
+
if (info.reason === 'completed') {
|
|
437
|
+
markOnboardingDone();
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
});
|
|
709
441
|
```
|
|
710
442
|
|
|
711
|
-
|
|
443
|
+
| `info.reason` | When |
|
|
444
|
+
| ------------------ | ----------------------------------------------------------------------------------- |
|
|
445
|
+
| `'completed'` | Finish on the last step |
|
|
446
|
+
| `'closed'` | The close button, `endTour()`, the Android back button, or another tour was started |
|
|
447
|
+
| `'blur'` | A click or tap on the dimmed area (only with `closeOnOverlayClick`, the default) |
|
|
448
|
+
| `'escape'` | <kbd>Esc</kbd> on the web, the VoiceOver escape gesture on iOS |
|
|
449
|
+
| `'target-missing'` | No remaining step could be shown |
|
|
450
|
+
| `'error'` | A step's `onBeforeStep` threw or rejected; the error is in `info.error` |
|
|
712
451
|
|
|
713
|
-
|
|
714
|
-
const customI18n: TourI18n = {
|
|
715
|
-
nextLabel: 'Continue',
|
|
716
|
-
prevLabel: 'Go Back',
|
|
717
|
-
finishLabel: 'Got it!',
|
|
718
|
-
stepCounterTemplate: 'Step {current} of {total}',
|
|
719
|
-
};
|
|
720
|
-
// Other values use defaults
|
|
721
|
-
```
|
|
452
|
+
`info` also has `stepIndex`, `step` and `totalSteps`. If a callback throws, the error is reported (`reportError` in browsers, `console.error` on React Native) and the tour still closes.
|
|
722
453
|
|
|
723
|
-
|
|
454
|
+
Set `closeOnOverlayClick={false}` on the provider to close only with the close button, <kbd>Esc</kbd> and the back button.
|
|
724
455
|
|
|
725
|
-
|
|
456
|
+
## Theme
|
|
726
457
|
|
|
727
458
|
```tsx
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
function App() {
|
|
738
|
-
return (
|
|
739
|
-
<TourProvider i18n={i18n}>
|
|
740
|
-
<YourApp />
|
|
741
|
-
</TourProvider>
|
|
742
|
-
);
|
|
743
|
-
}
|
|
459
|
+
<TourProvider
|
|
460
|
+
theme={{
|
|
461
|
+
primaryColor: '#8b5cf6',
|
|
462
|
+
tooltipBackground: '#1f2937',
|
|
463
|
+
titleColor: '#f9fafb',
|
|
464
|
+
contentColor: '#d1d5db',
|
|
465
|
+
overlayColor: 'rgba(0, 0, 0, 0.7)',
|
|
466
|
+
}}
|
|
467
|
+
>
|
|
744
468
|
```
|
|
745
469
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
|
749
|
-
|
|
|
750
|
-
| `
|
|
751
|
-
| `
|
|
752
|
-
| `
|
|
753
|
-
| `
|
|
754
|
-
| `closeAriaLabel` | `'Close tour'` | N/A |
|
|
755
|
-
| `dotAriaLabelTemplate` | `'Go to step {step}'` | N/A |
|
|
756
|
-
|
|
757
|
-
---
|
|
470
|
+
| Key | Used for | Default |
|
|
471
|
+
| ------------------- | ----------------------------------------------------------- | ---------------------- |
|
|
472
|
+
| `primaryColor` | Buttons, step badge, active dot, highlight ring | `'#2563eb'` |
|
|
473
|
+
| `primaryTextColor` | Text on `primaryColor` | `'#ffffff'` |
|
|
474
|
+
| `tooltipBackground` | The tooltip | `'#ffffff'` |
|
|
475
|
+
| `titleColor` | The title | `'#111827'` |
|
|
476
|
+
| `contentColor` | The content; muted parts (dots, Back button) derive from it | `'#4b5563'` |
|
|
477
|
+
| `overlayColor` | The dimmed area (include transparency) | `'rgba(0, 0, 0, 0.5)'` |
|
|
758
478
|
|
|
759
|
-
|
|
479
|
+
The defaults meet WCAG AA contrast. A dark theme only needs a dark background and light text colours.
|
|
760
480
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
Run code before a step is shown. Useful for opening dialogs, switching tabs, navigating, etc.
|
|
481
|
+
## Texts and languages
|
|
764
482
|
|
|
765
483
|
```tsx
|
|
766
|
-
|
|
767
|
-
{
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
onBeforeStep: async (target) => {
|
|
777
|
-
// Open the settings tab before showing this step
|
|
778
|
-
document.querySelector('[data-tour="settings-tab"]')?.click();
|
|
779
|
-
// Poll until the panel actually mounts instead of guessing a delay
|
|
780
|
-
while (!document.querySelector(target as string)) {
|
|
781
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
782
|
-
}
|
|
783
|
-
},
|
|
784
|
-
},
|
|
785
|
-
];
|
|
484
|
+
<TourProvider
|
|
485
|
+
i18n={{
|
|
486
|
+
nextLabel: 'Weiter',
|
|
487
|
+
prevLabel: 'Zurück',
|
|
488
|
+
finishLabel: 'Fertig',
|
|
489
|
+
stepCounterTemplate: '{current} von {total}',
|
|
490
|
+
closeAriaLabel: 'Tour schließen',
|
|
491
|
+
dotAriaLabelTemplate: 'Zu Schritt {step}',
|
|
492
|
+
}}
|
|
493
|
+
>
|
|
786
494
|
```
|
|
787
495
|
|
|
788
|
-
|
|
496
|
+
| Key | Default |
|
|
497
|
+
| ---------------------- | ------------------------ |
|
|
498
|
+
| `nextLabel` | `'Next'` |
|
|
499
|
+
| `prevLabel` | `'Back'` |
|
|
500
|
+
| `finishLabel` | `'Finish'` |
|
|
501
|
+
| `stepCounterTemplate` | `'{current} of {total}'` |
|
|
502
|
+
| `closeAriaLabel` | `'Close tour'` |
|
|
503
|
+
| `dotAriaLabelTemplate` | `'Go to step {step}'` |
|
|
789
504
|
|
|
790
|
-
|
|
505
|
+
## Custom tooltip
|
|
791
506
|
|
|
792
|
-
|
|
507
|
+
`TooltipComponent` replaces what is _inside_ the tooltip. The tour still measures it, places it, keeps it on screen, animates it, and moves focus to it — your component only renders content and calls the callbacks:
|
|
793
508
|
|
|
794
509
|
```tsx
|
|
795
|
-
import {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
<div
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
top: position.top,
|
|
806
|
-
left: position.left,
|
|
807
|
-
opacity: isPositioned ? 1 : 0,
|
|
808
|
-
}}
|
|
809
|
-
>
|
|
810
|
-
<h3>{title}</h3>
|
|
811
|
-
<p>{content}</p>
|
|
812
|
-
<div className="tooltip-footer">
|
|
510
|
+
import {type TourTooltipProps} from '@robertlinde/react-tour-kit/react';
|
|
511
|
+
|
|
512
|
+
function MyTooltip({step, currentStep, totalSteps, onNext, onPrev, onClose, titleId, descriptionId}: TourTooltipProps) {
|
|
513
|
+
const isLast = currentStep === totalSteps - 1;
|
|
514
|
+
return (
|
|
515
|
+
<div className="my-tooltip">
|
|
516
|
+
<h3 id={titleId}>{step.title}</h3>
|
|
517
|
+
<div id={descriptionId}>{step.content}</div>
|
|
518
|
+
<footer>
|
|
519
|
+
<button onClick={onClose}>Skip</button>
|
|
813
520
|
<span>
|
|
814
|
-
{currentStep + 1}
|
|
521
|
+
{currentStep + 1}/{totalSteps}
|
|
815
522
|
</span>
|
|
816
|
-
<button
|
|
523
|
+
<button disabled={currentStep === 0} onClick={onPrev}>
|
|
817
524
|
Back
|
|
818
525
|
</button>
|
|
819
|
-
<button onClick={onNext}>{
|
|
820
|
-
|
|
821
|
-
</div>
|
|
526
|
+
<button onClick={onNext}>{isLast ? 'Done' : 'Next'}</button>
|
|
527
|
+
</footer>
|
|
822
528
|
</div>
|
|
823
|
-
),
|
|
824
|
-
);
|
|
825
|
-
|
|
826
|
-
function App() {
|
|
827
|
-
return (
|
|
828
|
-
<TourProvider TooltipComponent={CustomTooltip}>
|
|
829
|
-
<YourApp />
|
|
830
|
-
</TourProvider>
|
|
831
529
|
);
|
|
832
530
|
}
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
**React Native:**
|
|
836
531
|
|
|
837
|
-
|
|
838
|
-
import {forwardRef} from 'react';
|
|
839
|
-
import {View, Text, TouchableOpacity} from 'react-native';
|
|
840
|
-
import {TourProvider, type TourTooltipProps} from '@robertlinde/react-tour-kit/react-native';
|
|
841
|
-
|
|
842
|
-
const CustomTooltip = forwardRef<View, TourTooltipProps>(
|
|
843
|
-
({title, content, position, isPositioned, onNext, onClose}, ref) => (
|
|
844
|
-
<View
|
|
845
|
-
ref={ref}
|
|
846
|
-
style={{
|
|
847
|
-
position: 'absolute',
|
|
848
|
-
top: position.top,
|
|
849
|
-
left: position.left,
|
|
850
|
-
opacity: isPositioned ? 1 : 0,
|
|
851
|
-
backgroundColor: '#fff',
|
|
852
|
-
padding: 16,
|
|
853
|
-
borderRadius: 8,
|
|
854
|
-
}}
|
|
855
|
-
>
|
|
856
|
-
<Text style={{fontWeight: 'bold'}}>{title}</Text>
|
|
857
|
-
<Text>{content}</Text>
|
|
858
|
-
<TouchableOpacity onPress={onNext}>
|
|
859
|
-
<Text>Next</Text>
|
|
860
|
-
</TouchableOpacity>
|
|
861
|
-
</View>
|
|
862
|
-
),
|
|
863
|
-
);
|
|
864
|
-
|
|
865
|
-
function App() {
|
|
866
|
-
return (
|
|
867
|
-
<TourProvider TooltipComponent={CustomTooltip}>
|
|
868
|
-
<YourApp />
|
|
869
|
-
</TourProvider>
|
|
870
|
-
);
|
|
871
|
-
}
|
|
532
|
+
<TourProvider TooltipComponent={MyTooltip}>…</TourProvider>;
|
|
872
533
|
```
|
|
873
534
|
|
|
874
|
-
|
|
535
|
+
On React Native, render views instead; everything else is the same.
|
|
875
536
|
|
|
876
|
-
|
|
537
|
+
| Prop | Description |
|
|
538
|
+
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
539
|
+
| `step` | The step being shown |
|
|
540
|
+
| `currentStep`, `totalSteps` | Zero-based index and count |
|
|
541
|
+
| `onNext`, `onPrev`, `onClose` | Navigation; `onNext` completes the tour on the last step |
|
|
542
|
+
| `onGoToStep(index)` | Jump to a step (for clickable dots) |
|
|
543
|
+
| `placement` | The side the tooltip ended up on, or `'center'` |
|
|
544
|
+
| `arrowOffset` | Where an arrow should point, in pixels from the tooltip's left (top/bottom) or top (left/right) edge; or `undefined` |
|
|
545
|
+
| `titleId`, `descriptionId` (web) | Put them on the title and content elements so screen readers announce them |
|
|
877
546
|
|
|
878
|
-
|
|
547
|
+
The card around your content has the theme's background, rounded corners and a shadow; style the inside as you like. `getTruncatedDots(current, total)` is exported for building a compact dot pager like the built-in one.
|
|
879
548
|
|
|
880
|
-
|
|
881
|
-
import {TourProvider, type TourOverlayProps} from '@robertlinde/react-tour-kit/react';
|
|
549
|
+
## Styling with CSS (web)
|
|
882
550
|
|
|
883
|
-
|
|
884
|
-
return (
|
|
885
|
-
<div className="custom-overlay" onClick={onClose}>
|
|
886
|
-
<div
|
|
887
|
-
className="highlight-border"
|
|
888
|
-
style={{
|
|
889
|
-
position: 'absolute',
|
|
890
|
-
top: highlightRect.top - 4,
|
|
891
|
-
left: highlightRect.left - 4,
|
|
892
|
-
width: highlightRect.width + 8,
|
|
893
|
-
height: highlightRect.height + 8,
|
|
894
|
-
border: '2px solid #your-brand-color',
|
|
895
|
-
borderRadius: '8px',
|
|
896
|
-
}}
|
|
897
|
-
/>
|
|
898
|
-
</div>
|
|
899
|
-
);
|
|
900
|
-
}
|
|
551
|
+
The built-in tooltip and overlay use stable class names and CSS variables, so you can restyle them without a custom component. All built-in rules live in a CSS cascade layer, so your own CSS wins over them — whatever its specificity, without `!important`. (If your CSS is itself inside a layer, such as Tailwind's, write these overrides outside of it.)
|
|
901
552
|
|
|
902
|
-
|
|
903
|
-
return (
|
|
904
|
-
<TourProvider OverlayComponent={CustomOverlay}>
|
|
905
|
-
<YourApp />
|
|
906
|
-
</TourProvider>
|
|
907
|
-
);
|
|
908
|
-
}
|
|
909
|
-
```
|
|
910
|
-
|
|
911
|
-
**React Native:**
|
|
553
|
+
The exceptions are the few properties that keep the tour in place, clickable and readable, which global styles would otherwise change (Tailwind's preflight, normalize.css, Bootstrap, or rules for every `dialog` or `button`): the position, size and spacing of the dialog, the highlight and the tooltip box, and the box, spacing, colours and font size of the buttons. They are `!important` in the built-in styles; their colours still come from the variables below. To change one of them anyway, use `!important` in your rule.
|
|
912
554
|
|
|
913
|
-
```
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
return (
|
|
919
|
-
<TouchableWithoutFeedback onPress={onClose}>
|
|
920
|
-
<View style={StyleSheet.absoluteFill}>
|
|
921
|
-
{/* Your custom overlay with cutout */}
|
|
922
|
-
<View
|
|
923
|
-
style={{
|
|
924
|
-
position: 'absolute',
|
|
925
|
-
top: highlightRect.top - 4,
|
|
926
|
-
left: highlightRect.left - 4,
|
|
927
|
-
width: highlightRect.width + 8,
|
|
928
|
-
height: highlightRect.height + 8,
|
|
929
|
-
borderWidth: 2,
|
|
930
|
-
borderColor: '#your-brand-color',
|
|
931
|
-
borderRadius: 8,
|
|
932
|
-
}}
|
|
933
|
-
/>
|
|
934
|
-
</View>
|
|
935
|
-
</TouchableWithoutFeedback>
|
|
936
|
-
);
|
|
555
|
+
```css
|
|
556
|
+
.rtk-root {
|
|
557
|
+
--rtk-radius: 4px; /* tooltip corners */
|
|
558
|
+
--rtk-spotlight-radius: 2px; /* highlight corners */
|
|
559
|
+
--rtk-z-index: 10000; /* only used where the browser has no top layer */
|
|
937
560
|
}
|
|
938
561
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
<TourProvider OverlayComponent={CustomOverlay}>
|
|
942
|
-
<YourApp />
|
|
943
|
-
</TourProvider>
|
|
944
|
-
);
|
|
562
|
+
.rtk-title {
|
|
563
|
+
font-family: 'Inter', sans-serif;
|
|
945
564
|
}
|
|
946
565
|
```
|
|
947
566
|
|
|
948
|
-
|
|
567
|
+
- **Variables**: `--rtk-radius`, `--rtk-spotlight-radius`, `--rtk-ease`, `--rtk-z-index`. The colours `--rtk-primary`, `--rtk-on-primary`, `--rtk-bg`, `--rtk-title`, `--rtk-text` and `--rtk-overlay` come from the `theme` prop (set on `.rtk-root` directly): change them with `theme`, or override them on an element inside the tour, such as `.rtk-card`.
|
|
568
|
+
- **Classes**: `.rtk-root` (the dialog), `.rtk-backdrop`, `.rtk-spotlight` (the highlight), `.rtk-tooltip` (positioned box), `.rtk-card` (the tooltip itself), `.rtk-header`, `.rtk-counter`, `.rtk-close`, `.rtk-body`, `.rtk-title`, `.rtk-content`, `.rtk-footer`, `.rtk-button`, `.rtk-button--prev`, `.rtk-button--next`, `.rtk-dots`, `.rtk-dot`, `.rtk-arrow`.
|
|
569
|
+
- **State**: `.rtk-tooltip[data-placement="top|bottom|left|right|center"]`, `.rtk-root[data-state="closing"]`.
|
|
949
570
|
|
|
950
|
-
|
|
571
|
+
The tour is rendered as a modal `<dialog>` in the browser's top layer, so it is always above your app, whatever its `z-index` values and stacking contexts.
|
|
951
572
|
|
|
952
|
-
|
|
953
|
-
import {TourProvider, type TourEndCallback} from '@robertlinde/react-tour-kit/react';
|
|
954
|
-
|
|
955
|
-
function App() {
|
|
956
|
-
const handleTourEnd: TourEndCallback = async (tourId, info) => {
|
|
957
|
-
if (tourId && info.reason === 'completed') {
|
|
958
|
-
// Save to your backend
|
|
959
|
-
await fetch('/api/users/me/completed-tours/' + tourId, {
|
|
960
|
-
method: 'POST',
|
|
961
|
-
});
|
|
962
|
-
}
|
|
963
|
-
};
|
|
573
|
+
## What the tour does for you
|
|
964
574
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
```
|
|
972
|
-
|
|
973
|
-
See [Tour End Callbacks](#tour-end-callbacks) for the full list of end reasons and how to register per-tour end handlers.
|
|
974
|
-
|
|
975
|
-
### Conditional Tour Start
|
|
976
|
-
|
|
977
|
-
Start tours based on user state:
|
|
978
|
-
|
|
979
|
-
```tsx
|
|
980
|
-
function TourTrigger({tourId, steps, autoStart = true}) {
|
|
981
|
-
const {startTour} = useTour();
|
|
982
|
-
const {user} = useUser(); // Your auth hook
|
|
983
|
-
|
|
984
|
-
useEffect(() => {
|
|
985
|
-
if (autoStart && user && !user.completedTours?.[tourId]) {
|
|
986
|
-
// Auto-start if user hasn't completed this tour
|
|
987
|
-
setTimeout(() => startTour(steps, tourId), 500);
|
|
988
|
-
}
|
|
989
|
-
}, [user, tourId, autoStart]);
|
|
990
|
-
|
|
991
|
-
return <button onClick={() => startTour(steps, tourId)}>Start Tour</button>;
|
|
992
|
-
}
|
|
993
|
-
```
|
|
994
|
-
|
|
995
|
-
## Styling
|
|
996
|
-
|
|
997
|
-
The default components use inline styles for zero-dependency styling. You have three options for customization:
|
|
998
|
-
|
|
999
|
-
1. **Theming**: Use the `theme` prop to customize colors (see [Theming](#theming) section)
|
|
1000
|
-
2. **Custom Components**: Provide your own `TooltipComponent` and `OverlayComponent` for full control
|
|
1001
|
-
3. **CSS Overrides** (Web only): Override styles via CSS selectors
|
|
1002
|
-
|
|
1003
|
-
## Platform Support
|
|
575
|
+
- **Keeps everything in sight.** Before a step is shown, its target is scrolled into view — through every scroll container around it, including horizontal ones — with room for the tooltip. Elements that stay put while the page scrolls (sticky headers, cookie banners, floating buttons; any number, any shape) are detected and the target is scrolled clear of them, in both directions. Targets larger than the screen are aligned to their start. The tooltip is placed inside the visible screen minus safe areas and the on-screen keyboard, flips to another side when there is no room, and scrolls its own content if it is taller than the screen. When the window is resized or rotated, or your app moves the target, the tour follows.
|
|
576
|
+
- **Waits instead of guessing.** Targets that are not rendered yet (lazy pages, animations, data loading) are awaited, event-driven, for up to 5 seconds.
|
|
577
|
+
- **Animates.** The tour fades in, the highlight glides from target to target together with the scroll, and the tooltip enters from the side it is on. On React Native every animation runs on the UI thread, so it stays smooth while JavaScript is busy. With reduced motion turned on in the system, movement is replaced by short fades.
|
|
578
|
+
- **Is accessible.** On the web the tour is a modal dialog (`aria-modal`, labelled by the step title, described by its content): focus moves into the tooltip and back to where it was when the tour ends, the rest of the page is inert, arrow keys and <kbd>Esc</kbd> work (but leave text fields, sliders, radio groups and other controls in a custom tooltip alone), a long tooltip scrolls with the keyboard, and step changes are announced. Buttons have accessible names and large enough touch targets. In Windows high-contrast mode the highlight is drawn as an outline. On React Native, screen-reader focus moves to the tooltip, the app behind it is hidden from screen readers (in a `TourHost`, see [Modal screens](#modal-screens)), and the back button closes the tour.
|
|
579
|
+
- **Costs nothing while idle.** While a step is on screen and nothing moves, the tour does no work at all: no timers, no measuring, no re-rendering. Scrolling and resizing are followed once per frame, without re-rendering your app. `useTour()` only re-renders components when the tour's state changes.
|
|
580
|
+
- **Is safe.** Callbacks run exactly once, also in Strict Mode, with double clicks and when tours replace each other. Errors in your callbacks are reported and never leave the tour stuck.
|
|
1004
581
|
|
|
1005
|
-
|
|
582
|
+
## Testing your app
|
|
1006
583
|
|
|
1007
|
-
-
|
|
584
|
+
- **jsdom (Jest, Vitest)**: tours run in jsdom, with or without a `ResizeObserver` polyfill. jsdom has no layout, so every target counts as visible at the top of the screen; test that your steps and callbacks work, and leave the visual part to end-to-end tests.
|
|
585
|
+
- **Jest**: the package is ESM-only, and Jest runs CommonJS by default, so Jest has to transform it. Exempt it from `transformIgnorePatterns`: on the web, for example `'/node_modules/(?!@robertlinde/react-tour-kit/)'` (with a Babel or SWC transform that turns ES modules into CommonJS, which `babel-jest` with `@babel/preset-env` does); with React Native's preset, `'node_modules/(?!((jest-)?react-native|@react-native(-community)?|@robertlinde/react-tour-kit)/)'`; with Next.js's `next/jest`, add the package to `transpilePackages` in `next.config`. Vitest needs nothing.
|
|
586
|
+
- **End-to-end (web)**: the tooltip is a `dialog` named after the step title, so `page.getByRole('dialog', {name: 'Settings'})` finds it. The buttons are named by your `i18n` labels.
|
|
587
|
+
- **End-to-end (React Native)**: the built-in tooltip has the testIDs `rtk-tooltip`, `rtk-next`, `rtk-prev` and `rtk-close`. Prefer them over texts: on Android, "Back" and "Home" also match the system navigation bar. While a tour is open, the app behind it is hidden from accessibility (except while a `TourHost` draws it), so UI-test tools mostly see only the tour.
|
|
1008
588
|
|
|
1009
|
-
|
|
589
|
+
## Requirements
|
|
1010
590
|
|
|
1011
|
-
-
|
|
1012
|
-
-
|
|
1013
|
-
- React Native 0.
|
|
591
|
+
- React 18 or 19.
|
|
592
|
+
- Web: current versions of Chrome, Edge, Firefox and Safari.
|
|
593
|
+
- React Native 0.79 or newer (Expo SDK 53 or newer).
|
|
594
|
+
- ESM-only: works with every bundler (Vite, webpack, Next.js, Metro, …) and with Node's `import`.
|
|
595
|
+
- TypeScript: `moduleResolution` `bundler`, `node16` or `nodenext` (the defaults of current React Native, Expo, Next.js and Vite projects).
|
|
1014
596
|
|
|
1015
|
-
|
|
597
|
+
## Migrating from 0.x
|
|
1016
598
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
Full TypeScript support is included. Import types as needed:
|
|
1020
|
-
|
|
1021
|
-
```tsx
|
|
1022
|
-
import type {
|
|
1023
|
-
TourStep,
|
|
1024
|
-
TourContextType,
|
|
1025
|
-
TourProviderProps,
|
|
1026
|
-
TourTooltipProps,
|
|
1027
|
-
TourOverlayProps,
|
|
1028
|
-
TourTheme,
|
|
1029
|
-
TourI18n,
|
|
1030
|
-
TourEndCallback,
|
|
1031
|
-
TourEndInfo,
|
|
1032
|
-
TourEndReason,
|
|
1033
|
-
StartTourOptions,
|
|
1034
|
-
} from '@robertlinde/react-tour-kit/react';
|
|
1035
|
-
```
|
|
1036
|
-
|
|
1037
|
-
## Utilities
|
|
1038
|
-
|
|
1039
|
-
The package exports utility functions for custom implementations:
|
|
1040
|
-
|
|
1041
|
-
```tsx
|
|
1042
|
-
import {
|
|
1043
|
-
calculateTooltipPosition, // Calculate optimal tooltip position
|
|
1044
|
-
findVisibleElement, // Find first visible element matching selector
|
|
1045
|
-
} from '@robertlinde/react-tour-kit/react';
|
|
1046
|
-
```
|
|
599
|
+
1.0 is a rewrite with a smaller, safer API. Most apps only need to change a custom tooltip, if they have one. See [MIGRATION.md](./MIGRATION.md) for every change with before-and-after examples, and [CHANGELOG.md](./CHANGELOG.md) for what is new.
|
|
1047
600
|
|
|
1048
601
|
## Development
|
|
1049
602
|
|
|
1050
603
|
```bash
|
|
1051
|
-
# Install dependencies
|
|
1052
604
|
npm install
|
|
1053
|
-
|
|
1054
|
-
#
|
|
1055
|
-
npm run build
|
|
1056
|
-
|
|
1057
|
-
# Watch mode
|
|
1058
|
-
npm run start:dev
|
|
1059
|
-
|
|
1060
|
-
# Lint
|
|
605
|
+
npm test # unit and component tests (Vitest, real browser)
|
|
606
|
+
npm run test:e2e # end-to-end tests on desktop and mobile browsers (Playwright)
|
|
1061
607
|
npm run lint
|
|
608
|
+
npm run typecheck
|
|
609
|
+
npm run build
|
|
610
|
+
```
|
|
1062
611
|
|
|
1063
|
-
|
|
1064
|
-
npm run format
|
|
612
|
+
How the code is organised, and how to add a feature so that both platforms get it, is described in [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
1065
613
|
|
|
1066
|
-
|
|
1067
|
-
cd demos/react && npm install && npm run dev
|
|
614
|
+
The demos use the library's source directly, so every change is live there:
|
|
1068
615
|
|
|
1069
|
-
|
|
1070
|
-
|
|
616
|
+
```bash
|
|
617
|
+
npm run demo:web # web demo: http://localhost:5173 — includes a /lab page with edge cases
|
|
618
|
+
npm run demo:ios # React Native demo in Expo Go (run `npm install` in demos/react-native first)
|
|
619
|
+
npm run demo:android
|
|
1071
620
|
```
|
|
1072
621
|
|
|
622
|
+
All tests are in `test/` (see [ARCHITECTURE.md](./ARCHITECTURE.md#tests)). The React Native demo is tested with [Maestro](https://maestro.dev) flows: with the app open, run `maestro test test/maestro -e APP_ID=host.exp.Exponent`. To see the Android navigation bar setting in effect, build the app natively with `npx expo run:android`.
|
|
623
|
+
|
|
1073
624
|
## License
|
|
1074
625
|
|
|
1075
626
|
MIT
|