react-product-tour-guide 0.2.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 +67 -0
- package/LICENSE +21 -0
- package/README.md +454 -0
- package/dist/index.css +291 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +166 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +1320 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1311 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +116 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2026-03-09
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Critical:** `useState` was called inside a plain helper function `renderContent` in `TourTooltip`, violating React's Rules of Hooks. Extracted `ImageContent` and `VideoContent` as proper React sub-components to fix the crash that occurred when content type changed between steps.
|
|
13
|
+
- **Critical:** `onComplete` prop in `Tour` was incorrectly wired to `skipTour`, causing the user-provided `onComplete` callback to never fire when clicking "Done" on the last step (it fired `onSkip` instead). Changed to `onComplete={next}`, which already handles the last-step case.
|
|
14
|
+
- **Critical:** CSS styles were not applied in consumer apps due to `"sideEffects": false` in `package.json`, which caused bundlers (webpack/Vite) to tree-shake away all CSS imports. Changed to `"sideEffects": ["**/*.css"]`.
|
|
15
|
+
- Invalid CSS selectors passed as `selector` on a step now fail silently instead of throwing an uncaught `SyntaxError` that crashed the component tree.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- CSS is now auto-imported when the library is imported — no separate CSS import required in consumer apps.
|
|
20
|
+
- Removed obsolete `src/styles/tour.css` which had duplicate class definitions with inconsistent CSS variable names that conflicted with `theme.css`.
|
|
21
|
+
- Build now uses tsup's native CSS bundling instead of manually copying CSS files. `dist/index.css` is generated automatically.
|
|
22
|
+
- Simplified `package.json` exports: CSS is now accessible as `import 'react-product-tour-guide/styles'` instead of the internal `dist/` path.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Unit tests for `TourTooltip` component (20 tests): all content types, image/video error fallbacks, button callbacks, custom button config, ARIA attributes.
|
|
27
|
+
- Unit tests for `TourManager` singleton (13 tests): initialize, start, stop, next, back, skip, subscribe/unsubscribe.
|
|
28
|
+
- Additional `TourContext` tests: `defaultActive` prop, `back()` no-op on first step, `useTour` throws outside provider, `waitFor` is awaited before step advances.
|
|
29
|
+
- Additional `Tour` integration tests: `onComplete` callback correctness, `showProgress` prop, `skip={false}` prop, invalid selector safety.
|
|
30
|
+
|
|
31
|
+
## [0.1.0] - 2024-03-19
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- Initial release of React Product Tour
|
|
35
|
+
- Core tour functionality with step navigation
|
|
36
|
+
- Multiple content types support (text, image, video, custom)
|
|
37
|
+
- Accessibility features (ARIA attributes, keyboard navigation)
|
|
38
|
+
- Screen reader support with customizable announcements
|
|
39
|
+
- Focus management and trapping
|
|
40
|
+
- Responsive design with RTL support
|
|
41
|
+
- Theme customization
|
|
42
|
+
- Progress indicator
|
|
43
|
+
- Error boundaries for graceful degradation
|
|
44
|
+
- Comprehensive TypeScript types
|
|
45
|
+
- Basic unit tests and component tests
|
|
46
|
+
- Storybook documentation
|
|
47
|
+
|
|
48
|
+
### Features
|
|
49
|
+
- Spotlight focus on target elements
|
|
50
|
+
- Customizable styling and theming
|
|
51
|
+
- Multiple content types support
|
|
52
|
+
- Navigation controls (next, back, skip)
|
|
53
|
+
- Media support (remote and local)
|
|
54
|
+
- Highlight target elements
|
|
55
|
+
- Partial blur overlay
|
|
56
|
+
- Progress indicator
|
|
57
|
+
- Error boundaries
|
|
58
|
+
- Source maps for debugging
|
|
59
|
+
- Debounced event handlers
|
|
60
|
+
|
|
61
|
+
### Technical
|
|
62
|
+
- Built with TypeScript
|
|
63
|
+
- React 18+ support
|
|
64
|
+
- Floating UI for positioning
|
|
65
|
+
- Tailwind CSS for styling
|
|
66
|
+
- Vitest for testing
|
|
67
|
+
- Storybook for documentation
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Dzmitry Ihnatovich
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# React Product Tour
|
|
2
|
+
|
|
3
|
+
A flexible and accessible product tour component for React applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Spotlight focus on target elements with smooth highlighting
|
|
8
|
+
- Multiple content types: text, images, videos, custom React components
|
|
9
|
+
- Fully accessible — ARIA attributes, keyboard navigation, screen reader support
|
|
10
|
+
- Dark mode support via CSS variables and `.dark` class
|
|
11
|
+
- Three animation styles: `slide`, `bounce`, `fade`
|
|
12
|
+
- Progress indicator
|
|
13
|
+
- Async step support via `waitFor`
|
|
14
|
+
- Customizable styling via CSS variables, class names, or fully custom button renders
|
|
15
|
+
- Error boundaries for graceful media failure degradation
|
|
16
|
+
- Debounced scroll/resize handling for smooth repositioning
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install react-product-tour-guide
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Styles are loaded automatically — no separate CSS import needed.
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { TourProvider, Tour, useTour } from 'react-product-tour-guide';
|
|
30
|
+
|
|
31
|
+
const steps = [
|
|
32
|
+
{
|
|
33
|
+
selector: '#welcome',
|
|
34
|
+
content: 'Welcome to our app!',
|
|
35
|
+
placement: 'bottom',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
selector: '#features',
|
|
39
|
+
content: 'Check out our amazing features!',
|
|
40
|
+
placement: 'right',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function TourButton() {
|
|
45
|
+
const { start } = useTour();
|
|
46
|
+
return <button onClick={start}>Start Tour</button>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function App() {
|
|
50
|
+
return (
|
|
51
|
+
<TourProvider steps={steps} onComplete={() => console.log('Tour done!')}>
|
|
52
|
+
<TourButton />
|
|
53
|
+
<Tour />
|
|
54
|
+
{/* rest of your app */}
|
|
55
|
+
</TourProvider>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`Tour` renders the overlay and tooltip into a portal. `TourProvider` manages state. `useTour` exposes controls anywhere in the tree.
|
|
61
|
+
|
|
62
|
+
## API Reference
|
|
63
|
+
|
|
64
|
+
### `TourProvider` Props
|
|
65
|
+
|
|
66
|
+
| Prop | Type | Default | Description |
|
|
67
|
+
|------|------|---------|-------------|
|
|
68
|
+
| `steps` | `TourStep[]` | required | Array of tour steps |
|
|
69
|
+
| `children` | `ReactNode` | required | Child components |
|
|
70
|
+
| `defaultActive` | `boolean` | `false` | Start tour automatically on mount |
|
|
71
|
+
| `onComplete` | `() => void` | — | Called when the last step is completed |
|
|
72
|
+
| `onSkip` | `() => void` | — | Called when the tour is skipped |
|
|
73
|
+
| `onStepChange` | `(index, step) => void` | — | Called after navigating to a new step |
|
|
74
|
+
| `onStepEnter` | `(index, step) => void` | — | Called when entering a step |
|
|
75
|
+
| `onStepExit` | `(index, step) => void` | — | Called when leaving a step |
|
|
76
|
+
|
|
77
|
+
### `Tour` Props
|
|
78
|
+
|
|
79
|
+
| Prop | Type | Default | Description |
|
|
80
|
+
|------|------|---------|-------------|
|
|
81
|
+
| `skip` | `boolean` | `true` | Show the Skip button |
|
|
82
|
+
| `showProgress` | `boolean` | `false` | Show a progress bar and "Step X of Y" counter |
|
|
83
|
+
| `animation` | `'slide' \| 'bounce' \| 'fade'` | `'slide'` | Tooltip entrance animation |
|
|
84
|
+
| `overlayClassName` | `string` | — | Class applied to the overlay |
|
|
85
|
+
| `tooltipClassName` | `string` | — | Class applied to the tooltip |
|
|
86
|
+
| `buttonClassName` | `string` | — | Class applied to all buttons |
|
|
87
|
+
| `buttonContainerClassName` | `string` | — | Class applied to the button container |
|
|
88
|
+
| `highlightTarget` | `boolean \| HighlightConfig` | `true` | Highlight the target element |
|
|
89
|
+
| `tooltipOffset` | `number` | `10` | Distance in pixels between the tooltip and its target |
|
|
90
|
+
| `dismissOnOverlayClick` | `boolean` | `true` | Close the tour when the dark overlay is clicked |
|
|
91
|
+
| `accessibility` | `AccessibilityConfig` | — | Screen reader and focus options |
|
|
92
|
+
| `buttonConfig` | `ButtonConfigObj` | — | Custom button content or render functions |
|
|
93
|
+
|
|
94
|
+
### `useTour`
|
|
95
|
+
|
|
96
|
+
Returns the tour context:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const {
|
|
100
|
+
steps, // TourStep[]
|
|
101
|
+
currentStep, // number
|
|
102
|
+
isActive, // boolean
|
|
103
|
+
start, // () => void
|
|
104
|
+
stop, // () => void
|
|
105
|
+
next, // () => Promise<void> — awaits step.waitFor if present
|
|
106
|
+
back, // () => void
|
|
107
|
+
skip, // () => void
|
|
108
|
+
} = useTour();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`useTour` must be used inside a `TourProvider`.
|
|
112
|
+
|
|
113
|
+
### `TourStep`
|
|
114
|
+
|
|
115
|
+
| Property | Type | Description |
|
|
116
|
+
|----------|------|-------------|
|
|
117
|
+
| `selector` | `string` | CSS selector for the target element |
|
|
118
|
+
| `title` | `string` | Optional heading shown at the top of the tooltip |
|
|
119
|
+
| `content` | `ReactNode \| ContentType` | Content to display in the tooltip |
|
|
120
|
+
| `placement` | `'top' \| 'bottom' \| 'left' \| 'right'` | Tooltip placement |
|
|
121
|
+
| `waitFor` | `() => Promise<void>` | Async gate before advancing to this step |
|
|
122
|
+
|
|
123
|
+
### `ContentType`
|
|
124
|
+
|
|
125
|
+
| Property | Type | Description |
|
|
126
|
+
|----------|------|-------------|
|
|
127
|
+
| `type` | `'text' \| 'image' \| 'video' \| 'custom'` | Content type |
|
|
128
|
+
| `value` | `ReactNode \| string \| MediaSource` | Content value |
|
|
129
|
+
| `alt` | `string` | Alt text for image content (default: `'Tour content'`) |
|
|
130
|
+
| `props` | `Record<string, unknown>` | Extra props forwarded to the element |
|
|
131
|
+
|
|
132
|
+
### `MediaSource`
|
|
133
|
+
|
|
134
|
+
| Property | Type | Description |
|
|
135
|
+
|----------|------|-------------|
|
|
136
|
+
| `type` | `'remote' \| 'local'` | Media source type |
|
|
137
|
+
| `src` | `string` | URL or asset path |
|
|
138
|
+
|
|
139
|
+
### `AccessibilityConfig`
|
|
140
|
+
|
|
141
|
+
| Property | Type | Description |
|
|
142
|
+
|----------|------|-------------|
|
|
143
|
+
| `enableScreenReader` | `boolean` | Enable aria-live announcements |
|
|
144
|
+
| `announcements` | `{ start, end, step }` | Custom announcement strings |
|
|
145
|
+
| `focusManagement` | `'auto' \| 'manual'` | Focus strategy |
|
|
146
|
+
| `focusTrap` | `boolean` | Trap Tab key within the tour dialog |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Content Types
|
|
151
|
+
|
|
152
|
+
### Plain text or ReactNode
|
|
153
|
+
|
|
154
|
+
Any React-renderable value works — strings, JSX elements, or full components:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
{ selector: '#el', content: 'Simple text' }
|
|
158
|
+
{ selector: '#el', content: <strong>Bold step</strong> }
|
|
159
|
+
{ selector: '#el', content: <FeatureCallout title="New" description="Try it out" /> }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
For explicit typing, use `type: 'custom'`:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
{ selector: '#el', content: { type: 'custom', value: <FeatureCallout /> } }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Image
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
{
|
|
172
|
+
selector: '#el',
|
|
173
|
+
content: {
|
|
174
|
+
type: 'image',
|
|
175
|
+
value: 'https://example.com/screenshot.jpg',
|
|
176
|
+
alt: 'Feature screenshot',
|
|
177
|
+
props: { className: 'rounded-lg' },
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Video
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
{
|
|
186
|
+
selector: '#el',
|
|
187
|
+
content: {
|
|
188
|
+
type: 'video',
|
|
189
|
+
value: { type: 'remote', src: 'https://example.com/demo.mp4' },
|
|
190
|
+
props: { poster: 'https://example.com/thumb.jpg' },
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Custom component
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
{
|
|
199
|
+
selector: '#el',
|
|
200
|
+
content: {
|
|
201
|
+
type: 'custom',
|
|
202
|
+
value: (
|
|
203
|
+
<div>
|
|
204
|
+
<h3>Custom content</h3>
|
|
205
|
+
<p>Any React elements work here.</p>
|
|
206
|
+
</div>
|
|
207
|
+
),
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Async step (`waitFor`)
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
{
|
|
216
|
+
selector: '#async-panel',
|
|
217
|
+
content: 'Panel loaded!',
|
|
218
|
+
waitFor: () => new Promise(resolve => {
|
|
219
|
+
// Resolve when the element is ready
|
|
220
|
+
const el = document.querySelector('#async-panel');
|
|
221
|
+
if (el) resolve();
|
|
222
|
+
else setTimeout(resolve, 500);
|
|
223
|
+
}),
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Theming
|
|
230
|
+
|
|
231
|
+
### CSS Variables
|
|
232
|
+
|
|
233
|
+
Override variables in your app's CSS to theme the tour globally:
|
|
234
|
+
|
|
235
|
+
```css
|
|
236
|
+
:root {
|
|
237
|
+
--tour--overlay--background: rgba(0, 0, 0, 0.6);
|
|
238
|
+
|
|
239
|
+
--tour--tooltip--background: white;
|
|
240
|
+
--tour--tooltip--text: black;
|
|
241
|
+
--tour--tooltip--border: #e5e7eb;
|
|
242
|
+
--tour--tooltip--border-width: 1px;
|
|
243
|
+
--tour--tooltip--radius: 0.5rem;
|
|
244
|
+
--tour--tooltip--padding: 1rem;
|
|
245
|
+
--tour--tooltip--gap: 0.5rem;
|
|
246
|
+
--tour--tooltip--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
247
|
+
--tour--tooltip--transition: all 0.2s ease-in-out;
|
|
248
|
+
--tour--tooltip--max-width: 300px;
|
|
249
|
+
|
|
250
|
+
--tour--button--primary--background: #646cff;
|
|
251
|
+
--tour--button--primary--text: white;
|
|
252
|
+
--tour--button--secondary--background: #e5e7eb;
|
|
253
|
+
--tour--button--secondary--text: #374151;
|
|
254
|
+
|
|
255
|
+
--tour--highlight--padding: 8px;
|
|
256
|
+
--tour--highlight--radius: 10px;
|
|
257
|
+
|
|
258
|
+
--tour--progress--background: #e5e7eb;
|
|
259
|
+
--tour--progress--fill: #f43f5e;
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Dark Mode
|
|
264
|
+
|
|
265
|
+
Add the `.dark` class to any ancestor element (typically `<html>`):
|
|
266
|
+
|
|
267
|
+
```css
|
|
268
|
+
.dark {
|
|
269
|
+
--tour--tooltip--background: #1f2937;
|
|
270
|
+
--tour--tooltip--border: #374151;
|
|
271
|
+
--tour--tooltip--text: #f9fafb;
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Custom Classes
|
|
276
|
+
|
|
277
|
+
Pass any CSS class names via the className props. When using **Tailwind**, prefix utilities with `!` to ensure they override the library's base styles:
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
<Tour
|
|
281
|
+
overlayClassName="!bg-indigo-900/70"
|
|
282
|
+
tooltipClassName="!bg-slate-900 !text-white !border-slate-700 !rounded-2xl"
|
|
283
|
+
buttonClassName="!rounded-full"
|
|
284
|
+
/>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Without Tailwind, plain CSS classes work when your rules are unlayered (unlayered styles always win over the library's `@layer react-product-tour` styles):
|
|
288
|
+
|
|
289
|
+
```css
|
|
290
|
+
.my-tooltip { background: #1e293b; color: #f1f5f9; border-radius: 1rem; }
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
<Tour tooltipClassName="my-tooltip" />
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Custom Button Rendering
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
<Tour
|
|
301
|
+
buttonConfig={{
|
|
302
|
+
primary: {
|
|
303
|
+
content: 'Continue →',
|
|
304
|
+
className: 'bg-indigo-500 text-white px-6 py-2 rounded-lg',
|
|
305
|
+
},
|
|
306
|
+
secondary: {
|
|
307
|
+
content: '← Back',
|
|
308
|
+
},
|
|
309
|
+
// Or full custom render:
|
|
310
|
+
primary: {
|
|
311
|
+
render: ({ onNext, onComplete, isLastStep, currentStep, totalSteps }) => (
|
|
312
|
+
<button onClick={isLastStep ? onComplete : onNext}>
|
|
313
|
+
{isLastStep ? 'Finish' : `Next (${currentStep + 1}/${totalSteps})`}
|
|
314
|
+
</button>
|
|
315
|
+
),
|
|
316
|
+
},
|
|
317
|
+
// Custom container layout:
|
|
318
|
+
container: {
|
|
319
|
+
render: (props) => (
|
|
320
|
+
<div className="flex flex-col gap-3 mt-4 pt-4 border-t">
|
|
321
|
+
<button onClick={props.isLastStep ? props.onComplete : props.onNext}>
|
|
322
|
+
{props.isLastStep ? 'Done' : 'Next'}
|
|
323
|
+
</button>
|
|
324
|
+
{!props.isFirstStep && (
|
|
325
|
+
<button onClick={props.onBack}>Back</button>
|
|
326
|
+
)}
|
|
327
|
+
</div>
|
|
328
|
+
),
|
|
329
|
+
},
|
|
330
|
+
}}
|
|
331
|
+
/>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Callbacks
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
<TourProvider
|
|
340
|
+
steps={steps}
|
|
341
|
+
onStepChange={(index, step) => analytics.track('tour_step', { index })}
|
|
342
|
+
onStepEnter={(index, step) => console.log('entered', step.selector)}
|
|
343
|
+
onStepExit={(index, step) => console.log('exited', step.selector)}
|
|
344
|
+
onComplete={() => localStorage.setItem('tour_done', '1')}
|
|
345
|
+
onSkip={() => console.log('skipped')}
|
|
346
|
+
>
|
|
347
|
+
<Tour />
|
|
348
|
+
</TourProvider>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Keyboard Navigation
|
|
354
|
+
|
|
355
|
+
The tour responds to keyboard events out of the box — no configuration required:
|
|
356
|
+
|
|
357
|
+
| Key | Action |
|
|
358
|
+
|-----|--------|
|
|
359
|
+
| `Escape` | Close / skip the tour |
|
|
360
|
+
| `→` / `↓` | Advance to the next step |
|
|
361
|
+
| `←` / `↑` | Go back to the previous step |
|
|
362
|
+
| `Tab` / `Shift+Tab` | Move focus between buttons (focus trap when `focusTrap: true`) |
|
|
363
|
+
|
|
364
|
+
Arrow keys are ignored when focus is inside an input, textarea, or select.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Accessibility
|
|
369
|
+
|
|
370
|
+
Focus management and the Tab focus trap are active by default — they do **not** require `enableScreenReader`. Set `enableScreenReader: true` only to add aria-live screen reader announcements:
|
|
371
|
+
|
|
372
|
+
```tsx
|
|
373
|
+
<Tour
|
|
374
|
+
accessibility={{
|
|
375
|
+
enableScreenReader: true,
|
|
376
|
+
announcements: {
|
|
377
|
+
start: 'Product tour started. Press Tab to navigate.',
|
|
378
|
+
end: 'Tour complete.',
|
|
379
|
+
step: 'Step {step} of {total}: {content}',
|
|
380
|
+
},
|
|
381
|
+
focusTrap: true, // default — trap Tab within the dialog
|
|
382
|
+
focusManagement: 'auto', // default — auto-focus first button; restore on close
|
|
383
|
+
}}
|
|
384
|
+
/>
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Global Manager (without context)
|
|
390
|
+
|
|
391
|
+
For use cases where `TourProvider` can't wrap your component tree:
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
import { tourManager } from 'react-product-tour-guide';
|
|
395
|
+
|
|
396
|
+
tourManager.initialize(steps);
|
|
397
|
+
tourManager.start();
|
|
398
|
+
tourManager.next();
|
|
399
|
+
tourManager.stop();
|
|
400
|
+
|
|
401
|
+
// Subscribe to state changes
|
|
402
|
+
const unsubscribe = tourManager.subscribe((state) => {
|
|
403
|
+
console.log(state.isActive, state.currentStep);
|
|
404
|
+
});
|
|
405
|
+
unsubscribe(); // cleanup
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Exported Types
|
|
411
|
+
|
|
412
|
+
All public types are exported for TypeScript consumers:
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
import type {
|
|
416
|
+
TourStep, // A single step definition
|
|
417
|
+
TourProviderProps, // Props for <TourProvider>
|
|
418
|
+
TourProps, // Props for <Tour>
|
|
419
|
+
Placement, // 'top' | 'bottom' | 'left' | 'right'
|
|
420
|
+
ContentType, // Structured content (image/video/custom)
|
|
421
|
+
MediaSource, // { type: 'remote'|'local', src: string }
|
|
422
|
+
HighlightConfig, // { className?, style? } for the spotlight ring
|
|
423
|
+
AccessibilityConfig,// Screen reader and focus trap options
|
|
424
|
+
ButtonConfig, // Per-button content/className/style/render
|
|
425
|
+
ButtonLayoutConfig, // Button container direction/align/gap/render
|
|
426
|
+
ButtonRenderProps, // Props passed to custom button render functions
|
|
427
|
+
} from 'react-product-tour-guide';
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## Testing
|
|
433
|
+
|
|
434
|
+
The library ships with 95 unit + snapshot tests. Run them with:
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
npm test
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
To update DOM snapshots after intentional UI changes:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
npm test -- -u
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Contributing
|
|
449
|
+
|
|
450
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions, coding standards, and PR guidelines.
|
|
451
|
+
|
|
452
|
+
## License
|
|
453
|
+
|
|
454
|
+
MIT — see [LICENSE](LICENSE) for details.
|