@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-tools",
3
- "version": "2.1.162",
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.162",
77
- "@djangocfg/ui-core": "^2.1.162",
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.162",
115
+ "@djangocfg/i18n": "^2.1.164",
111
116
  "@djangocfg/playground": "workspace:*",
112
- "@djangocfg/typescript-config": "^2.1.162",
113
- "@djangocfg/ui-core": "^2.1.162",
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
+ }