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 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.