react-native-my-survey-sdk 2.2.9 → 2.2.10

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": "react-native-my-survey-sdk",
3
- "version": "2.2.9",
3
+ "version": "2.2.10",
4
4
  "description": "Xebo survey collection SDK for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -24,4 +24,4 @@
24
24
  "@types/react-native": "^0.73.0",
25
25
  "typescript": "^5.3.0"
26
26
  }
27
- }
27
+ }
@@ -61,28 +61,28 @@ export const XeboDropdownView: React.FC<Props> = ({ question, isLastQuestion, on
61
61
  </Text>
62
62
  )}
63
63
 
64
- {/* Dropdown trigger */}
65
- <TouchableOpacity
66
- style={[styles.trigger, { borderColor: isOpen ? theme.primaryColor : '#E5E7EB', borderRadius: theme.cornerRadius }]}
67
- onPress={() => setIsOpen(prev => !prev)}
68
- activeOpacity={0.8}
69
- >
70
- <Text
71
- style={[
72
- styles.triggerText,
73
- { color: selectedChoice ? theme.textColor : '#9CA3AF', fontFamily: theme.fontFamily },
74
- ]}
64
+ <Animated.View style={{ transform: [{ translateX: shakeAnim }] }}>
65
+ <TouchableOpacity
66
+ style={[styles.trigger, { borderColor: isOpen ? theme.primaryColor : '#E5E7EB', borderRadius: theme.cornerRadius }]}
67
+ onPress={() => setIsOpen(prev => !prev)}
68
+ activeOpacity={0.8}
75
69
  >
76
- {selectedChoice ? selectedChoice.text : (question.placeholder || 'Select an option')}
77
- </Text>
78
- <Text style={[styles.chevron, { color: theme.textColor }]}>{isOpen ? '▲' : '▼'}</Text>
79
- </TouchableOpacity>
70
+ <Text
71
+ style={[
72
+ styles.triggerText,
73
+ { color: selectedChoice ? theme.textColor : '#9CA3AF', fontFamily: theme.fontFamily },
74
+ ]}
75
+ >
76
+ {selectedChoice ? selectedChoice.text : (question.placeholder || 'Select an option')}
77
+ </Text>
78
+ <Text style={[styles.chevron, { color: theme.textColor }]}>{isOpen ? '▲' : '▼'}</Text>
79
+ </TouchableOpacity>
80
+ </Animated.View>
80
81
 
