@korsolutions/guidon 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,334 @@
1
+ # guidon
2
+
3
+ A cross-platform walkthrough/onboarding component library for React Native with web support. Features spotlight effects, customizable tooltips, and flexible persistence options.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ yarn add @guidon
9
+ # or
10
+ npm install @guidon
11
+ ```
12
+
13
+ ### Peer Dependencies
14
+
15
+ Make sure you have these dependencies installed:
16
+
17
+ ```bash
18
+ yarn add react react-native react-native-reanimated react-native-safe-area-context react-native-svg zustand
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Define Your Guidon Steps
24
+
25
+ ```tsx
26
+ import type { GuidonConfig } from '@guidon';
27
+
28
+ const exploreGuidonConfig: GuidonConfig = {
29
+ id: 'explore-guidon',
30
+ steps: [
31
+ {
32
+ id: 'welcome',
33
+ targetId: 'search-button',
34
+ title: 'Welcome!',
35
+ description: 'Let us show you around the app.',
36
+ tooltipPosition: 'bottom',
37
+ },
38
+ {
39
+ id: 'filters',
40
+ targetId: 'filter-button',
41
+ title: 'Filters',
42
+ description: 'Use filters to find exactly what you need.',
43
+ tooltipPosition: 'bottom',
44
+ },
45
+ {
46
+ id: 'card-actions',
47
+ targetId: 'card-dismiss',
48
+ title: 'Card Actions',
49
+ description: 'Swipe left to dismiss or tap the X button.',
50
+ tooltipPosition: 'top',
51
+ },
52
+ ],
53
+ theme: {
54
+ primaryColor: '#007AFF',
55
+ backdropOpacity: 0.75,
56
+ },
57
+ onComplete: () => {
58
+ console.log('Guidon completed!');
59
+ },
60
+ };
61
+ ```
62
+
63
+ ### 2. Wrap Your Screen with GuidonProvider
64
+
65
+ ```tsx
66
+ import { GuidonProvider } from 'guidon';
67
+
68
+ function ExploreScreen() {
69
+ return (
70
+ <GuidonProvider
71
+ config={exploreGuidonConfig}
72
+ autoStart={true}
73
+ >
74
+ <YourScreenContent />
75
+ </GuidonProvider>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ### 3. Mark Target Elements with GuidonTarget
81
+
82
+ ```tsx
83
+ import { GuidonTarget } from 'guidon';
84
+
85
+ function SearchButton() {
86
+ return (
87
+ <GuidonTarget targetId="search-button">
88
+ <TouchableOpacity onPress={handleSearch}>
89
+ <SearchIcon />
90
+ </TouchableOpacity>
91
+ </GuidonTarget>
92
+ );
93
+ }
94
+ ```
95
+
96
+ ## Persistence
97
+
98
+ The library supports flexible persistence through adapters. You can save guidon progress to local storage, AsyncStorage, or your backend API.
99
+
100
+ ### Using Built-in Adapters
101
+
102
+ ```tsx
103
+ import {
104
+ GuidonProvider,
105
+ createLocalStorageAdapter,
106
+ createAsyncStorageAdapter,
107
+ } from 'guidon';
108
+ import AsyncStorage from '@react-native-async-storage/async-storage';
109
+
110
+ // For web (localStorage)
111
+ const webAdapter = createLocalStorageAdapter();
112
+
113
+ // For React Native (AsyncStorage)
114
+ const nativeAdapter = createAsyncStorageAdapter(AsyncStorage);
115
+
116
+ function App() {
117
+ return (
118
+ <GuidonProvider
119
+ config={config}
120
+ persistenceAdapter={nativeAdapter}
121
+ >
122
+ <YourApp />
123
+ </GuidonProvider>
124
+ );
125
+ }
126
+ ```
127
+
128
+ ### Creating a Custom API Adapter
129
+
130
+ ```tsx
131
+ import { createApiAdapter } from 'guidon';
132
+
133
+ const apiAdapter = createApiAdapter({
134
+ loadProgress: async (guidonId) => {
135
+ const response = await fetch(`/api/guidon/${guidonId}`);
136
+ if (!response.ok) return null;
137
+ return response.json();
138
+ },
139
+ saveProgress: async (progress) => {
140
+ await fetch(`/api/guidon/${progress.guidonId}`, {
141
+ method: 'POST',
142
+ headers: { 'Content-Type': 'application/json' },
143
+ body: JSON.stringify(progress),
144
+ });
145
+ },
146
+ clearProgress: async (guidonId) => {
147
+ await fetch(`/api/guidon/${guidonId}`, {
148
+ method: 'DELETE',
149
+ });
150
+ },
151
+ });
152
+ ```
153
+
154
+ ### Combining Multiple Adapters
155
+
156
+ ```tsx
157
+ import { createCompositeAdapter, createLocalStorageAdapter } from 'guidon';
158
+
159
+ // Save to both local storage and API
160
+ const compositeAdapter = createCompositeAdapter([
161
+ createLocalStorageAdapter(),
162
+ apiAdapter,
163
+ ]);
164
+ ```
165
+
166
+ ## API Reference
167
+
168
+ ### GuidonProvider Props
169
+
170
+ | Prop | Type | Default | Description |
171
+ |------|------|---------|-------------|
172
+ | `config` | `GuidonConfig` | required | Configuration for the guidon |
173
+ | `autoStart` | `boolean` | `true` | Whether to auto-start when mounted |
174
+ | `shouldStart` | `() => boolean \| Promise<boolean>` | - | Custom condition for starting |
175
+ | `persistenceAdapter` | `GuidonPersistenceAdapter` | - | Adapter for saving progress |
176
+ | `portalComponent` | `React.ComponentType` | - | Custom portal for overlay rendering |
177
+ | `renderTooltip` | `(props) => ReactNode` | - | Custom tooltip renderer |
178
+ | `tooltipLabels` | `object` | - | Customize button labels |
179
+ | `onBackdropPress` | `() => void` | - | Called when backdrop is pressed |
180
+
181
+ ### GuidonConfig
182
+
183
+ ```tsx
184
+ interface GuidonConfig {
185
+ id: string; // Unique identifier
186
+ steps: GuidonStep[]; // Array of steps
187
+ theme?: GuidonTheme; // Theme customization
188
+ animationDuration?: number; // Animation duration (ms)
189
+ onComplete?: () => void; // Called on completion
190
+ onSkip?: () => void; // Called when skipped
191
+ onStepChange?: (index, step) => void; // Called on step change
192
+ }
193
+ ```
194
+
195
+ ### GuidonStep
196
+
197
+ ```tsx
198
+ interface GuidonStep {
199
+ id: string; // Unique step identifier
200
+ targetId: string; // ID of the target element
201
+ title: string; // Tooltip title
202
+ description: string; // Tooltip description
203
+ tooltipPosition?: 'top' | 'bottom' | 'left' | 'right' | 'auto';
204
+ customContent?: ReactNode; // Additional content in tooltip
205
+ onStepEnter?: () => void; // Called when step becomes active
206
+ onStepExit?: () => void; // Called when leaving step
207
+ }
208
+ ```
209
+
210
+ ### GuidonTheme
211
+
212
+ ```tsx
213
+ interface GuidonTheme {
214
+ backdropColor?: string; // Overlay color (default: '#000000')
215
+ backdropOpacity?: number; // Overlay opacity (default: 0.75)
216
+ tooltipBackgroundColor?: string;
217
+ tooltipBorderColor?: string;
218
+ tooltipBorderRadius?: number;
219
+ titleColor?: string;
220
+ descriptionColor?: string;
221
+ primaryColor?: string; // Button color
222
+ mutedColor?: string; // Secondary text color
223
+ spotlightBorderRadius?: number; // Spotlight cutout radius
224
+ spotlightPadding?: number; // Padding around spotlight
225
+ }
226
+ ```
227
+
228
+ ## Controlling the Guidon
229
+
230
+ ### Using the Context Hook
231
+
232
+ ```tsx
233
+ import { useGuidonContext } from 'guidon';
234
+
235
+ function ReplayButton() {
236
+ const { replay, isCompleted } = useGuidonContext();
237
+
238
+ if (!isCompleted) return null;
239
+
240
+ return (
241
+ <Button onPress={replay}>
242
+ Replay Tutorial
243
+ </Button>
244
+ );
245
+ }
246
+ ```
247
+
248
+ ### Using the Guidon API (Outside React)
249
+
250
+ ```tsx
251
+ import { Guidon } from 'guidon';
252
+
253
+ // Start programmatically
254
+ Guidon.start();
255
+
256
+ // Skip to a specific step
257
+ Guidon.goToStep(2);
258
+
259
+ // Complete the guidon
260
+ Guidon.complete();
261
+
262
+ // Reset and replay
263
+ Guidon.reset();
264
+ Guidon.start();
265
+ ```
266
+
267
+ ### Using Hook Selectors
268
+
269
+ ```tsx
270
+ import {
271
+ useGuidonActive,
272
+ useGuidonStep,
273
+ useGuidonProgress,
274
+ } from 'guidon';
275
+
276
+ function GuidonStatus() {
277
+ const isActive = useGuidonActive();
278
+ const currentStep = useGuidonStep();
279
+ const { currentStep: stepNum, totalSteps, percentage } = useGuidonProgress();
280
+
281
+ if (!isActive) return null;
282
+
283
+ return (
284
+ <Text>
285
+ Step {stepNum} of {totalSteps} ({percentage}%)
286
+ </Text>
287
+ );
288
+ }
289
+ ```
290
+
291
+ ## Custom Tooltip Rendering
292
+
293
+ ```tsx
294
+ <GuidonProvider
295
+ config={config}
296
+ renderTooltip={({ step, currentIndex, totalSteps, onNext, onPrevious, onSkip }) => (
297
+ <View style={styles.customTooltip}>
298
+ <Text style={styles.title}>{step.title}</Text>
299
+ <Text>{step.description}</Text>
300
+ <View style={styles.buttons}>
301
+ <Button onPress={onSkip}>Skip</Button>
302
+ {currentIndex > 0 && <Button onPress={onPrevious}>Back</Button>}
303
+ <Button onPress={onNext}>
304
+ {currentIndex === totalSteps - 1 ? 'Done' : 'Next'}
305
+ </Button>
306
+ </View>
307
+ </View>
308
+ )}
309
+ >
310
+ ```
311
+
312
+ ## Conditional Starting
313
+
314
+ ```tsx
315
+ <GuidonProvider
316
+ config={config}
317
+ autoStart={true}
318
+ shouldStart={async () => {
319
+ // Check if user is new
320
+ const user = await getUser();
321
+ return user.isFirstLogin;
322
+ }}
323
+ >
324
+ ```
325
+
326
+ ## Platform Support
327
+
328
+ - iOS
329
+ - Android
330
+ - Web (React Native Web)
331
+
332
+ ## License
333
+
334
+ MIT
@@ -0,0 +1,314 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /**
4
+ * Position of tooltip relative to the highlighted element
5
+ */
6
+ type TooltipPosition = 'top' | 'bottom' | 'left' | 'right' | 'auto';
7
+ /**
8
+ * Defines the measurements of a target element
9
+ */
10
+ interface TargetMeasurements {
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ }
16
+ /**
17
+ * A single step in the guidon
18
+ */
19
+ interface GuidonStep {
20
+ /** Unique identifier for this step */
21
+ id: string;
22
+ /** ID of the target element to highlight */
23
+ targetId: string;
24
+ /** Title displayed in the tooltip */
25
+ title: string;
26
+ /** Description/content displayed in the tooltip */
27
+ description: string;
28
+ /** Preferred position of the tooltip */
29
+ tooltipPosition?: TooltipPosition;
30
+ /** Custom content to render in the tooltip */
31
+ customContent?: ReactNode;
32
+ /** Called when this step becomes active */
33
+ onStepEnter?: () => void;
34
+ /** Called when leaving this step */
35
+ onStepExit?: () => void;
36
+ }
37
+ /**
38
+ * Theme configuration for guidon styling
39
+ */
40
+ interface GuidonTheme {
41
+ /** Color of the backdrop overlay */
42
+ backdropColor?: string;
43
+ /** Opacity of the backdrop (0-1) */
44
+ backdropOpacity?: number;
45
+ /** Background color of the tooltip */
46
+ tooltipBackgroundColor?: string;
47
+ /** Border color of the tooltip */
48
+ tooltipBorderColor?: string;
49
+ /** Border radius of the tooltip */
50
+ tooltipBorderRadius?: number;
51
+ /** Color of the title text */
52
+ titleColor?: string;
53
+ /** Color of the description text */
54
+ descriptionColor?: string;
55
+ /** Primary/accent color (buttons, progress) */
56
+ primaryColor?: string;
57
+ /** Muted/secondary color */
58
+ mutedColor?: string;
59
+ /** Border radius of the spotlight cutout */
60
+ spotlightBorderRadius?: number;
61
+ /** Padding around the highlighted element */
62
+ spotlightPadding?: number;
63
+ }
64
+ /**
65
+ * State of a specific guidon tour
66
+ */
67
+ interface GuidonProgress {
68
+ /** Unique identifier for the guidon */
69
+ guidonId: string;
70
+ /** Whether this guidon has been completed */
71
+ completed: boolean;
72
+ /** Index of the last viewed step (for resuming) */
73
+ lastStepIndex?: number;
74
+ /** Timestamp of completion */
75
+ completedAt?: string;
76
+ /** Number of times this guidon has been completed */
77
+ completionCount?: number;
78
+ }
79
+ /**
80
+ * Persistence adapter interface for saving/loading guidon progress
81
+ * Implement this interface to connect the guidon to your backend
82
+ */
83
+ interface GuidonPersistenceAdapter {
84
+ /**
85
+ * Load the progress for a specific guidon
86
+ * @param guidonId - Unique identifier for the guidon
87
+ * @returns The progress data or null if not found
88
+ */
89
+ loadProgress: (guidonId: string) => Promise<GuidonProgress | null>;
90
+ /**
91
+ * Save the progress for a specific guidon
92
+ * @param progress - The progress data to save
93
+ */
94
+ saveProgress: (progress: GuidonProgress) => Promise<void>;
95
+ /**
96
+ * Load all guidon progress (optional, for bulk operations)
97
+ * @returns Map of guidon IDs to progress data
98
+ */
99
+ loadAllProgress?: () => Promise<Record<string, GuidonProgress>>;
100
+ /**
101
+ * Clear progress for a specific guidon (for replay functionality)
102
+ * @param guidonId - Unique identifier for the guidon
103
+ */
104
+ clearProgress?: (guidonId: string) => Promise<void>;
105
+ }
106
+ /**
107
+ * Configuration for a guidon
108
+ */
109
+ interface GuidonConfig {
110
+ /** Unique identifier for this guidon */
111
+ id: string;
112
+ /** Steps in the guidon */
113
+ steps: GuidonStep[];
114
+ /** Theme customization */
115
+ theme?: GuidonTheme;
116
+ /** Animation duration in milliseconds */
117
+ animationDuration?: number;
118
+ /** Called when the guidon is completed */
119
+ onComplete?: () => void;
120
+ /** Called when the guidon is skipped */
121
+ onSkip?: () => void;
122
+ /** Called when the step changes */
123
+ onStepChange?: (stepIndex: number, step: GuidonStep) => void;
124
+ }
125
+ /**
126
+ * Labels for tooltip buttons
127
+ */
128
+ interface GuidonTooltipLabels {
129
+ next?: string;
130
+ previous?: string;
131
+ skip?: string;
132
+ finish?: string;
133
+ stepOf?: (current: number, total: number) => string;
134
+ }
135
+ /**
136
+ * Props for custom tooltip renderer
137
+ */
138
+ interface GuidonTooltipRenderProps {
139
+ step: GuidonStep;
140
+ currentIndex: number;
141
+ totalSteps: number;
142
+ onNext: () => void;
143
+ onPrevious: () => void;
144
+ onSkip: () => void;
145
+ }
146
+ /**
147
+ * Props for the GuidonProvider component
148
+ */
149
+ interface GuidonProviderProps {
150
+ children: ReactNode;
151
+ /** Configuration for the guidon */
152
+ config: GuidonConfig;
153
+ /** Whether to auto-start when the component mounts */
154
+ autoStart?: boolean;
155
+ /** Condition function to determine if guidon should start */
156
+ shouldStart?: () => boolean | Promise<boolean>;
157
+ /** Persistence adapter for saving/loading progress */
158
+ persistenceAdapter?: GuidonPersistenceAdapter;
159
+ /** Custom portal component for rendering overlay */
160
+ portalComponent?: React.ComponentType<{
161
+ children: ReactNode;
162
+ }>;
163
+ /** Custom tooltip renderer */
164
+ renderTooltip?: (props: GuidonTooltipRenderProps) => ReactNode;
165
+ /** Customize tooltip labels */
166
+ tooltipLabels?: GuidonTooltipLabels;
167
+ /** Called when backdrop is pressed */
168
+ onBackdropPress?: () => void;
169
+ }
170
+ /**
171
+ * Props for the GuidonTarget component
172
+ */
173
+ interface GuidonTargetProps {
174
+ children: ReactNode;
175
+ /** Target ID that matches a step's targetId */
176
+ targetId: string;
177
+ /** Whether this target is currently active (for conditional rendering) */
178
+ active?: boolean;
179
+ }
180
+ /**
181
+ * Internal store state
182
+ */
183
+ interface GuidonState {
184
+ /** Currently active guidon config */
185
+ config: GuidonConfig | null;
186
+ /** Whether the guidon is currently active */
187
+ isActive: boolean;
188
+ /** Current step index */
189
+ currentStepIndex: number;
190
+ /** Whether the guidon has been completed */
191
+ isCompleted: boolean;
192
+ /** Map of target IDs to their measurements */
193
+ targetMeasurements: Record<string, TargetMeasurements>;
194
+ /** Loading state for persistence operations */
195
+ isLoading: boolean;
196
+ /** Error from persistence operations */
197
+ error: string | null;
198
+ }
199
+ /**
200
+ * Internal store actions
201
+ */
202
+ interface GuidonActions {
203
+ /** Configure the guidon */
204
+ configure: (config: GuidonConfig) => void;
205
+ /** Start the guidon */
206
+ start: () => void;
207
+ /** Go to the next step */
208
+ next: () => void;
209
+ /** Go to the previous step */
210
+ previous: () => void;
211
+ /** Go to a specific step */
212
+ goToStep: (index: number) => void;
213
+ /** Skip the guidon */
214
+ skip: () => void;
215
+ /** Complete the guidon */
216
+ complete: () => void;
217
+ /** Reset the guidon state */
218
+ reset: () => void;
219
+ /** Register a target's measurements */
220
+ registerTarget: (targetId: string, measurements: TargetMeasurements) => void;
221
+ /** Unregister a target */
222
+ unregisterTarget: (targetId: string) => void;
223
+ /** Set loading state */
224
+ setLoading: (isLoading: boolean) => void;
225
+ /** Set error state */
226
+ setError: (error: string | null) => void;
227
+ }
228
+ type GuidonStore = GuidonState & GuidonActions;
229
+
230
+ /**
231
+ * No-op adapter that doesn't persist anything
232
+ * Useful for testing or when persistence is not needed
233
+ */
234
+ declare const createNoopAdapter: () => GuidonPersistenceAdapter;
235
+ /**
236
+ * Memory adapter that stores progress in memory
237
+ * Data is lost when the app is closed
238
+ */
239
+ declare const createMemoryAdapter: () => GuidonPersistenceAdapter;
240
+ /**
241
+ * localStorage adapter for web
242
+ * Only works in browser environments
243
+ */
244
+ declare const createLocalStorageAdapter: (keyPrefix?: string) => GuidonPersistenceAdapter;
245
+ /**
246
+ * AsyncStorage adapter for React Native
247
+ * Requires @react-native-async-storage/async-storage to be installed
248
+ *
249
+ * @example
250
+ * import AsyncStorage from '@react-native-async-storage/async-storage';
251
+ * const adapter = createAsyncStorageAdapter(AsyncStorage);
252
+ */
253
+ declare const createAsyncStorageAdapter: (asyncStorage: {
254
+ getItem: (key: string) => Promise<string | null>;
255
+ setItem: (key: string, value: string) => Promise<void>;
256
+ removeItem: (key: string) => Promise<void>;
257
+ getAllKeys: () => Promise<readonly string[]>;
258
+ multiGet: (keys: readonly string[]) => Promise<readonly [string, string | null][]>;
259
+ }, keyPrefix?: string) => GuidonPersistenceAdapter;
260
+ /**
261
+ * Create a custom API adapter for backend persistence
262
+ * This is a factory function that creates an adapter based on your API endpoints
263
+ *
264
+ * @example
265
+ * const adapter = createApiAdapter({
266
+ * loadProgress: async (guidonId) => {
267
+ * const response = await fetch(`/api/guidon/${guidonId}`);
268
+ * return response.json();
269
+ * },
270
+ * saveProgress: async (progress) => {
271
+ * await fetch(`/api/guidon/${progress.guidonId}`, {
272
+ * method: 'POST',
273
+ * body: JSON.stringify(progress),
274
+ * });
275
+ * },
276
+ * });
277
+ */
278
+ declare const createApiAdapter: (handlers: Partial<GuidonPersistenceAdapter>) => GuidonPersistenceAdapter;
279
+ /**
280
+ * Combine multiple adapters (e.g., save to both local and API)
281
+ * Loads from the first adapter that returns data
282
+ * Saves to all adapters
283
+ */
284
+ declare const createCompositeAdapter: (adapters: GuidonPersistenceAdapter[]) => GuidonPersistenceAdapter;
285
+
286
+ /**
287
+ * Hook to manage guidon's walkthrough progress with a persistence adapter
288
+ */
289
+ declare function useGuidonPersistence(adapter: GuidonPersistenceAdapter | undefined, guidonId: string): {
290
+ progress: GuidonProgress | null;
291
+ isLoading: boolean;
292
+ error: string | null;
293
+ isCompleted: boolean;
294
+ hasStarted: boolean;
295
+ saveProgress: (newProgress: Omit<GuidonProgress, "guidonId">) => Promise<void>;
296
+ clearProgress: () => Promise<void>;
297
+ markCompleted: () => Promise<void>;
298
+ markStepViewed: (stepIndex: number) => Promise<void>;
299
+ };
300
+ /**
301
+ * Hook to check if a guidon should be shown
302
+ */
303
+ declare function useShouldShowGuidon(adapter: GuidonPersistenceAdapter | undefined, guidonId: string, options?: {
304
+ /** Show even if completed (for replay) */
305
+ forceShow?: boolean;
306
+ /** Additional condition to check */
307
+ additionalCondition?: () => boolean | Promise<boolean>;
308
+ }): {
309
+ shouldShow: boolean;
310
+ isChecking: boolean;
311
+ isCompleted: boolean;
312
+ };
313
+
314
+ export { type GuidonTargetProps as G, type TargetMeasurements as T, type GuidonTheme as a, type GuidonStep as b, type GuidonProviderProps as c, type GuidonStore as d, type GuidonConfig as e, type GuidonProgress as f, type GuidonPersistenceAdapter as g, type GuidonTooltipLabels as h, type GuidonTooltipRenderProps as i, type TooltipPosition as j, type GuidonState as k, type GuidonActions as l, createNoopAdapter as m, createMemoryAdapter as n, createLocalStorageAdapter as o, createAsyncStorageAdapter as p, createApiAdapter as q, createCompositeAdapter as r, useShouldShowGuidon as s, useGuidonPersistence as u };