@nextsparkjs/plugin-walkme 0.1.0-beta.104
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/.env.example +23 -0
- package/LICENSE +21 -0
- package/README.md +625 -0
- package/components/WalkmeBeacon.tsx +64 -0
- package/components/WalkmeControls.tsx +111 -0
- package/components/WalkmeModal.tsx +144 -0
- package/components/WalkmeOverlay.tsx +107 -0
- package/components/WalkmeProgress.tsx +53 -0
- package/components/WalkmeProvider.tsx +674 -0
- package/components/WalkmeSpotlight.tsx +188 -0
- package/components/WalkmeTooltip.tsx +152 -0
- package/examples/basic-tour.ts +38 -0
- package/examples/conditional-tour.ts +56 -0
- package/examples/cross-window-tour.ts +54 -0
- package/hooks/useTour.ts +52 -0
- package/hooks/useTourProgress.ts +38 -0
- package/hooks/useTourState.ts +146 -0
- package/hooks/useWalkme.ts +52 -0
- package/jest.config.cjs +27 -0
- package/lib/conditions.ts +113 -0
- package/lib/core.ts +323 -0
- package/lib/plugin-env.ts +87 -0
- package/lib/positioning.ts +172 -0
- package/lib/storage.ts +203 -0
- package/lib/targeting.ts +186 -0
- package/lib/triggers.ts +127 -0
- package/lib/validation.ts +122 -0
- package/messages/en.json +21 -0
- package/messages/es.json +21 -0
- package/package.json +18 -0
- package/plugin.config.ts +26 -0
- package/providers/walkme-context.ts +17 -0
- package/tests/lib/conditions.test.ts +172 -0
- package/tests/lib/core.test.ts +514 -0
- package/tests/lib/positioning.test.ts +43 -0
- package/tests/lib/storage.test.ts +232 -0
- package/tests/lib/targeting.test.ts +191 -0
- package/tests/lib/triggers.test.ts +198 -0
- package/tests/lib/validation.test.ts +249 -0
- package/tests/setup.ts +52 -0
- package/tests/tsconfig.json +32 -0
- package/tsconfig.json +47 -0
- package/types/walkme.types.ts +316 -0
package/.env.example
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# ============================================
|
|
2
|
+
# WALKME PLUGIN CONFIGURATION
|
|
3
|
+
# ============================================
|
|
4
|
+
# Copy this file to .env
|
|
5
|
+
# Priority: Plugin .env > Root .env > Defaults
|
|
6
|
+
|
|
7
|
+
# Enable/disable the plugin
|
|
8
|
+
WALKME_ENABLED=true
|
|
9
|
+
|
|
10
|
+
# Debug mode (logs extra info to console)
|
|
11
|
+
WALKME_DEBUG=false
|
|
12
|
+
|
|
13
|
+
# Auto-start tours on first visit
|
|
14
|
+
WALKME_AUTO_START=true
|
|
15
|
+
|
|
16
|
+
# Delay before starting auto tours (ms)
|
|
17
|
+
WALKME_AUTO_START_DELAY=1000
|
|
18
|
+
|
|
19
|
+
# Persist state in localStorage
|
|
20
|
+
WALKME_PERSIST_STATE=true
|
|
21
|
+
|
|
22
|
+
# Analytics integration (emit events)
|
|
23
|
+
WALKME_ANALYTICS_ENABLED=false
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 NextSpark
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
# @nextsparkjs/plugin-walkme
|
|
2
|
+
|
|
3
|
+
Guided tours and onboarding system for NextSpark applications. Supports declarative tour definitions, multiple step types (tooltip, modal, spotlight, beacon), cross-page navigation, conditional triggers, localStorage persistence, full keyboard accessibility, and i18n.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
The plugin is available as an npm package or can be copied directly into your project.
|
|
8
|
+
|
|
9
|
+
### npm (recommended)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @nextsparkjs/plugin-walkme
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Manual
|
|
16
|
+
|
|
17
|
+
Copy the `plugins/walkme/` directory to `contents/plugins/walkme/` in your NextSpark project.
|
|
18
|
+
|
|
19
|
+
### Register the plugin
|
|
20
|
+
|
|
21
|
+
Add `'walkme'` to your theme configuration:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// contents/themes/<your-theme>/config/theme.config.ts
|
|
25
|
+
export const themeConfig: ThemeConfig = {
|
|
26
|
+
plugins: ['walkme'],
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Rebuild the registry:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
node core/scripts/build/registry.mjs
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### 1. Define a tour
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import type { Tour } from '@nextsparkjs/plugin-walkme/types/walkme.types'
|
|
42
|
+
|
|
43
|
+
const onboardingTour: Tour = {
|
|
44
|
+
id: 'getting-started',
|
|
45
|
+
name: 'Getting Started',
|
|
46
|
+
trigger: { type: 'onFirstVisit', delay: 1000 },
|
|
47
|
+
steps: [
|
|
48
|
+
{
|
|
49
|
+
id: 'welcome',
|
|
50
|
+
type: 'modal',
|
|
51
|
+
title: 'Welcome!',
|
|
52
|
+
content: 'Let us show you around the application.',
|
|
53
|
+
actions: ['next', 'skip'],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'sidebar',
|
|
57
|
+
type: 'tooltip',
|
|
58
|
+
target: '[data-cy="sidebar-nav"]',
|
|
59
|
+
title: 'Navigation',
|
|
60
|
+
content: 'Use the sidebar to navigate between sections.',
|
|
61
|
+
position: 'right',
|
|
62
|
+
actions: ['next', 'prev', 'skip'],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'create',
|
|
66
|
+
type: 'spotlight',
|
|
67
|
+
target: '[data-cy="create-button"]',
|
|
68
|
+
title: 'Create New Item',
|
|
69
|
+
content: 'Click here to create your first item.',
|
|
70
|
+
actions: ['complete', 'prev'],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Wrap your app with the provider
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { WalkmeProvider } from '@nextsparkjs/plugin-walkme/components/WalkmeProvider'
|
|
80
|
+
|
|
81
|
+
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
82
|
+
return (
|
|
83
|
+
<WalkmeProvider tours={[onboardingTour]} autoStart>
|
|
84
|
+
{children}
|
|
85
|
+
</WalkmeProvider>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. (Optional) Control tours programmatically
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { useWalkme } from '@nextsparkjs/plugin-walkme/hooks/useWalkme'
|
|
94
|
+
|
|
95
|
+
function HelpButton() {
|
|
96
|
+
const { startTour, isActive } = useWalkme()
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<button onClick={() => startTour('getting-started')} disabled={isActive}>
|
|
100
|
+
Start Tour
|
|
101
|
+
</button>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
### Tour
|
|
109
|
+
|
|
110
|
+
| Property | Type | Required | Description |
|
|
111
|
+
|---|---|---|---|
|
|
112
|
+
| `id` | `string` | Yes | Unique identifier |
|
|
113
|
+
| `name` | `string` | Yes | Human-readable name |
|
|
114
|
+
| `description` | `string` | No | Tour description |
|
|
115
|
+
| `trigger` | `TourTrigger` | Yes | When and how the tour activates |
|
|
116
|
+
| `conditions` | `TourConditions` | No | Conditions that must be met to show the tour |
|
|
117
|
+
| `steps` | `TourStep[]` | Yes | Ordered list of steps (minimum 1) |
|
|
118
|
+
| `onComplete` | `() => void` | No | Callback on tour completion |
|
|
119
|
+
| `onSkip` | `() => void` | No | Callback on tour skip |
|
|
120
|
+
| `priority` | `number` | No | Auto-trigger ordering (lower = higher priority) |
|
|
121
|
+
|
|
122
|
+
### TourStep
|
|
123
|
+
|
|
124
|
+
| Property | Type | Required | Description |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `id` | `string` | Yes | Unique step identifier |
|
|
127
|
+
| `type` | `StepType` | Yes | `'tooltip'` \| `'modal'` \| `'spotlight'` \| `'beacon'` \| `'floating'` |
|
|
128
|
+
| `title` | `string` | Yes | Step title |
|
|
129
|
+
| `content` | `string` | Yes | Step body text |
|
|
130
|
+
| `target` | `string` | Conditional | CSS selector or `data-walkme-target` value. Required for tooltip, spotlight, beacon |
|
|
131
|
+
| `route` | `string` | No | Route path for cross-page steps |
|
|
132
|
+
| `position` | `StepPosition` | No | `'top'` \| `'bottom'` \| `'left'` \| `'right'` \| `'auto'`. Default: `'auto'` |
|
|
133
|
+
| `actions` | `StepAction[]` | Yes | Available actions: `'next'`, `'prev'`, `'skip'`, `'complete'`, `'close'` |
|
|
134
|
+
| `delay` | `number` | No | Delay in ms before showing |
|
|
135
|
+
| `autoAdvance` | `number` | No | Auto-advance after this many ms |
|
|
136
|
+
| `beforeShow` | `() => void` | No | Callback before the step renders |
|
|
137
|
+
| `afterShow` | `() => void` | No | Callback after the step renders |
|
|
138
|
+
|
|
139
|
+
### Step Types
|
|
140
|
+
|
|
141
|
+
- **`modal`** - Centered overlay modal. No target element required. Use for welcome screens and informational messages.
|
|
142
|
+
- **`tooltip`** - Anchored tooltip positioned next to a target element. Requires `target`.
|
|
143
|
+
- **`spotlight`** - Overlay with a cutout around the target element plus a tooltip. Requires `target`.
|
|
144
|
+
- **`beacon`** - Pulsing indicator on a target element that expands on click. Requires `target`.
|
|
145
|
+
- **`floating`** - Same as modal. Alias for centered content without a target.
|
|
146
|
+
|
|
147
|
+
### TourTrigger
|
|
148
|
+
|
|
149
|
+
| Property | Type | Required | Description |
|
|
150
|
+
|---|---|---|---|
|
|
151
|
+
| `type` | `TriggerType` | Yes | `'onFirstVisit'` \| `'onRouteEnter'` \| `'onEvent'` \| `'manual'` \| `'scheduled'` |
|
|
152
|
+
| `delay` | `number` | No | Delay in ms before activating |
|
|
153
|
+
| `route` | `string` | No | Route pattern for `onRouteEnter` (supports `*` and `**` wildcards) |
|
|
154
|
+
| `event` | `string` | No | Event name for `onEvent` trigger |
|
|
155
|
+
| `afterVisits` | `number` | No | Activate after N visits (for `scheduled`) |
|
|
156
|
+
| `afterDays` | `number` | No | Activate after N days since first visit (for `scheduled`) |
|
|
157
|
+
|
|
158
|
+
**Trigger types:**
|
|
159
|
+
|
|
160
|
+
- `onFirstVisit` - Fires on the user's first page visit (visitCount === 1).
|
|
161
|
+
- `onRouteEnter` - Fires when the user navigates to a matching route. Supports exact matches (`/dashboard`), wildcard (`/admin/*`), and glob (`/docs/**`).
|
|
162
|
+
- `onEvent` - Fires when a custom event is emitted via `emitEvent()`.
|
|
163
|
+
- `manual` - Never auto-triggers. Start programmatically with `startTour(tourId)`.
|
|
164
|
+
- `scheduled` - Fires after a number of visits (`afterVisits`) or days since first visit (`afterDays`).
|
|
165
|
+
|
|
166
|
+
### TourConditions
|
|
167
|
+
|
|
168
|
+
All conditions use AND logic. Every specified condition must pass.
|
|
169
|
+
|
|
170
|
+
| Property | Type | Description |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `userRole` | `string[]` | User must have one of these roles |
|
|
173
|
+
| `featureFlags` | `string[]` | All specified flags must be active |
|
|
174
|
+
| `completedTours` | `string[]` | All specified tours must be completed first |
|
|
175
|
+
| `notCompletedTours` | `string[]` | None of these tours should be completed |
|
|
176
|
+
| `custom` | `(ctx: ConditionContext) => boolean` | Custom condition function |
|
|
177
|
+
|
|
178
|
+
## WalkmeProvider Props
|
|
179
|
+
|
|
180
|
+
| Prop | Type | Default | Description |
|
|
181
|
+
|---|---|---|---|
|
|
182
|
+
| `tours` | `Tour[]` | Required | Array of tour definitions (validated with Zod at runtime) |
|
|
183
|
+
| `children` | `ReactNode` | Required | Application content |
|
|
184
|
+
| `debug` | `boolean` | `false` | Enable debug logging to console |
|
|
185
|
+
| `autoStart` | `boolean` | `true` | Auto-start eligible tours based on triggers |
|
|
186
|
+
| `autoStartDelay` | `number` | `1000` | Default delay before auto-starting tours (ms) |
|
|
187
|
+
| `persistState` | `boolean` | `true` | Persist tour state in localStorage |
|
|
188
|
+
| `onTourStart` | `(event: TourEvent) => void` | - | Callback when a tour starts |
|
|
189
|
+
| `onTourComplete` | `(event: TourEvent) => void` | - | Callback when a tour completes |
|
|
190
|
+
| `onTourSkip` | `(event: TourEvent) => void` | - | Callback when a tour is skipped |
|
|
191
|
+
| `onStepChange` | `(event: TourEvent) => void` | - | Callback when the active step changes |
|
|
192
|
+
| `conditionContext` | `Partial<ConditionContext>` | - | External context for condition evaluation (userRole, featureFlags) |
|
|
193
|
+
|
|
194
|
+
## Hooks
|
|
195
|
+
|
|
196
|
+
### `useWalkme()`
|
|
197
|
+
|
|
198
|
+
Main hook for controlling tours. Must be used within a `<WalkmeProvider>`.
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
const {
|
|
202
|
+
// Tour control
|
|
203
|
+
startTour, // (tourId: string) => void
|
|
204
|
+
pauseTour, // () => void
|
|
205
|
+
resumeTour, // () => void
|
|
206
|
+
skipTour, // () => void
|
|
207
|
+
completeTour, // () => void
|
|
208
|
+
resetTour, // (tourId: string) => void
|
|
209
|
+
resetAllTours, // () => void
|
|
210
|
+
|
|
211
|
+
// Step navigation
|
|
212
|
+
nextStep, // () => void
|
|
213
|
+
prevStep, // () => void
|
|
214
|
+
goToStep, // (stepIndex: number) => void
|
|
215
|
+
|
|
216
|
+
// State
|
|
217
|
+
isActive, // boolean - whether any tour is active
|
|
218
|
+
activeTourId, // string | null
|
|
219
|
+
currentStepIndex, // number
|
|
220
|
+
|
|
221
|
+
// Queries
|
|
222
|
+
getActiveTour, // () => Tour | null
|
|
223
|
+
getActiveStep, // () => TourStep | null
|
|
224
|
+
isTourCompleted, // (tourId: string) => boolean
|
|
225
|
+
isTourActive, // (tourId: string) => boolean
|
|
226
|
+
|
|
227
|
+
// Events
|
|
228
|
+
emitEvent, // (eventName: string) => void
|
|
229
|
+
} = useWalkme()
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### `useTour(tourId)`
|
|
233
|
+
|
|
234
|
+
Hook for tracking the state of a specific tour.
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
const {
|
|
238
|
+
tour, // Tour | null - full tour definition
|
|
239
|
+
isActive, // boolean
|
|
240
|
+
isCompleted, // boolean
|
|
241
|
+
isSkipped, // boolean
|
|
242
|
+
currentStep, // number (-1 if not active)
|
|
243
|
+
totalSteps, // number
|
|
244
|
+
progress, // number (0-100)
|
|
245
|
+
start, // () => void - start this tour
|
|
246
|
+
reset, // () => void - reset this tour
|
|
247
|
+
} = useTour('getting-started')
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### `useTourProgress()`
|
|
251
|
+
|
|
252
|
+
Hook for tracking global completion progress across all tours.
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
const {
|
|
256
|
+
completedTours, // number
|
|
257
|
+
totalTours, // number
|
|
258
|
+
percentage, // number (0-100)
|
|
259
|
+
completedTourIds, // string[]
|
|
260
|
+
skippedTourIds, // string[]
|
|
261
|
+
remainingTours, // number
|
|
262
|
+
} = useTourProgress()
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Examples
|
|
266
|
+
|
|
267
|
+
### Single-Page Tour
|
|
268
|
+
|
|
269
|
+
A basic onboarding flow on one page:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const basicTour: Tour = {
|
|
273
|
+
id: 'getting-started',
|
|
274
|
+
name: 'Getting Started',
|
|
275
|
+
trigger: { type: 'onFirstVisit', delay: 1000 },
|
|
276
|
+
steps: [
|
|
277
|
+
{
|
|
278
|
+
id: 'welcome',
|
|
279
|
+
type: 'modal',
|
|
280
|
+
title: 'Welcome!',
|
|
281
|
+
content: 'Let us show you around.',
|
|
282
|
+
actions: ['next', 'skip'],
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: 'sidebar',
|
|
286
|
+
type: 'tooltip',
|
|
287
|
+
target: '[data-cy="sidebar-nav"]',
|
|
288
|
+
title: 'Navigation',
|
|
289
|
+
content: 'Use the sidebar to navigate.',
|
|
290
|
+
position: 'right',
|
|
291
|
+
actions: ['next', 'prev', 'skip'],
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: 'create',
|
|
295
|
+
type: 'spotlight',
|
|
296
|
+
target: '[data-cy="create-button"]',
|
|
297
|
+
title: 'Create Item',
|
|
298
|
+
content: 'Click here to create your first item.',
|
|
299
|
+
actions: ['complete', 'prev'],
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Cross-Page Tour
|
|
306
|
+
|
|
307
|
+
Navigate users between pages during a tour:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
const crossPageTour: Tour = {
|
|
311
|
+
id: 'explore-app',
|
|
312
|
+
name: 'Explore the App',
|
|
313
|
+
trigger: { type: 'manual' },
|
|
314
|
+
conditions: { completedTours: ['getting-started'] },
|
|
315
|
+
steps: [
|
|
316
|
+
{
|
|
317
|
+
id: 'dashboard',
|
|
318
|
+
type: 'tooltip',
|
|
319
|
+
target: '[data-cy="dashboard-stats"]',
|
|
320
|
+
title: 'Your Stats',
|
|
321
|
+
content: 'Key metrics at a glance.',
|
|
322
|
+
position: 'bottom',
|
|
323
|
+
route: '/dashboard',
|
|
324
|
+
actions: ['next', 'skip'],
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: 'profile',
|
|
328
|
+
type: 'spotlight',
|
|
329
|
+
target: '[data-cy="profile-form"]',
|
|
330
|
+
title: 'Your Profile',
|
|
331
|
+
content: 'Complete your profile.',
|
|
332
|
+
route: '/settings/profile',
|
|
333
|
+
actions: ['complete', 'prev'],
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
When the tour reaches a step with a `route` that differs from the current page, the provider automatically navigates using `router.push()` and waits for the target element to appear.
|
|
340
|
+
|
|
341
|
+
### Conditional Tour
|
|
342
|
+
|
|
343
|
+
Show tours only to specific user roles or when feature flags are active:
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
const adminTour: Tour = {
|
|
347
|
+
id: 'admin-features',
|
|
348
|
+
name: 'Admin Features',
|
|
349
|
+
priority: 10,
|
|
350
|
+
trigger: { type: 'onRouteEnter', route: '/admin/*', delay: 500 },
|
|
351
|
+
conditions: {
|
|
352
|
+
userRole: ['admin', 'superadmin'],
|
|
353
|
+
completedTours: ['getting-started'],
|
|
354
|
+
featureFlags: ['admin-panel-v2'],
|
|
355
|
+
},
|
|
356
|
+
steps: [
|
|
357
|
+
{
|
|
358
|
+
id: 'admin-welcome',
|
|
359
|
+
type: 'modal',
|
|
360
|
+
title: 'Admin Dashboard',
|
|
361
|
+
content: 'Here are the key admin features.',
|
|
362
|
+
actions: ['next', 'skip'],
|
|
363
|
+
},
|
|
364
|
+
// ...more steps
|
|
365
|
+
],
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Pass the user context to the provider:
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
<WalkmeProvider
|
|
373
|
+
tours={[adminTour]}
|
|
374
|
+
conditionContext={{
|
|
375
|
+
userRole: currentUser.role,
|
|
376
|
+
featureFlags: activeFlags,
|
|
377
|
+
}}
|
|
378
|
+
>
|
|
379
|
+
<App />
|
|
380
|
+
</WalkmeProvider>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Programmatic Tour Control
|
|
384
|
+
|
|
385
|
+
Start tours on demand and track completion:
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
function OnboardingDashboard() {
|
|
389
|
+
const { startTour } = useWalkme()
|
|
390
|
+
const { completedTours, totalTours, percentage } = useTourProgress()
|
|
391
|
+
const intro = useTour('getting-started')
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<div>
|
|
395
|
+
<h2>Onboarding Progress: {percentage}%</h2>
|
|
396
|
+
<p>{completedTours} of {totalTours} tours completed</p>
|
|
397
|
+
|
|
398
|
+
{!intro.isCompleted && (
|
|
399
|
+
<button onClick={intro.start}>Start Getting Started Tour</button>
|
|
400
|
+
)}
|
|
401
|
+
|
|
402
|
+
<button onClick={() => startTour('advanced-features')}>
|
|
403
|
+
Show Advanced Features
|
|
404
|
+
</button>
|
|
405
|
+
</div>
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Analytics Integration
|
|
411
|
+
|
|
412
|
+
Track tour events for analytics:
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
<WalkmeProvider
|
|
416
|
+
tours={tours}
|
|
417
|
+
onTourStart={(event) => {
|
|
418
|
+
analytics.track('tour_started', {
|
|
419
|
+
tourId: event.tourId,
|
|
420
|
+
tourName: event.tourName,
|
|
421
|
+
})
|
|
422
|
+
}}
|
|
423
|
+
onTourComplete={(event) => {
|
|
424
|
+
analytics.track('tour_completed', {
|
|
425
|
+
tourId: event.tourId,
|
|
426
|
+
totalSteps: event.totalSteps,
|
|
427
|
+
})
|
|
428
|
+
}}
|
|
429
|
+
onTourSkip={(event) => {
|
|
430
|
+
analytics.track('tour_skipped', {
|
|
431
|
+
tourId: event.tourId,
|
|
432
|
+
stepIndex: event.stepIndex,
|
|
433
|
+
})
|
|
434
|
+
}}
|
|
435
|
+
onStepChange={(event) => {
|
|
436
|
+
analytics.track('step_changed', {
|
|
437
|
+
tourId: event.tourId,
|
|
438
|
+
stepId: event.stepId,
|
|
439
|
+
stepIndex: event.stepIndex,
|
|
440
|
+
})
|
|
441
|
+
}}
|
|
442
|
+
>
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Customization
|
|
446
|
+
|
|
447
|
+
### Element Targeting
|
|
448
|
+
|
|
449
|
+
Steps can target elements using:
|
|
450
|
+
|
|
451
|
+
1. **CSS selectors** - `#my-id`, `.my-class`, `[data-cy="value"]`
|
|
452
|
+
2. **data-walkme-target** - Add `data-walkme-target="name"` to any element, then reference as `target: "name"` (plain string without special CSS chars)
|
|
453
|
+
3. **data-cy** - Standard test selectors: `target: '[data-cy="create-button"]'`
|
|
454
|
+
|
|
455
|
+
If the target element is not found within 5 seconds, the step renders without an anchor and a warning is logged in debug mode.
|
|
456
|
+
|
|
457
|
+
### Internationalization
|
|
458
|
+
|
|
459
|
+
The plugin ships with English and Spanish translations under `plugins/walkme/messages/`. To add more languages, create a JSON file following the same structure:
|
|
460
|
+
|
|
461
|
+
```json
|
|
462
|
+
{
|
|
463
|
+
"walkme": {
|
|
464
|
+
"next": "Next",
|
|
465
|
+
"prev": "Previous",
|
|
466
|
+
"skip": "Skip tour",
|
|
467
|
+
"complete": "Complete",
|
|
468
|
+
"close": "Close",
|
|
469
|
+
"progress": "Step {current} of {total}",
|
|
470
|
+
"tourAvailable": "Tour available",
|
|
471
|
+
"beaconLabel": "Click to start guided tour",
|
|
472
|
+
"modalTitle": "Guided Tour",
|
|
473
|
+
"tooltipLabel": "Tour step",
|
|
474
|
+
"spotlightLabel": "Highlighted element",
|
|
475
|
+
"keyboardHint": "Press Arrow Right for next step, Escape to skip",
|
|
476
|
+
"tourCompleted": "Tour completed!",
|
|
477
|
+
"tourSkipped": "Tour skipped",
|
|
478
|
+
"errorTargetNotFound": "Element not found, skipping step",
|
|
479
|
+
"resumeTour": "Resume tour",
|
|
480
|
+
"restartTour": "Restart tour"
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### CSS Variables (Theming)
|
|
486
|
+
|
|
487
|
+
All walkme components use CSS custom properties for styling. Override them in your global CSS or scoped styles:
|
|
488
|
+
|
|
489
|
+
```css
|
|
490
|
+
:root {
|
|
491
|
+
--walkme-bg: #ffffff;
|
|
492
|
+
--walkme-text: #111827;
|
|
493
|
+
--walkme-text-muted: #6b7280;
|
|
494
|
+
--walkme-primary: #3b82f6;
|
|
495
|
+
--walkme-border: #e5e7eb;
|
|
496
|
+
--walkme-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
|
497
|
+
--walkme-beacon-color: #3b82f6;
|
|
498
|
+
--walkme-overlay-color: rgba(0, 0, 0, 0.5);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/* Dark mode */
|
|
502
|
+
.dark {
|
|
503
|
+
--walkme-bg: #1f2937;
|
|
504
|
+
--walkme-text: #f9fafb;
|
|
505
|
+
--walkme-text-muted: #9ca3af;
|
|
506
|
+
--walkme-primary: #60a5fa;
|
|
507
|
+
--walkme-border: #374151;
|
|
508
|
+
--walkme-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
|
509
|
+
--walkme-beacon-color: #60a5fa;
|
|
510
|
+
--walkme-overlay-color: rgba(0, 0, 0, 0.7);
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Keyboard Navigation
|
|
515
|
+
|
|
516
|
+
When a tour is active:
|
|
517
|
+
|
|
518
|
+
| Key | Action |
|
|
519
|
+
|---|---|
|
|
520
|
+
| Arrow Right | Next step |
|
|
521
|
+
| Arrow Left | Previous step |
|
|
522
|
+
| Escape | Skip tour |
|
|
523
|
+
| Tab | Cycle focus within modals (focus trap) |
|
|
524
|
+
|
|
525
|
+
### Accessibility
|
|
526
|
+
|
|
527
|
+
- All interactive elements have ARIA labels and roles
|
|
528
|
+
- Focus is trapped within modals
|
|
529
|
+
- Focus is restored to the previously focused element when a tour ends
|
|
530
|
+
- Progress bar uses `role="progressbar"` with `aria-valuenow`/`aria-valuemin`/`aria-valuemax`
|
|
531
|
+
- Keyboard navigation is fully supported
|
|
532
|
+
|
|
533
|
+
## Environment Variables
|
|
534
|
+
|
|
535
|
+
| Variable | Default | Description |
|
|
536
|
+
|---|---|---|
|
|
537
|
+
| `WALKME_ENABLED` | `true` | Enable/disable the plugin |
|
|
538
|
+
| `WALKME_DEBUG` | `false` | Debug mode |
|
|
539
|
+
| `WALKME_AUTO_START` | `true` | Auto-start tours on first visit |
|
|
540
|
+
| `WALKME_AUTO_START_DELAY` | `1000` | Delay before auto-starting (ms) |
|
|
541
|
+
| `WALKME_PERSIST_STATE` | `true` | Persist state in localStorage |
|
|
542
|
+
| `WALKME_ANALYTICS_ENABLED` | `false` | Enable analytics event emission |
|
|
543
|
+
|
|
544
|
+
## Troubleshooting
|
|
545
|
+
|
|
546
|
+
### Tour does not start automatically
|
|
547
|
+
|
|
548
|
+
1. Check that `autoStart` is `true` on the provider (default).
|
|
549
|
+
2. Verify the trigger type matches the situation (e.g., `onFirstVisit` only fires when `visitCount === 1`).
|
|
550
|
+
3. Check that all conditions are met (role, feature flags, completed tours).
|
|
551
|
+
4. Open debug mode (`debug={true}`) to see console logs.
|
|
552
|
+
5. If using `persistState`, the tour may already be marked as completed or skipped in localStorage. Call `resetTour(tourId)` or clear `walkme-state` from localStorage.
|
|
553
|
+
|
|
554
|
+
### Target element not found
|
|
555
|
+
|
|
556
|
+
1. Ensure the target selector is correct and the element exists in the DOM.
|
|
557
|
+
2. For dynamically rendered elements, the plugin waits up to 5 seconds using MutationObserver + polling.
|
|
558
|
+
3. Use `data-walkme-target="name"` for elements that are hard to select with CSS.
|
|
559
|
+
4. Enable debug mode to see warnings about missing targets.
|
|
560
|
+
|
|
561
|
+
### Cross-page navigation not working
|
|
562
|
+
|
|
563
|
+
1. Verify the `route` property on each step matches the actual path.
|
|
564
|
+
2. The provider uses `router.push()` from `next/navigation`. Ensure you are using the App Router.
|
|
565
|
+
3. If navigation fails, the step is automatically skipped and the tour advances.
|
|
566
|
+
|
|
567
|
+
### State not persisting
|
|
568
|
+
|
|
569
|
+
1. Check that `persistState` is `true` (default).
|
|
570
|
+
2. Verify localStorage is available (not in private browsing mode or incognito with storage disabled).
|
|
571
|
+
3. The state is stored under the key `walkme-state` in localStorage.
|
|
572
|
+
|
|
573
|
+
## File Structure
|
|
574
|
+
|
|
575
|
+
```
|
|
576
|
+
plugins/walkme/
|
|
577
|
+
plugin.config.ts # Plugin configuration
|
|
578
|
+
package.json # Dependencies and metadata
|
|
579
|
+
.env.example # Environment variable template
|
|
580
|
+
types/
|
|
581
|
+
walkme.types.ts # All TypeScript type definitions
|
|
582
|
+
lib/
|
|
583
|
+
core.ts # Pure-function state machine (reducer + helpers)
|
|
584
|
+
validation.ts # Zod schemas for tour config validation
|
|
585
|
+
storage.ts # localStorage persistence adapter
|
|
586
|
+
targeting.ts # DOM element targeting (CSS, data-walkme, data-cy)
|
|
587
|
+
positioning.ts # @floating-ui/react wrapper for smart positioning
|
|
588
|
+
triggers.ts # Tour trigger evaluation
|
|
589
|
+
conditions.ts # Tour condition evaluation (AND logic)
|
|
590
|
+
plugin-env.ts # Environment variable loader
|
|
591
|
+
hooks/
|
|
592
|
+
useWalkme.ts # Main public hook (tour control + navigation)
|
|
593
|
+
useTour.ts # Per-tour state hook
|
|
594
|
+
useTourProgress.ts # Global completion progress hook
|
|
595
|
+
useTourState.ts # Internal: state machine + localStorage sync
|
|
596
|
+
providers/
|
|
597
|
+
walkme-context.ts # React Context definition
|
|
598
|
+
components/
|
|
599
|
+
WalkmeProvider.tsx # Main provider (state, triggers, rendering)
|
|
600
|
+
WalkmeOverlay.tsx # Semi-transparent backdrop overlay
|
|
601
|
+
WalkmeTooltip.tsx # Positioned tooltip step
|
|
602
|
+
WalkmeModal.tsx # Centered modal step with focus trap
|
|
603
|
+
WalkmeSpotlight.tsx # Overlay with cutout + tooltip
|
|
604
|
+
WalkmeBeacon.tsx # Pulsing indicator
|
|
605
|
+
WalkmeProgress.tsx # Progress bar
|
|
606
|
+
WalkmeControls.tsx # Navigation buttons (Next/Prev/Skip/Complete)
|
|
607
|
+
messages/
|
|
608
|
+
en.json # English translations
|
|
609
|
+
es.json # Spanish translations
|
|
610
|
+
examples/
|
|
611
|
+
basic-tour.ts # Single-page tour example
|
|
612
|
+
cross-window-tour.ts # Multi-page tour example
|
|
613
|
+
conditional-tour.ts # Role-based conditional tour example
|
|
614
|
+
tests/
|
|
615
|
+
setup.ts # Jest test setup
|
|
616
|
+
tsconfig.json # Test-specific TypeScript config
|
|
617
|
+
lib/ # Unit tests for all lib modules (165 tests)
|
|
618
|
+
jest.config.cjs # Jest configuration
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## Dependencies
|
|
622
|
+
|
|
623
|
+
- **`@floating-ui/react`** ^0.27.0 - Smart positioning for tooltips and popovers
|
|
624
|
+
|
|
625
|
+
Peer dependencies (provided by the host project): `react`, `react-dom`, `next`, `zod`, `next-intl`, `lucide-react`, `class-variance-authority`, `clsx`, `tailwind-merge`.
|