rn-smart-tour 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/README.md +107 -0
- package/dist/DapContext.d.ts +2 -0
- package/dist/DapContext.js +5 -0
- package/dist/DapOverlay.d.ts +2 -0
- package/dist/DapOverlay.js +145 -0
- package/dist/DapProvider.d.ts +8 -0
- package/dist/DapProvider.js +154 -0
- package/dist/DapTarget.d.ts +8 -0
- package/dist/DapTarget.js +70 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +2 -0
- package/dist/useDap.d.ts +1 -0
- package/dist/useDap.js +13 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# react-native-app-tour
|
|
2
|
+
|
|
3
|
+
An enterprise-grade Digital Adoption Platform (DAP) package for React Native. Easily add product tours, guided walkthroughs, and onboarding overlays directly into your app without intrusive code changes.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- **Auto-Start Engine**: Automatically triggers tours the moment specific features mount on the screen.
|
|
7
|
+
- **Seen State Caching**: Provide any storage adapter (`AsyncStorage`, `MMKV`) to ensure users only see tours once.
|
|
8
|
+
- **Zero Config Measurement**: Wraps your components and seamlessly uses Native `measureInWindow` API.
|
|
9
|
+
- **Smart Overlays**: Creates highlighted holes in backdrops over completely custom UIs.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install rn-smart-tour
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start Guide
|
|
18
|
+
|
|
19
|
+
### 1. Wrap your App with the Provider
|
|
20
|
+
|
|
21
|
+
Create your `tours` configuration and mount `<DapProvider>` at the root of your application (above your Navigators):
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import React from 'react';
|
|
25
|
+
import { DapProvider } from 'rn-smart-tour';
|
|
26
|
+
import AsyncStorage from '@react-native-async-storage/async-storage'; // Optional, but recommended
|
|
27
|
+
|
|
28
|
+
const TOURS = {
|
|
29
|
+
'onboarding-flow': {
|
|
30
|
+
id: 'onboarding-flow',
|
|
31
|
+
autoStart: true, // Will automatically start when the first target renders
|
|
32
|
+
steps: [
|
|
33
|
+
{
|
|
34
|
+
targetId: 'save-playlist-btn',
|
|
35
|
+
title: 'Save your work',
|
|
36
|
+
description: 'Tap this heart to save your favorite songs anytime.',
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Map any storage library you prefer so it remembers if users have seen the tour!
|
|
43
|
+
const storageAdapter = {
|
|
44
|
+
getItem: async (key) => await AsyncStorage.getItem(key),
|
|
45
|
+
setItem: async (key, value) => { await AsyncStorage.setItem(key, value); }
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default function App() {
|
|
49
|
+
return (
|
|
50
|
+
<DapProvider tours={TOURS} storageAdapter={storageAdapter}>
|
|
51
|
+
<MainAppScreen />
|
|
52
|
+
</DapProvider>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Wrap the targets you want to highlight
|
|
58
|
+
|
|
59
|
+
Go to any deep-nested screen and wrap buttons or views with `<DapTarget name="targetId">`:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { DapTarget } from 'rn-smart-tour';
|
|
63
|
+
|
|
64
|
+
const MainAppScreen = () => {
|
|
65
|
+
return (
|
|
66
|
+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
|
67
|
+
<DapTarget name="save-playlist-btn">
|
|
68
|
+
<TouchableOpacity style={{ backgroundColor: 'red', padding: 15 }}>
|
|
69
|
+
<Text>❤️ Add to Favorites</Text>
|
|
70
|
+
</TouchableOpacity>
|
|
71
|
+
</DapTarget>
|
|
72
|
+
</View>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Trigger tours Manually (Optional)
|
|
78
|
+
|
|
79
|
+
If a tour lacks `autoStart: true` or you want to trigger it from a "Help" menu, use the `useDap` hook:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { useDap } from 'rn-smart-tour';
|
|
83
|
+
|
|
84
|
+
const HelpMenu = () => {
|
|
85
|
+
const { startTour } = useDap();
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Button title="Need help?" onPress={() => startTour('onboarding-flow')} />
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Advanced Properties & Types
|
|
94
|
+
|
|
95
|
+
### Tour Object
|
|
96
|
+
| Property | Type | Description |
|
|
97
|
+
|-----------|------|-------------|
|
|
98
|
+
| `id` | `string` | Unique identifier. Needed for cache tracking. |
|
|
99
|
+
| `autoStart` | `boolean` | If true, automatically renders when the first step's target mounts on the screen. |
|
|
100
|
+
| `steps` | `TourStep[]` | The sequence of highlighted elements and tooltips. |
|
|
101
|
+
|
|
102
|
+
### TourStep Object
|
|
103
|
+
| Property | Type | Description |
|
|
104
|
+
|-----------|------|-------------|
|
|
105
|
+
| `targetId` | `string` | Must directly match the `name=""` prop passed to `<DapTarget>`. |
|
|
106
|
+
| `title` | `string` | Large text inside the tooltip. |
|
|
107
|
+
| `description` | `string` | Context explanation inside the tooltip. |
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DapOverlay = void 0;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_1 = require("react-native");
|
|
39
|
+
const DapContext_1 = require("./DapContext");
|
|
40
|
+
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = react_native_1.Dimensions.get('window');
|
|
41
|
+
const DapOverlay = () => {
|
|
42
|
+
const context = (0, react_1.useContext)(DapContext_1.DapContext);
|
|
43
|
+
if (!context || !context.activeTour)
|
|
44
|
+
return null;
|
|
45
|
+
const { activeTour, currentStepIndex, targets, nextStep, stopTour } = context;
|
|
46
|
+
const currentStep = activeTour.steps[currentStepIndex];
|
|
47
|
+
if (!currentStep)
|
|
48
|
+
return null;
|
|
49
|
+
const targetId = currentStep.targetId;
|
|
50
|
+
const measurement = targets[targetId];
|
|
51
|
+
// If the target hasn't been measured yet, we can't show the overlay correctly.
|
|
52
|
+
if (!measurement) {
|
|
53
|
+
return (<react_native_1.Modal transparent visible animationType="fade">
|
|
54
|
+
<react_native_1.View style={styles.fullscreen}>
|
|
55
|
+
<react_native_1.Text style={{ color: 'white', marginTop: 100, textAlign: 'center' }}>
|
|
56
|
+
Waiting for target "{targetId}" to mount...
|
|
57
|
+
</react_native_1.Text>
|
|
58
|
+
</react_native_1.View>
|
|
59
|
+
</react_native_1.Modal>);
|
|
60
|
+
}
|
|
61
|
+
const { x, y, width, height } = measurement;
|
|
62
|
+
return (<react_native_1.Modal transparent visible animationType="fade">
|
|
63
|
+
<react_native_1.View style={styles.fullscreen}>
|
|
64
|
+
{/* Top backdrop */}
|
|
65
|
+
<react_native_1.View style={[styles.backdrop, { top: 0, left: 0, right: 0, height: Math.max(0, y) }]}/>
|
|
66
|
+
{/* Bottom backdrop */}
|
|
67
|
+
<react_native_1.View style={[styles.backdrop, { top: y + height, left: 0, right: 0, bottom: 0 }]}/>
|
|
68
|
+
{/* Left backdrop */}
|
|
69
|
+
<react_native_1.View style={[styles.backdrop, { top: y, left: 0, width: Math.max(0, x), height }]}/>
|
|
70
|
+
{/* Right backdrop */}
|
|
71
|
+
<react_native_1.View style={[styles.backdrop, { top: y, left: x + width, right: 0, height }]}/>
|
|
72
|
+
|
|
73
|
+
{/* The Tooltip Card */}
|
|
74
|
+
<react_native_1.View style={[styles.tooltipContainer, { top: y + height + 10, left: Math.max(10, x - 20) }]}>
|
|
75
|
+
<react_native_1.Text style={styles.title}>{currentStep.title}</react_native_1.Text>
|
|
76
|
+
<react_native_1.Text style={styles.description}>{currentStep.description}</react_native_1.Text>
|
|
77
|
+
<react_native_1.View style={styles.actions}>
|
|
78
|
+
<react_native_1.TouchableOpacity onPress={stopTour} style={styles.actionBtn}>
|
|
79
|
+
<react_native_1.Text style={styles.actionText}>Skip</react_native_1.Text>
|
|
80
|
+
</react_native_1.TouchableOpacity>
|
|
81
|
+
<react_native_1.TouchableOpacity onPress={nextStep} style={[styles.actionBtn, styles.primaryBtn]}>
|
|
82
|
+
<react_native_1.Text style={[styles.actionText, styles.primaryText]}>
|
|
83
|
+
{currentStepIndex === activeTour.steps.length - 1 ? 'Finish' : 'Next'}
|
|
84
|
+
</react_native_1.Text>
|
|
85
|
+
</react_native_1.TouchableOpacity>
|
|
86
|
+
</react_native_1.View>
|
|
87
|
+
</react_native_1.View>
|
|
88
|
+
</react_native_1.View>
|
|
89
|
+
</react_native_1.Modal>);
|
|
90
|
+
};
|
|
91
|
+
exports.DapOverlay = DapOverlay;
|
|
92
|
+
const styles = react_native_1.StyleSheet.create({
|
|
93
|
+
fullscreen: {
|
|
94
|
+
...react_native_1.StyleSheet.absoluteFillObject,
|
|
95
|
+
},
|
|
96
|
+
backdrop: {
|
|
97
|
+
position: 'absolute',
|
|
98
|
+
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
99
|
+
},
|
|
100
|
+
tooltipContainer: {
|
|
101
|
+
position: 'absolute',
|
|
102
|
+
backgroundColor: 'white',
|
|
103
|
+
padding: 16,
|
|
104
|
+
borderRadius: 8,
|
|
105
|
+
width: 250,
|
|
106
|
+
shadowColor: '#000',
|
|
107
|
+
shadowOffset: { width: 0, height: 2 },
|
|
108
|
+
shadowOpacity: 0.25,
|
|
109
|
+
shadowRadius: 3.84,
|
|
110
|
+
elevation: 5,
|
|
111
|
+
},
|
|
112
|
+
title: {
|
|
113
|
+
fontSize: 16,
|
|
114
|
+
fontWeight: 'bold',
|
|
115
|
+
marginBottom: 8,
|
|
116
|
+
color: '#333',
|
|
117
|
+
},
|
|
118
|
+
description: {
|
|
119
|
+
fontSize: 14,
|
|
120
|
+
color: '#666',
|
|
121
|
+
marginBottom: 16,
|
|
122
|
+
},
|
|
123
|
+
actions: {
|
|
124
|
+
flexDirection: 'row',
|
|
125
|
+
justifyContent: 'flex-end',
|
|
126
|
+
marginTop: 10,
|
|
127
|
+
},
|
|
128
|
+
actionBtn: {
|
|
129
|
+
paddingHorizontal: 15,
|
|
130
|
+
paddingVertical: 8,
|
|
131
|
+
marginLeft: 10,
|
|
132
|
+
borderRadius: 4,
|
|
133
|
+
},
|
|
134
|
+
primaryBtn: {
|
|
135
|
+
backgroundColor: '#007AFF',
|
|
136
|
+
},
|
|
137
|
+
actionText: {
|
|
138
|
+
color: '#666',
|
|
139
|
+
fontWeight: '600',
|
|
140
|
+
},
|
|
141
|
+
primaryText: {
|
|
142
|
+
color: 'white',
|
|
143
|
+
fontWeight: '600',
|
|
144
|
+
},
|
|
145
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tour, StorageAdapter } from './types';
|
|
3
|
+
export interface DapProviderProps {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
tours: Record<string, Tour>;
|
|
6
|
+
storageAdapter?: StorageAdapter;
|
|
7
|
+
}
|
|
8
|
+
export declare const DapProvider: React.FC<DapProviderProps>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DapProvider = void 0;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const DapContext_1 = require("./DapContext");
|
|
39
|
+
const DapOverlay_1 = require("./DapOverlay");
|
|
40
|
+
const STORAGE_KEY = '@rn-dap:seen_tours';
|
|
41
|
+
const DapProvider = ({ children, tours, storageAdapter }) => {
|
|
42
|
+
const [targets, setTargets] = (0, react_1.useState)({});
|
|
43
|
+
const [activeTourId, setActiveTourId] = (0, react_1.useState)(null);
|
|
44
|
+
const [currentStepIndex, setCurrentStepIndex] = (0, react_1.useState)(0);
|
|
45
|
+
const [seenTours, setSeenTours] = (0, react_1.useState)({});
|
|
46
|
+
const [isStorageLoaded, setIsStorageLoaded] = (0, react_1.useState)(!storageAdapter);
|
|
47
|
+
// Load seen tours on mount if a storage adapter is provided
|
|
48
|
+
(0, react_1.useEffect)(() => {
|
|
49
|
+
const loadStorage = async () => {
|
|
50
|
+
if (storageAdapter) {
|
|
51
|
+
try {
|
|
52
|
+
const stored = await storageAdapter.getItem(STORAGE_KEY);
|
|
53
|
+
if (stored) {
|
|
54
|
+
setSeenTours(JSON.parse(stored));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
console.error('[rn-dap] failed to load storage', e);
|
|
59
|
+
}
|
|
60
|
+
setIsStorageLoaded(true);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
loadStorage();
|
|
64
|
+
}, [storageAdapter]);
|
|
65
|
+
const saveSeenTour = async (tourId) => {
|
|
66
|
+
const nextSeen = { ...seenTours, [tourId]: true };
|
|
67
|
+
setSeenTours(nextSeen);
|
|
68
|
+
if (storageAdapter) {
|
|
69
|
+
try {
|
|
70
|
+
await storageAdapter.setItem(STORAGE_KEY, JSON.stringify(nextSeen));
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
console.error('[rn-dap] failed to save storage', e);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const registerTarget = (0, react_1.useCallback)((id, measurement) => {
|
|
78
|
+
setTargets(prev => ({ ...prev, [id]: measurement }));
|
|
79
|
+
}, []);
|
|
80
|
+
const unregisterTarget = (0, react_1.useCallback)((id) => {
|
|
81
|
+
setTargets(prev => {
|
|
82
|
+
const next = { ...prev };
|
|
83
|
+
delete next[id];
|
|
84
|
+
return next;
|
|
85
|
+
});
|
|
86
|
+
}, []);
|
|
87
|
+
const startTour = (0, react_1.useCallback)((tourId) => {
|
|
88
|
+
if (tours[tourId] && !seenTours[tourId]) {
|
|
89
|
+
setActiveTourId(tourId);
|
|
90
|
+
setCurrentStepIndex(0);
|
|
91
|
+
}
|
|
92
|
+
else if (!tours[tourId]) {
|
|
93
|
+
console.warn(`[rn-dap] Tour with id ${tourId} not found.`);
|
|
94
|
+
}
|
|
95
|
+
}, [tours, seenTours]);
|
|
96
|
+
const stopTour = (0, react_1.useCallback)((markAsSeen = true) => {
|
|
97
|
+
if (activeTourId && markAsSeen) {
|
|
98
|
+
saveSeenTour(activeTourId);
|
|
99
|
+
}
|
|
100
|
+
setActiveTourId(null);
|
|
101
|
+
setCurrentStepIndex(0);
|
|
102
|
+
}, [activeTourId, seenTours]);
|
|
103
|
+
const nextStep = (0, react_1.useCallback)(() => {
|
|
104
|
+
if (activeTourId && tours[activeTourId]) {
|
|
105
|
+
const tour = tours[activeTourId];
|
|
106
|
+
if (currentStepIndex < tour.steps.length - 1) {
|
|
107
|
+
setCurrentStepIndex(prev => prev + 1);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Finished the last step
|
|
111
|
+
stopTour(true);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}, [activeTourId, currentStepIndex, tours, stopTour]);
|
|
115
|
+
const prevStep = (0, react_1.useCallback)(() => {
|
|
116
|
+
if (currentStepIndex > 0) {
|
|
117
|
+
setCurrentStepIndex(prev => prev - 1);
|
|
118
|
+
}
|
|
119
|
+
}, [currentStepIndex]);
|
|
120
|
+
// Auto-Start Engine
|
|
121
|
+
(0, react_1.useEffect)(() => {
|
|
122
|
+
// Wait until storage is loaded, and ensure no tour is currently running
|
|
123
|
+
if (!isStorageLoaded || activeTourId)
|
|
124
|
+
return;
|
|
125
|
+
for (const tourId of Object.keys(tours)) {
|
|
126
|
+
const tour = tours[tourId];
|
|
127
|
+
if (tour.autoStart && !seenTours[tourId] && tour.steps.length > 0) {
|
|
128
|
+
const firstTargetId = tour.steps[0].targetId;
|
|
129
|
+
// If the first target of an unread, auto-starting tour is mounted
|
|
130
|
+
if (targets[firstTargetId]) {
|
|
131
|
+
startTour(tourId);
|
|
132
|
+
break; // Start only one auto-tour at a time
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}, [targets, tours, seenTours, isStorageLoaded, activeTourId, startTour]);
|
|
137
|
+
const activeTour = activeTourId ? tours[activeTourId] : null;
|
|
138
|
+
return (<DapContext_1.DapContext.Provider value={{
|
|
139
|
+
registerTarget,
|
|
140
|
+
unregisterTarget,
|
|
141
|
+
startTour,
|
|
142
|
+
stopTour,
|
|
143
|
+
nextStep,
|
|
144
|
+
prevStep,
|
|
145
|
+
activeTour,
|
|
146
|
+
currentStepIndex,
|
|
147
|
+
targets,
|
|
148
|
+
seenTours
|
|
149
|
+
}}>
|
|
150
|
+
{children}
|
|
151
|
+
<DapOverlay_1.DapOverlay />
|
|
152
|
+
</DapContext_1.DapContext.Provider>);
|
|
153
|
+
};
|
|
154
|
+
exports.DapProvider = DapProvider;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DapTarget = void 0;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_1 = require("react-native");
|
|
39
|
+
const DapContext_1 = require("./DapContext");
|
|
40
|
+
const DapTarget = ({ name, children, ...props }) => {
|
|
41
|
+
const viewRef = (0, react_1.useRef)(null);
|
|
42
|
+
const context = (0, react_1.useContext)(DapContext_1.DapContext);
|
|
43
|
+
const measureAndRegister = () => {
|
|
44
|
+
if (viewRef.current && context) {
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
viewRef.current?.measureInWindow((x, y, width, height) => {
|
|
47
|
+
if (width > 0 && height > 0) {
|
|
48
|
+
context.registerTarget(name, { x, y, width, height });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}, 100);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const handleLayout = (e) => {
|
|
55
|
+
measureAndRegister();
|
|
56
|
+
if (props.onLayout) {
|
|
57
|
+
props.onLayout(e);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
(0, react_1.useEffect)(() => {
|
|
61
|
+
return () => {
|
|
62
|
+
context?.unregisterTarget(name);
|
|
63
|
+
};
|
|
64
|
+
}, [name, context]);
|
|
65
|
+
// collapsable={false} is vital for Android, otherwise it gets optimized away and measure fails
|
|
66
|
+
return (<react_native_1.View ref={viewRef} onLayout={handleLayout} collapsable={false} {...props}>
|
|
67
|
+
{children}
|
|
68
|
+
</react_native_1.View>);
|
|
69
|
+
};
|
|
70
|
+
exports.DapTarget = DapTarget;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./DapProvider"), exports);
|
|
18
|
+
__exportStar(require("./DapTarget"), exports);
|
|
19
|
+
__exportStar(require("./useDap"), exports);
|
|
20
|
+
__exportStar(require("./types"), exports);
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface TargetMeasurement {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface TourStep {
|
|
8
|
+
targetId: string;
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
position?: 'top' | 'bottom' | 'left' | 'right';
|
|
12
|
+
}
|
|
13
|
+
export interface Tour {
|
|
14
|
+
id: string;
|
|
15
|
+
steps: TourStep[];
|
|
16
|
+
autoStart?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface StorageAdapter {
|
|
19
|
+
getItem: (key: string) => Promise<string | null> | string | null;
|
|
20
|
+
setItem: (key: string, value: string) => Promise<void> | void;
|
|
21
|
+
}
|
|
22
|
+
export interface DapContextType {
|
|
23
|
+
registerTarget: (id: string, measurement: TargetMeasurement) => void;
|
|
24
|
+
unregisterTarget: (id: string) => void;
|
|
25
|
+
startTour: (tourId: string) => void;
|
|
26
|
+
nextStep: () => void;
|
|
27
|
+
prevStep: () => void;
|
|
28
|
+
stopTour: () => void;
|
|
29
|
+
activeTour: Tour | null;
|
|
30
|
+
currentStepIndex: number;
|
|
31
|
+
targets: Record<string, TargetMeasurement>;
|
|
32
|
+
seenTours: Record<string, boolean>;
|
|
33
|
+
}
|
package/dist/types.js
ADDED
package/dist/useDap.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useDap: () => import("./types").DapContextType;
|
package/dist/useDap.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useDap = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const DapContext_1 = require("./DapContext");
|
|
6
|
+
const useDap = () => {
|
|
7
|
+
const context = (0, react_1.useContext)(DapContext_1.DapContext);
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new Error('useDap must be used within a DapProvider');
|
|
10
|
+
}
|
|
11
|
+
return context;
|
|
12
|
+
};
|
|
13
|
+
exports.useDap = useDap;
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rn-smart-tour",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Enterprise-grade Digital Adoption Platform (DAP) package for React Native. Provides guided walkthroughs, tooltips, and app tours.",
|
|
5
|
+
"author": "Vishwas Gaur",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"react-native",
|
|
14
|
+
"tour",
|
|
15
|
+
"walkthrough",
|
|
16
|
+
"onboarding",
|
|
17
|
+
"dap",
|
|
18
|
+
"guide",
|
|
19
|
+
"product-tour"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": "*",
|
|
26
|
+
"react-native": "*"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/react": "^18.3.28",
|
|
30
|
+
"@types/react-native": "^0.72.8",
|
|
31
|
+
"react": "^19.2.4",
|
|
32
|
+
"react-native": "^0.84.1",
|
|
33
|
+
"typescript": "^5.9.3"
|
|
34
|
+
}
|
|
35
|
+
}
|