@onairos/react-native 3.0.8 → 3.0.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.
Files changed (33) hide show
  1. package/lib/commonjs/assets/images/onairos_logo.svg +6 -0
  2. package/lib/commonjs/components/Onairos.js +214 -60
  3. package/lib/commonjs/components/Onairos.js.map +1 -1
  4. package/lib/commonjs/components/OnairosButton.js +105 -42
  5. package/lib/commonjs/components/OnairosButton.js.map +1 -1
  6. package/lib/commonjs/components/Overlay.js +26 -23
  7. package/lib/commonjs/components/Overlay.js.map +1 -1
  8. package/lib/commonjs/components/UniversalOnboarding.js +98 -26
  9. package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
  10. package/lib/module/assets/images/onairos_logo.svg +6 -0
  11. package/lib/module/components/Onairos.js +217 -62
  12. package/lib/module/components/Onairos.js.map +1 -1
  13. package/lib/module/components/OnairosButton.js +107 -43
  14. package/lib/module/components/OnairosButton.js.map +1 -1
  15. package/lib/module/components/Overlay.js +27 -24
  16. package/lib/module/components/Overlay.js.map +1 -1
  17. package/lib/module/components/UniversalOnboarding.js +100 -28
  18. package/lib/module/components/UniversalOnboarding.js.map +1 -1
  19. package/lib/typescript/components/Onairos.d.ts +26 -24
  20. package/lib/typescript/components/Onairos.d.ts.map +1 -1
  21. package/lib/typescript/components/OnairosButton.d.ts +5 -1
  22. package/lib/typescript/components/OnairosButton.d.ts.map +1 -1
  23. package/lib/typescript/components/Overlay.d.ts.map +1 -1
  24. package/lib/typescript/components/UniversalOnboarding.d.ts.map +1 -1
  25. package/lib/typescript/types.d.ts +2 -1
  26. package/lib/typescript/types.d.ts.map +1 -1
  27. package/package.json +5 -3
  28. package/src/assets/images/onairos_logo.svg +6 -0
  29. package/src/components/Onairos.tsx +283 -89
  30. package/src/components/OnairosButton.tsx +110 -46
  31. package/src/components/Overlay.tsx +118 -110
  32. package/src/components/UniversalOnboarding.tsx +105 -28
  33. package/src/types.ts +2 -1
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useCallback } from 'react';
1
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -47,9 +47,9 @@ export const Overlay: React.FC<OverlayProps> = ({
47
47
  const [selections, setSelections] = useState<{ [key: string]: boolean }>({});
48
48
  const [details, setDetails] = useState<string>('');
49
49
  const [visible, setVisible] = useState(true);
50
- const [slideAnim] = useState(new Animated.Value(SCREEN_HEIGHT));
50
+ const bottomSheetAnim = useRef(new Animated.Value(0)).current;
51
51
 
52
- // Initialize selection state and slide animation on mount
52
+ // Initialize selection state on mount
53
53
  useEffect(() => {
54
54
  // Initialize selection state
55
55
  const initialSelections: { [key: string]: boolean } = {};
@@ -59,11 +59,11 @@ export const Overlay: React.FC<OverlayProps> = ({
59
59
  setSelections(initialSelections);
60
60
  getDetails();
61
61
 
62
- // Animate the overlay sliding up from the bottom
63
- Animated.timing(slideAnim, {
64
- toValue: 0,
65
- duration: 300,
62
+ // Animate the bottom sheet sliding up
63
+ Animated.spring(bottomSheetAnim, {
64
+ toValue: 1,
66
65
  useNativeDriver: true,
66
+ bounciness: 0,
67
67
  }).start();
68
68
  }, []);
69
69
 
@@ -97,14 +97,14 @@ export const Overlay: React.FC<OverlayProps> = ({
97
97
 
98
98
  const closeOverlay = useCallback(() => {
99
99
  // Animate the overlay sliding down
100
- Animated.timing(slideAnim, {
101
- toValue: SCREEN_HEIGHT,
102
- duration: 300,
100
+ Animated.timing(bottomSheetAnim, {
101
+ toValue: 0,
102
+ duration: 250,
103
103
  useNativeDriver: true,
104
104
  }).start(() => {
105
105
  setVisible(false);
106
106
  });
107
- }, [slideAnim]);
107
+ }, [bottomSheetAnim]);
108
108
 
109
109
  const confirmSelection = useCallback(async () => {
110
110
  try {
@@ -193,125 +193,132 @@ export const Overlay: React.FC<OverlayProps> = ({
193
193
  };
194
194
 
195
195
  const selectedCount = Object.values(selections).filter(Boolean).length;
196
+
197
+ // Calculate the maximum height of the bottom sheet (80% of screen height)
198
+ const maxSheetHeight = SCREEN_HEIGHT * 0.8;
196
199
 
197
- // Calculate the height of the bottom sheet (80% of screen height)
198
- const sheetHeight = SCREEN_HEIGHT * 0.8;
199
-
200
- // Main component rendering - Use Modal with absolute positioning
200
+ // Main component rendering
201
201
  return (
202
202
  <Modal
203
203
  visible={visible}
204
204
  transparent={true}
205
- animationType="none" // We'll handle animation ourselves
205
+ animationType="none"
206
206
  onRequestClose={closeOverlay}
207
207
  statusBarTranslucent={true}
208
208
  >
209
- <TouchableWithoutFeedback onPress={closeOverlay}>
210
- <View style={styles.modalBackground}>
211
- <TouchableWithoutFeedback onPress={e => e.stopPropagation()}>
212
- <Animated.View
213
- style={[
214
- styles.bottomSheet,
215
- darkMode && styles.darkContainer,
216
- {
217
- height: sheetHeight,
218
- transform: [{ translateY: slideAnim }]
219
- }
220
- ]}
221
- >
222
- <View style={styles.handleContainer}>
223
- <View style={[styles.handle, darkMode && styles.darkHandle]} />
224
- </View>
225
-
226
- <View style={[styles.header, darkMode && styles.darkHeader]}>
227
- <View style={styles.headerContent}>
228
- <View style={[styles.appIcon, darkMode && styles.darkAppIcon]}>
229
- <Text style={[styles.appIconText, darkMode && styles.darkText]}>A</Text>
230
- </View>
231
- <Text style={[styles.arrow, darkMode && styles.darkArrow]}>→</Text>
232
- <View style={[styles.onairosIcon, darkMode && styles.darkAppIcon]}>
233
- <Text style={[styles.onairosIconText, darkMode && styles.darkText]}>O</Text>
234
- </View>
209
+ <TouchableOpacity
210
+ style={styles.modalOverlay}
211
+ activeOpacity={1}
212
+ onPress={closeOverlay}
213
+ >
214
+ <TouchableWithoutFeedback onPress={e => e.stopPropagation()}>
215
+ <Animated.View
216
+ style={[
217
+ styles.bottomSheet,
218
+ darkMode && styles.darkContainer,
219
+ {
220
+ maxHeight: maxSheetHeight,
221
+ transform: [{
222
+ translateY: bottomSheetAnim.interpolate({
223
+ inputRange: [0, 1],
224
+ outputRange: [SCREEN_HEIGHT, 0],
225
+ })
226
+ }]
227
+ }
228
+ ]}
229
+ >
230
+ <View style={styles.handleContainer}>
231
+ <View style={[styles.handle, darkMode && styles.darkHandle]} />
232
+ </View>
233
+
234
+ <View style={[styles.header, darkMode && styles.darkHeader]}>
235
+ <View style={styles.headerContent}>
236
+ <View style={[styles.appIcon, darkMode && styles.darkAppIcon]}>
237
+ <Text style={[styles.appIconText, darkMode && styles.darkText]}>A</Text>
238
+ </View>
239
+ <Text style={[styles.arrow, darkMode && styles.darkArrow]}>→</Text>
240
+ <View style={[styles.onairosIcon, darkMode && styles.darkAppIcon]}>
241
+ <Text style={[styles.onairosIconText, darkMode && styles.darkText]}>O</Text>
235
242
  </View>
236
- <Text style={[styles.appName, darkMode && styles.darkSubText]}>{appName}</Text>
237
243
  </View>
244
+ <Text style={[styles.appName, darkMode && styles.darkSubText]}>{appName}</Text>
245
+ </View>
238
246
 
239
- <Text style={[styles.privacyMessage, darkMode && styles.darkSubText]}>
240
- No one will ever see your raw app data
241
- </Text>
247
+ <Text style={[styles.privacyMessage, darkMode && styles.darkSubText]}>
248
+ No one will ever see your raw app data
249
+ </Text>
242
250
 
243
- <ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
244
- {Object.entries(data).map(([key, value]) => (
245
- <View key={key} style={[styles.card, darkMode && styles.darkCard]}>
246
- <TouchableOpacity
247
- style={styles.checkboxContainer}
248
- onPress={() =>
249
- setSelections((prev) => ({
250
- ...prev,
251
- [key]: !prev[key],
252
- }))
253
- }
254
- >
255
- <View
256
- style={[
257
- styles.checkbox,
258
- selections[key] && styles.checkboxSelected,
259
- darkMode && styles.darkCheckbox,
260
- ]}
261
- />
262
- <View style={styles.cardContent}>
263
- <Text style={[styles.cardTitle, darkMode && styles.darkText]}>
264
- {value.type} Insight
265
- </Text>
266
- <Text style={[styles.cardDescription, darkMode && styles.darkSubText]}>
267
- {value.descriptions}
251
+ <ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
252
+ {Object.entries(data).map(([key, value]) => (
253
+ <View key={key} style={[styles.card, darkMode && styles.darkCard]}>
254
+ <TouchableOpacity
255
+ style={styles.checkboxContainer}
256
+ onPress={() =>
257
+ setSelections((prev) => ({
258
+ ...prev,
259
+ [key]: !prev[key],
260
+ }))
261
+ }
262
+ >
263
+ <View
264
+ style={[
265
+ styles.checkbox,
266
+ selections[key] && styles.checkboxSelected,
267
+ darkMode && styles.darkCheckbox,
268
+ ]}
269
+ />
270
+ <View style={styles.cardContent}>
271
+ <Text style={[styles.cardTitle, darkMode && styles.darkText]}>
272
+ {value.type} Insight
273
+ </Text>
274
+ <Text style={[styles.cardDescription, darkMode && styles.darkSubText]}>
275
+ {value.descriptions}
276
+ </Text>
277
+ {value.reward && (
278
+ <Text style={[styles.cardReward, darkMode && styles.darkReward]}>
279
+ Reward: {value.reward}
268
280
  </Text>
269
- {value.reward && (
270
- <Text style={[styles.cardReward, darkMode && styles.darkReward]}>
271
- Reward: {value.reward}
272
- </Text>
273
- )}
274
- </View>
275
- </TouchableOpacity>
276
- </View>
277
- ))}
278
- </ScrollView>
281
+ )}
282
+ </View>
283
+ </TouchableOpacity>
284
+ </View>
285
+ ))}
286
+ </ScrollView>
279
287
 
280
- <KeyboardAvoidingView
281
- behavior={Platform.OS === 'ios' ? 'padding' : undefined}
282
- >
283
- <SafeAreaView>
284
- <View style={[styles.footer, darkMode && styles.darkFooter]}>
285
- <TouchableOpacity
286
- style={[styles.footerButtonCancel, darkMode && styles.darkFooterButton]}
287
- onPress={closeOverlay}
288
- >
289
- <Text style={[styles.footerButtonText, darkMode && styles.darkSubText]}>Cancel</Text>
290
- </TouchableOpacity>
291
- <Text style={[styles.selectedCount, darkMode && styles.darkSubText]}>
292
- Selected: {selectedCount}
288
+ <KeyboardAvoidingView
289
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
290
+ >
291
+ <SafeAreaView>
292
+ <View style={[styles.footer, darkMode && styles.darkFooter]}>
293
+ <TouchableOpacity
294
+ style={[styles.footerButtonCancel, darkMode && styles.darkFooterButton]}
295
+ onPress={closeOverlay}
296
+ >
297
+ <Text style={[styles.footerButtonText, darkMode && styles.darkSubText]}>Cancel</Text>
298
+ </TouchableOpacity>
299
+ <Text style={[styles.selectedCount, darkMode && styles.darkSubText]}>
300
+ Selected: {selectedCount}
301
+ </Text>
302
+ <TouchableOpacity
303
+ style={[styles.footerButtonConfirm, darkMode && styles.darkFooterButtonConfirm]}
304
+ onPress={confirmSelection}
305
+ >
306
+ <Text style={[styles.footerButtonTextConfirm, darkMode && styles.darkText]}>
307
+ Confirm
293
308
  </Text>
294
- <TouchableOpacity
295
- style={[styles.footerButtonConfirm, darkMode && styles.darkFooterButtonConfirm]}
296
- onPress={confirmSelection}
297
- >
298
- <Text style={[styles.footerButtonTextConfirm, darkMode && styles.darkText]}>
299
- Confirm
300
- </Text>
301
- </TouchableOpacity>
302
- </View>
303
- </SafeAreaView>
304
- </KeyboardAvoidingView>
305
- </Animated.View>
306
- </TouchableWithoutFeedback>
307
- </View>
308
- </TouchableWithoutFeedback>
309
+ </TouchableOpacity>
310
+ </View>
311
+ </SafeAreaView>
312
+ </KeyboardAvoidingView>
313
+ </Animated.View>
314
+ </TouchableWithoutFeedback>
315
+ </TouchableOpacity>
309
316
  </Modal>
310
317
  );
311
318
  };
312
319
 
313
320
  const styles = StyleSheet.create({
314
- modalBackground: {
321
+ modalOverlay: {
315
322
  flex: 1,
316
323
  backgroundColor: 'rgba(0, 0, 0, 0.5)',
317
324
  justifyContent: 'flex-end',
@@ -320,6 +327,7 @@ const styles = StyleSheet.create({
320
327
  backgroundColor: '#fff',
321
328
  borderTopLeftRadius: 24,
322
329
  borderTopRightRadius: 24,
330
+ width: SCREEN_WIDTH,
323
331
  overflow: 'hidden',
324
332
  },
325
333
  handleContainer: {
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -7,8 +7,11 @@ import {
7
7
  ActivityIndicator,
8
8
  Dimensions,
9
9
  Platform,
10
+ Modal,
11
+ Animated,
12
+ SafeAreaView,
13
+ TouchableWithoutFeedback,
10
14
  } from 'react-native';
11
- import BottomSheet from '@gorhom/bottom-sheet';
12
15
  import Icon from 'react-native-vector-icons/MaterialIcons';
13
16
  import { PlatformList } from './PlatformList';
14
17
  import { PinInput } from './PinInput';
@@ -17,7 +20,7 @@ import { useConnections } from '../hooks/useConnections';
17
20
  import { COLORS } from '../constants';
18
21
  import type { UniversalOnboardingProps, ConnectionStatus } from '../types';
19
22
 
20
- const { height } = Dimensions.get('window');
23
+ const { height, width } = Dimensions.get('window');
21
24
 
22
25
  export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
23
26
  visible,
@@ -29,8 +32,8 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
29
32
  embedd = false,
30
33
  debug = false,
31
34
  test = false,
35
+ preferredPlatform,
32
36
  }) => {
33
- const bottomSheetRef = useRef<BottomSheet>(null);
34
37
  const [step, setStep] = useState<'connect' | 'pin' | 'training'>('connect');
35
38
  const [connections, setConnections] = useState<ConnectionStatus>({});
36
39
  const [pin, setPin] = useState<string>('');
@@ -39,6 +42,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
39
42
  progress: number;
40
43
  eta: string;
41
44
  }>({ progress: 0, eta: '' });
45
+ const [slideAnim] = useState(new Animated.Value(height));
42
46
 
43
47
  const {
44
48
  connectPlatform,
@@ -47,12 +51,15 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
47
51
  isConnecting,
48
52
  } = useConnections();
49
53
 
50
- const snapPoints = useMemo(() => ['90%'], []);
51
-
52
54
  useEffect(() => {
53
55
  if (visible) {
54
- bottomSheetRef.current?.expand();
55
56
  loadInitialStatus();
57
+ // Animate in
58
+ Animated.spring(slideAnim, {
59
+ toValue: 0,
60
+ useNativeDriver: true,
61
+ bounciness: 0,
62
+ }).start();
56
63
 
57
64
  // Debug mode for Expo Go
58
65
  if (debug || Platform.OS === 'web') {
@@ -65,10 +72,36 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
65
72
  });
66
73
  }
67
74
  }
75
+
76
+ // If there's a preferred platform, pre-connect
77
+ if (preferredPlatform && debug) {
78
+ setConnections(prev => ({
79
+ ...prev,
80
+ [preferredPlatform]: { userName: `${preferredPlatform}_user`, connected: true }
81
+ }));
82
+ }
68
83
  } else {
69
- bottomSheetRef.current?.close();
84
+ // Animate out
85
+ Animated.timing(slideAnim, {
86
+ toValue: height,
87
+ duration: 250,
88
+ useNativeDriver: true,
89
+ }).start(() => {
90
+ // Reset state if needed
91
+ });
70
92
  }
71
- }, [visible]);
93
+ }, [visible, preferredPlatform]);
94
+
95
+ const handleClose = () => {
96
+ // Animate out and then call onClose
97
+ Animated.timing(slideAnim, {
98
+ toValue: height,
99
+ duration: 250,
100
+ useNativeDriver: true,
101
+ }).start(() => {
102
+ onClose();
103
+ });
104
+ };
72
105
 
73
106
  const loadInitialStatus = useCallback(async () => {
74
107
  const status = await getConnectionStatus();
@@ -111,7 +144,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
111
144
  }, 1000);
112
145
  }, [connections, onComplete, selectedTier, requestData]);
113
146
 
114
- const canProceedToPin = useMemo(() => {
147
+ const canProceedToPin = useCallback(() => {
115
148
  const connectedPlatforms = Object.values(connections).filter(Boolean).length;
116
149
  return connectedPlatforms >= 2;
117
150
  }, [connections]);
@@ -119,8 +152,8 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
119
152
  const renderHeader = () => (
120
153
  <View style={styles.header}>
121
154
  <Icon name="auto_awesome" size={24} color={COLORS.primary} />
122
- <Text style={styles.headerTitle}>Connect with Onairos</Text>
123
- <TouchableOpacity onPress={onClose} style={styles.closeButton}>
155
+ <Text style={styles.headerTitle}>Connect with {AppName}</Text>
156
+ <TouchableOpacity onPress={handleClose} style={styles.closeButton}>
124
157
  <Icon name="close" size={24} color="#000" />
125
158
  </TouchableOpacity>
126
159
  </View>
@@ -134,7 +167,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
134
167
  connections={connections}
135
168
  onToggle={handlePlatformToggle}
136
169
  isLoading={isConnecting}
137
- canProceed={canProceedToPin}
170
+ canProceed={canProceedToPin()}
138
171
  onProceed={() => setStep('pin')}
139
172
  />
140
173
  );
@@ -151,7 +184,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
151
184
  return (
152
185
  <TrainingModal
153
186
  visible={step === 'training'}
154
- onClose={onClose}
187
+ onClose={handleClose}
155
188
  onComplete={() => {
156
189
  onComplete('https://api2.onairos.uk', 'dummy-token', {
157
190
  pin,
@@ -164,40 +197,84 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
164
197
  username="demo_user"
165
198
  progress={training.progress}
166
199
  eta={training.eta}
167
- onCancel={onClose}
200
+ onCancel={handleClose}
168
201
  />
169
202
  );
170
203
  }
171
204
  };
172
205
 
206
+ if (!visible) return null;
207
+
173
208
  return (
174
- <BottomSheet
175
- ref={bottomSheetRef}
176
- snapPoints={snapPoints}
177
- enablePanDownToClose
178
- onClose={onClose}
179
- index={visible ? 0 : -1}
209
+ <Modal
210
+ visible={visible}
211
+ transparent
212
+ animationType="none"
213
+ statusBarTranslucent
214
+ onRequestClose={handleClose}
180
215
  >
181
- <View style={styles.container}>
182
- {renderHeader()}
183
- {renderContent()}
184
- </View>
185
- </BottomSheet>
216
+ <TouchableWithoutFeedback onPress={handleClose}>
217
+ <View style={styles.modalOverlay}>
218
+ <TouchableWithoutFeedback onPress={e => e.stopPropagation()}>
219
+ <Animated.View
220
+ style={[
221
+ styles.bottomSheet,
222
+ {
223
+ transform: [{ translateY: slideAnim }],
224
+ }
225
+ ]}
226
+ >
227
+ <SafeAreaView style={styles.container}>
228
+ <View style={styles.handleContainer}>
229
+ <View style={styles.handle} />
230
+ </View>
231
+ {renderHeader()}
232
+ {renderContent()}
233
+ </SafeAreaView>
234
+ </Animated.View>
235
+ </TouchableWithoutFeedback>
236
+ </View>
237
+ </TouchableWithoutFeedback>
238
+ </Modal>
186
239
  );
187
240
  };
188
241
 
189
242
  const styles = StyleSheet.create({
243
+ modalOverlay: {
244
+ flex: 1,
245
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
246
+ justifyContent: 'flex-end',
247
+ alignItems: 'center',
248
+ },
249
+ bottomSheet: {
250
+ backgroundColor: '#fff',
251
+ width: width,
252
+ height: height * 0.9,
253
+ borderTopLeftRadius: 24,
254
+ borderTopRightRadius: 24,
255
+ overflow: 'hidden',
256
+ },
190
257
  container: {
191
258
  flex: 1,
192
259
  backgroundColor: '#fff',
193
260
  },
261
+ handleContainer: {
262
+ width: '100%',
263
+ alignItems: 'center',
264
+ paddingTop: 12,
265
+ paddingBottom: 8,
266
+ },
267
+ handle: {
268
+ width: 40,
269
+ height: 5,
270
+ borderRadius: 3,
271
+ backgroundColor: '#E0E0E0',
272
+ },
194
273
  header: {
195
274
  flexDirection: 'row',
196
275
  alignItems: 'center',
197
276
  padding: 16,
198
277
  backgroundColor: COLORS.headerBg,
199
- borderTopLeftRadius: 16,
200
- borderTopRightRadius: 16,
201
278
  },
202
279
  headerTitle: {
203
280
  flex: 1,
package/src/types.ts CHANGED
@@ -44,13 +44,14 @@ export interface OnairosButtonProps {
44
44
  buttonHeight?: number;
45
45
  hasStroke?: boolean;
46
46
  enabled?: boolean;
47
- buttonForm?: 'default' | 'login' | 'signup';
47
+ buttonForm?: 'default' | 'login' | 'signup' | 'connect';
48
48
  onRejection?: (error?: string) => void;
49
49
  onResolved?: (apiUrl: string, token: string, userData: any) => void;
50
50
  preCheck?: () => Promise<boolean>;
51
51
  color?: string;
52
52
  swerv?: boolean;
53
53
  debug?: boolean;
54
+ darkMode?: boolean;
54
55
  preferredPlatform?: string;
55
56
  testMode?: boolean;
56
57
  }