@robertlinde/react-tour-kit 0.0.1
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/LICENSE +21 -0
- package/README.md +838 -0
- package/dist/react-native-platform/components/index.d.ts +2 -0
- package/dist/react-native-platform/components/index.js +8 -0
- package/dist/react-native-platform/components/index.js.map +1 -0
- package/dist/react-native-platform/components/tour-overlay.d.ts +3 -0
- package/dist/react-native-platform/components/tour-overlay.js +83 -0
- package/dist/react-native-platform/components/tour-overlay.js.map +1 -0
- package/dist/react-native-platform/components/tour-tooltip.d.ts +3 -0
- package/dist/react-native-platform/components/tour-tooltip.js +138 -0
- package/dist/react-native-platform/components/tour-tooltip.js.map +1 -0
- package/dist/react-native-platform/hooks/index.d.ts +1 -0
- package/dist/react-native-platform/hooks/index.js +6 -0
- package/dist/react-native-platform/hooks/index.js.map +1 -0
- package/dist/react-native-platform/hooks/use-tour-target.d.ts +2 -0
- package/dist/react-native-platform/hooks/use-tour-target.js +16 -0
- package/dist/react-native-platform/hooks/use-tour-target.js.map +1 -0
- package/dist/react-native-platform/index.d.ts +7 -0
- package/dist/react-native-platform/index.js +16 -0
- package/dist/react-native-platform/index.js.map +1 -0
- package/dist/react-native-platform/platform-adapter.d.ts +2 -0
- package/dist/react-native-platform/platform-adapter.js +87 -0
- package/dist/react-native-platform/platform-adapter.js.map +1 -0
- package/dist/react-native-platform/target-registry.d.ts +10 -0
- package/dist/react-native-platform/target-registry.js +20 -0
- package/dist/react-native-platform/target-registry.js.map +1 -0
- package/dist/react-native-platform/tour-provider.d.ts +3 -0
- package/dist/react-native-platform/tour-provider.js +200 -0
- package/dist/react-native-platform/tour-provider.js.map +1 -0
- package/dist/react-native-platform/types/tour-provider.props.type.d.ts +10 -0
- package/dist/react-native-platform/types/tour-provider.props.type.js +3 -0
- package/dist/react-native-platform/types/tour-provider.props.type.js.map +1 -0
- package/dist/react-native.d.ts +8 -0
- package/dist/react-native.js +19 -0
- package/dist/react-native.js.map +1 -0
- package/dist/react-platform/components/index.d.ts +2 -0
- package/dist/react-platform/components/index.js +8 -0
- package/dist/react-platform/components/index.js.map +1 -0
- package/dist/react-platform/components/tour-overlay.d.ts +3 -0
- package/dist/react-platform/components/tour-overlay.js +31 -0
- package/dist/react-platform/components/tour-overlay.js.map +1 -0
- package/dist/react-platform/components/tour-tooltip.d.ts +2 -0
- package/dist/react-platform/components/tour-tooltip.js +126 -0
- package/dist/react-platform/components/tour-tooltip.js.map +1 -0
- package/dist/react-platform/index.d.ts +6 -0
- package/dist/react-platform/index.js +14 -0
- package/dist/react-platform/index.js.map +1 -0
- package/dist/react-platform/platform-adapter.d.ts +2 -0
- package/dist/react-platform/platform-adapter.js +95 -0
- package/dist/react-platform/platform-adapter.js.map +1 -0
- package/dist/react-platform/tour-provider.d.ts +3 -0
- package/dist/react-platform/tour-provider.js +207 -0
- package/dist/react-platform/tour-provider.js.map +1 -0
- package/dist/react-platform/types/tour-provider.props.type.d.ts +10 -0
- package/dist/react-platform/types/tour-provider.props.type.js +3 -0
- package/dist/react-platform/types/tour-provider.props.type.js.map +1 -0
- package/dist/react-platform/utils/find-visible-element.util.d.ts +1 -0
- package/dist/react-platform/utils/find-visible-element.util.js +14 -0
- package/dist/react-platform/utils/find-visible-element.util.js.map +1 -0
- package/dist/react-platform/utils/index.d.ts +1 -0
- package/dist/react-platform/utils/index.js +6 -0
- package/dist/react-platform/utils/index.js.map +1 -0
- package/dist/react.d.ts +8 -0
- package/dist/react.js +18 -0
- package/dist/react.js.map +1 -0
- package/dist/shared/context/index.d.ts +1 -0
- package/dist/shared/context/index.js +7 -0
- package/dist/shared/context/index.js.map +1 -0
- package/dist/shared/context/tour-context.d.ts +3 -0
- package/dist/shared/context/tour-context.js +17 -0
- package/dist/shared/context/tour-context.js.map +1 -0
- package/dist/shared/hooks/index.d.ts +1 -0
- package/dist/shared/hooks/index.js +6 -0
- package/dist/shared/hooks/index.js.map +1 -0
- package/dist/shared/hooks/use-tour.hook.d.ts +2 -0
- package/dist/shared/hooks/use-tour.hook.js +10 -0
- package/dist/shared/hooks/use-tour.hook.js.map +1 -0
- package/dist/shared/index.d.ts +5 -0
- package/dist/shared/index.js +14 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/types/index.d.ts +8 -0
- package/dist/shared/types/index.js +7 -0
- package/dist/shared/types/index.js.map +1 -0
- package/dist/shared/types/platform-adapter.type.d.ts +24 -0
- package/dist/shared/types/platform-adapter.type.js +3 -0
- package/dist/shared/types/platform-adapter.type.js.map +1 -0
- package/dist/shared/types/rect.type.d.ts +10 -0
- package/dist/shared/types/rect.type.js +3 -0
- package/dist/shared/types/rect.type.js.map +1 -0
- package/dist/shared/types/tour-context.type.d.ts +12 -0
- package/dist/shared/types/tour-context.type.js +3 -0
- package/dist/shared/types/tour-context.type.js.map +1 -0
- package/dist/shared/types/tour-overlay-props.type.d.ts +7 -0
- package/dist/shared/types/tour-overlay-props.type.js +3 -0
- package/dist/shared/types/tour-overlay-props.type.js.map +1 -0
- package/dist/shared/types/tour-step.type.d.ts +8 -0
- package/dist/shared/types/tour-step.type.js +3 -0
- package/dist/shared/types/tour-step.type.js.map +1 -0
- package/dist/shared/types/tour-theme.type.d.ts +9 -0
- package/dist/shared/types/tour-theme.type.js +21 -0
- package/dist/shared/types/tour-theme.type.js.map +1 -0
- package/dist/shared/types/tour-tooltip-props.type.d.ts +17 -0
- package/dist/shared/types/tour-tooltip-props.type.js +3 -0
- package/dist/shared/types/tour-tooltip-props.type.js.map +1 -0
- package/dist/shared/utils/calculate-tooltip-position.util.d.ts +14 -0
- package/dist/shared/utils/calculate-tooltip-position.util.js +56 -0
- package/dist/shared/utils/calculate-tooltip-position.util.js.map +1 -0
- package/dist/shared/utils/index.d.ts +1 -0
- package/dist/shared/utils/index.js +6 -0
- package/dist/shared/utils/index.js.map +1 -0
- package/package.json +88 -0
package/README.md
ADDED
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
# react-tour-kit
|
|
2
|
+
|
|
3
|
+
Cross-platform guided tour library for React and React Native. Build interactive onboarding experiences with customizable tooltips, smart positioning, cross-page navigation, and async step actions. Fully themeable with TypeScript support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Cross-Platform**: Works with React (web) and React Native
|
|
8
|
+
- **Cross-Page Tours**: Navigate between pages/screens during a tour
|
|
9
|
+
- **Themeable**: Customize colors without building custom components
|
|
10
|
+
- **Fully Customizable**: Replace the default tooltip and overlay with your own components
|
|
11
|
+
- **Smart Positioning**: Automatic tooltip positioning with viewport boundary detection
|
|
12
|
+
- **Keyboard Navigation**: Arrow keys and Escape (web) or hardware back button (Android)
|
|
13
|
+
- **Async Step Actions**: Run async code before each step (e.g., navigate, switch tabs, open dialogs)
|
|
14
|
+
- **TypeScript First**: Full TypeScript support
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @robertlinde/react-tour-kit
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Live Demos
|
|
23
|
+
|
|
24
|
+
Try the demos locally:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Web demo (React + React Router)
|
|
28
|
+
cd demos/react
|
|
29
|
+
npm install
|
|
30
|
+
npm run dev
|
|
31
|
+
|
|
32
|
+
# React Native demo (Expo + React Navigation)
|
|
33
|
+
cd demos/react-native
|
|
34
|
+
npm install
|
|
35
|
+
npm run ios # or npm run android
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Platform Guide
|
|
39
|
+
|
|
40
|
+
| Platform | Import from | Target elements with |
|
|
41
|
+
| ---------------- | ----------------------------- | --------------------------------------------- |
|
|
42
|
+
| **React (Web)** | `react-tour-kit/react` | CSS selectors (e.g., `[data-tour="welcome"]`) |
|
|
43
|
+
| **React Native** | `react-tour-kit/react-native` | `useTourTarget` hook or refs |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## React (Web)
|
|
48
|
+
|
|
49
|
+
### Quick Start
|
|
50
|
+
|
|
51
|
+
#### 1. Wrap your app with TourProvider
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import {TourProvider} from 'react-tour-kit/react';
|
|
55
|
+
|
|
56
|
+
function App() {
|
|
57
|
+
return (
|
|
58
|
+
<TourProvider>
|
|
59
|
+
<YourApp />
|
|
60
|
+
</TourProvider>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### 2. Add data attributes to target elements
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
function Dashboard() {
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
<header data-tour="welcome">
|
|
72
|
+
<h1>Dashboard</h1>
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
<nav data-tour="sidebar">{/* Navigation items */}</nav>
|
|
76
|
+
|
|
77
|
+
<button data-tour="settings">Settings</button>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### 3. Start a tour
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import {useTour, type TourStep} from 'react-tour-kit/react';
|
|
87
|
+
|
|
88
|
+
const tourSteps: TourStep[] = [
|
|
89
|
+
{
|
|
90
|
+
target: '[data-tour="welcome"]', // CSS selector
|
|
91
|
+
title: 'Welcome!',
|
|
92
|
+
content: 'This is your dashboard. Let me show you around.',
|
|
93
|
+
placement: 'bottom',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
target: '[data-tour="sidebar"]',
|
|
97
|
+
title: 'Navigation',
|
|
98
|
+
content: 'Use the sidebar to navigate between different sections.',
|
|
99
|
+
placement: 'right',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
target: '[data-tour="settings"]',
|
|
103
|
+
title: 'Settings',
|
|
104
|
+
content: 'Click here to customize your preferences.',
|
|
105
|
+
placement: 'left',
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
function WelcomeButton() {
|
|
110
|
+
const {startTour} = useTour();
|
|
111
|
+
|
|
112
|
+
return <button onClick={() => startTour(tourSteps, 'onboarding')}>Start Tour</button>;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Keyboard Navigation (Web)
|
|
117
|
+
|
|
118
|
+
| Key | Action |
|
|
119
|
+
| --------------- | ------------- |
|
|
120
|
+
| `→` Arrow Right | Next step |
|
|
121
|
+
| `←` Arrow Left | Previous step |
|
|
122
|
+
| `Escape` | Close tour |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## React Native
|
|
127
|
+
|
|
128
|
+
### Quick Start
|
|
129
|
+
|
|
130
|
+
#### 1. Wrap your app with TourProvider
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import {TourProvider} from 'react-tour-kit/react-native';
|
|
134
|
+
|
|
135
|
+
function App() {
|
|
136
|
+
return (
|
|
137
|
+
<TourProvider>
|
|
138
|
+
<YourApp />
|
|
139
|
+
</TourProvider>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### 2. Register target elements with useTourTarget
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import {View} from 'react-native';
|
|
148
|
+
import {useTourTarget} from 'react-tour-kit/react-native';
|
|
149
|
+
|
|
150
|
+
function MyComponent() {
|
|
151
|
+
const welcomeRef = useTourTarget<View>('welcome-button');
|
|
152
|
+
const settingsRef = useTourTarget<View>('settings-button');
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<View>
|
|
156
|
+
<TouchableOpacity ref={welcomeRef}>
|
|
157
|
+
<Text>Welcome</Text>
|
|
158
|
+
</TouchableOpacity>
|
|
159
|
+
|
|
160
|
+
<TouchableOpacity ref={settingsRef}>
|
|
161
|
+
<Text>Settings</Text>
|
|
162
|
+
</TouchableOpacity>
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### 3. Start a tour
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import {useTour, type TourStep} from 'react-tour-kit/react-native';
|
|
172
|
+
|
|
173
|
+
const tourSteps: TourStep[] = [
|
|
174
|
+
{
|
|
175
|
+
target: 'welcome-button', // String ID from useTourTarget
|
|
176
|
+
title: 'Welcome!',
|
|
177
|
+
content: 'This is your dashboard. Let me show you around.',
|
|
178
|
+
placement: 'bottom',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
target: 'settings-button',
|
|
182
|
+
title: 'Settings',
|
|
183
|
+
content: 'Tap here to customize your preferences.',
|
|
184
|
+
placement: 'left',
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
function StartTourButton() {
|
|
189
|
+
const {startTour} = useTour();
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<TouchableOpacity onPress={() => startTour(tourSteps, 'onboarding')}>
|
|
193
|
+
<Text>Start Tour</Text>
|
|
194
|
+
</TouchableOpacity>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Using Refs Directly (React Native)
|
|
200
|
+
|
|
201
|
+
You can also pass refs directly instead of string IDs:
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
import {useRef} from 'react';
|
|
205
|
+
import {useTour, type TourStep} from 'react-tour-kit/react-native';
|
|
206
|
+
|
|
207
|
+
function MyComponent() {
|
|
208
|
+
const buttonRef = useRef(null);
|
|
209
|
+
|
|
210
|
+
const steps: TourStep[] = [
|
|
211
|
+
{
|
|
212
|
+
target: buttonRef, // Pass ref directly
|
|
213
|
+
title: 'Welcome!',
|
|
214
|
+
content: 'Tap here to get started.',
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
return <TouchableOpacity ref={buttonRef}>...</TouchableOpacity>;
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Navigation (React Native)
|
|
223
|
+
|
|
224
|
+
| Action | Trigger |
|
|
225
|
+
| ------------- | ------------------------------------------------- |
|
|
226
|
+
| Next step | Tap "Next" button |
|
|
227
|
+
| Previous step | Tap "Back" button |
|
|
228
|
+
| Close tour | Tap close button, overlay, or Android back button |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Cross-Page/Cross-Screen Tours
|
|
233
|
+
|
|
234
|
+
Tours can span multiple pages (web) or screens (React Native). The key is to:
|
|
235
|
+
|
|
236
|
+
1. Wrap your **router/navigator** with `TourProvider`
|
|
237
|
+
2. Use `onBeforeStep` to navigate before showing a step
|
|
238
|
+
|
|
239
|
+
### Web (React Router)
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import {BrowserRouter, useNavigate} from 'react-router-dom';
|
|
243
|
+
import {TourProvider, useTour, type TourStep} from 'react-tour-kit/react';
|
|
244
|
+
|
|
245
|
+
// TourProvider wraps the router
|
|
246
|
+
function App() {
|
|
247
|
+
return (
|
|
248
|
+
<TourProvider>
|
|
249
|
+
<BrowserRouter>
|
|
250
|
+
<Routes>
|
|
251
|
+
<Route path="/" element={<Home />} />
|
|
252
|
+
<Route path="/settings" element={<Settings />} />
|
|
253
|
+
</Routes>
|
|
254
|
+
</BrowserRouter>
|
|
255
|
+
</TourProvider>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Define steps with navigation
|
|
260
|
+
function Home() {
|
|
261
|
+
const navigate = useNavigate();
|
|
262
|
+
const {startTour} = useTour();
|
|
263
|
+
|
|
264
|
+
const steps: TourStep[] = [
|
|
265
|
+
{
|
|
266
|
+
target: '[data-tour="welcome"]',
|
|
267
|
+
title: 'Welcome',
|
|
268
|
+
content: 'Let me show you around.',
|
|
269
|
+
placement: 'bottom',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
target: '[data-tour="nav-settings"]',
|
|
273
|
+
title: 'Settings Link',
|
|
274
|
+
content: "Now let's visit the settings page.",
|
|
275
|
+
placement: 'bottom',
|
|
276
|
+
// Navigate before the NEXT step shows
|
|
277
|
+
onBeforeStep: async () => {
|
|
278
|
+
navigate('/settings');
|
|
279
|
+
await new Promise((r) => setTimeout(r, 100)); // Wait for navigation
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
target: '[data-tour="theme-toggle"]',
|
|
284
|
+
title: 'Theme Settings',
|
|
285
|
+
content: 'This element is on the Settings page!',
|
|
286
|
+
placement: 'right',
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
target: '[data-tour="welcome"]',
|
|
290
|
+
title: 'Back Home',
|
|
291
|
+
content: 'And we can navigate back.',
|
|
292
|
+
placement: 'bottom',
|
|
293
|
+
onBeforeStep: async () => {
|
|
294
|
+
navigate('/');
|
|
295
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
return <button onClick={() => startTour(steps, 'cross-page')}>Start Tour</button>;
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### React Native (React Navigation)
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
import {NavigationContainer, useNavigation} from '@react-navigation/native';
|
|
308
|
+
import {createNativeStackNavigator} from '@react-navigation/native-stack';
|
|
309
|
+
import {TourProvider, useTour, useTourTarget, type TourStep} from 'react-tour-kit/react-native';
|
|
310
|
+
|
|
311
|
+
const Stack = createNativeStackNavigator();
|
|
312
|
+
|
|
313
|
+
// TourProvider wraps the navigator
|
|
314
|
+
function App() {
|
|
315
|
+
return (
|
|
316
|
+
<TourProvider>
|
|
317
|
+
<NavigationContainer>
|
|
318
|
+
<Stack.Navigator>
|
|
319
|
+
<Stack.Screen name="Home" component={HomeScreen} />
|
|
320
|
+
<Stack.Screen name="Settings" component={SettingsScreen} />
|
|
321
|
+
</Stack.Navigator>
|
|
322
|
+
</NavigationContainer>
|
|
323
|
+
</TourProvider>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Define steps with navigation
|
|
328
|
+
function HomeScreen() {
|
|
329
|
+
const navigation = useNavigation();
|
|
330
|
+
const {startTour} = useTour();
|
|
331
|
+
const headerRef = useTourTarget<View>('header');
|
|
332
|
+
|
|
333
|
+
const steps: TourStep[] = [
|
|
334
|
+
{
|
|
335
|
+
target: 'header',
|
|
336
|
+
title: 'Welcome',
|
|
337
|
+
content: 'Let me show you around.',
|
|
338
|
+
placement: 'bottom',
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
target: 'settings-header',
|
|
342
|
+
title: 'Settings Screen',
|
|
343
|
+
content: 'This element is on the Settings screen!',
|
|
344
|
+
placement: 'bottom',
|
|
345
|
+
onBeforeStep: async () => {
|
|
346
|
+
navigation.navigate('Settings');
|
|
347
|
+
await new Promise((r) => setTimeout(r, 300)); // Wait for animation
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
target: 'header',
|
|
352
|
+
title: 'Back Home',
|
|
353
|
+
content: 'And we can navigate back.',
|
|
354
|
+
placement: 'bottom',
|
|
355
|
+
onBeforeStep: async () => {
|
|
356
|
+
navigation.navigate('Home');
|
|
357
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<View>
|
|
364
|
+
<View ref={headerRef}>{/* ... */}</View>
|
|
365
|
+
<Button onPress={() => startTour(steps, 'cross-screen')} title="Start Tour" />
|
|
366
|
+
</View>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function SettingsScreen() {
|
|
371
|
+
const headerRef = useTourTarget<View>('settings-header');
|
|
372
|
+
|
|
373
|
+
return <View ref={headerRef}>{/* ... */}</View>;
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Important Notes for Cross-Page Tours
|
|
378
|
+
|
|
379
|
+
1. **TourProvider placement**: Must wrap your router/navigator so tour state persists across navigation
|
|
380
|
+
2. **Target availability**: Targets must be mounted when their step is active. Use `onBeforeStep` to navigate first
|
|
381
|
+
3. **Wait for navigation**: Add a small delay after navigation to ensure the new page/screen is rendered
|
|
382
|
+
4. **Persistent elements**: Elements like navigation bars that exist on all pages don't need `onBeforeStep`
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## API Reference
|
|
387
|
+
|
|
388
|
+
### TourProvider
|
|
389
|
+
|
|
390
|
+
The provider component that enables tour functionality throughout your app.
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
import {TourProvider} from 'react-tour-kit/react';
|
|
394
|
+
|
|
395
|
+
<TourProvider
|
|
396
|
+
TooltipComponent={CustomTooltip} // Optional: custom tooltip component
|
|
397
|
+
OverlayComponent={CustomOverlay} // Optional: custom overlay component
|
|
398
|
+
theme={theme} // Optional: theme configuration
|
|
399
|
+
onTourEnd={(tourId) => {
|
|
400
|
+
// Optional: callback when tour ends
|
|
401
|
+
console.log(`Tour ${tourId} completed`);
|
|
402
|
+
}}
|
|
403
|
+
>
|
|
404
|
+
{children}
|
|
405
|
+
</TourProvider>;
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
#### Props
|
|
409
|
+
|
|
410
|
+
| Prop | Type | Description |
|
|
411
|
+
| ------------------ | --------------------------------------------- | --------------------------------------------------- |
|
|
412
|
+
| `children` | `ReactNode` | Your application content |
|
|
413
|
+
| `theme` | `TourTheme` | Theme configuration for colors (see Theming below) |
|
|
414
|
+
| `TooltipComponent` | `ForwardRefExoticComponent<TourTooltipProps>` | Custom tooltip component (must use forwardRef) |
|
|
415
|
+
| `OverlayComponent` | `ComponentType<TourOverlayProps>` | Custom overlay component |
|
|
416
|
+
| `onTourEnd` | `(tourId: string \| null) => void` | Callback fired when tour ends (completed or closed) |
|
|
417
|
+
|
|
418
|
+
### useTour Hook
|
|
419
|
+
|
|
420
|
+
Access the tour context from any component.
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
import {useTour} from 'react-tour-kit/react';
|
|
424
|
+
|
|
425
|
+
function MyComponent() {
|
|
426
|
+
const {
|
|
427
|
+
isActive, // boolean: is a tour currently running?
|
|
428
|
+
currentStep, // number: current step index (0-based)
|
|
429
|
+
steps, // TourStep[]: all steps in current tour
|
|
430
|
+
currentTourId, // string | null: ID of current tour
|
|
431
|
+
startTour, // (steps: TourStep[], tourId?: string) => void
|
|
432
|
+
endTour, // () => void
|
|
433
|
+
nextStep, // () => void
|
|
434
|
+
prevStep, // () => void
|
|
435
|
+
goToStep, // (step: number) => void
|
|
436
|
+
} = useTour();
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### TourStep
|
|
441
|
+
|
|
442
|
+
Define the steps in your tour.
|
|
443
|
+
|
|
444
|
+
```tsx
|
|
445
|
+
type TourStep = {
|
|
446
|
+
target: TourTarget; // Target element (see below)
|
|
447
|
+
title: string; // Step title
|
|
448
|
+
content: string; // Step description/content
|
|
449
|
+
placement?: 'top' | 'bottom' | 'left' | 'right'; // Tooltip position (default: 'bottom')
|
|
450
|
+
onBeforeStep?: () => void | Promise<void>; // Async action before showing step
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// Target types differ by platform:
|
|
454
|
+
// Web: CSS selector string (e.g., '[data-tour="welcome"]', '#my-button')
|
|
455
|
+
// React Native: String ID (registered via useTourTarget) or RefObject
|
|
456
|
+
type TourTarget = string | RefObject<unknown>;
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Theming
|
|
460
|
+
|
|
461
|
+
Customize the look of the default tooltip and overlay without building custom components.
|
|
462
|
+
|
|
463
|
+
### Theme Options
|
|
464
|
+
|
|
465
|
+
```tsx
|
|
466
|
+
import {TourProvider, type TourTheme} from 'react-tour-kit/react';
|
|
467
|
+
|
|
468
|
+
const theme: TourTheme = {
|
|
469
|
+
primaryColor: '#10b981', // Buttons, step badge, highlight border (default: '#3b82f6')
|
|
470
|
+
tooltipBackground: '#ffffff', // Tooltip background color (default: '#ffffff')
|
|
471
|
+
titleColor: '#1f2937', // Title text color (default: '#1f2937')
|
|
472
|
+
contentColor: '#4b5563', // Body text color (default: '#4b5563')
|
|
473
|
+
overlayColor: 'rgba(0, 0, 0, 0.5)', // Semi-transparent overlay (default: 'rgba(0, 0, 0, 0.5)')
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
function App() {
|
|
477
|
+
return (
|
|
478
|
+
<TourProvider theme={theme}>
|
|
479
|
+
<YourApp />
|
|
480
|
+
</TourProvider>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Theme Examples
|
|
486
|
+
|
|
487
|
+
**Dark theme:**
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
490
|
+
const darkTheme: TourTheme = {
|
|
491
|
+
primaryColor: '#8b5cf6',
|
|
492
|
+
tooltipBackground: '#1f2937',
|
|
493
|
+
titleColor: '#f9fafb',
|
|
494
|
+
contentColor: '#d1d5db',
|
|
495
|
+
overlayColor: 'rgba(0, 0, 0, 0.7)',
|
|
496
|
+
};
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
**Brand colors:**
|
|
500
|
+
|
|
501
|
+
```tsx
|
|
502
|
+
const brandTheme: TourTheme = {
|
|
503
|
+
primaryColor: '#your-brand-color',
|
|
504
|
+
};
|
|
505
|
+
// Other values use defaults
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### React Native
|
|
509
|
+
|
|
510
|
+
Theming works the same way in React Native:
|
|
511
|
+
|
|
512
|
+
```tsx
|
|
513
|
+
import {TourProvider, type TourTheme} from 'react-tour-kit/react-native';
|
|
514
|
+
|
|
515
|
+
const theme: TourTheme = {
|
|
516
|
+
primaryColor: '#10b981',
|
|
517
|
+
overlayColor: 'rgba(0, 0, 0, 0.7)',
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
function App() {
|
|
521
|
+
return (
|
|
522
|
+
<TourProvider theme={theme}>
|
|
523
|
+
<YourApp />
|
|
524
|
+
</TourProvider>
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## Advanced Usage
|
|
532
|
+
|
|
533
|
+
### Async Step Actions
|
|
534
|
+
|
|
535
|
+
Run code before a step is shown. Useful for opening dialogs, switching tabs, navigating, etc.
|
|
536
|
+
|
|
537
|
+
```tsx
|
|
538
|
+
const steps: TourStep[] = [
|
|
539
|
+
{
|
|
540
|
+
target: '[data-tour="settings-tab"]',
|
|
541
|
+
title: 'Settings Tab',
|
|
542
|
+
content: 'Click here to access settings.',
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
target: '[data-tour="notifications-panel"]',
|
|
546
|
+
title: 'Notifications',
|
|
547
|
+
content: 'Configure your notification preferences here.',
|
|
548
|
+
onBeforeStep: async () => {
|
|
549
|
+
// Open the settings tab before showing this step
|
|
550
|
+
document.querySelector('[data-tour="settings-tab"]')?.click();
|
|
551
|
+
// Wait for the panel to appear
|
|
552
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
];
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Custom Tooltip Component
|
|
559
|
+
|
|
560
|
+
Create your own tooltip with your design system.
|
|
561
|
+
|
|
562
|
+
**Web:**
|
|
563
|
+
|
|
564
|
+
```tsx
|
|
565
|
+
import {forwardRef} from 'react';
|
|
566
|
+
import {TourProvider, type TourTooltipProps} from 'react-tour-kit/react';
|
|
567
|
+
|
|
568
|
+
const CustomTooltip = forwardRef<HTMLDivElement, TourTooltipProps>(
|
|
569
|
+
({title, content, currentStep, totalSteps, position, isPositioned, onClose, onNext, onPrev}, ref) => (
|
|
570
|
+
<div
|
|
571
|
+
ref={ref}
|
|
572
|
+
className="custom-tooltip"
|
|
573
|
+
style={{
|
|
574
|
+
position: 'fixed',
|
|
575
|
+
top: position.top,
|
|
576
|
+
left: position.left,
|
|
577
|
+
opacity: isPositioned ? 1 : 0,
|
|
578
|
+
}}
|
|
579
|
+
>
|
|
580
|
+
<h3>{title}</h3>
|
|
581
|
+
<p>{content}</p>
|
|
582
|
+
<div className="tooltip-footer">
|
|
583
|
+
<span>
|
|
584
|
+
{currentStep + 1} of {totalSteps}
|
|
585
|
+
</span>
|
|
586
|
+
<button onClick={onPrev} disabled={currentStep === 0}>
|
|
587
|
+
Back
|
|
588
|
+
</button>
|
|
589
|
+
<button onClick={onNext}>{currentStep === totalSteps - 1 ? 'Finish' : 'Next'}</button>
|
|
590
|
+
<button onClick={onClose}>×</button>
|
|
591
|
+
</div>
|
|
592
|
+
</div>
|
|
593
|
+
),
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
function App() {
|
|
597
|
+
return (
|
|
598
|
+
<TourProvider TooltipComponent={CustomTooltip}>
|
|
599
|
+
<YourApp />
|
|
600
|
+
</TourProvider>
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**React Native:**
|
|
606
|
+
|
|
607
|
+
```tsx
|
|
608
|
+
import {forwardRef} from 'react';
|
|
609
|
+
import {View, Text, TouchableOpacity} from 'react-native';
|
|
610
|
+
import {TourProvider, type TourTooltipProps} from 'react-tour-kit/react-native';
|
|
611
|
+
|
|
612
|
+
const CustomTooltip = forwardRef<View, TourTooltipProps>(
|
|
613
|
+
({title, content, position, isPositioned, onNext, onClose}, ref) => (
|
|
614
|
+
<View
|
|
615
|
+
ref={ref}
|
|
616
|
+
style={{
|
|
617
|
+
position: 'absolute',
|
|
618
|
+
top: position.top,
|
|
619
|
+
left: position.left,
|
|
620
|
+
opacity: isPositioned ? 1 : 0,
|
|
621
|
+
backgroundColor: '#fff',
|
|
622
|
+
padding: 16,
|
|
623
|
+
borderRadius: 8,
|
|
624
|
+
}}
|
|
625
|
+
>
|
|
626
|
+
<Text style={{fontWeight: 'bold'}}>{title}</Text>
|
|
627
|
+
<Text>{content}</Text>
|
|
628
|
+
<TouchableOpacity onPress={onNext}>
|
|
629
|
+
<Text>Next</Text>
|
|
630
|
+
</TouchableOpacity>
|
|
631
|
+
</View>
|
|
632
|
+
),
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
function App() {
|
|
636
|
+
return (
|
|
637
|
+
<TourProvider TooltipComponent={CustomTooltip}>
|
|
638
|
+
<YourApp />
|
|
639
|
+
</TourProvider>
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### Custom Overlay Component
|
|
645
|
+
|
|
646
|
+
Customize the backdrop overlay.
|
|
647
|
+
|
|
648
|
+
**Web:**
|
|
649
|
+
|
|
650
|
+
```tsx
|
|
651
|
+
import {TourProvider, type TourOverlayProps} from 'react-tour-kit/react';
|
|
652
|
+
|
|
653
|
+
function CustomOverlay({highlightRect, onClose}: TourOverlayProps) {
|
|
654
|
+
return (
|
|
655
|
+
<div className="custom-overlay" onClick={onClose}>
|
|
656
|
+
<div
|
|
657
|
+
className="highlight-border"
|
|
658
|
+
style={{
|
|
659
|
+
position: 'absolute',
|
|
660
|
+
top: highlightRect.top - 4,
|
|
661
|
+
left: highlightRect.left - 4,
|
|
662
|
+
width: highlightRect.width + 8,
|
|
663
|
+
height: highlightRect.height + 8,
|
|
664
|
+
border: '2px solid #your-brand-color',
|
|
665
|
+
borderRadius: '8px',
|
|
666
|
+
}}
|
|
667
|
+
/>
|
|
668
|
+
</div>
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function App() {
|
|
673
|
+
return (
|
|
674
|
+
<TourProvider OverlayComponent={CustomOverlay}>
|
|
675
|
+
<YourApp />
|
|
676
|
+
</TourProvider>
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
**React Native:**
|
|
682
|
+
|
|
683
|
+
```tsx
|
|
684
|
+
import {View, TouchableWithoutFeedback, StyleSheet} from 'react-native';
|
|
685
|
+
import {TourProvider, type TourOverlayProps} from 'react-tour-kit/react-native';
|
|
686
|
+
|
|
687
|
+
function CustomOverlay({highlightRect, onClose}: TourOverlayProps) {
|
|
688
|
+
return (
|
|
689
|
+
<TouchableWithoutFeedback onPress={onClose}>
|
|
690
|
+
<View style={StyleSheet.absoluteFill}>
|
|
691
|
+
{/* Your custom overlay with cutout */}
|
|
692
|
+
<View
|
|
693
|
+
style={{
|
|
694
|
+
position: 'absolute',
|
|
695
|
+
top: highlightRect.top - 4,
|
|
696
|
+
left: highlightRect.left - 4,
|
|
697
|
+
width: highlightRect.width + 8,
|
|
698
|
+
height: highlightRect.height + 8,
|
|
699
|
+
borderWidth: 2,
|
|
700
|
+
borderColor: '#your-brand-color',
|
|
701
|
+
borderRadius: 8,
|
|
702
|
+
}}
|
|
703
|
+
/>
|
|
704
|
+
</View>
|
|
705
|
+
</TouchableWithoutFeedback>
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function App() {
|
|
710
|
+
return (
|
|
711
|
+
<TourProvider OverlayComponent={CustomOverlay}>
|
|
712
|
+
<YourApp />
|
|
713
|
+
</TourProvider>
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### Persisting Tour Completion
|
|
719
|
+
|
|
720
|
+
Track which tours users have completed:
|
|
721
|
+
|
|
722
|
+
```tsx
|
|
723
|
+
import {TourProvider} from 'react-tour-kit/react';
|
|
724
|
+
|
|
725
|
+
function App() {
|
|
726
|
+
const handleTourEnd = async (tourId: string | null) => {
|
|
727
|
+
if (tourId) {
|
|
728
|
+
// Save to your backend
|
|
729
|
+
await fetch('/api/users/me/completed-tours/' + tourId, {
|
|
730
|
+
method: 'POST',
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
return (
|
|
736
|
+
<TourProvider onTourEnd={handleTourEnd}>
|
|
737
|
+
<YourApp />
|
|
738
|
+
</TourProvider>
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Conditional Tour Start
|
|
744
|
+
|
|
745
|
+
Start tours based on user state:
|
|
746
|
+
|
|
747
|
+
```tsx
|
|
748
|
+
function TourTrigger({tourId, steps, autoStart = true}) {
|
|
749
|
+
const {startTour} = useTour();
|
|
750
|
+
const {user} = useUser(); // Your auth hook
|
|
751
|
+
|
|
752
|
+
useEffect(() => {
|
|
753
|
+
if (autoStart && user && !user.completedTours?.[tourId]) {
|
|
754
|
+
// Auto-start if user hasn't completed this tour
|
|
755
|
+
setTimeout(() => startTour(steps, tourId), 500);
|
|
756
|
+
}
|
|
757
|
+
}, [user, tourId, autoStart]);
|
|
758
|
+
|
|
759
|
+
return <button onClick={() => startTour(steps, tourId)}>Start Tour</button>;
|
|
760
|
+
}
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
## Styling
|
|
764
|
+
|
|
765
|
+
The default components use inline styles for zero-dependency styling. You have three options for customization:
|
|
766
|
+
|
|
767
|
+
1. **Theming**: Use the `theme` prop to customize colors (see [Theming](#theming) section)
|
|
768
|
+
2. **Custom Components**: Provide your own `TooltipComponent` and `OverlayComponent` for full control
|
|
769
|
+
3. **CSS Overrides** (Web only): Override styles via CSS selectors
|
|
770
|
+
|
|
771
|
+
## Platform Support
|
|
772
|
+
|
|
773
|
+
**Web:**
|
|
774
|
+
|
|
775
|
+
- Chrome, Firefox, Safari, Edge (latest versions)
|
|
776
|
+
|
|
777
|
+
**React Native:**
|
|
778
|
+
|
|
779
|
+
- iOS 13+
|
|
780
|
+
- Android 5.0+ (API 21+)
|
|
781
|
+
- React Native 0.70+
|
|
782
|
+
|
|
783
|
+
Requires React 18+.
|
|
784
|
+
|
|
785
|
+
## TypeScript
|
|
786
|
+
|
|
787
|
+
Full TypeScript support is included. Import types as needed:
|
|
788
|
+
|
|
789
|
+
```tsx
|
|
790
|
+
import type {
|
|
791
|
+
TourStep,
|
|
792
|
+
TourContextType,
|
|
793
|
+
TourProviderProps,
|
|
794
|
+
TourTooltipProps,
|
|
795
|
+
TourOverlayProps,
|
|
796
|
+
TourTheme,
|
|
797
|
+
} from 'react-tour-kit/react';
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
## Utilities
|
|
801
|
+
|
|
802
|
+
The package exports utility functions for custom implementations:
|
|
803
|
+
|
|
804
|
+
```tsx
|
|
805
|
+
import {
|
|
806
|
+
calculateTooltipPosition, // Calculate optimal tooltip position
|
|
807
|
+
findVisibleElement, // Find first visible element matching selector
|
|
808
|
+
} from 'react-tour-kit/react';
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
## Development
|
|
812
|
+
|
|
813
|
+
```bash
|
|
814
|
+
# Install dependencies
|
|
815
|
+
npm install
|
|
816
|
+
|
|
817
|
+
# Build
|
|
818
|
+
npm run build
|
|
819
|
+
|
|
820
|
+
# Watch mode
|
|
821
|
+
npm run start:dev
|
|
822
|
+
|
|
823
|
+
# Lint
|
|
824
|
+
npm run lint
|
|
825
|
+
|
|
826
|
+
# Format
|
|
827
|
+
npm run format
|
|
828
|
+
|
|
829
|
+
# Run web demo
|
|
830
|
+
cd demos/react && npm install && npm run dev
|
|
831
|
+
|
|
832
|
+
# Run native demo
|
|
833
|
+
cd demos/react-native && npm install && npm run ios
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
## License
|
|
837
|
+
|
|
838
|
+
MIT
|