81
- {/* Options list */}
82
82
  {isOpen && (
83
83
  <View style={[styles.optionsList, { borderColor: theme.primaryColor, borderRadius: theme.cornerRadius }]}>
84
84
  <ScrollView nestedScrollEnabled style={styles.optionsScroll} showsVerticalScrollIndicator={false}>
85
- {(question.options ?? []).map(choice => (
85
+ {(question.options ?? []).map((choice: XeboChoice) => (
86
86
  <TouchableOpacity
87
87
  key={choice.id}
88
88
  style={[
@@ -112,26 +112,25 @@ export const XeboDropdownView: React.FC<Props> = ({ question, isLastQuestion, on
112
112
 
113
113
  {!!error && <Text style={styles.errorText}>{error}</Text>}
114
114
 
115
- {selectedChoice && (
116
- <Animated.View style={[styles.buttonWrap, { transform: [{ translateX: shakeAnim }] }]}>
117
- <TouchableOpacity
118
- style={[styles.button, { backgroundColor: theme.primaryColor, borderRadius: theme.cornerRadius }]}
119
- onPress={handleNext}
120
- activeOpacity={0.8}
121
- >
122
- <Text style={[styles.buttonText, { fontFamily: theme.fontFamily }]}>
123
- {isLastQuestion ? 'Submit' : 'Next'}
124
- </Text>
125
- </TouchableOpacity>
126
- </Animated.View>
127
- )}
115
+ <TouchableOpacity
116
+ style={[
117
+ styles.button,
118
+ { backgroundColor: theme.primaryColor, borderRadius: theme.cornerRadius },
119
+ (question.isRequired && !selectedChoice) && styles.buttonDisabled,
120
+ ]}
121
+ onPress={handleNext}
122
+ activeOpacity={0.8}
123
+ >
124
+ <Text style={[styles.buttonText, { fontFamily: theme.fontFamily }]}>
125
+ {isLastQuestion ? 'Submit' : 'Next'}
126
+ </Text>
127
+ </TouchableOpacity>
128
128
  </View>
129
129
  );
130
130
  };
131
131
 
132
132
  const styles = StyleSheet.create({
133
133
  container: {
134
- flex: 1,
135
134
  paddingHorizontal: 20,
136
135
  paddingTop: 16,
137
136
  paddingBottom: 24,
@@ -139,7 +138,7 @@ const styles = StyleSheet.create({
139
138
  title: {
140
139
  fontSize: 18,
141
140
  fontWeight: '700',
142
- marginBottom: 6,
141
+ marginBottom: 16,
143
142
  },
144
143
  subtitle: {
145
144
  fontSize: 14,
@@ -151,7 +150,6 @@ const styles = StyleSheet.create({
151
150
  alignItems: 'center',
152
151
  borderWidth: 1.5,
153
152
  padding: 14,
154
- marginBottom: 4,
155
153
  },
156
154
  triggerText: {
157
155
  fontSize: 15,
@@ -162,11 +160,13 @@ const styles = StyleSheet.create({
162
160
  },
163
161
  optionsList: {
164
162
  borderWidth: 1.5,
165
- maxHeight: 220,
163
+ maxHeight: 200,
164
+ marginTop: 6,
166
165
  marginBottom: 12,
166
+ overflow: 'hidden',
167
167
  },
168
168
  optionsScroll: {
169
- flexGrow: 0,
169
+ flex: 1,
170
170
  },
171
171
  optionRow: {
172
172
  padding: 14,
@@ -179,15 +179,16 @@ const styles = StyleSheet.create({
179
179
  errorText: {
180
180
  color: '#EF4444',
181
181
  fontSize: 13,
182
- marginBottom: 8,
183
- },
184
- buttonWrap: {
185
- marginTop: 'auto',
182
+ marginTop: 6,
183
+ marginBottom: 4,
186
184
  },
187
185
  button: {
188
186
  paddingVertical: 14,
189
187
  alignItems: 'center',
190
- marginTop: 8,
188
+ marginTop: 16,
189
+ },
190
+ buttonDisabled: {
191
+ opacity: 0.4,
191
192
  },
192
193
  buttonText: {
193
194
  color: '#FFFFFF',
@@ -12,20 +12,18 @@ export const XeboIntroView: React.FC<Props> = ({ introPage, onStart }) => {
12
12
  const theme = getTheme();
13
13
 
14
14
  return (
15
- // Same pattern as NPS/Rating: outer flex view, scroll takes remaining space,
16
- // button sits OUTSIDE scroll in normal flow — no absolute positioning needed.
17
15
  <View style={styles.outer}>
18
- <ScrollView
19
- style={styles.scroll}
20
- contentContainerStyle={styles.scrollContent}
21
- showsVerticalScrollIndicator={false}
22
- >
23
- {!!introPage.content && (
16
+ {!!introPage.content && (
17
+ <ScrollView
18
+ style={styles.scroll}
19
+ showsVerticalScrollIndicator={false}
20
+ contentContainerStyle={styles.scrollContent}
21
+ >
24
22
  <Text style={[styles.content, { color: theme.textColor, fontFamily: theme.fontFamily }]}>
25
23
  {introPage.content}
26
24
  </Text>
27
- )}
28
- </ScrollView>
25
+ </ScrollView>
26
+ )}
29
27
 
30
28
  <TouchableOpacity
31
29
  style={[styles.button, { backgroundColor: theme.primaryColor, borderRadius: theme.cornerRadius }]}
@@ -42,26 +40,25 @@ export const XeboIntroView: React.FC<Props> = ({ introPage, onStart }) => {
42
40
 
43
41
  const styles = StyleSheet.create({
44
42
  outer: {
45
- flex: 1,
46
43
  paddingHorizontal: 20,
47
- paddingTop: 16,
48
- paddingBottom: 16,
44
+ paddingTop: 8,
45
+ paddingBottom: 24,
49
46
  },
50
47
  scroll: {
51
- flex: 1,
52
- minHeight: 0,
48
+ maxHeight: 220,
49
+ marginBottom: 4,
53
50
  },
54
51
  scrollContent: {
55
52
  paddingBottom: 8,
56
53
  },
57
54
  content: {
58
55
  fontSize: 16,
59
- lineHeight: 24,
56
+ lineHeight: 26,
60
57
  },
61
58
  button: {
62
59
  paddingVertical: 14,
63
60
  alignItems: 'center',
64
- marginTop: 12,
61
+ marginTop: 16,
65
62
  },
66
63
  buttonText: {
67
64
  color: '#FFFFFF',
@@ -142,11 +142,11 @@ export const XeboMultiRatingView: React.FC<Props> = ({ question, isLastQuestion,
142
142
  };
143
143
 
144
144
  const styles = StyleSheet.create({
145
- container: { flex: 1, paddingHorizontal: 20, paddingTop: 16, paddingBottom: 24 },
146
- title: { fontSize: 18, fontWeight: '700', marginBottom: 6 },
145
+ container: { paddingHorizontal: 20, paddingTop: 16, paddingBottom: 24 },
146
+ title: { fontSize: 18, fontWeight: '700', marginBottom: 16 },
147
147
  subtitle: { fontSize: 14, opacity: 0.6, marginBottom: 16 },
148
- scroll: { flex: 1, minHeight: 0, marginBottom: 12 },
149
- rowWrap: { marginBottom: 20 },
148
+ scroll: { maxHeight: 280, marginBottom: 12 },
149
+ rowWrap: { marginBottom: 24 },
150
150
  rowLabel: { fontSize: 15, fontWeight: '500', marginBottom: 8 },
151
151
  iconsRow: { flexDirection: 'row', flexWrap: 'nowrap', justifyContent: 'space-between' },
152
152
  iconButton: { alignItems: 'center', justifyContent: 'center' },
@@ -54,7 +54,7 @@ export const XeboMultipleChoiceView: React.FC<Props> = ({ question, isLastQuesti
54
54
  questionId: question.id,
55
55
  value: Array.from(selectedIds).map(id => ({
56
56
  rowId: id,
57
- text: question.options?.find(o => o.id === id)?.text,
57
+ text: question.options?.find((o: XeboChoice) => o.id === id)?.text,
58
58
  })),
59
59
  });
60
60
  };
@@ -71,7 +71,7 @@ export const XeboMultipleChoiceView: React.FC<Props> = ({ question, isLastQuesti
71
71
  )}
72
72
 
73
73
  <ScrollView style={styles.optionsList} showsVerticalScrollIndicator={false}>
74
- {(question.options ?? []).map(choice => {
74
+ {(question.options ?? []).map((choice: XeboChoice) => {
75
75
  const selected = selectedIds.has(choice.id);
76
76
  return (
77
77
  <TouchableOpacity
@@ -121,7 +121,6 @@ export const XeboMultipleChoiceView: React.FC<Props> = ({ question, isLastQuesti
121
121
 
122
122
  const styles = StyleSheet.create({
123
123
  container: {
124
- flex: 1,
125
124
  paddingHorizontal: 20,
126
125
  paddingTop: 16,
127
126
  paddingBottom: 24,
@@ -129,7 +128,7 @@ const styles = StyleSheet.create({
129
128
  title: {
130
129
  fontSize: 18,
131
130
  fontWeight: '700',
132
- marginBottom: 6,
131
+ marginBottom: 16,
133
132
  },
134
133
  subtitle: {
135
134
  fontSize: 14,
@@ -137,8 +136,7 @@ const styles = StyleSheet.create({
137
136
  marginBottom: 16,
138
137
  },
139
138
  optionsList: {
140
- flex: 1,
141
- minHeight: 0,
139
+ maxHeight: 280,
142
140
  marginBottom: 12,
143
141
  },
144
142
  optionRow: {
@@ -233,15 +233,14 @@ export const XeboRatingView: React.FC<Props> = ({ question, isLastQuestion, onAn
233
233
 
234
234
  const styles = StyleSheet.create({
235
235
  outer: {
236
- flex: 1,
237
236
  paddingHorizontal: 20,
238
- paddingBottom: 16,
237
+ paddingBottom: 24,
239
238
  },
240
- scroll: { flex: 1, minHeight: 0 },
239
+ scroll: { maxHeight: 320 },
241
240
  scrollContent: { paddingTop: 16, paddingBottom: 8 },
242
- title: { fontSize: 18, fontWeight: '700', marginBottom: 6 },
241
+ title: { fontSize: 18, fontWeight: '700', marginBottom: 16 },
243
242
  subtitle: { fontSize: 14, opacity: 0.6, marginBottom: 16 },
244
- iconsRow: { flexDirection: 'row', marginBottom: 8, flexWrap: 'nowrap', justifyContent: 'space-between' },
243
+ iconsRow: { flexDirection: 'row', marginTop: 4, marginBottom: 20, flexWrap: 'nowrap', justifyContent: 'space-between' },
245
244
  iconButton: { alignItems: 'center', justifyContent: 'center' },
246
245
  colLabels: {
247
246
  flexDirection: 'row',
@@ -106,7 +106,6 @@ export const XeboSingleChoiceView: React.FC<Props> = ({ question, isLastQuestion
106
106
 
107
107
  const styles = StyleSheet.create({
108
108
  container: {
109
- flex: 1,
110
109
  paddingHorizontal: 20,
111
110
  paddingTop: 16,
112
111
  paddingBottom: 24,
@@ -114,7 +113,7 @@ const styles = StyleSheet.create({
114
113
  title: {
115
114
  fontSize: 18,
116
115
  fontWeight: '700',
117
- marginBottom: 6,
116
+ marginBottom: 16,
118
117
  },
119
118
  subtitle: {
120
119
  fontSize: 14,
@@ -122,8 +121,7 @@ const styles = StyleSheet.create({
122
121
  marginBottom: 16,
123
122
  },
124
123
  optionsList: {
125
- flex: 1,
126
- minHeight: 0,
124
+ maxHeight: 280,
127
125
  marginBottom: 12,
128
126
  },
129
127
  optionRow: {
@@ -42,7 +42,7 @@ const _androidNavBar = Platform.OS === 'android'
42
42
 
43
43
  function computeSheetHeight(question: XeboQuestion | null, screen: ModalScreen): number {
44
44
  if (screen === 'thankYou') return SCREEN_HEIGHT * 0.35;
45
- if (screen === 'intro') return SCREEN_HEIGHT * 0.55;
45
+ if (screen === 'intro') return SCREEN_HEIGHT * 0.40;
46
46
  if (!question) return SCREEN_HEIGHT * 0.48;
47
47
  switch (question.type) {
48
48
  case XeboQuestionType.multiNps:
@@ -289,14 +289,43 @@ export const XeboSurveyModal: React.FC = () => {
289
289
  <View style={styles.handle} />
290
290
  </View>
291
291
 
292
- {/* Skip button right aligned */}
293
- {screen !== 'thankYou' && (
294
- <View style={styles.skipRow}>
292
+ {/* Header row: progress bar + Skip / X close */}
293
+ <View style={styles.headerRow}>
294
+ <View style={styles.progressArea}>
295
+ {screen === 'question' && survey && (() => {
296
+ const realQs = survey.questions.filter((q: XeboQuestion) => q.id !== 'thank_you_auto');
297
+ const total = realQs.length;
298
+ if (total === 0) return null;
299
+ return (
300
+ <View style={styles.progressContainer}>
301
+ <View style={[styles.progressTrack, { backgroundColor: '#E5E7EB' }]}>
302
+ <View
303
+ style={[
304
+ styles.progressFill,
305
+ {
306
+ backgroundColor: theme.primaryColor,
307
+ width: `${((questionIndex + 1) / total) * 100}%` as any,
308
+ },
309
+ ]}
310
+ />
311
+ </View>
312
+ <Text style={[styles.progressText, { color: theme.textColor }]}>
313
+ {questionIndex + 1} / {total}
314
+ </Text>
315
+ </View>
316
+ );
317
+ })()}
318
+ </View>
319
+ {screen === 'thankYou' ? (
320
+ <TouchableOpacity onPress={handleSkip} style={styles.closeButton} activeOpacity={0.7}>
321
+ <Text style={[styles.closeText, { color: theme.textColor }]}>✕</Text>
322
+ </TouchableOpacity>
323
+ ) : (
295
324
  <TouchableOpacity onPress={handleSkip} style={styles.skipButton} activeOpacity={0.7}>
296
325
  <Text style={[styles.skipText, { color: theme.textColor }]}>Skip</Text>
297
326
  </TouchableOpacity>
298
- </View>
299
- )}
327
+ )}
328
+ </View>
300
329
 
301
330
  {/* Main content */}
302
331
  <View style={styles.content}>{renderContent()}</View>
@@ -336,11 +365,36 @@ const styles = StyleSheet.create({
336
365
  borderRadius: 2,
337
366
  backgroundColor: '#D1D5DB',
338
367
  },
339
- skipRow: {
368
+ headerRow: {
340
369
  flexDirection: 'row',
341
- justifyContent: 'flex-end',
370
+ alignItems: 'center',
342
371
  paddingHorizontal: 16,
343
372
  paddingBottom: 4,
373
+ minHeight: 36,
374
+ },
375
+ progressArea: {
376
+ flex: 1,
377
+ },
378
+ progressContainer: {
379
+ flexDirection: 'row',
380
+ alignItems: 'center',
381
+ gap: 8,
382
+ },
383
+ progressTrack: {
384
+ flex: 1,
385
+ height: 4,
386
+ borderRadius: 2,
387
+ overflow: 'hidden',
388
+ },
389
+ progressFill: {
390
+ height: 4,
391
+ borderRadius: 2,
392
+ },
393
+ progressText: {
394
+ fontSize: 12,
395
+ opacity: 0.6,
396
+ minWidth: 36,
397
+ textAlign: 'right',
344
398
  },
345
399
  skipButton: {
346
400
  paddingHorizontal: 12,
@@ -351,6 +405,15 @@ const styles = StyleSheet.create({
351
405
  fontWeight: '500',
352
406
  opacity: 0.6,
353
407
  },
408
+ closeButton: {
409
+ paddingHorizontal: 12,
410
+ paddingVertical: 6,
411
+ },
412
+ closeText: {
413
+ fontSize: 18,
414
+ fontWeight: '600',
415
+ opacity: 0.7,
416
+ },
354
417
  content: {
355
418
  flex: 1,
356
419
  },
@@ -136,13 +136,11 @@ export const XeboTextBoxView: React.FC<Props> = ({ question, isLastQuestion, onA
136
136
 
137
137
  const styles = StyleSheet.create({
138
138
  outer: {
139
- flex: 1,
140
139
  paddingHorizontal: 20,
141
- paddingBottom: 16,
140
+ paddingBottom: 24,
142
141
  },
143
142
  scroll: {
144
- flex: 1,
145
- minHeight: 0,
143
+ maxHeight: 340,
146
144
  },
147
145
  scrollContent: {
148
146
  paddingTop: 16,
@@ -151,7 +149,7 @@ const styles = StyleSheet.create({
151
149
  title: {
152
150
  fontSize: 18,
153
151
  fontWeight: '700',
154
- marginBottom: 6,
152
+ marginBottom: 16,
155
153
  },
156
154
  subtitle: {
157
155
  fontSize: 14,