react-native-lumen 1.1.1 → 1.1.2
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 +20 -20
- package/README.md +74 -718
- package/lib/module/components/TourOverlay.js +1 -1
- package/lib/module/components/TourProvider.js +7 -7
- package/lib/module/components/TourTooltip.js +1 -1
- package/lib/module/components/TourZone.js +1 -1
- package/lib/module/constants/animations.js +16 -16
- package/lib/module/constants/defaults.js +5 -5
- package/lib/module/hooks/useTourScrollView.js +22 -22
- package/lib/module/utils/storage.js +26 -26
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 thedev204
|
|
4
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
-
in the Software without restriction, including without limitation the rights
|
|
7
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
furnished to do so, subject to the following conditions:
|
|
10
|
-
|
|
11
|
-
The above copyright notice and this permission notice shall be included in all
|
|
12
|
-
copies or substantial portions of the Software.
|
|
13
|
-
|
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 thedev204
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<div align="center">
|
|
3
|
+
<a href="https://www.npmjs.com/package/react-native-lumen">
|
|
4
|
+
<img src="https://img.shields.io/npm/v/react-native-lumen.svg" alt="npm version" />
|
|
5
|
+
</a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/react-native-lumen">
|
|
7
|
+
<img src="https://img.shields.io/npm/dm/react-native-lumen.svg?colorB=007ec6" alt="npm downloads" />
|
|
8
|
+
</a>
|
|
9
|
+
<a href="https://github.com/thedev204/react-native-lumen/blob/main/LICENSE">
|
|
10
|
+
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="license" />
|
|
11
|
+
</a>
|
|
12
|
+
</div>
|
|
13
|
+
<div align="center">
|
|
14
|
+
<a href="https://github.com/expo/expo">
|
|
15
|
+
<img src="https://img.shields.io/badge/Runs%20with%20Expo-000.svg?style=flat&logo=EXPO&labelColor=ffffff&logoColor=000" alt="runs with expo" />
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://github.com/thedev204/react-native-lumen/issues">
|
|
18
|
+
<img src="https://img.shields.io/github/issues/thedev204/react-native-lumen.svg" alt="github issues" />
|
|
19
|
+
</a>
|
|
20
|
+
<a href="https://semver.org/spec/v2.0.0.html">
|
|
21
|
+
<img src="https://img.shields.io/badge/semver-2.0.0-e10079.svg" alt="semver" />
|
|
22
|
+
</a>
|
|
23
|
+
</div>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<br/>
|
|
2
27
|
|
|
3
|
-
|
|
28
|
+
<h1 align="center">React Native Lumen 💡</h1>
|
|
4
29
|
|
|
5
|
-
>
|
|
30
|
+
<p align="center">
|
|
31
|
+
A high-performance, fully customizable app tour library for React Native, powered by Reanimated 3.
|
|
32
|
+
</p>
|
|
6
33
|
|
|
7
|
-
|
|
34
|
+
<p align="center">
|
|
35
|
+
<img src="./assets/banner.png" alt="React Native Lumen Banner" />
|
|
36
|
+
</p>
|
|
8
37
|
|
|
9
38
|
## Demo
|
|
10
39
|
|
|
@@ -13,27 +42,16 @@
|
|
|
13
42
|
<img src="./assets/showcase2.gif" width="220" alt="App Tour Demo" />
|
|
14
43
|
</p>
|
|
15
44
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- ⚡ **High Performance**: Built with `react-native-reanimated` worklets for 60fps animations.
|
|
19
|
-
- 🎨 **Fully Customizable**: Custom Renderers for tooltips, customizable shapes, and backdrops.
|
|
20
|
-
- 🌟 **Glow Effects**: Beautiful, customizable glow effects around your highlighted elements.
|
|
21
|
-
- 📱 **Expo Compatible**: Works seamlessly with Expo and bare React Native projects.
|
|
22
|
-
- 🤸 **Smooth Transitions**: Fluid morphing animations between steps.
|
|
23
|
-
- ✨ **Animation Presets**: Ships with beautiful bouncy, gentle, and snappy spring presets.
|
|
24
|
-
- 📜 **Auto Scrolling**: Automatically scrolls to next steps.
|
|
25
|
-
- 👆 **Interaction Control**: Choose to block or allow interactions with the underlying app.
|
|
26
|
-
- 🔒 **Step Enforcement**: Gate step progression with `required` and `completed` props.
|
|
27
|
-
- 📱 **Multi-Screen Tours**: Seamlessly run tours across multiple screens/tabs with screen-grouped step ordering.
|
|
45
|
+
---
|
|
28
46
|
|
|
29
47
|
## Requirements
|
|
30
48
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
| Package | Version |
|
|
50
|
+
| :----------------------------- | :-------- |
|
|
51
|
+
| `react-native` | >= 0.70.0 |
|
|
52
|
+
| `react-native-reanimated` | >= 3.0.0 |
|
|
53
|
+
| `react-native-svg` | >= 12.0.0 |
|
|
54
|
+
| `react-native-gesture-handler` | >= 2.0.0 |
|
|
37
55
|
|
|
38
56
|
## Installation
|
|
39
57
|
|
|
@@ -41,730 +59,68 @@ This library relies on strict peer dependencies to ensure performance:
|
|
|
41
59
|
npm install react-native-lumen react-native-reanimated react-native-svg react-native-gesture-handler react-native-worklets
|
|
42
60
|
```
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
```sh
|
|
63
|
+
yarn add react-native-lumen react-native-reanimated react-native-svg react-native-gesture-handler react-native-worklets
|
|
64
|
+
```
|
|
47
65
|
|
|
48
|
-
|
|
66
|
+
## Quick Start
|
|
49
67
|
|
|
50
68
|
```tsx
|
|
51
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
TourProvider,
|
|
71
|
+
TourZone,
|
|
72
|
+
useTour,
|
|
73
|
+
SnappySpringConfig,
|
|
74
|
+
} from 'react-native-lumen';
|
|
52
75
|
|
|
76
|
+
// 1. Wrap your app
|
|
53
77
|
export default function App() {
|
|
54
78
|
return (
|
|
55
79
|
<TourProvider
|
|
56
|
-
stepsOrder={['
|
|
57
|
-
config={{
|
|
58
|
-
springConfig: SnappySpringConfig,
|
|
59
|
-
enableGlow: true, // Enable the glow effect globally
|
|
60
|
-
// See config details below
|
|
61
|
-
}}
|
|
80
|
+
stepsOrder={['feature-1', 'feature-2']}
|
|
81
|
+
config={{ springConfig: SnappySpringConfig, enableGlow: true }}
|
|
62
82
|
>
|
|
63
|
-
<
|
|
83
|
+
<YourApp />
|
|
64
84
|
</TourProvider>
|
|
65
85
|
);
|
|
66
86
|
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
2. **Highlight Elements with `TourZone`**:
|
|
70
|
-
|
|
71
|
-
```tsx
|
|
72
|
-
import { TourZone } from 'react-native-lumen';
|
|
73
87
|
|
|
88
|
+
// 2. Highlight elements
|
|
74
89
|
<TourZone
|
|
75
|
-
stepKey="
|
|
90
|
+
stepKey="feature-1"
|
|
76
91
|
name="My Feature"
|
|
77
|
-
description="This is
|
|
92
|
+
description="This is a cool feature."
|
|
78
93
|
order={1}
|
|
79
|
-
borderRadius={10}
|
|
80
94
|
>
|
|
81
95
|
<MyButton />
|
|
82
96
|
</TourZone>;
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
3. **Control the Tour**:
|
|
86
|
-
|
|
87
|
-
```tsx
|
|
88
|
-
import { useTour } from 'react-native-lumen';
|
|
89
|
-
|
|
90
|
-
const MyComponent = () => {
|
|
91
|
-
const { start } = useTour();
|
|
92
|
-
return <Button title="Start Tour" onPress={() => start()} />;
|
|
93
|
-
};
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## API Documentation
|
|
97
|
-
|
|
98
|
-
### `TourProvider`
|
|
99
|
-
|
|
100
|
-
The main context provider. Place this at the root of your application.
|
|
101
|
-
|
|
102
|
-
| Prop | Type | Default | Description |
|
|
103
|
-
| :---------------- | :------------------------------------- | :---------- | :---------------------------------------------------- |
|
|
104
|
-
| `children` | `React.ReactNode` | Required | Application content. |
|
|
105
|
-
| `stepsOrder` | `string[] \| Record<string, string[]>` | `undefined` | Step ordering. Flat array or screen-grouped object. |
|
|
106
|
-
| `backdropOpacity` | `number` | `0.5` | Opacity of the dark background overlay (0-1). |
|
|
107
|
-
| `config` | `TourConfig` | `undefined` | Global configuration options, including `enableGlow`. |
|
|
108
|
-
|
|
109
|
-
### `TourZone`
|
|
110
|
-
|
|
111
|
-
Wrapper component to register an element as a tour step. All styling props can also be passed via the `zoneStyle` object prop.
|
|
112
|
-
|
|
113
|
-
| Prop | Type | Default | Description |
|
|
114
|
-
| :------------------- | :------------------------------------- | :--------------- | :--------------------------------------------------- |
|
|
115
|
-
| `stepKey` | `string` | Required | Unique identifier for the step. |
|
|
116
|
-
| `name` | `string` | `undefined` | Title of the step. |
|
|
117
|
-
| `description` | `string` | Required | Description text shown in the tooltip. |
|
|
118
|
-
| `order` | `number` | `undefined` | Order of appearance (if `stepsOrder` not used). |
|
|
119
|
-
| `shape` | `'rounded-rect' \| 'circle' \| 'pill'` | `'rounded-rect'` | Shape of the zone cutout. |
|
|
120
|
-
| `borderRadius` | `number` | `10` | Border radius of the zone (for rounded-rect). |
|
|
121
|
-
| `clickable` | `boolean` | `false` | If `true`, the step remains interactive. |
|
|
122
|
-
| `preventInteraction` | `boolean` | `undefined` | Overrides global `preventInteraction` for this step. |
|
|
123
|
-
| `required` | `boolean` | `false` | If `true`, hides the skip button for this step. |
|
|
124
|
-
| `completed` | `boolean` | `undefined` | If `false`, disables the next button until `true`. |
|
|
125
|
-
| `style` | `ViewStyle` | `undefined` | Style for the wrapping container. |
|
|
126
|
-
| `zonePadding` | `number` | `0` | Uniform padding around the highlighted element. |
|
|
127
|
-
| `zonePaddingTop` | `number` | `undefined` | Top padding override. |
|
|
128
|
-
| `zonePaddingRight` | `number` | `undefined` | Right padding override. |
|
|
129
|
-
| `zonePaddingBottom` | `number` | `undefined` | Bottom padding override. |
|
|
130
|
-
| `zonePaddingLeft` | `number` | `undefined` | Left padding override. |
|
|
131
|
-
| `zoneBorderWidth` | `number` | `0` | Width of the zone border. |
|
|
132
|
-
| `zoneBorderColor` | `string` | `'transparent'` | Color of the zone border. |
|
|
133
|
-
| `zoneGlowColor` | `string` | `'#FFFFFF'` | Color of the outer glow effect. |
|
|
134
|
-
| `zoneGlowRadius` | `number` | `10` | Blur radius of the glow effect. |
|
|
135
|
-
| `zoneGlowSpread` | `number` | `5` | Spread radius of the glow effect. |
|
|
136
|
-
| `zoneGlowOffsetX` | `number` | `0` | Horizontal offset of the glow effect. |
|
|
137
|
-
| `zoneGlowOffsetY` | `number` | `0` | Vertical offset of the glow effect. |
|
|
138
|
-
| `zoneStyle` | `ZoneStyle` | `undefined` | Complete zone style object (groups above props). |
|
|
139
|
-
| `renderCustomCard` | `(props) => ReactNode` | `undefined` | Custom render function for this step's card. |
|
|
140
|
-
|
|
141
|
-
### `TourConfig`
|
|
142
|
-
|
|
143
|
-
Configuration object needed for `TourProvider`.
|
|
144
|
-
|
|
145
|
-
```tsx
|
|
146
|
-
import { SnappySpringConfig, WigglySpringConfig } from 'react-native-lumen';
|
|
147
97
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
* You can use presets like WigglySpringConfig, GentleSpringConfig etc.
|
|
152
|
-
*/
|
|
153
|
-
springConfig?: WithSpringConfig;
|
|
154
|
-
/**
|
|
155
|
-
* If true, prevents interaction with the underlying app while tour is active.
|
|
156
|
-
*/
|
|
157
|
-
preventInteraction?: boolean;
|
|
158
|
-
/**
|
|
159
|
-
* Custom labels for buttons.
|
|
160
|
-
*/
|
|
161
|
-
labels?: {
|
|
162
|
-
next?: string;
|
|
163
|
-
previous?: string;
|
|
164
|
-
finish?: string;
|
|
165
|
-
skip?: string;
|
|
166
|
-
};
|
|
167
|
-
/**
|
|
168
|
-
* Custom renderer for the card/tooltip.
|
|
169
|
-
*/
|
|
170
|
-
renderCard?: (props: CardProps) => React.ReactNode;
|
|
171
|
-
/**
|
|
172
|
-
* Initial overlay opacity. Default 0.5
|
|
173
|
-
*/
|
|
174
|
-
backdropOpacity?: number;
|
|
175
|
-
/**
|
|
176
|
-
* Global zone style settings.
|
|
177
|
-
* Can be overridden per-step via TourZone props.
|
|
178
|
-
*/
|
|
179
|
-
zoneStyle?: ZoneStyle;
|
|
180
|
-
/**
|
|
181
|
-
* Persistence configuration for saving/restoring tour progress.
|
|
182
|
-
*/
|
|
183
|
-
persistence?: TourPersistenceConfig;
|
|
184
|
-
/**
|
|
185
|
-
* Defines whether to apply a shadow/glow effect to the active tour zone highlight.
|
|
186
|
-
*/
|
|
187
|
-
enableGlow?: boolean;
|
|
188
|
-
/**
|
|
189
|
-
* Custom styles for the default tooltip appearance.
|
|
190
|
-
*/
|
|
191
|
-
tooltipStyles?: TooltipStyles;
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### `ZoneStyle`
|
|
196
|
-
|
|
197
|
-
Customization options for the zone appearance.
|
|
198
|
-
|
|
199
|
-
```tsx
|
|
200
|
-
interface ZoneStyle {
|
|
201
|
-
padding?: number; // Uniform padding around the element
|
|
202
|
-
paddingTop?: number; // Top padding override
|
|
203
|
-
paddingRight?: number; // Right padding override
|
|
204
|
-
paddingBottom?: number; // Bottom padding override
|
|
205
|
-
paddingLeft?: number; // Left padding override
|
|
206
|
-
borderRadius?: number; // Border radius for 'rounded-rect' shape
|
|
207
|
-
shape?: 'rounded-rect' | 'circle' | 'pill'; // Zone shape
|
|
208
|
-
borderWidth?: number; // Border ring width
|
|
209
|
-
borderColor?: string; // Border color
|
|
210
|
-
glowColor?: string; // Outer glow color (use rgba/hex-alpha for opacity) (requires enableGlow: true)
|
|
211
|
-
glowRadius?: number; // Glow blur radius (requires enableGlow: true)
|
|
212
|
-
glowSpread?: number; // Glow spread radius (requires enableGlow: true)
|
|
213
|
-
glowOffsetX?: number; // Horizontal offset for the glow (requires enableGlow: true)
|
|
214
|
-
glowOffsetY?: number; // Vertical offset for the glow (requires enableGlow: true)
|
|
215
|
-
springDamping?: number; // Per-step spring damping override
|
|
216
|
-
springStiffness?: number; // Per-step spring stiffness override
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### `CardProps`
|
|
221
|
-
|
|
222
|
-
Props passed to custom card render functions (`renderCard`, `renderCustomCard`).
|
|
223
|
-
|
|
224
|
-
```tsx
|
|
225
|
-
interface CardProps {
|
|
226
|
-
step: TourStep;
|
|
227
|
-
currentStepIndex: number;
|
|
228
|
-
totalSteps: number;
|
|
229
|
-
next: () => void;
|
|
230
|
-
prev: () => void;
|
|
231
|
-
stop: () => void;
|
|
232
|
-
isFirst: boolean;
|
|
233
|
-
isLast: boolean;
|
|
234
|
-
labels?: TourLabels;
|
|
235
|
-
required?: boolean; // Whether skip should be hidden
|
|
236
|
-
completed?: boolean; // Whether next should be disabled (false = disabled)
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
## Step Enforcement
|
|
241
|
-
|
|
242
|
-
Use `required` and `completed` props on `TourZone` to control how users progress through the tour.
|
|
243
|
-
|
|
244
|
-
### `required` - Hide the Skip Button
|
|
245
|
-
|
|
246
|
-
When `required={true}`, the skip button is hidden. The user must complete the step or press next to continue.
|
|
247
|
-
|
|
248
|
-
```tsx
|
|
249
|
-
<TourZone
|
|
250
|
-
stepKey="bio"
|
|
251
|
-
order={1}
|
|
252
|
-
description="Edit your bio before continuing."
|
|
253
|
-
required={true}
|
|
254
|
-
>
|
|
255
|
-
<Bio user={user} />
|
|
256
|
-
</TourZone>
|
|
98
|
+
// 3. Control the tour
|
|
99
|
+
const { start } = useTour();
|
|
100
|
+
<Button title="Start Tour" onPress={() => start()} />;
|
|
257
101
|
```
|
|
258
102
|
|
|
259
|
-
|
|
103
|
+
## Documentation
|
|
260
104
|
|
|
261
|
-
|
|
105
|
+
Full API reference, guides, and examples are available on the **[official documentation website](https://thedev204.github.io/react-native-lumen/)**.
|
|
262
106
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
description="Double tap to edit your bio and tell others about yourself."
|
|
270
|
-
required={true}
|
|
271
|
-
completed={tourComplete}
|
|
272
|
-
>
|
|
273
|
-
<Bio
|
|
274
|
-
user={User}
|
|
275
|
-
onUpdateBio={(newBio) => {
|
|
276
|
-
updateBio(newBio);
|
|
277
|
-
setTourComplete(true);
|
|
278
|
-
}}
|
|
279
|
-
/>
|
|
280
|
-
</TourZone>;
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### Combining `required` and `completed`
|
|
284
|
-
|
|
285
|
-
Use both props together for full enforcement:
|
|
286
|
-
|
|
287
|
-
- `required={true}` - No skip button, user cannot bypass the step.
|
|
288
|
-
- `completed={tourComplete}` - Next button is disabled until `tourComplete` is `true`.
|
|
289
|
-
|
|
290
|
-
This is useful for onboarding flows where you need the user to perform an action (e.g., edit their bio, upload a photo) before proceeding.
|
|
291
|
-
|
|
292
|
-
### Enforcement in Custom Cards
|
|
293
|
-
|
|
294
|
-
When using `renderCard` or `renderCustomCard`, the `required` and `completed` values are passed through `CardProps`. You can use them to control your custom UI:
|
|
295
|
-
|
|
296
|
-
```tsx
|
|
297
|
-
const MyCustomCard = ({ step, next, stop, required, completed }: CardProps) => (
|
|
298
|
-
<View style={styles.card}>
|
|
299
|
-
<Text>{step.description}</Text>
|
|
300
|
-
<View style={styles.buttons}>
|
|
301
|
-
{!required && <Button onPress={stop} title="Skip" />}
|
|
302
|
-
<Button
|
|
303
|
-
onPress={next}
|
|
304
|
-
title="Next"
|
|
305
|
-
disabled={completed === false}
|
|
306
|
-
color={completed === false ? '#ccc' : '#007AFF'}
|
|
307
|
-
/>
|
|
308
|
-
</View>
|
|
309
|
-
</View>
|
|
310
|
-
);
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
## Multi-Screen Tours
|
|
314
|
-
|
|
315
|
-
React Native Lumen supports tours that span multiple screens (e.g., tabs in a bottom navigation bar). This is common when your app has separate screens for profile, home, settings, etc.
|
|
316
|
-
|
|
317
|
-
### The Problem
|
|
318
|
-
|
|
319
|
-
When using a navigator (e.g., `BottomTabNavigator`, `CurvedBottomBar`), TourZones on inactive screens are not mounted. If the tour tries to advance to a step on an unmounted screen, it would fail because that step hasn't been registered or measured.
|
|
320
|
-
|
|
321
|
-
### The Solution: Screen-Grouped `stepsOrder`
|
|
322
|
-
|
|
323
|
-
Pass `stepsOrder` as an object where keys are screen names and values are arrays of step keys:
|
|
324
|
-
|
|
325
|
-
```tsx
|
|
326
|
-
<TourProvider
|
|
327
|
-
stepsOrder={{
|
|
328
|
-
ProfileSelf: ['bio', 'prompt', 'poll'],
|
|
329
|
-
HomeSwipe: ['filters'],
|
|
330
|
-
SwipeableCards: ['swipeableCards'],
|
|
331
|
-
}}
|
|
332
|
-
config={{
|
|
333
|
-
springConfig: SnappySpringConfig,
|
|
334
|
-
preventInteraction: true,
|
|
335
|
-
persistence: {
|
|
336
|
-
enabled: true,
|
|
337
|
-
tourId: 'main-tour',
|
|
338
|
-
autoResume: true,
|
|
339
|
-
clearOnComplete: true,
|
|
340
|
-
},
|
|
341
|
-
}}
|
|
342
|
-
>
|
|
343
|
-
<CurvedBottomBar.Navigator ...>
|
|
344
|
-
{/* screens */}
|
|
345
|
-
</CurvedBottomBar.Navigator>
|
|
346
|
-
</TourProvider>
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
This is equivalent to a flat array in terms of step ordering:
|
|
350
|
-
|
|
351
|
-
```tsx
|
|
352
|
-
stepsOrder={['bio', 'prompt', 'poll', 'filters', 'swipeableCards']}
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
But with the screen-grouped format, the library knows which steps belong to which screen. When the tour advances to a step on a different screen:
|
|
356
|
-
|
|
357
|
-
1. The overlay hides automatically.
|
|
358
|
-
2. The tour enters a **pending** state, waiting for that step's `TourZone` to mount.
|
|
359
|
-
3. When the user navigates to the correct screen and the `TourZone` mounts, the tour resumes automatically.
|
|
360
|
-
|
|
361
|
-
### Flat Array (Also Works Cross-Screen)
|
|
362
|
-
|
|
363
|
-
You can also use a flat array for multi-screen tours. The behavior is the same: if the next step isn't mounted, the tour waits for it:
|
|
364
|
-
|
|
365
|
-
```tsx
|
|
366
|
-
stepsOrder={['bio', 'prompt', 'poll', 'filters', 'swipeableCards']}
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### Example: Multi-Screen Tour
|
|
370
|
-
|
|
371
|
-
```tsx
|
|
372
|
-
// App.tsx
|
|
373
|
-
<TourProvider
|
|
374
|
-
stepsOrder={{
|
|
375
|
-
ProfileSelf: ['bio', 'prompt', 'poll'],
|
|
376
|
-
HomeSwipe: ['filters'],
|
|
377
|
-
SwipeableCards: ['swipeableCards'],
|
|
378
|
-
}}
|
|
379
|
-
config={{ springConfig: SnappySpringConfig }}
|
|
380
|
-
>
|
|
381
|
-
<BottomTabNavigator />
|
|
382
|
-
</TourProvider>
|
|
383
|
-
|
|
384
|
-
// ProfileSelf.tsx
|
|
385
|
-
<TourZone stepKey="bio" order={1} description="Edit your bio.">
|
|
386
|
-
<Bio />
|
|
387
|
-
</TourZone>
|
|
388
|
-
|
|
389
|
-
<TourZone stepKey="prompt" order={2} description="Add prompts.">
|
|
390
|
-
<PromptCard />
|
|
391
|
-
</TourZone>
|
|
392
|
-
|
|
393
|
-
<TourZone stepKey="poll" order={3} description="Create polls.">
|
|
394
|
-
<PollCard />
|
|
395
|
-
</TourZone>
|
|
396
|
-
|
|
397
|
-
// HomeSwipe.tsx
|
|
398
|
-
<TourZone stepKey="filters" order={1} description="Filter your matches.">
|
|
399
|
-
<FilterButton />
|
|
400
|
-
</TourZone>
|
|
401
|
-
|
|
402
|
-
// SwipeableCards.tsx
|
|
403
|
-
<TourZone stepKey="swipeableCards" order={1} description="Swipe to match!">
|
|
404
|
-
<CardStack />
|
|
405
|
-
</TourZone>
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
When the tour finishes the ProfileSelf steps and calls `next()` on the last ProfileSelf step (`poll`), the overlay hides. Once the user navigates to HomeSwipe, the `filters` TourZone mounts and the tour automatically resumes.
|
|
409
|
-
|
|
410
|
-
## Tour Control (`useTour` Hook)
|
|
411
|
-
|
|
412
|
-
The `useTour` hook provides full control over the tour lifecycle.
|
|
413
|
-
|
|
414
|
-
```tsx
|
|
415
|
-
import { useTour } from 'react-native-lumen';
|
|
416
|
-
|
|
417
|
-
const {
|
|
418
|
-
start, // Start or resume the tour
|
|
419
|
-
stop, // Stop the tour (hides overlay, preserves progress)
|
|
420
|
-
next, // Advance to the next step
|
|
421
|
-
prev, // Go back to the previous step
|
|
422
|
-
currentStep, // Currently active step key (null if inactive)
|
|
423
|
-
steps, // Map of all registered steps
|
|
424
|
-
orderedStepKeys, // Full ordered list of step keys
|
|
425
|
-
clearProgress, // Clear saved progress from storage
|
|
426
|
-
hasSavedProgress, // Whether there's saved progress to resume
|
|
427
|
-
scrollViewRef, // Attach to your ScrollView for auto-scrolling
|
|
428
|
-
} = useTour();
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
### `start(stepKey?: string)`
|
|
432
|
-
|
|
433
|
-
Starts (or resumes) the tour.
|
|
434
|
-
|
|
435
|
-
- **No arguments**: Starts at the first step. If persistence is enabled with `autoResume: true`, resumes from the last saved step.
|
|
436
|
-
- **With `stepKey`**: Starts at the specified step.
|
|
437
|
-
- If the target step is not mounted yet (on a different screen), the tour enters a pending state and automatically resumes when the step's `TourZone` mounts.
|
|
438
|
-
|
|
439
|
-
```tsx
|
|
440
|
-
// Start from the beginning (or resume if autoResume is enabled)
|
|
441
|
-
start();
|
|
442
|
-
|
|
443
|
-
// Start at a specific step
|
|
444
|
-
start('filters');
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
### `stop()`
|
|
448
|
-
|
|
449
|
-
Stops the tour and hides the overlay. Does **NOT** clear saved progress. The user can resume later by calling `start()` again.
|
|
450
|
-
|
|
451
|
-
```tsx
|
|
452
|
-
stop();
|
|
453
|
-
```
|
|
107
|
+
- [Getting Started](https://thedev204.github.io/react-native-lumen/docs/getting-started)
|
|
108
|
+
- [TourProvider](https://thedev204.github.io/react-native-lumen/docs/api/tour-provider)
|
|
109
|
+
- [TourZone](https://thedev204.github.io/react-native-lumen/docs/api/tour-zone)
|
|
110
|
+
- [useTour Hook](https://thedev204.github.io/react-native-lumen/docs/api/use-tour)
|
|
111
|
+
- [Multi-Screen Tours](https://thedev204.github.io/react-native-lumen/docs/guides/multi-screen-tours)
|
|
112
|
+
- [Persistence](https://thedev204.github.io/react-native-lumen/docs/guides/persistence)
|
|
454
113
|
|
|
455
|
-
|
|
114
|
+
## Changelog
|
|
456
115
|
|
|
457
|
-
|
|
116
|
+
See [CHANGELOG.md](./CHANGELOG.md) for release history.
|
|
458
117
|
|
|
459
|
-
|
|
118
|
+
## Contributing
|
|
460
119
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
### `prev()`
|
|
464
|
-
|
|
465
|
-
Goes back to the previous step. If the previous step is on an unmounted screen, the overlay hides and the tour waits for it to mount.
|
|
466
|
-
|
|
467
|
-
### `clearProgress()`
|
|
468
|
-
|
|
469
|
-
Manually clears all saved progress from storage. Useful for a "Reset Tour" button.
|
|
470
|
-
|
|
471
|
-
```tsx
|
|
472
|
-
const { clearProgress } = useTour();
|
|
473
|
-
|
|
474
|
-
const handleResetTour = async () => {
|
|
475
|
-
await clearProgress();
|
|
476
|
-
};
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
## Customization Guide
|
|
480
|
-
|
|
481
|
-
### Zone Shapes
|
|
482
|
-
|
|
483
|
-
React Native Lumen supports three zone shapes:
|
|
484
|
-
|
|
485
|
-
```tsx
|
|
486
|
-
// Rounded rectangle (default)
|
|
487
|
-
<TourZone stepKey="feature" shape="rounded-rect" borderRadius={12}>
|
|
488
|
-
<MyComponent />
|
|
489
|
-
</TourZone>
|
|
490
|
-
|
|
491
|
-
// Circle - great for FAB buttons
|
|
492
|
-
<TourZone stepKey="action" shape="circle">
|
|
493
|
-
<FloatingActionButton />
|
|
494
|
-
</TourZone>
|
|
495
|
-
|
|
496
|
-
// Pill - great for horizontal elements like tag rows
|
|
497
|
-
<TourZone stepKey="tags" shape="pill">
|
|
498
|
-
<TagList />
|
|
499
|
-
</TourZone>
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
### Glow Styles & Per-Step Zone Styling
|
|
503
|
-
|
|
504
|
-
React Native Lumen features an optional glow effect around the highlighted zone, which can bring a beautiful focus to your UI elements.
|
|
505
|
-
|
|
506
|
-
**1. Enable Glow Globally**
|
|
507
|
-
Set `enableGlow: true` in your `TourProvider` config. This allows the glow effect to render.
|
|
508
|
-
|
|
509
|
-
```tsx
|
|
510
|
-
<TourProvider config={{ enableGlow: true }}>{/* App Content */}</TourProvider>
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
**2. Customize Per-Step styles**
|
|
514
|
-
You can customize the standard padding, border, and glow effects per-step using individual `zone*` props or the `zoneStyle` object:
|
|
515
|
-
|
|
516
|
-
```tsx
|
|
517
|
-
// Using individual props
|
|
518
|
-
<TourZone
|
|
519
|
-
stepKey="important"
|
|
520
|
-
zoneGlowColor="rgba(255, 107, 107, 0.6)"
|
|
521
|
-
zoneBorderColor="#FF6B6B"
|
|
522
|
-
zoneGlowSpread={5}
|
|
523
|
-
zoneGlowOffsetY={2}
|
|
524
|
-
zonePadding={16}
|
|
525
|
-
>
|
|
526
|
-
<ImportantFeature />
|
|
527
|
-
</TourZone>
|
|
528
|
-
|
|
529
|
-
// Using zoneStyle object
|
|
530
|
-
<TourZone
|
|
531
|
-
stepKey="premium"
|
|
532
|
-
zoneStyle={{
|
|
533
|
-
shape: 'pill',
|
|
534
|
-
glowColor: 'rgba(255, 215, 0, 0.5)',
|
|
535
|
-
borderColor: '#FFD700',
|
|
536
|
-
borderWidth: 3,
|
|
537
|
-
}}
|
|
538
|
-
>
|
|
539
|
-
<PremiumBadge />
|
|
540
|
-
</TourZone>
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
### Per-Step Custom Cards
|
|
544
|
-
|
|
545
|
-
Render custom tooltip cards for specific steps:
|
|
546
|
-
|
|
547
|
-
```tsx
|
|
548
|
-
<TourZone
|
|
549
|
-
stepKey="special"
|
|
550
|
-
renderCustomCard={({ step, next, stop, currentStepIndex, totalSteps }) => (
|
|
551
|
-
<View style={styles.customCard}>
|
|
552
|
-
<Text>{step.name}</Text>
|
|
553
|
-
<Text>{step.description}</Text>
|
|
554
|
-
<Button title="Continue" onPress={next} />
|
|
555
|
-
</View>
|
|
556
|
-
)}
|
|
557
|
-
>
|
|
558
|
-
<SpecialFeature />
|
|
559
|
-
</TourZone>
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
### Animation Presets
|
|
563
|
-
|
|
564
|
-
React Native Lumen comes with built-in Reanimated spring configs for easy usage.
|
|
565
|
-
|
|
566
|
-
```tsx
|
|
567
|
-
import { TourProvider, WigglySpringConfig } from 'react-native-lumen';
|
|
568
|
-
|
|
569
|
-
<TourProvider config={{ springConfig: WigglySpringConfig }}>...</TourProvider>;
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
### Auto Scroll Support
|
|
573
|
-
|
|
574
|
-
React Native Lumen supports auto-scrolling to steps that are off-screen. It perfectly handles long scrollable lists by detecting exactly when the scroll animation finishes before snapping the highlight, ensuring the UI remains smooth.
|
|
575
|
-
|
|
576
|
-
To enable this, use the `useTourScrollView` hook and spread its props onto your scroll container:
|
|
577
|
-
|
|
578
|
-
```tsx
|
|
579
|
-
import { useTourScrollView } from 'react-native-lumen';
|
|
580
|
-
import Animated from 'react-native-reanimated';
|
|
581
|
-
|
|
582
|
-
const MyScrollableScreen = () => {
|
|
583
|
-
const { scrollViewProps } = useTourScrollView({
|
|
584
|
-
// Optional: disable user scrolling while the tour is active
|
|
585
|
-
disableScrollDuringTour: true,
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
return (
|
|
589
|
-
<Animated.ScrollView {...scrollViewProps}>
|
|
590
|
-
{/* ... content with TourZones ... */}
|
|
591
|
-
</Animated.ScrollView>
|
|
592
|
-
);
|
|
593
|
-
};
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
> **Note:** The scroll view must be compatible with Reanimated refs (e.g. `Animated.ScrollView`). Spreading `scrollViewProps` automatically wires up the `ref`, `scrollEnabled`, and the `onMomentumScrollEnd` event needed for perfect scroll-end detection and smooth transitions.
|
|
597
|
-
|
|
598
|
-
Available presets:
|
|
599
|
-
|
|
600
|
-
- `Reanimated3DefaultSpringConfig`
|
|
601
|
-
- `WigglySpringConfig` (Bouncy)
|
|
602
|
-
- `GentleSpringConfig` (Smooth)
|
|
603
|
-
- `SnappySpringConfig` (Fast & Responsive)
|
|
604
|
-
- `and more!`
|
|
605
|
-
|
|
606
|
-
### Custom Tooltip Card
|
|
607
|
-
|
|
608
|
-
You can fully replace the default tooltip with your own beautiful UI using the `renderCard` prop in `config`.
|
|
609
|
-
|
|
610
|
-
```tsx
|
|
611
|
-
import { TourProvider, CardProps } from 'react-native-lumen';
|
|
612
|
-
|
|
613
|
-
const CustomCard = ({
|
|
614
|
-
step,
|
|
615
|
-
next,
|
|
616
|
-
prev,
|
|
617
|
-
stop,
|
|
618
|
-
isLast,
|
|
619
|
-
currentStepIndex,
|
|
620
|
-
totalSteps,
|
|
621
|
-
required,
|
|
622
|
-
completed,
|
|
623
|
-
}: CardProps) => (
|
|
624
|
-
<View style={{ padding: 20, backgroundColor: 'white', borderRadius: 20 }}>
|
|
625
|
-
<Text style={{ fontWeight: 'bold', fontSize: 20 }}>{step.name}</Text>
|
|
626
|
-
<Text>{step.description}</Text>
|
|
627
|
-
<Text style={{ color: 'gray' }}>
|
|
628
|
-
Step {currentStepIndex + 1} of {totalSteps}
|
|
629
|
-
</Text>
|
|
630
|
-
|
|
631
|
-
<View style={{ flexDirection: 'row', marginTop: 10 }}>
|
|
632
|
-
{!required && <Button onPress={stop} title="Close" />}
|
|
633
|
-
<View style={{ flex: 1 }} />
|
|
634
|
-
{!isLast ? (
|
|
635
|
-
<Button onPress={next} title="Next" disabled={completed === false} />
|
|
636
|
-
) : (
|
|
637
|
-
<Button onPress={next} title="Finish" disabled={completed === false} />
|
|
638
|
-
)}
|
|
639
|
-
</View>
|
|
640
|
-
</View>
|
|
641
|
-
);
|
|
642
|
-
|
|
643
|
-
export default function App() {
|
|
644
|
-
return (
|
|
645
|
-
<TourProvider config={{ renderCard: (props) => <CustomCard {...props} /> }}>
|
|
646
|
-
<AppContent />
|
|
647
|
-
</TourProvider>
|
|
648
|
-
);
|
|
649
|
-
}
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
### Persistence (Resume Tours)
|
|
653
|
-
|
|
654
|
-
React Native Lumen supports saving tour progress so users can resume where they left off. The library auto-detects available storage (MMKV v4 or AsyncStorage).
|
|
655
|
-
|
|
656
|
-
```tsx
|
|
657
|
-
import { TourProvider } from 'react-native-lumen';
|
|
658
|
-
|
|
659
|
-
export default function App() {
|
|
660
|
-
return (
|
|
661
|
-
<TourProvider
|
|
662
|
-
config={{
|
|
663
|
-
persistence: {
|
|
664
|
-
enabled: true,
|
|
665
|
-
tourId: 'onboarding-v1', // Unique ID for this tour
|
|
666
|
-
autoResume: true, // Auto-resume from saved step (default: true)
|
|
667
|
-
clearOnComplete: true, // Clear progress when tour finishes (default: true)
|
|
668
|
-
maxAge: 7 * 24 * 60 * 60 * 1000, // Optional: expire after 7 days
|
|
669
|
-
},
|
|
670
|
-
}}
|
|
671
|
-
>
|
|
672
|
-
<AppContent />
|
|
673
|
-
</TourProvider>
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
#### Storage Support
|
|
679
|
-
|
|
680
|
-
The library automatically detects and uses:
|
|
681
|
-
|
|
682
|
-
- **MMKV v4** (`react-native-mmkv` ^4.0.0) - Fastest, recommended
|
|
683
|
-
- **AsyncStorage** (`@react-native-async-storage/async-storage`) - Fallback
|
|
684
|
-
|
|
685
|
-
No additional setup required if either package is installed.
|
|
686
|
-
|
|
687
|
-
#### Custom Storage
|
|
688
|
-
|
|
689
|
-
You can provide a custom storage adapter:
|
|
690
|
-
|
|
691
|
-
```tsx
|
|
692
|
-
import { TourProvider, type StorageAdapter } from 'react-native-lumen';
|
|
693
|
-
|
|
694
|
-
const customStorage: StorageAdapter = {
|
|
695
|
-
getItem: (key) => myStorage.get(key),
|
|
696
|
-
setItem: (key, value) => myStorage.set(key, value),
|
|
697
|
-
removeItem: (key) => myStorage.remove(key),
|
|
698
|
-
};
|
|
699
|
-
|
|
700
|
-
<TourProvider
|
|
701
|
-
config={{
|
|
702
|
-
persistence: {
|
|
703
|
-
enabled: true,
|
|
704
|
-
tourId: 'my-tour',
|
|
705
|
-
storage: customStorage,
|
|
706
|
-
},
|
|
707
|
-
}}
|
|
708
|
-
>
|
|
709
|
-
...
|
|
710
|
-
</TourProvider>;
|
|
711
|
-
```
|
|
712
|
-
|
|
713
|
-
#### Persistence API
|
|
714
|
-
|
|
715
|
-
```tsx
|
|
716
|
-
const { start, clearProgress, hasSavedProgress } = useTour();
|
|
717
|
-
|
|
718
|
-
// Check if there's saved progress
|
|
719
|
-
if (hasSavedProgress) {
|
|
720
|
-
// Show "Resume Tour" button
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// Start tour (auto-resumes if enabled)
|
|
724
|
-
start();
|
|
725
|
-
|
|
726
|
-
// Clear saved progress manually
|
|
727
|
-
await clearProgress();
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
#### Manually Clearing Storage
|
|
731
|
-
|
|
732
|
-
If you need to clear tour data directly from storage outside the tour context (e.g., during logout or a full app reset):
|
|
733
|
-
|
|
734
|
-
**MMKV v4:**
|
|
735
|
-
|
|
736
|
-
```tsx
|
|
737
|
-
import { createMMKV } from 'react-native-mmkv';
|
|
738
|
-
|
|
739
|
-
// The library uses a dedicated MMKV instance with this ID
|
|
740
|
-
const storage = createMMKV({ id: 'react-native-lumen-tour' });
|
|
741
|
-
|
|
742
|
-
// Clear a specific tour
|
|
743
|
-
storage.remove('@lumen_tour_onboarding-v1');
|
|
744
|
-
|
|
745
|
-
// Or clear all tour data - delete all keys starting with @lumen_tour_
|
|
746
|
-
const allKeys = storage.getAllKeys();
|
|
747
|
-
allKeys
|
|
748
|
-
.filter((key) => key.startsWith('@lumen_tour_'))
|
|
749
|
-
.forEach((key) => storage.remove(key));
|
|
750
|
-
```
|
|
751
|
-
|
|
752
|
-
**AsyncStorage:**
|
|
753
|
-
|
|
754
|
-
```tsx
|
|
755
|
-
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
756
|
-
|
|
757
|
-
// Clear a specific tour
|
|
758
|
-
await AsyncStorage.removeItem('@lumen_tour_onboarding-v1');
|
|
759
|
-
|
|
760
|
-
// Or clear all tour data
|
|
761
|
-
const allKeys = await AsyncStorage.getAllKeys();
|
|
762
|
-
const tourKeys = allKeys.filter((key) => key.startsWith('@lumen_tour_'));
|
|
763
|
-
await AsyncStorage.multiRemove(tourKeys);
|
|
764
|
-
```
|
|
120
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) before submitting a pull request.
|
|
765
121
|
|
|
766
|
-
|
|
122
|
+
- Report bugs in the [Issue Tracker](https://github.com/thedev204/react-native-lumen/issues).
|
|
767
123
|
|
|
768
124
|
## License
|
|
769
125
|
|
|
770
|
-
MIT
|
|
126
|
+
MIT
|
|
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
const {
|
|
11
11
|
width: SCREEN_WIDTH,
|
|
12
12
|
height: SCREEN_HEIGHT
|
|
13
|
-
} = Dimensions.get('
|
|
13
|
+
} = Dimensions.get('screen');
|
|
14
14
|
const AnimatedPath = Animated.createAnimatedComponent(Path);
|
|
15
15
|
const AnimatedView = Animated.View;
|
|
16
16
|
|
|
@@ -12,11 +12,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
const {
|
|
13
13
|
width: SCREEN_WIDTH,
|
|
14
14
|
height: SCREEN_HEIGHT
|
|
15
|
-
} = Dimensions.get('
|
|
15
|
+
} = Dimensions.get('screen');
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
* Computes the zone geometry based on element bounds and zone style.
|
|
19
|
-
* Handles different shapes: rounded-rect, circle, pill.
|
|
17
|
+
/**
|
|
18
|
+
* Computes the zone geometry based on element bounds and zone style.
|
|
19
|
+
* Handles different shapes: rounded-rect, circle, pill.
|
|
20
20
|
*/
|
|
21
21
|
function computeZoneGeometry(element, style) {
|
|
22
22
|
const {
|
|
@@ -111,9 +111,9 @@ export const TourProvider = ({
|
|
|
111
111
|
onScrollEndCallbackRef.current = null;
|
|
112
112
|
}, []);
|
|
113
113
|
|
|
114
|
-
/**
|
|
115
|
-
* One-shot: fires the registered callback and immediately clears it so that
|
|
116
|
-
* user-initiated momentum scrolls that happen between steps don't re-trigger.
|
|
114
|
+
/**
|
|
115
|
+
* One-shot: fires the registered callback and immediately clears it so that
|
|
116
|
+
* user-initiated momentum scrolls that happen between steps don't re-trigger.
|
|
117
117
|
*/
|
|
118
118
|
const triggerScrollEnd = useCallback(() => {
|
|
119
119
|
const cb = onScrollEndCallbackRef.current;
|
|
@@ -9,7 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
const {
|
|
10
10
|
width: SCREEN_WIDTH,
|
|
11
11
|
height: SCREEN_HEIGHT
|
|
12
|
-
} = Dimensions.get('
|
|
12
|
+
} = Dimensions.get('screen');
|
|
13
13
|
const getDynamicStyles = tooltipStyles => {
|
|
14
14
|
const defaultStyles = {
|
|
15
15
|
backgroundColor: tooltipStyles?.backgroundColor || 'white',
|
|
@@ -7,7 +7,7 @@ import { Dimensions } from 'react-native';
|
|
|
7
7
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
8
|
const {
|
|
9
9
|
height: SCREEN_HEIGHT
|
|
10
|
-
} = Dimensions.get('
|
|
10
|
+
} = Dimensions.get('screen');
|
|
11
11
|
const AnimatedView = Animated.View;
|
|
12
12
|
export const TourZone = ({
|
|
13
13
|
stepKey,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Default spring configuration matching Reanimated 3 defaults.
|
|
3
|
+
/**
|
|
4
|
+
* Default spring configuration matching Reanimated 3 defaults.
|
|
5
5
|
*/
|
|
6
6
|
export const Reanimated3DefaultSpringConfig = {
|
|
7
7
|
damping: 10,
|
|
@@ -9,16 +9,16 @@ export const Reanimated3DefaultSpringConfig = {
|
|
|
9
9
|
stiffness: 100
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Spring configuration with duration.
|
|
12
|
+
/**
|
|
13
|
+
* Spring configuration with duration.
|
|
14
14
|
*/
|
|
15
15
|
export const Reanimated3DefaultSpringConfigWithDuration = {
|
|
16
16
|
duration: 1333,
|
|
17
17
|
dampingRatio: 0.5
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* A bouncy and energetic spring configuration.
|
|
20
|
+
/**
|
|
21
|
+
* A bouncy and energetic spring configuration.
|
|
22
22
|
*/
|
|
23
23
|
export const WigglySpringConfig = {
|
|
24
24
|
damping: 90,
|
|
@@ -26,16 +26,16 @@ export const WigglySpringConfig = {
|
|
|
26
26
|
stiffness: 900
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
* A bouncy spring configuration with fixed duration.
|
|
29
|
+
/**
|
|
30
|
+
* A bouncy spring configuration with fixed duration.
|
|
31
31
|
*/
|
|
32
32
|
export const WigglySpringConfigWithDuration = {
|
|
33
33
|
duration: 550,
|
|
34
34
|
dampingRatio: 0.75
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* A gentle and smooth spring configuration.
|
|
37
|
+
/**
|
|
38
|
+
* A gentle and smooth spring configuration.
|
|
39
39
|
*/
|
|
40
40
|
export const GentleSpringConfig = {
|
|
41
41
|
damping: 120,
|
|
@@ -43,16 +43,16 @@ export const GentleSpringConfig = {
|
|
|
43
43
|
stiffness: 900
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* A gentle spring configuration with fixed duration.
|
|
46
|
+
/**
|
|
47
|
+
* A gentle spring configuration with fixed duration.
|
|
48
48
|
*/
|
|
49
49
|
export const GentleSpringConfigWithDuration = {
|
|
50
50
|
duration: 550,
|
|
51
51
|
dampingRatio: 1
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
/**
|
|
55
|
-
* A snappy and responsive spring configuration.
|
|
54
|
+
/**
|
|
55
|
+
* A snappy and responsive spring configuration.
|
|
56
56
|
*/
|
|
57
57
|
export const SnappySpringConfig = {
|
|
58
58
|
damping: 110,
|
|
@@ -61,8 +61,8 @@ export const SnappySpringConfig = {
|
|
|
61
61
|
overshootClamping: true
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
/**
|
|
65
|
-
* A snappy spring configuration with fixed duration.
|
|
64
|
+
/**
|
|
65
|
+
* A snappy spring configuration with fixed duration.
|
|
66
66
|
*/
|
|
67
67
|
export const SnappySpringConfigWithDuration = {
|
|
68
68
|
duration: 550,
|
|
@@ -12,9 +12,9 @@ export const DEFAULT_LABELS = {
|
|
|
12
12
|
skip: 'Skip'
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* Default zone style configuration.
|
|
17
|
-
* These values are used when no custom style is provided.
|
|
15
|
+
/**
|
|
16
|
+
* Default zone style configuration.
|
|
17
|
+
* These values are used when no custom style is provided.
|
|
18
18
|
*/
|
|
19
19
|
export const DEFAULT_ZONE_STYLE = {
|
|
20
20
|
padding: 0,
|
|
@@ -35,8 +35,8 @@ export const DEFAULT_ZONE_STYLE = {
|
|
|
35
35
|
springStiffness: 90
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
* Merges global and per-step zone styles with defaults.
|
|
38
|
+
/**
|
|
39
|
+
* Merges global and per-step zone styles with defaults.
|
|
40
40
|
*/
|
|
41
41
|
export function resolveZoneStyle(globalStyle, stepStyle) {
|
|
42
42
|
const merged = {
|
|
@@ -2,28 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useMemo } from 'react';
|
|
4
4
|
import { useTour } from "./useTour.js";
|
|
5
|
-
/**
|
|
6
|
-
* Hook to simplify integrating custom scroll views with the tour system.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* // Basic usage with Animated.ScrollView
|
|
10
|
-
* const { scrollViewRef } = useTourScrollView();
|
|
11
|
-
* return <Animated.ScrollView ref={scrollViewRef}>...</Animated.ScrollView>;
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* // With scroll disabled during tour
|
|
15
|
-
* const { scrollViewProps } = useTourScrollView({ disableScrollDuringTour: true });
|
|
16
|
-
* return <Animated.ScrollView {...scrollViewProps}>...</Animated.ScrollView>;
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* // Custom scroll view wrapper (forwardRef pattern)
|
|
20
|
-
* const MyScrollView = forwardRef((props, ref) => {
|
|
21
|
-
* const { scrollViewRef } = useTourScrollView();
|
|
22
|
-
* useImperativeHandle(ref, () => ({
|
|
23
|
-
* getScrollRef: () => scrollViewRef.current,
|
|
24
|
-
* }));
|
|
25
|
-
* return <Animated.ScrollView ref={scrollViewRef} {...props} />;
|
|
26
|
-
* });
|
|
5
|
+
/**
|
|
6
|
+
* Hook to simplify integrating custom scroll views with the tour system.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Basic usage with Animated.ScrollView
|
|
10
|
+
* const { scrollViewRef } = useTourScrollView();
|
|
11
|
+
* return <Animated.ScrollView ref={scrollViewRef}>...</Animated.ScrollView>;
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // With scroll disabled during tour
|
|
15
|
+
* const { scrollViewProps } = useTourScrollView({ disableScrollDuringTour: true });
|
|
16
|
+
* return <Animated.ScrollView {...scrollViewProps}>...</Animated.ScrollView>;
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Custom scroll view wrapper (forwardRef pattern)
|
|
20
|
+
* const MyScrollView = forwardRef((props, ref) => {
|
|
21
|
+
* const { scrollViewRef } = useTourScrollView();
|
|
22
|
+
* useImperativeHandle(ref, () => ({
|
|
23
|
+
* getScrollRef: () => scrollViewRef.current,
|
|
24
|
+
* }));
|
|
25
|
+
* return <Animated.ScrollView ref={scrollViewRef} {...props} />;
|
|
26
|
+
* });
|
|
27
27
|
*/
|
|
28
28
|
export function useTourScrollView(options = {}) {
|
|
29
29
|
const {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Storage adapter for tour persistence.
|
|
5
|
-
* Auto-detects available storage (MMKV v4 or AsyncStorage) and provides a unified interface.
|
|
3
|
+
/**
|
|
4
|
+
* Storage adapter for tour persistence.
|
|
5
|
+
* Auto-detects available storage (MMKV v4 or AsyncStorage) and provides a unified interface.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
let cachedStorageType = null;
|
|
13
13
|
let cachedAdapter = null;
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* Attempts to detect and return the MMKV v4 default instance.
|
|
17
|
-
* Returns null if MMKV is not available.
|
|
15
|
+
/**
|
|
16
|
+
* Attempts to detect and return the MMKV v4 default instance.
|
|
17
|
+
* Returns null if MMKV is not available.
|
|
18
18
|
*/
|
|
19
19
|
function tryGetMMKV() {
|
|
20
20
|
try {
|
|
@@ -43,9 +43,9 @@ function tryGetMMKV() {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* Attempts to detect and return AsyncStorage.
|
|
48
|
-
* Returns null if AsyncStorage is not available.
|
|
46
|
+
/**
|
|
47
|
+
* Attempts to detect and return AsyncStorage.
|
|
48
|
+
* Returns null if AsyncStorage is not available.
|
|
49
49
|
*/
|
|
50
50
|
function tryGetAsyncStorage() {
|
|
51
51
|
try {
|
|
@@ -66,9 +66,9 @@ function tryGetAsyncStorage() {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/**
|
|
70
|
-
* Detects the available storage type and returns an adapter.
|
|
71
|
-
* Priority: MMKV v4 > AsyncStorage > none
|
|
69
|
+
/**
|
|
70
|
+
* Detects the available storage type and returns an adapter.
|
|
71
|
+
* Priority: MMKV v4 > AsyncStorage > none
|
|
72
72
|
*/
|
|
73
73
|
export function detectStorage() {
|
|
74
74
|
// Return cached result if available
|
|
@@ -110,9 +110,9 @@ export function detectStorage() {
|
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
/**
|
|
114
|
-
* Clears the cached storage detection result.
|
|
115
|
-
* Useful for testing or when storage availability changes.
|
|
113
|
+
/**
|
|
114
|
+
* Clears the cached storage detection result.
|
|
115
|
+
* Useful for testing or when storage availability changes.
|
|
116
116
|
*/
|
|
117
117
|
export function clearStorageCache() {
|
|
118
118
|
cachedStorageType = null;
|
|
@@ -123,8 +123,8 @@ export function clearStorageCache() {
|
|
|
123
123
|
|
|
124
124
|
const STORAGE_KEY_PREFIX = '@lumen_tour_';
|
|
125
125
|
|
|
126
|
-
/**
|
|
127
|
-
* Generates a storage key for a specific tour.
|
|
126
|
+
/**
|
|
127
|
+
* Generates a storage key for a specific tour.
|
|
128
128
|
*/
|
|
129
129
|
export function getTourStorageKey(tourId) {
|
|
130
130
|
return `${STORAGE_KEY_PREFIX}${tourId}`;
|
|
@@ -132,8 +132,8 @@ export function getTourStorageKey(tourId) {
|
|
|
132
132
|
|
|
133
133
|
// ─── Storage Operations ──────────────────────────────────────────────────────
|
|
134
134
|
|
|
135
|
-
/**
|
|
136
|
-
* Saves the current tour progress to storage.
|
|
135
|
+
/**
|
|
136
|
+
* Saves the current tour progress to storage.
|
|
137
137
|
*/
|
|
138
138
|
export async function saveTourProgress(adapter, tourId, currentStepKey, stepIndex) {
|
|
139
139
|
const state = {
|
|
@@ -147,9 +147,9 @@ export async function saveTourProgress(adapter, tourId, currentStepKey, stepInde
|
|
|
147
147
|
await adapter.setItem(key, value);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
/**
|
|
151
|
-
* Loads the saved tour progress from storage.
|
|
152
|
-
* Returns null if no progress is saved or if the data is invalid.
|
|
150
|
+
/**
|
|
151
|
+
* Loads the saved tour progress from storage.
|
|
152
|
+
* Returns null if no progress is saved or if the data is invalid.
|
|
153
153
|
*/
|
|
154
154
|
export async function loadTourProgress(adapter, tourId) {
|
|
155
155
|
try {
|
|
@@ -170,16 +170,16 @@ export async function loadTourProgress(adapter, tourId) {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
/**
|
|
174
|
-
* Clears the saved tour progress from storage.
|
|
173
|
+
/**
|
|
174
|
+
* Clears the saved tour progress from storage.
|
|
175
175
|
*/
|
|
176
176
|
export async function clearTourProgress(adapter, tourId) {
|
|
177
177
|
const key = getTourStorageKey(tourId);
|
|
178
178
|
await adapter.removeItem(key);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
/**
|
|
182
|
-
* Checks if there is saved progress for a tour.
|
|
181
|
+
/**
|
|
182
|
+
* Checks if there is saved progress for a tour.
|
|
183
183
|
*/
|
|
184
184
|
export async function hasTourProgress(adapter, tourId) {
|
|
185
185
|
const progress = await loadTourProgress(adapter, tourId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-lumen",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "A customizable app tour library for React Native",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"bugs": {
|
|
51
51
|
"url": "https://github.com/thedev204/react-native-lumen/issues"
|
|
52
52
|
},
|
|
53
|
-
"homepage": "https://github.
|
|
53
|
+
"homepage": "https://thedev204.github.io/react-native-lumen/",
|
|
54
54
|
"publishConfig": {
|
|
55
55
|
"registry": "https://registry.npmjs.org/"
|
|
56
56
|
},
|