@djangocfg/ui-tools 2.1.162 → 2.1.164
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/package.json +11 -6
- package/src/tools/Tour/README.md +373 -0
- package/src/tools/Tour/Tour.story.tsx +279 -0
- package/src/tools/Tour/components/Tour.tsx +12 -0
- package/src/tools/Tour/components/TourContent.tsx +162 -0
- package/src/tools/Tour/components/TourNavigation.tsx +90 -0
- package/src/tools/Tour/components/TourProgress.tsx +88 -0
- package/src/tools/Tour/components/TourSpotlight.tsx +199 -0
- package/src/tools/Tour/components/index.ts +5 -0
- package/src/tools/Tour/context/TourContext.ts +19 -0
- package/src/tools/Tour/context/TourProvider.tsx +292 -0
- package/src/tools/Tour/context/index.ts +2 -0
- package/src/tools/Tour/hooks/index.ts +3 -0
- package/src/tools/Tour/hooks/useKeyboardNavigation.ts +59 -0
- package/src/tools/Tour/hooks/useStepTarget.ts +121 -0
- package/src/tools/Tour/hooks/useTour.ts +42 -0
- package/src/tools/Tour/index.ts +38 -0
- package/src/tools/Tour/types/index.ts +227 -0
- package/src/tools/Tour/utils/dom.ts +70 -0
- package/src/tools/Tour/utils/index.ts +3 -0
- package/src/tools/Tour/utils/logger.ts +3 -0
- package/src/tools/Tour/utils/scrollIntoView.ts +24 -0
- package/src/tools/Uploader/components/UploadDropzone.tsx +38 -30
- package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +38 -23
- package/src/tools/Uploader/components/UploadPreviewItem.tsx +69 -53
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-tools",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.164",
|
|
4
4
|
"description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-tools",
|
|
@@ -57,6 +57,11 @@
|
|
|
57
57
|
"import": "./src/tools/Uploader/index.ts",
|
|
58
58
|
"require": "./src/tools/Uploader/index.ts"
|
|
59
59
|
},
|
|
60
|
+
"./tour": {
|
|
61
|
+
"types": "./src/tools/Tour/index.ts",
|
|
62
|
+
"import": "./src/tools/Tour/index.ts",
|
|
63
|
+
"require": "./src/tools/Tour/index.ts"
|
|
64
|
+
},
|
|
60
65
|
"./styles": "./src/styles/index.css"
|
|
61
66
|
},
|
|
62
67
|
"files": [
|
|
@@ -73,8 +78,8 @@
|
|
|
73
78
|
"check": "tsc --noEmit"
|
|
74
79
|
},
|
|
75
80
|
"peerDependencies": {
|
|
76
|
-
"@djangocfg/i18n": "^2.1.
|
|
77
|
-
"@djangocfg/ui-core": "^2.1.
|
|
81
|
+
"@djangocfg/i18n": "^2.1.164",
|
|
82
|
+
"@djangocfg/ui-core": "^2.1.164",
|
|
78
83
|
"lucide-react": "^0.545.0",
|
|
79
84
|
"react": "^19.0.0",
|
|
80
85
|
"react-dom": "^19.0.0",
|
|
@@ -107,10 +112,10 @@
|
|
|
107
112
|
"@maplibre/maplibre-gl-geocoder": "^1.7.0"
|
|
108
113
|
},
|
|
109
114
|
"devDependencies": {
|
|
110
|
-
"@djangocfg/i18n": "^2.1.
|
|
115
|
+
"@djangocfg/i18n": "^2.1.164",
|
|
111
116
|
"@djangocfg/playground": "workspace:*",
|
|
112
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
113
|
-
"@djangocfg/ui-core": "^2.1.
|
|
117
|
+
"@djangocfg/typescript-config": "^2.1.164",
|
|
118
|
+
"@djangocfg/ui-core": "^2.1.164",
|
|
114
119
|
"@types/mapbox__mapbox-gl-draw": "^1.4.8",
|
|
115
120
|
"@types/node": "^24.7.2",
|
|
116
121
|
"@types/react": "^19.1.0",
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# Tour
|
|
2
|
+
|
|
3
|
+
Smart, decomposed onboarding Tour component with spotlight highlighting, animations, and i18n support.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @djangocfg/ui-tools
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Basic Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Tour, useTour } from '@djangocfg/ui-tools/tour';
|
|
15
|
+
|
|
16
|
+
const tours = [
|
|
17
|
+
{
|
|
18
|
+
id: 'onboarding',
|
|
19
|
+
steps: [
|
|
20
|
+
{
|
|
21
|
+
id: 'welcome',
|
|
22
|
+
target: '[data-tour-step-id="header"]',
|
|
23
|
+
title: 'Welcome',
|
|
24
|
+
content: 'Let us show you around!',
|
|
25
|
+
position: 'bottom',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'sidebar',
|
|
29
|
+
target: '#sidebar',
|
|
30
|
+
title: 'Navigation',
|
|
31
|
+
content: 'Use the sidebar to navigate.',
|
|
32
|
+
position: 'right',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
function App() {
|
|
39
|
+
return (
|
|
40
|
+
<Tour
|
|
41
|
+
tours={tours}
|
|
42
|
+
onComplete={(tourId) => console.log('Tour completed:', tourId)}
|
|
43
|
+
>
|
|
44
|
+
<Layout />
|
|
45
|
+
</Tour>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function StartButton() {
|
|
50
|
+
const { start } = useTour();
|
|
51
|
+
return <button onClick={() => start('onboarding')}>Start Tour</button>;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Components
|
|
56
|
+
|
|
57
|
+
### Tour
|
|
58
|
+
|
|
59
|
+
All-in-one wrapper combining TourProvider with overlay rendering.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<Tour
|
|
63
|
+
tours={tours} // Array of tour configs
|
|
64
|
+
spotlight={true} // Enable spotlight (default: true)
|
|
65
|
+
spotlightOpacity={0.5} // Overlay opacity (default: 0.5)
|
|
66
|
+
spotlightBlur={0} // Backdrop blur in px (default: 0)
|
|
67
|
+
animated={true} // Enable animations (default: true)
|
|
68
|
+
pulseRing={false} // Pulsing ring around target (default: false)
|
|
69
|
+
showArrow={false} // Show arrow to target (default: false)
|
|
70
|
+
keyboardNavigation={true} // Arrow keys navigation (default: true)
|
|
71
|
+
closeOnEscape={true} // Close on Escape (default: true)
|
|
72
|
+
closeOnOverlayClick={false} // Close on overlay click (default: false)
|
|
73
|
+
scrollToTarget={true} // Scroll target into view (default: true)
|
|
74
|
+
progressVariant="dots" // 'dots' | 'bar' | 'fraction' | 'none'
|
|
75
|
+
showSkipButton={true} // Show skip button (default: true)
|
|
76
|
+
onStart={(tourId) => {}}
|
|
77
|
+
onComplete={(tourId) => {}}
|
|
78
|
+
onSkip={(tourId, stepIndex) => {}}
|
|
79
|
+
onStepChange={(index, step, direction) => {}}
|
|
80
|
+
>
|
|
81
|
+
{children}
|
|
82
|
+
</Tour>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Custom Composition
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import {
|
|
89
|
+
TourProvider,
|
|
90
|
+
TourSpotlight,
|
|
91
|
+
TourContent,
|
|
92
|
+
TourProgress,
|
|
93
|
+
TourNavigation,
|
|
94
|
+
useTour,
|
|
95
|
+
} from '@djangocfg/ui-tools/tour';
|
|
96
|
+
|
|
97
|
+
function CustomTourOverlay() {
|
|
98
|
+
const { currentStep, currentStepIndex, totalSteps, next, previous, close } = useTour();
|
|
99
|
+
// ... render custom UI
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
<TourProvider tours={tours}>
|
|
103
|
+
<App />
|
|
104
|
+
<CustomTourOverlay />
|
|
105
|
+
</TourProvider>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Step Configuration
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
interface TourStep {
|
|
112
|
+
id: string; // Unique identifier
|
|
113
|
+
target?: string; // CSS selector or data-tour-step-id
|
|
114
|
+
title: ReactNode; // Step title
|
|
115
|
+
content: ReactNode; // Step content
|
|
116
|
+
position?: 'top' | 'bottom' | 'left' | 'right' | 'auto';
|
|
117
|
+
offset?: number; // Popover offset (default: 12)
|
|
118
|
+
align?: 'start' | 'center' | 'end';
|
|
119
|
+
|
|
120
|
+
// Multi-page tours
|
|
121
|
+
nextRoute?: string; // Navigate on Next
|
|
122
|
+
previousRoute?: string; // Navigate on Previous
|
|
123
|
+
|
|
124
|
+
// Labels (overrides i18n)
|
|
125
|
+
nextLabel?: ReactNode;
|
|
126
|
+
previousLabel?: ReactNode;
|
|
127
|
+
|
|
128
|
+
// Spotlight
|
|
129
|
+
disableSpotlight?: boolean;
|
|
130
|
+
spotlightPadding?: number; // Padding around target (default: 8)
|
|
131
|
+
spotlightRadius?: number; // Border radius (default: 4)
|
|
132
|
+
allowTargetInteraction?: boolean;
|
|
133
|
+
|
|
134
|
+
// Auto-advance
|
|
135
|
+
autoAdvance?: number; // Auto-advance after ms
|
|
136
|
+
|
|
137
|
+
// Custom actions
|
|
138
|
+
actions?: TourAction[];
|
|
139
|
+
|
|
140
|
+
// Callbacks
|
|
141
|
+
onBeforeShow?: () => boolean | Promise<boolean>;
|
|
142
|
+
onShow?: () => void;
|
|
143
|
+
onHide?: () => void;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Targeting Elements
|
|
148
|
+
|
|
149
|
+
### Option 1: CSS Selector
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
{
|
|
153
|
+
id: 'step-1',
|
|
154
|
+
target: '#my-button', // or '.my-class', '[data-id="123"]'
|
|
155
|
+
title: 'Click here',
|
|
156
|
+
content: '...',
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Option 2: data-tour-step-id Attribute
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
// In your component
|
|
164
|
+
<header data-tour-step-id="header">...</header>
|
|
165
|
+
|
|
166
|
+
// In tour config
|
|
167
|
+
{
|
|
168
|
+
id: 'header', // matches data-tour-step-id
|
|
169
|
+
title: 'Welcome',
|
|
170
|
+
content: '...',
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Hooks
|
|
175
|
+
|
|
176
|
+
### useTour
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
const {
|
|
180
|
+
start, // (tourId, startIndex?) => void
|
|
181
|
+
next, // () => void
|
|
182
|
+
previous, // () => void
|
|
183
|
+
goTo, // (index) => void
|
|
184
|
+
close, // () => void
|
|
185
|
+
isActive, // boolean
|
|
186
|
+
activeTourId, // string | null
|
|
187
|
+
currentStep, // TourStep | null
|
|
188
|
+
currentStepIndex,// number
|
|
189
|
+
totalSteps, // number
|
|
190
|
+
isFirstStep, // boolean
|
|
191
|
+
isLastStep, // boolean
|
|
192
|
+
progress, // number (0-1)
|
|
193
|
+
} = useTour();
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Keyboard Navigation
|
|
197
|
+
|
|
198
|
+
When enabled (default):
|
|
199
|
+
- `ArrowRight` / `ArrowDown`: Next step
|
|
200
|
+
- `ArrowLeft` / `ArrowUp`: Previous step
|
|
201
|
+
- `Escape`: Close tour
|
|
202
|
+
|
|
203
|
+
## Multi-page Tours
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
const tours = [{
|
|
207
|
+
id: 'walkthrough',
|
|
208
|
+
steps: [
|
|
209
|
+
{
|
|
210
|
+
id: 'home',
|
|
211
|
+
target: '#hero',
|
|
212
|
+
title: 'Welcome',
|
|
213
|
+
content: 'Start here.',
|
|
214
|
+
nextRoute: '/dashboard', // Navigate on Next
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: 'dashboard',
|
|
218
|
+
target: '#stats',
|
|
219
|
+
title: 'Dashboard',
|
|
220
|
+
content: 'View your metrics.',
|
|
221
|
+
previousRoute: '/', // Navigate on Previous
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
}];
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Callbacks
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
<Tour
|
|
231
|
+
tours={tours}
|
|
232
|
+
onStart={(tourId) => {
|
|
233
|
+
analytics.track('tour_started', { tourId });
|
|
234
|
+
}}
|
|
235
|
+
onComplete={(tourId) => {
|
|
236
|
+
localStorage.setItem(`tour_${tourId}_completed`, 'true');
|
|
237
|
+
}}
|
|
238
|
+
onSkip={(tourId, stepIndex) => {
|
|
239
|
+
analytics.track('tour_skipped', { tourId, stepIndex });
|
|
240
|
+
}}
|
|
241
|
+
onStepChange={(index, step, direction) => {
|
|
242
|
+
analytics.track('tour_step', { stepId: step.id, direction });
|
|
243
|
+
}}
|
|
244
|
+
onBeforeStepChange={async (currentIndex, nextIndex) => {
|
|
245
|
+
// Return false to prevent navigation
|
|
246
|
+
return true;
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Progress Variants
|
|
252
|
+
|
|
253
|
+
- `dots` - Clickable dots (default)
|
|
254
|
+
- `bar` - Progress bar
|
|
255
|
+
- `fraction` - "1 / 5" text
|
|
256
|
+
- `none` - No progress indicator
|
|
257
|
+
|
|
258
|
+
## Visual Options
|
|
259
|
+
|
|
260
|
+
### Arrow
|
|
261
|
+
|
|
262
|
+
Shows an arrow pointing from the popover to the target element:
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
<Tour tours={tours} showArrow>
|
|
266
|
+
{children}
|
|
267
|
+
</Tour>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Backdrop Blur
|
|
271
|
+
|
|
272
|
+
Blurs the background outside the spotlight for enhanced focus:
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
<Tour tours={tours} spotlightBlur={4} spotlightOpacity={0.3}>
|
|
276
|
+
{children}
|
|
277
|
+
</Tour>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Pulse Ring
|
|
281
|
+
|
|
282
|
+
Animated pulsing ring around the target to draw attention:
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
<Tour tours={tours} pulseRing>
|
|
286
|
+
{children}
|
|
287
|
+
</Tour>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Disable Animations
|
|
291
|
+
|
|
292
|
+
Turn off all spotlight transition animations:
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
<Tour tours={tours} animated={false}>
|
|
296
|
+
{children}
|
|
297
|
+
</Tour>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Full Featured
|
|
301
|
+
|
|
302
|
+
Combine all visual enhancements:
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
<Tour
|
|
306
|
+
tours={tours}
|
|
307
|
+
showArrow
|
|
308
|
+
spotlightBlur={2}
|
|
309
|
+
spotlightOpacity={0.4}
|
|
310
|
+
pulseRing
|
|
311
|
+
>
|
|
312
|
+
{children}
|
|
313
|
+
</Tour>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Internationalization (i18n)
|
|
317
|
+
|
|
318
|
+
Tour uses `@djangocfg/i18n` for translations. Default labels come from `tools.tour.*` keys:
|
|
319
|
+
|
|
320
|
+
- `tools.tour.next` - "Next"
|
|
321
|
+
- `tools.tour.previous` - "Back"
|
|
322
|
+
- `tools.tour.skip` - "Skip"
|
|
323
|
+
- `tools.tour.finish` - "Finish"
|
|
324
|
+
- `tools.tour.close` - "Close"
|
|
325
|
+
- `tools.tour.stepOf` - "Step {current} of {total}"
|
|
326
|
+
|
|
327
|
+
To customize, either:
|
|
328
|
+
|
|
329
|
+
1. **Override via props** (per-step or global):
|
|
330
|
+
```tsx
|
|
331
|
+
<Tour
|
|
332
|
+
tours={tours}
|
|
333
|
+
skipLabel="Exit tour"
|
|
334
|
+
finishLabel="Done!"
|
|
335
|
+
>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
2. **Provide custom translations** via I18nProvider:
|
|
339
|
+
```tsx
|
|
340
|
+
import { I18nProvider, mergeTranslations, en } from '@djangocfg/i18n';
|
|
341
|
+
|
|
342
|
+
const customEn = mergeTranslations(en, {
|
|
343
|
+
tools: {
|
|
344
|
+
tour: {
|
|
345
|
+
next: 'Continue',
|
|
346
|
+
skip: 'Exit',
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
<I18nProvider locale="en" translations={customEn}>
|
|
352
|
+
<Tour tours={tours}>
|
|
353
|
+
<App />
|
|
354
|
+
</Tour>
|
|
355
|
+
</I18nProvider>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Features
|
|
359
|
+
|
|
360
|
+
- Spotlight highlighting with SVG mask
|
|
361
|
+
- Optional backdrop blur for enhanced focus
|
|
362
|
+
- Arrow pointing from popover to target
|
|
363
|
+
- Smooth animations between steps
|
|
364
|
+
- Pulse ring effect for attention
|
|
365
|
+
- Keyboard navigation
|
|
366
|
+
- Multi-step and multi-page tours
|
|
367
|
+
- Progress indicators (dots, bar, fraction)
|
|
368
|
+
- Auto-scroll to target
|
|
369
|
+
- Lifecycle callbacks
|
|
370
|
+
- Custom positioning
|
|
371
|
+
- Skip/close functionality
|
|
372
|
+
- ARIA accessibility
|
|
373
|
+
- Full i18n support
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { defineStory, useSelect } from '@djangocfg/playground';
|
|
5
|
+
import { Button, Card, CardContent, CardHeader, CardTitle } from '@djangocfg/ui-core/components';
|
|
6
|
+
import { Tour, useTour } from './index';
|
|
7
|
+
import type { Tour as TourConfig } from './types';
|
|
8
|
+
|
|
9
|
+
export default defineStory({
|
|
10
|
+
title: 'Tools/Tour',
|
|
11
|
+
component: Tour,
|
|
12
|
+
description: 'Onboarding tours with spotlight highlighting and keyboard navigation.',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Demo tours
|
|
16
|
+
const demoTours: TourConfig[] = [
|
|
17
|
+
{
|
|
18
|
+
id: 'basic',
|
|
19
|
+
steps: [
|
|
20
|
+
{
|
|
21
|
+
id: 'welcome',
|
|
22
|
+
target: '[data-tour-step-id="header"]',
|
|
23
|
+
title: 'Welcome to the Demo',
|
|
24
|
+
content: 'This tour will show you the main features of this page.',
|
|
25
|
+
position: 'bottom',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'sidebar',
|
|
29
|
+
target: '[data-tour-step-id="sidebar"]',
|
|
30
|
+
title: 'Navigation Sidebar',
|
|
31
|
+
content: 'Use this sidebar to navigate between different sections.',
|
|
32
|
+
position: 'right',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'content',
|
|
36
|
+
target: '[data-tour-step-id="main-content"]',
|
|
37
|
+
title: 'Main Content Area',
|
|
38
|
+
content: 'This is where all your content will be displayed.',
|
|
39
|
+
position: 'top',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'actions',
|
|
43
|
+
target: '[data-tour-step-id="action-button"]',
|
|
44
|
+
title: 'Quick Actions',
|
|
45
|
+
content: 'Click here to perform quick actions.',
|
|
46
|
+
position: 'left',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'cards',
|
|
52
|
+
defaults: {
|
|
53
|
+
spotlightPadding: 12,
|
|
54
|
+
},
|
|
55
|
+
steps: [
|
|
56
|
+
{
|
|
57
|
+
id: 'step-1',
|
|
58
|
+
target: '[data-tour-step-id="card-1"]',
|
|
59
|
+
title: 'First Card',
|
|
60
|
+
content: 'This card shows important information.',
|
|
61
|
+
position: 'bottom',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'step-2',
|
|
65
|
+
target: '[data-tour-step-id="card-2"]',
|
|
66
|
+
title: 'Second Card',
|
|
67
|
+
content: 'Another important card with different data.',
|
|
68
|
+
position: 'bottom',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'step-3',
|
|
72
|
+
target: '[data-tour-step-id="card-3"]',
|
|
73
|
+
title: 'Third Card',
|
|
74
|
+
content: 'The last card in this row.',
|
|
75
|
+
position: 'bottom',
|
|
76
|
+
nextLabel: 'Complete Tour',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// Demo component with tour targets
|
|
83
|
+
function DemoLayout() {
|
|
84
|
+
const { start, isActive, currentStepIndex, totalSteps } = useTour();
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="min-h-[500px] rounded-lg bg-muted/30 p-4">
|
|
88
|
+
{/* Header */}
|
|
89
|
+
<header
|
|
90
|
+
data-tour-step-id="header"
|
|
91
|
+
className="mb-4 rounded-lg bg-primary p-4 text-primary-foreground"
|
|
92
|
+
>
|
|
93
|
+
<div className="flex items-center justify-between">
|
|
94
|
+
<h1 className="text-xl font-bold">Tour Demo</h1>
|
|
95
|
+
<div className="flex gap-2">
|
|
96
|
+
<Button
|
|
97
|
+
variant="secondary"
|
|
98
|
+
size="sm"
|
|
99
|
+
onClick={() => start('basic')}
|
|
100
|
+
disabled={isActive}
|
|
101
|
+
>
|
|
102
|
+
Basic Tour
|
|
103
|
+
</Button>
|
|
104
|
+
<Button
|
|
105
|
+
variant="secondary"
|
|
106
|
+
size="sm"
|
|
107
|
+
onClick={() => start('cards')}
|
|
108
|
+
disabled={isActive}
|
|
109
|
+
>
|
|
110
|
+
Cards Tour
|
|
111
|
+
</Button>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</header>
|
|
115
|
+
|
|
116
|
+
<div className="flex gap-4">
|
|
117
|
+
{/* Sidebar */}
|
|
118
|
+
<aside
|
|
119
|
+
data-tour-step-id="sidebar"
|
|
120
|
+
className="w-48 rounded-lg bg-card p-4 shadow"
|
|
121
|
+
>
|
|
122
|
+
<nav className="space-y-2">
|
|
123
|
+
<div className="rounded bg-muted p-2 text-sm">Dashboard</div>
|
|
124
|
+
<div className="rounded p-2 text-sm hover:bg-muted">Settings</div>
|
|
125
|
+
<div className="rounded p-2 text-sm hover:bg-muted">Profile</div>
|
|
126
|
+
<div className="rounded p-2 text-sm hover:bg-muted">Help</div>
|
|
127
|
+
</nav>
|
|
128
|
+
</aside>
|
|
129
|
+
|
|
130
|
+
{/* Main content */}
|
|
131
|
+
<main className="flex-1 space-y-4">
|
|
132
|
+
<div
|
|
133
|
+
data-tour-step-id="main-content"
|
|
134
|
+
className="rounded-lg bg-card p-6 shadow"
|
|
135
|
+
>
|
|
136
|
+
<h2 className="mb-4 text-lg font-semibold">Main Content</h2>
|
|
137
|
+
<p className="text-muted-foreground">
|
|
138
|
+
This is the main content area.
|
|
139
|
+
{isActive && (
|
|
140
|
+
<span className="ml-2 text-primary">
|
|
141
|
+
(Step {currentStepIndex + 1} of {totalSteps})
|
|
142
|
+
</span>
|
|
143
|
+
)}
|
|
144
|
+
</p>
|
|
145
|
+
|
|
146
|
+
<Button data-tour-step-id="action-button" className="mt-4">
|
|
147
|
+
Quick Action
|
|
148
|
+
</Button>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Cards row */}
|
|
152
|
+
<div className="grid grid-cols-3 gap-4">
|
|
153
|
+
<Card data-tour-step-id="card-1">
|
|
154
|
+
<CardHeader>
|
|
155
|
+
<CardTitle className="text-base">Card 1</CardTitle>
|
|
156
|
+
</CardHeader>
|
|
157
|
+
<CardContent>
|
|
158
|
+
<p className="text-sm text-muted-foreground">First card content.</p>
|
|
159
|
+
</CardContent>
|
|
160
|
+
</Card>
|
|
161
|
+
|
|
162
|
+
<Card data-tour-step-id="card-2">
|
|
163
|
+
<CardHeader>
|
|
164
|
+
<CardTitle className="text-base">Card 2</CardTitle>
|
|
165
|
+
</CardHeader>
|
|
166
|
+
<CardContent>
|
|
167
|
+
<p className="text-sm text-muted-foreground">Second card content.</p>
|
|
168
|
+
</CardContent>
|
|
169
|
+
</Card>
|
|
170
|
+
|
|
171
|
+
<Card data-tour-step-id="card-3">
|
|
172
|
+
<CardHeader>
|
|
173
|
+
<CardTitle className="text-base">Card 3</CardTitle>
|
|
174
|
+
</CardHeader>
|
|
175
|
+
<CardContent>
|
|
176
|
+
<p className="text-sm text-muted-foreground">Third card content.</p>
|
|
177
|
+
</CardContent>
|
|
178
|
+
</Card>
|
|
179
|
+
</div>
|
|
180
|
+
</main>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const Interactive = () => {
|
|
187
|
+
const [progressVariant] = useSelect('progressVariant', {
|
|
188
|
+
options: ['dots', 'bar', 'fraction', 'none'] as const,
|
|
189
|
+
defaultValue: 'dots',
|
|
190
|
+
label: 'Progress Variant',
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<Tour
|
|
195
|
+
tours={demoTours}
|
|
196
|
+
progressVariant={progressVariant}
|
|
197
|
+
onStart={(id) => console.log('Tour started:', id)}
|
|
198
|
+
onComplete={(id) => console.log('Tour completed:', id)}
|
|
199
|
+
onSkip={(id, step) => console.log('Tour skipped:', id, 'at step', step)}
|
|
200
|
+
onStepChange={(index, step, direction) =>
|
|
201
|
+
console.log('Step:', index, step.id, direction)
|
|
202
|
+
}
|
|
203
|
+
>
|
|
204
|
+
<DemoLayout />
|
|
205
|
+
</Tour>
|
|
206
|
+
);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const WithProgressBar = () => (
|
|
210
|
+
<Tour tours={demoTours} progressVariant="bar">
|
|
211
|
+
<DemoLayout />
|
|
212
|
+
</Tour>
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
export const WithFraction = () => (
|
|
216
|
+
<Tour tours={demoTours} progressVariant="fraction">
|
|
217
|
+
<DemoLayout />
|
|
218
|
+
</Tour>
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
export const NoSpotlight = () => (
|
|
222
|
+
<Tour tours={demoTours} spotlight={false}>
|
|
223
|
+
<DemoLayout />
|
|
224
|
+
</Tour>
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
export const CloseOnOverlayClick = () => (
|
|
228
|
+
<Tour tours={demoTours} closeOnOverlayClick>
|
|
229
|
+
<DemoLayout />
|
|
230
|
+
</Tour>
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
export const NoSkipButton = () => (
|
|
234
|
+
<Tour tours={demoTours} showSkipButton={false}>
|
|
235
|
+
<DemoLayout />
|
|
236
|
+
</Tour>
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
export const WithArrow = () => (
|
|
240
|
+
<Tour tours={demoTours} showArrow>
|
|
241
|
+
<DemoLayout />
|
|
242
|
+
</Tour>
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
export const WithBlur = () => (
|
|
246
|
+
<Tour tours={demoTours} spotlightBlur={4} spotlightOpacity={0.3}>
|
|
247
|
+
<DemoLayout />
|
|
248
|
+
</Tour>
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
export const WithArrowAndBlur = () => (
|
|
252
|
+
<Tour tours={demoTours} showArrow spotlightBlur={3} spotlightOpacity={0.4}>
|
|
253
|
+
<DemoLayout />
|
|
254
|
+
</Tour>
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
export const WithPulseRing = () => (
|
|
258
|
+
<Tour tours={demoTours} pulseRing>
|
|
259
|
+
<DemoLayout />
|
|
260
|
+
</Tour>
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
export const NoAnimation = () => (
|
|
264
|
+
<Tour tours={demoTours} animated={false}>
|
|
265
|
+
<DemoLayout />
|
|
266
|
+
</Tour>
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
export const FullFeatured = () => (
|
|
270
|
+
<Tour
|
|
271
|
+
tours={demoTours}
|
|
272
|
+
showArrow
|
|
273
|
+
spotlightBlur={2}
|
|
274
|
+
spotlightOpacity={0.4}
|
|
275
|
+
pulseRing
|
|
276
|
+
>
|
|
277
|
+
<DemoLayout />
|
|
278
|
+
</Tour>
|
|
279
|
+
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { TourProvider } from '../context/TourProvider';
|
|
4
|
+
import type { TourProps } from '../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* All-in-one Tour component.
|
|
8
|
+
* Combines TourProvider with children for simple usage.
|
|
9
|
+
*/
|
|
10
|
+
export function Tour({ children, ...providerProps }: TourProps) {
|
|
11
|
+
return <TourProvider {...providerProps}>{children}</TourProvider>;
|
|
12
|
+
}
|