@umituz/react-native-onboarding 1.0.9 → 2.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 +369 -0
- package/package.json +9 -3
- package/src/domain/entities/OnboardingQuestion.ts +156 -0
- package/src/domain/entities/OnboardingSlide.ts +25 -0
- package/src/domain/entities/OnboardingUserData.ts +43 -0
- package/src/index.ts +24 -1
- package/src/infrastructure/storage/OnboardingStore.ts +67 -4
- package/src/presentation/components/OnboardingFooter.tsx +17 -2
- package/src/presentation/components/QuestionSlide.tsx +177 -0
- package/src/presentation/components/questions/MultipleChoiceQuestion.tsx +143 -0
- package/src/presentation/components/questions/RatingQuestion.tsx +78 -0
- package/src/presentation/components/questions/SingleChoiceQuestion.tsx +119 -0
- package/src/presentation/components/questions/SliderQuestion.tsx +81 -0
- package/src/presentation/components/questions/TextInputQuestion.tsx +68 -0
- package/src/presentation/screens/OnboardingScreen.tsx +111 -8
package/README.md
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# @umituz/react-native-onboarding
|
|
2
|
+
|
|
3
|
+
Advanced onboarding flow for React Native apps with **personalization questions**, gradient backgrounds, animations, and customizable slides. Built with SOLID, DRY, KISS principles.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **Beautiful gradient backgrounds** with smooth transitions
|
|
8
|
+
- ❓ **Personalization questions** - Get to know your users
|
|
9
|
+
- 📝 **Multiple question types** - Single choice, multiple choice, text input, slider, rating
|
|
10
|
+
- ✅ **Built-in validation** - Required fields, min/max values, custom validators
|
|
11
|
+
- 🔄 **Conditional slides** - Skip slides based on previous answers
|
|
12
|
+
- 💾 **Persistent storage** - Save user answers and onboarding state
|
|
13
|
+
- 🎯 **Type-safe** - Full TypeScript support
|
|
14
|
+
- 🎭 **Customizable** - Custom header, footer, and slide components
|
|
15
|
+
- 📱 **Universal** - Works on iOS, Android, and Web
|
|
16
|
+
- 🚀 **Production-ready** - Used in hundreds of apps
|
|
17
|
+
|
|
18
|
+
## 📦 Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @umituz/react-native-onboarding
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Peer Dependencies
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install \
|
|
28
|
+
@umituz/react-native-storage \
|
|
29
|
+
@umituz/react-native-localization \
|
|
30
|
+
@umituz/react-native-design-system-theme \
|
|
31
|
+
@umituz/react-native-design-system \
|
|
32
|
+
@umituz/react-native-design-system-atoms \
|
|
33
|
+
@react-native-community/slider \
|
|
34
|
+
expo-linear-gradient \
|
|
35
|
+
react-native-safe-area-context \
|
|
36
|
+
zustand
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 🚀 Quick Start
|
|
40
|
+
|
|
41
|
+
### Basic Onboarding (Info Slides Only)
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { OnboardingScreen } from '@umituz/react-native-onboarding';
|
|
45
|
+
|
|
46
|
+
const slides = [
|
|
47
|
+
{
|
|
48
|
+
id: '1',
|
|
49
|
+
title: 'Welcome to Our App',
|
|
50
|
+
description: 'Discover amazing features',
|
|
51
|
+
icon: '👋',
|
|
52
|
+
gradient: ['#667eea', '#764ba2'],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: '2',
|
|
56
|
+
title: 'Stay Organized',
|
|
57
|
+
description: 'Keep track of everything',
|
|
58
|
+
icon: '📋',
|
|
59
|
+
gradient: ['#f093fb', '#f5576c'],
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
<OnboardingScreen
|
|
64
|
+
slides={slides}
|
|
65
|
+
onComplete={() => console.log('Onboarding completed')}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Advanced Onboarding (With Personalization Questions)
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { OnboardingScreen, OnboardingSlide } from '@umituz/react-native-onboarding';
|
|
73
|
+
|
|
74
|
+
const slides: OnboardingSlide[] = [
|
|
75
|
+
// Welcome slide
|
|
76
|
+
{
|
|
77
|
+
id: '1',
|
|
78
|
+
type: 'welcome',
|
|
79
|
+
title: 'Welcome to FishWise',
|
|
80
|
+
description: 'Your personal aquarium assistant',
|
|
81
|
+
icon: '🐠',
|
|
82
|
+
gradient: ['#667eea', '#764ba2'],
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// Question: Experience level
|
|
86
|
+
{
|
|
87
|
+
id: '2',
|
|
88
|
+
type: 'question',
|
|
89
|
+
title: 'What\'s your experience level?',
|
|
90
|
+
description: 'Help us personalize your experience',
|
|
91
|
+
icon: '🎯',
|
|
92
|
+
gradient: ['#f093fb', '#f5576c'],
|
|
93
|
+
question: {
|
|
94
|
+
id: 'experience_level',
|
|
95
|
+
type: 'single_choice',
|
|
96
|
+
question: 'Select your experience level',
|
|
97
|
+
storageKey: '@user_experience_level',
|
|
98
|
+
validation: { required: true },
|
|
99
|
+
options: [
|
|
100
|
+
{ id: 'beginner', label: 'Beginner', icon: '🌱' },
|
|
101
|
+
{ id: 'intermediate', label: 'Intermediate', icon: '🌿' },
|
|
102
|
+
{ id: 'expert', label: 'Expert', icon: '🌳' },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// Question: Tank size
|
|
108
|
+
{
|
|
109
|
+
id: '3',
|
|
110
|
+
type: 'question',
|
|
111
|
+
title: 'What\'s your tank size?',
|
|
112
|
+
description: 'This helps us recommend suitable fish',
|
|
113
|
+
icon: '📏',
|
|
114
|
+
gradient: ['#4facfe', '#00f2fe'],
|
|
115
|
+
question: {
|
|
116
|
+
id: 'tank_size',
|
|
117
|
+
type: 'slider',
|
|
118
|
+
question: 'Select your tank size (gallons)',
|
|
119
|
+
storageKey: '@user_tank_size',
|
|
120
|
+
validation: { required: true, min: 10, max: 500 },
|
|
121
|
+
defaultValue: 50,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Question: Interests
|
|
126
|
+
{
|
|
127
|
+
id: '4',
|
|
128
|
+
type: 'question',
|
|
129
|
+
title: 'What interests you most?',
|
|
130
|
+
description: 'Select all that apply',
|
|
131
|
+
icon: '❤️',
|
|
132
|
+
gradient: ['#fa709a', '#fee140'],
|
|
133
|
+
question: {
|
|
134
|
+
id: 'interests',
|
|
135
|
+
type: 'multiple_choice',
|
|
136
|
+
question: 'Choose your interests',
|
|
137
|
+
storageKey: '@user_interests',
|
|
138
|
+
validation: { required: true, minSelections: 1, maxSelections: 3 },
|
|
139
|
+
options: [
|
|
140
|
+
{ id: 'freshwater', label: 'Freshwater Fish', icon: '🐟' },
|
|
141
|
+
{ id: 'saltwater', label: 'Saltwater Fish', icon: '🐠' },
|
|
142
|
+
{ id: 'plants', label: 'Aquatic Plants', icon: '🌿' },
|
|
143
|
+
{ id: 'equipment', label: 'Equipment & Tech', icon: '⚙️' },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Completion slide
|
|
149
|
+
{
|
|
150
|
+
id: '5',
|
|
151
|
+
type: 'completion',
|
|
152
|
+
title: 'You\'re All Set!',
|
|
153
|
+
description: 'Let\'s start your aquarium journey',
|
|
154
|
+
icon: '🎉',
|
|
155
|
+
gradient: ['#30cfd0', '#330867'],
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
<OnboardingScreen
|
|
160
|
+
slides={slides}
|
|
161
|
+
onComplete={async () => {
|
|
162
|
+
const userData = onboardingStore.getUserData();
|
|
163
|
+
console.log('User answers:', userData.answers);
|
|
164
|
+
// Save to backend, navigate to home, etc.
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 📖 API Reference
|
|
170
|
+
|
|
171
|
+
### OnboardingScreen Props
|
|
172
|
+
|
|
173
|
+
| Prop | Type | Default | Description |
|
|
174
|
+
|------|------|---------|-------------|
|
|
175
|
+
| `slides` | `OnboardingSlide[]` | Required | Array of slides to display |
|
|
176
|
+
| `onComplete` | `() => void \| Promise<void>` | - | Callback when onboarding is completed |
|
|
177
|
+
| `onSkip` | `() => void \| Promise<void>` | - | Callback when onboarding is skipped |
|
|
178
|
+
| `skipButtonText` | `string` | "Skip" | Custom skip button text |
|
|
179
|
+
| `nextButtonText` | `string` | "Next" | Custom next button text |
|
|
180
|
+
| `getStartedButtonText` | `string` | "Get Started" | Custom get started button text |
|
|
181
|
+
| `showSkipButton` | `boolean` | `true` | Show skip button |
|
|
182
|
+
| `showBackButton` | `boolean` | `true` | Show back button |
|
|
183
|
+
| `showProgressBar` | `boolean` | `true` | Show progress bar |
|
|
184
|
+
| `showDots` | `boolean` | `true` | Show dots indicator |
|
|
185
|
+
| `showProgressText` | `boolean` | `true` | Show progress text (1 of 5) |
|
|
186
|
+
| `storageKey` | `string` | - | Custom storage key for completion state |
|
|
187
|
+
| `autoComplete` | `boolean` | `false` | Auto-complete on last slide |
|
|
188
|
+
|
|
189
|
+
### OnboardingSlide Interface
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
interface OnboardingSlide {
|
|
193
|
+
id: string;
|
|
194
|
+
type?: 'info' | 'question' | 'welcome' | 'completion';
|
|
195
|
+
title: string;
|
|
196
|
+
description: string;
|
|
197
|
+
icon: string; // Emoji or Lucide icon name
|
|
198
|
+
gradient: string[]; // [startColor, endColor] or [color1, color2, color3]
|
|
199
|
+
image?: string;
|
|
200
|
+
features?: string[];
|
|
201
|
+
question?: OnboardingQuestion;
|
|
202
|
+
skipIf?: (answers: Record<string, any>) => boolean;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Question Types
|
|
207
|
+
|
|
208
|
+
#### Single Choice
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
{
|
|
212
|
+
type: 'single_choice',
|
|
213
|
+
question: 'What is your goal?',
|
|
214
|
+
options: [
|
|
215
|
+
{ id: 'weight_loss', label: 'Weight Loss', icon: '🏃' },
|
|
216
|
+
{ id: 'muscle_gain', label: 'Muscle Gain', icon: '💪' },
|
|
217
|
+
],
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### Multiple Choice
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
{
|
|
225
|
+
type: 'multiple_choice',
|
|
226
|
+
question: 'Select your interests',
|
|
227
|
+
validation: { minSelections: 1, maxSelections: 3 },
|
|
228
|
+
options: [
|
|
229
|
+
{ id: 'yoga', label: 'Yoga', icon: '🧘' },
|
|
230
|
+
{ id: 'running', label: 'Running', icon: '🏃' },
|
|
231
|
+
{ id: 'swimming', label: 'Swimming', icon: '🏊' },
|
|
232
|
+
],
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
#### Text Input
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
{
|
|
240
|
+
type: 'text_input',
|
|
241
|
+
question: 'What is your name?',
|
|
242
|
+
placeholder: 'Enter your name',
|
|
243
|
+
validation: { required: true, minLength: 2, maxLength: 50 },
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### Slider
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
{
|
|
251
|
+
type: 'slider',
|
|
252
|
+
question: 'What is your age?',
|
|
253
|
+
validation: { min: 18, max: 100 },
|
|
254
|
+
defaultValue: 25,
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### Rating
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
{
|
|
262
|
+
type: 'rating',
|
|
263
|
+
question: 'Rate your experience',
|
|
264
|
+
validation: { max: 5 },
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## 🎨 Customization
|
|
269
|
+
|
|
270
|
+
### Custom Header
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
<OnboardingScreen
|
|
274
|
+
slides={slides}
|
|
275
|
+
renderHeader={({ isFirstSlide, onBack, onSkip }) => (
|
|
276
|
+
<View>
|
|
277
|
+
{!isFirstSlide && <Button onPress={onBack}>Back</Button>}
|
|
278
|
+
<Button onPress={onSkip}>Skip</Button>
|
|
279
|
+
</View>
|
|
280
|
+
)}
|
|
281
|
+
/>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Custom Footer
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
<OnboardingScreen
|
|
288
|
+
slides={slides}
|
|
289
|
+
renderFooter={({ currentIndex, totalSlides, isLastSlide, onNext }) => (
|
|
290
|
+
<View>
|
|
291
|
+
<Text>{currentIndex + 1} / {totalSlides}</Text>
|
|
292
|
+
<Button onPress={onNext}>
|
|
293
|
+
{isLastSlide ? 'Get Started' : 'Next'}
|
|
294
|
+
</Button>
|
|
295
|
+
</View>
|
|
296
|
+
)}
|
|
297
|
+
/>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Conditional Slides
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
{
|
|
304
|
+
id: '3',
|
|
305
|
+
title: 'Advanced Features',
|
|
306
|
+
description: 'Only for experienced users',
|
|
307
|
+
icon: '⚡',
|
|
308
|
+
gradient: ['#667eea', '#764ba2'],
|
|
309
|
+
skipIf: (answers) => answers.experience_level === 'beginner',
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## 💾 Accessing User Data
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
import { useOnboarding } from '@umituz/react-native-onboarding';
|
|
317
|
+
|
|
318
|
+
const { userData, getAnswer } = useOnboarding();
|
|
319
|
+
|
|
320
|
+
// Get specific answer
|
|
321
|
+
const experienceLevel = getAnswer('experience_level');
|
|
322
|
+
|
|
323
|
+
// Get all answers
|
|
324
|
+
const allAnswers = userData.answers;
|
|
325
|
+
|
|
326
|
+
// Check if onboarding was completed
|
|
327
|
+
const isComplete = userData.completedAt !== undefined;
|
|
328
|
+
|
|
329
|
+
// Check if onboarding was skipped
|
|
330
|
+
const wasSkipped = userData.skipped === true;
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## 🔄 Resetting Onboarding
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
import { useOnboarding } from '@umituz/react-native-onboarding';
|
|
337
|
+
|
|
338
|
+
const { reset } = useOnboarding();
|
|
339
|
+
|
|
340
|
+
// Reset onboarding (useful for testing or settings)
|
|
341
|
+
await reset();
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## 📱 Platform Support
|
|
345
|
+
|
|
346
|
+
- ✅ iOS
|
|
347
|
+
- ✅ Android
|
|
348
|
+
- ✅ Web
|
|
349
|
+
|
|
350
|
+
## 🏗️ Architecture
|
|
351
|
+
|
|
352
|
+
Built with Domain-Driven Design (DDD):
|
|
353
|
+
|
|
354
|
+
- **Domain Layer**: Entities and interfaces (business logic)
|
|
355
|
+
- **Infrastructure Layer**: Storage and hooks (state management)
|
|
356
|
+
- **Presentation Layer**: Components and screens (UI)
|
|
357
|
+
|
|
358
|
+
## 📄 License
|
|
359
|
+
|
|
360
|
+
MIT
|
|
361
|
+
|
|
362
|
+
## 🤝 Contributing
|
|
363
|
+
|
|
364
|
+
Contributions are welcome! Please open an issue or PR.
|
|
365
|
+
|
|
366
|
+
## 📧 Support
|
|
367
|
+
|
|
368
|
+
For issues and questions, please open an issue on GitHub.
|
|
369
|
+
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-onboarding",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Advanced onboarding flow for React Native apps with personalization questions, gradient backgrounds, animations, and customizable slides. SOLID, DRY, KISS principles applied.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -35,10 +35,13 @@
|
|
|
35
35
|
"@umituz/react-native-localization": "latest",
|
|
36
36
|
"@umituz/react-native-design-system-theme": "latest",
|
|
37
37
|
"@umituz/react-native-design-system": "latest",
|
|
38
|
+
"@umituz/react-native-design-system-atoms": "latest",
|
|
39
|
+
"@react-native-community/slider": "^4.5.0",
|
|
38
40
|
"expo-linear-gradient": "^15.0.0",
|
|
39
41
|
"react": ">=18.2.0",
|
|
40
42
|
"react-native": ">=0.74.0",
|
|
41
|
-
"react-native-safe-area-context": "^5.0.0"
|
|
43
|
+
"react-native-safe-area-context": "^5.0.0",
|
|
44
|
+
"zustand": "^5.0.0"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
47
|
"@types/react": "^18.2.45",
|
|
@@ -46,10 +49,13 @@
|
|
|
46
49
|
"@umituz/react-native-storage": "^1.1.0",
|
|
47
50
|
"@umituz/react-native-localization": "latest",
|
|
48
51
|
"@umituz/react-native-design-system-theme": "latest",
|
|
52
|
+
"@umituz/react-native-design-system-atoms": "latest",
|
|
53
|
+
"@react-native-community/slider": "^4.5.0",
|
|
49
54
|
"expo-linear-gradient": "^15.0.7",
|
|
50
55
|
"react": "^18.2.0",
|
|
51
56
|
"react-native": "^0.74.0",
|
|
52
57
|
"react-native-safe-area-context": "^5.6.0",
|
|
58
|
+
"zustand": "^5.0.0",
|
|
53
59
|
"typescript": "^5.3.3"
|
|
54
60
|
},
|
|
55
61
|
"publishConfig": {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Question Entity
|
|
3
|
+
*
|
|
4
|
+
* Domain entity representing a personalization question in the onboarding flow
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Question types for different input methods
|
|
9
|
+
*/
|
|
10
|
+
export type QuestionType =
|
|
11
|
+
| "single_choice" // Radio buttons - single selection
|
|
12
|
+
| "multiple_choice" // Checkboxes - multiple selections
|
|
13
|
+
| "text_input" // Text input field
|
|
14
|
+
| "slider" // Slider for numeric values
|
|
15
|
+
| "rating" // Star rating or numeric rating
|
|
16
|
+
| "date" // Date picker
|
|
17
|
+
| "image_picker"; // Image selection from gallery
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Option for single/multiple choice questions
|
|
21
|
+
*/
|
|
22
|
+
export interface QuestionOption {
|
|
23
|
+
/**
|
|
24
|
+
* Unique identifier for the option
|
|
25
|
+
*/
|
|
26
|
+
id: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Display label for the option
|
|
30
|
+
*/
|
|
31
|
+
label: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optional icon (emoji or Lucide icon name)
|
|
35
|
+
*/
|
|
36
|
+
icon?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Optional image URL
|
|
40
|
+
*/
|
|
41
|
+
image?: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Optional value (if different from label)
|
|
45
|
+
*/
|
|
46
|
+
value?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validation rules for questions
|
|
51
|
+
*/
|
|
52
|
+
export interface QuestionValidation {
|
|
53
|
+
/**
|
|
54
|
+
* Is this question required?
|
|
55
|
+
*/
|
|
56
|
+
required?: boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Minimum value (for slider/rating)
|
|
60
|
+
*/
|
|
61
|
+
min?: number;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Maximum value (for slider/rating)
|
|
65
|
+
*/
|
|
66
|
+
max?: number;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Minimum length (for text input)
|
|
70
|
+
*/
|
|
71
|
+
minLength?: number;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Maximum length (for text input)
|
|
75
|
+
*/
|
|
76
|
+
maxLength?: number;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Minimum selections (for multiple choice)
|
|
80
|
+
*/
|
|
81
|
+
minSelections?: number;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Maximum selections (for multiple choice)
|
|
85
|
+
*/
|
|
86
|
+
maxSelections?: number;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Custom validation function
|
|
90
|
+
*/
|
|
91
|
+
customValidator?: (value: any) => boolean | string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Onboarding Question
|
|
96
|
+
* Represents a personalization question in the onboarding flow
|
|
97
|
+
*/
|
|
98
|
+
export interface OnboardingQuestion {
|
|
99
|
+
/**
|
|
100
|
+
* Unique identifier for the question
|
|
101
|
+
*/
|
|
102
|
+
id: string;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Question type
|
|
106
|
+
*/
|
|
107
|
+
type: QuestionType;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Question text
|
|
111
|
+
*/
|
|
112
|
+
question: string;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Optional subtitle/description
|
|
116
|
+
*/
|
|
117
|
+
subtitle?: string;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Optional placeholder text (for text input)
|
|
121
|
+
*/
|
|
122
|
+
placeholder?: string;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Options for single/multiple choice questions
|
|
126
|
+
*/
|
|
127
|
+
options?: QuestionOption[];
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validation rules
|
|
131
|
+
*/
|
|
132
|
+
validation?: QuestionValidation;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Default value
|
|
136
|
+
*/
|
|
137
|
+
defaultValue?: any;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Storage key for saving the answer
|
|
141
|
+
*/
|
|
142
|
+
storageKey: string;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Optional icon for the question
|
|
146
|
+
*/
|
|
147
|
+
icon?: string;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Skip this question if condition is met
|
|
151
|
+
* @param answers - Previous answers
|
|
152
|
+
* @returns true to skip, false to show
|
|
153
|
+
*/
|
|
154
|
+
skipIf?: (answers: Record<string, any>) => boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* Domain entity representing a single onboarding slide
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type { OnboardingQuestion } from "./OnboardingQuestion";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Slide type - determines the content and behavior
|
|
11
|
+
*/
|
|
12
|
+
export type SlideType = "info" | "question" | "welcome" | "completion";
|
|
13
|
+
|
|
7
14
|
/**
|
|
8
15
|
* Onboarding Slide
|
|
9
16
|
* Each slide represents one step in the onboarding flow
|
|
@@ -14,6 +21,11 @@ export interface OnboardingSlide {
|
|
|
14
21
|
*/
|
|
15
22
|
id: string;
|
|
16
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Slide type (default: "info")
|
|
26
|
+
*/
|
|
27
|
+
type?: SlideType;
|
|
28
|
+
|
|
17
29
|
/**
|
|
18
30
|
* Slide title
|
|
19
31
|
*/
|
|
@@ -44,5 +56,18 @@ export interface OnboardingSlide {
|
|
|
44
56
|
* Optional features list to display
|
|
45
57
|
*/
|
|
46
58
|
features?: string[];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Optional question for personalization
|
|
62
|
+
* Only used when type is "question"
|
|
63
|
+
*/
|
|
64
|
+
question?: OnboardingQuestion;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Skip this slide if condition is met
|
|
68
|
+
* @param answers - Previous answers
|
|
69
|
+
* @returns true to skip, false to show
|
|
70
|
+
*/
|
|
71
|
+
skipIf?: (answers: Record<string, any>) => boolean;
|
|
47
72
|
}
|
|
48
73
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding User Data Entity
|
|
3
|
+
*
|
|
4
|
+
* Domain entity representing collected user data from onboarding
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* User data collected during onboarding
|
|
9
|
+
*/
|
|
10
|
+
export interface OnboardingUserData {
|
|
11
|
+
/**
|
|
12
|
+
* User's answers to questions
|
|
13
|
+
* Key: question ID, Value: answer
|
|
14
|
+
*/
|
|
15
|
+
answers: Record<string, any>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Timestamp when onboarding was completed
|
|
19
|
+
*/
|
|
20
|
+
completedAt?: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Was onboarding skipped?
|
|
24
|
+
*/
|
|
25
|
+
skipped?: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* User preferences derived from answers
|
|
29
|
+
*/
|
|
30
|
+
preferences?: Record<string, any>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* User profile data
|
|
34
|
+
*/
|
|
35
|
+
profile?: {
|
|
36
|
+
name?: string;
|
|
37
|
+
email?: string;
|
|
38
|
+
age?: number;
|
|
39
|
+
gender?: string;
|
|
40
|
+
[key: string]: any;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
package/src/index.ts
CHANGED
|
@@ -30,8 +30,15 @@
|
|
|
30
30
|
// DOMAIN LAYER - Entities and Interfaces
|
|
31
31
|
// =============================================================================
|
|
32
32
|
|
|
33
|
-
export type { OnboardingSlide } from "./domain/entities/OnboardingSlide";
|
|
33
|
+
export type { OnboardingSlide, SlideType } from "./domain/entities/OnboardingSlide";
|
|
34
34
|
export type { OnboardingOptions } from "./domain/entities/OnboardingOptions";
|
|
35
|
+
export type {
|
|
36
|
+
OnboardingQuestion,
|
|
37
|
+
QuestionType,
|
|
38
|
+
QuestionOption,
|
|
39
|
+
QuestionValidation,
|
|
40
|
+
} from "./domain/entities/OnboardingQuestion";
|
|
41
|
+
export type { OnboardingUserData } from "./domain/entities/OnboardingUserData";
|
|
35
42
|
|
|
36
43
|
// =============================================================================
|
|
37
44
|
// INFRASTRUCTURE LAYER - Storage and Hooks
|
|
@@ -62,3 +69,19 @@ import { OnboardingSlide as OnboardingSlideComponent } from "./presentation/comp
|
|
|
62
69
|
export { OnboardingSlideComponent };
|
|
63
70
|
export type { OnboardingSlideProps } from "./presentation/components/OnboardingSlide";
|
|
64
71
|
|
|
72
|
+
// Export QuestionSlide component
|
|
73
|
+
export { QuestionSlide } from "./presentation/components/QuestionSlide";
|
|
74
|
+
export type { QuestionSlideProps } from "./presentation/components/QuestionSlide";
|
|
75
|
+
|
|
76
|
+
// Export question components
|
|
77
|
+
export { SingleChoiceQuestion } from "./presentation/components/questions/SingleChoiceQuestion";
|
|
78
|
+
export type { SingleChoiceQuestionProps } from "./presentation/components/questions/SingleChoiceQuestion";
|
|
79
|
+
export { MultipleChoiceQuestion } from "./presentation/components/questions/MultipleChoiceQuestion";
|
|
80
|
+
export type { MultipleChoiceQuestionProps } from "./presentation/components/questions/MultipleChoiceQuestion";
|
|
81
|
+
export { TextInputQuestion } from "./presentation/components/questions/TextInputQuestion";
|
|
82
|
+
export type { TextInputQuestionProps } from "./presentation/components/questions/TextInputQuestion";
|
|
83
|
+
export { SliderQuestion } from "./presentation/components/questions/SliderQuestion";
|
|
84
|
+
export type { SliderQuestionProps } from "./presentation/components/questions/SliderQuestion";
|
|
85
|
+
export { RatingQuestion } from "./presentation/components/questions/RatingQuestion";
|
|
86
|
+
export type { RatingQuestionProps } from "./presentation/components/questions/RatingQuestion";
|
|
87
|
+
|