expo-app-ui 1.0.2 → 1.0.4

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 CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  A UI component library for Expo React Native. Copy components directly into your project and customize them to your needs.
4
4
 
5
+ ## Component Showcase
6
+
7
+ <div align="center">
8
+
9
+ <img src="https://expo-apps-ui.vercel.app/examples/buttons-example.png" alt="Button Component" width="150" />
10
+ <img src="https://expo-apps-ui.vercel.app/examples/custom-modal-example.gif" alt="Custom Modal" width="150" />
11
+ <img src="https://expo-apps-ui.vercel.app/examples/otp-input-example.gif" alt="OTP Input" width="150" />
12
+ <img src="https://expo-apps-ui.vercel.app/examples/top-loading-bar-example.gif" alt="Top Loading Bar" width="150" />
13
+
14
+ *Button • Custom Modal • OTP Input • Loading Bar*
15
+
16
+ </div>
17
+
5
18
  ## 📚 Documentation
6
19
 
7
20
  **👉 [View Full Documentation →](https://expo-apps-ui.vercel.app)**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-app-ui",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A UI component library for Expo React Native. Copy components directly into your project and customize them to your needs. Documentation: https://expo-apps-ui.vercel.app",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -153,7 +153,20 @@ async function addComponent(componentName, options = {}) {
153
153
  const packageDir = getPackageDir();
154
154
  const templatesDir = path.join(packageDir, 'templates');
155
155
 
156
+ // Track processing to prevent circular dependencies
157
+ const processingSet = options.processingSet || new Set();
156
158
  const kebabName = toKebabCase(componentName);
159
+
160
+ // Check if already processing this component to prevent infinite loops
161
+ const itemKey = `component:${kebabName}`;
162
+ if (processingSet.has(itemKey)) {
163
+ logger.debug(`Skipping ${componentName} - already being processed`);
164
+ return true;
165
+ }
166
+
167
+ // Mark as processing
168
+ processingSet.add(itemKey);
169
+
157
170
  const templatePath = path.join(templatesDir, 'components', 'ui', `${kebabName}.tsx`);
158
171
  const targetPath = path.join(config.getComponentsDir(), `${kebabName}.tsx`);
159
172
 
@@ -182,12 +195,17 @@ async function addComponent(componentName, options = {}) {
182
195
  }
183
196
  }
184
197
 
185
- // Check for related context
186
- const relatedContext = detectRelatedContext(kebabName, templatesDir);
187
- if (relatedContext) {
188
- const contextPath = path.join(projectRoot, 'context', `${relatedContext}.tsx`);
189
- if (!fs.existsSync(contextPath)) {
190
- dependenciesToAdd.push(`${relatedContext} context`);
198
+ // Check for related context (but skip if already processing to prevent loops)
199
+ // Only check if we're not being called from addContext (to prevent circular detection)
200
+ let relatedContext = null;
201
+ if (!options.skipRelatedCheck) {
202
+ relatedContext = detectRelatedContext(kebabName, templatesDir);
203
+ if (relatedContext) {
204
+ const contextPath = path.join(projectRoot, 'context', `${relatedContext}.tsx`);
205
+ const contextKey = `context:${relatedContext}`;
206
+ if (!fs.existsSync(contextPath) && !processingSet.has(contextKey)) {
207
+ dependenciesToAdd.push(`${relatedContext} context`);
208
+ }
191
209
  }
192
210
  }
193
211
 
@@ -222,15 +240,19 @@ async function addComponent(componentName, options = {}) {
222
240
  }
223
241
  }
224
242
 
225
- // Add related context if needed
226
- if (relatedContext) {
243
+ // Add related context if needed (pass processingSet to prevent loops)
244
+ // Only add if we're not being called from addContext (to prevent circular detection)
245
+ if (!options.skipRelatedCheck && relatedContext) {
227
246
  const contextPath = path.join(projectRoot, 'context', `${relatedContext}.tsx`);
228
- if (!fs.existsSync(contextPath)) {
247
+ const contextKey = `context:${relatedContext}`;
248
+ if (!fs.existsSync(contextPath) && !processingSet.has(contextKey)) {
229
249
  await addContext(relatedContext, {
230
250
  logger,
231
251
  config,
232
252
  silent: false,
233
253
  overwrite: false,
254
+ processingSet,
255
+ skipRelatedCheck: true, // Prevent context from checking for related component again
234
256
  });
235
257
  }
236
258
  }
@@ -282,7 +304,20 @@ async function addContext(contextName, options = {}) {
282
304
  const packageDir = getPackageDir();
283
305
  const templatesDir = path.join(packageDir, 'templates');
284
306
 
307
+ // Track processing to prevent circular dependencies
308
+ const processingSet = options.processingSet || new Set();
285
309
  const kebabName = toKebabCase(contextName);
310
+
311
+ // Check if already processing this context to prevent infinite loops
312
+ const itemKey = `context:${kebabName}`;
313
+ if (processingSet.has(itemKey)) {
314
+ logger.debug(`Skipping ${contextName} - already being processed`);
315
+ return true;
316
+ }
317
+
318
+ // Mark as processing
319
+ processingSet.add(itemKey);
320
+
286
321
  const templatePath = path.join(templatesDir, 'context', `${kebabName}.tsx`);
287
322
  const contextDir = path.join(projectRoot, 'context');
288
323
  const targetPath = path.join(contextDir, `${kebabName}.tsx`);
@@ -295,13 +330,14 @@ async function addContext(contextName, options = {}) {
295
330
  // Detect dependencies including related component
296
331
  const dependencies = detectDependencies(content);
297
332
 
298
- // Check for related component
333
+ // Check for related component (but skip if already processing to prevent loops)
299
334
  const relatedComponent = detectRelatedComponent(kebabName, templatesDir);
300
335
  const dependenciesToAdd = [];
301
336
 
302
337
  if (relatedComponent) {
303
338
  const componentPath = path.join(config.getComponentsDir(), `${relatedComponent}.tsx`);
304
- if (!fs.existsSync(componentPath)) {
339
+ const componentKey = `component:${relatedComponent}`;
340
+ if (!fs.existsSync(componentPath) && !processingSet.has(componentKey)) {
305
341
  dependenciesToAdd.push(`${relatedComponent} component`);
306
342
  }
307
343
  }
@@ -312,15 +348,19 @@ async function addContext(contextName, options = {}) {
312
348
  logger.debug('Adding required dependencies...\n');
313
349
  }
314
350
 
315
- // Add related component if needed
351
+ // Add related component if needed (pass processingSet to prevent loops)
352
+ // Skip related check to prevent circular detection
316
353
  if (relatedComponent) {
317
354
  const componentPath = path.join(config.getComponentsDir(), `${relatedComponent}.tsx`);
318
- if (!fs.existsSync(componentPath)) {
355
+ const componentKey = `component:${relatedComponent}`;
356
+ if (!fs.existsSync(componentPath) && !processingSet.has(componentKey)) {
319
357
  await addComponent(relatedComponent, {
320
358
  logger,
321
359
  config,
322
360
  silent: false,
323
361
  overwrite: false,
362
+ processingSet,
363
+ skipRelatedCheck: true, // Prevent component from checking for related context again
324
364
  });
325
365
  }
326
366
  }
@@ -456,10 +496,13 @@ async function handleAdd(name, options = {}) {
456
496
  return await addTopLoadingBar({ logger, config, overwrite });
457
497
  }
458
498
 
499
+ // Initialize processing set to track items and prevent circular dependencies
500
+ const processingSet = new Set();
501
+
459
502
  // Check if it's a component (look in components/ui/)
460
503
  const componentPath = path.join(templatesDir, 'components', 'ui', `${kebabName}.tsx`);
461
504
  if (fs.existsSync(componentPath)) {
462
- return await addComponent(name, { logger, config, overwrite });
505
+ return await addComponent(name, { logger, config, overwrite, processingSet });
463
506
  }
464
507
 
465
508
  // Check if it's a helper
@@ -478,11 +521,11 @@ async function handleAdd(name, options = {}) {
478
521
  const contextPathKebab = path.join(templatesDir, 'context', `${kebabName}.tsx`);
479
522
  const contextPathPascal = path.join(templatesDir, 'context', `${toPascalCase(kebabName)}.tsx`);
480
523
  if (fs.existsSync(contextPathKebab)) {
481
- return await addContext(name, { logger, config, overwrite });
524
+ return await addContext(name, { logger, config, overwrite, processingSet });
482
525
  } else if (fs.existsSync(contextPathPascal)) {
483
526
  // Use the actual file name
484
527
  const actualName = path.basename(contextPathPascal, '.tsx');
485
- return await addContext(actualName, { logger, config, overwrite });
528
+ return await addContext(actualName, { logger, config, overwrite, processingSet });
486
529
  }
487
530
 
488
531
  // Not found
@@ -55,38 +55,121 @@ function validatePath(filePath, baseDir) {
55
55
  return resolved;
56
56
  }
57
57
 
58
+ // Cache the package directory to avoid repeated lookups
59
+ let cachedPackageDir = null;
60
+
58
61
  /**
59
62
  * Get package directory - works from any context
60
63
  * @returns {string} Package directory path
61
64
  */
62
65
  function getPackageDir() {
63
- // Start from the current file's directory
66
+ // Return cached value if available
67
+ if (cachedPackageDir) {
68
+ return cachedPackageDir;
69
+ }
70
+
71
+ // Strategy 1: Use require.main.filename (works when running as CLI via npx or node)
72
+ // When running via npx, require.main points to the bin script
73
+ try {
74
+ if (require.main && require.main.filename) {
75
+ let currentDir = path.dirname(require.main.filename);
76
+
77
+ // Search up from bin script location
78
+ let depth = 0;
79
+ while (currentDir !== path.dirname(currentDir) && depth < 10) {
80
+ const packageJsonPath = path.join(currentDir, 'package.json');
81
+ if (fs.existsSync(packageJsonPath)) {
82
+ try {
83
+ const pkg = fs.readJsonSync(packageJsonPath);
84
+ if (pkg.name === 'expo-app-ui') {
85
+ const templatesPath = path.join(currentDir, 'templates');
86
+ if (fs.existsSync(templatesPath)) {
87
+ cachedPackageDir = currentDir;
88
+ return cachedPackageDir;
89
+ }
90
+ }
91
+ } catch (err) {
92
+ // Continue searching
93
+ }
94
+ }
95
+ currentDir = path.dirname(currentDir);
96
+ depth++;
97
+ }
98
+ }
99
+ } catch (error) {
100
+ // Continue to next strategy
101
+ }
102
+
103
+ // Strategy 2: Try require.resolve for package.json (works when installed via npm/npx)
104
+ try {
105
+ const packageJsonPath = require.resolve('expo-app-ui/package.json', { paths: [process.cwd(), __dirname] });
106
+ cachedPackageDir = path.dirname(packageJsonPath);
107
+ // Verify it has templates directory
108
+ const templatesPath = path.join(cachedPackageDir, 'templates');
109
+ if (fs.existsSync(templatesPath)) {
110
+ return cachedPackageDir;
111
+ }
112
+ } catch (error) {
113
+ // Continue to next strategy
114
+ }
115
+
116
+ // Strategy 3: Use relative path from current file (works for local development)
117
+ try {
118
+ // Go up from src/utils/pathUtils.js to package root
119
+ let currentDir = path.dirname(path.dirname(__dirname));
120
+
121
+ const packageJsonPath = path.join(currentDir, 'package.json');
122
+ const templatesPath = path.join(currentDir, 'templates');
123
+ if (fs.existsSync(packageJsonPath) && fs.existsSync(templatesPath)) {
124
+ try {
125
+ const pkg = fs.readJsonSync(packageJsonPath);
126
+ if (pkg.name === 'expo-app-ui') {
127
+ cachedPackageDir = currentDir;
128
+ return cachedPackageDir;
129
+ }
130
+ } catch (err) {
131
+ // Continue to next strategy
132
+ }
133
+ }
134
+ } catch (error) {
135
+ // Continue to next strategy
136
+ }
137
+
138
+ // Strategy 3: Search from current file's directory
64
139
  let currentDir = __dirname;
65
140
 
66
- // If we're in src/utils, go up to project root
67
- // If we're in bin/, go up to project root
141
+ // If we're in src/utils or src/commands, go up two levels
142
+ if (currentDir.endsWith(path.join('src', 'utils')) || currentDir.endsWith(path.join('src', 'commands'))) {
143
+ currentDir = path.dirname(path.dirname(currentDir));
144
+ } else if (currentDir.includes(path.sep + 'src' + path.sep)) {
145
+ currentDir = path.dirname(currentDir);
146
+ }
147
+
148
+ // Verify and search up if needed
68
149
  while (currentDir !== path.dirname(currentDir)) {
69
150
  const packageJsonPath = path.join(currentDir, 'package.json');
70
151
  if (fs.existsSync(packageJsonPath)) {
71
152
  try {
72
153
  const pkg = fs.readJsonSync(packageJsonPath);
73
- if (pkg.bin && (pkg.bin['expo-app-ui'] || pkg.name === 'expo-app-ui')) {
74
- return currentDir;
154
+ if (pkg.name === 'expo-app-ui') {
155
+ cachedPackageDir = currentDir;
156
+ return cachedPackageDir;
75
157
  }
76
- } catch (error) {
158
+ } catch (err) {
77
159
  // Continue searching
78
160
  }
79
161
  }
80
162
  currentDir = path.dirname(currentDir);
81
163
  }
82
164
 
83
- // Fallback: if we're in src/utils, go up two levels
165
+ // Final fallback (shouldn't normally reach here)
84
166
  if (__dirname.includes('src')) {
85
- return path.dirname(path.dirname(__dirname));
167
+ cachedPackageDir = path.dirname(path.dirname(__dirname));
168
+ } else {
169
+ cachedPackageDir = path.dirname(__dirname);
86
170
  }
87
171
 
88
- // If we're in bin/, go up one level
89
- return path.dirname(__dirname);
172
+ return cachedPackageDir;
90
173
  }
91
174
 
92
175
  /**
@@ -7,9 +7,8 @@ import {
7
7
  TextStyle,
8
8
  TouchableOpacityProps,
9
9
  } from "react-native";
10
- import { colors, fonts, size } from "@/constants/theme";
10
+
11
11
  import { ActivityIndicator } from "react-native";
12
- import { normalizeSize } from "@/helper/normalizeSize";
13
12
 
14
13
  type ButtonVariant = {
15
14
  backgroundColor?: string;
@@ -62,11 +61,11 @@ const Button = ({
62
61
  IconCenter, // New IconCenter prop
63
62
  style, // Custom style for the button container
64
63
  textStyle, // Custom style for the text
65
- fontSize = normalizeSize(size.md), // Default font size
64
+ fontSize = 18, // Default font size
66
65
  fontWeight = "normal", // Default font weight
67
66
  textColor = "#fff", // Default text color
68
67
  borderColor = "#d3d3d3",
69
- bgColor = colors.primary, // Default background color
68
+ bgColor = "black", // Default background color
70
69
  hideTextWithCenterIcon = false, // Default to false (show text with center icon)
71
70
  ...props
72
71
  }: ButtonPropsExtended) => {
@@ -171,7 +170,7 @@ const Button = ({
171
170
  styles.buttonText,
172
171
  {
173
172
  color: variantStyles.textColor,
174
- fontFamily: fonts.inter,
173
+ // fontFamily: fonts.inter, // Commented out to avoid using fonts.inter
175
174
  // Hide text visually (but keep it for screen readers) if IconCenter is present and hideTextWithCenterIcon is true
176
175
  opacity: IconCenter && hideTextWithCenterIcon ? 0 : 1,
177
176
  },
@@ -6,8 +6,11 @@ import {
6
6
  StyleSheet,
7
7
  TouchableWithoutFeedback,
8
8
  useWindowDimensions,
9
- StyleProp, // Import StyleProp
10
- ViewStyle, // Import ViewStyle
9
+ StyleProp,
10
+ ViewStyle,
11
+ Keyboard,
12
+ Platform,
13
+ BackHandler,
11
14
  } from "react-native";
12
15
  import Animated, {
13
16
  useSharedValue,
@@ -17,14 +20,25 @@ import Animated, {
17
20
  runOnJS,
18
21
  } from "react-native-reanimated";
19
22
 
23
+ // Default colors - using black and white as defaults
24
+ const defaultColors = {
25
+ white: "#FFFFFF",
26
+ black: "#000000",
27
+ backdrop: "rgba(0, 0, 0, 0.5)",
28
+ };
29
+
20
30
  // Define the props interface
21
31
  interface CustomModalProps {
22
32
  visible: boolean;
23
33
  onClose: () => void;
24
34
  preventBackgroundTouchEvent?: boolean;
25
35
  children: React.ReactNode;
26
- style?: StyleProp<ViewStyle>; // <-- Prop for root container
27
- modalStyle?: StyleProp<ViewStyle>; // <-- Prop for the content box
36
+ style?: StyleProp<ViewStyle>; // Prop for root container
37
+ modalStyle?: StyleProp<ViewStyle>; // Prop for the content box
38
+ noBackdrop?: boolean; // Hide backdrop
39
+ backgroundColor?: string; // Modal background color
40
+ backdropColor?: string; // Backdrop color
41
+ borderRadius?: number; // Border radius for modal
28
42
  }
29
43
 
30
44
  const MODAL_ANIMATION_DURATION = 300;
@@ -36,27 +50,106 @@ const CustomModal: React.FC<CustomModalProps> = ({
36
50
  onClose,
37
51
  preventBackgroundTouchEvent,
38
52
  children,
39
- style, // <-- Destructure new prop
40
- modalStyle, // <-- Destructure new prop
53
+ style,
54
+ modalStyle,
55
+ noBackdrop = false,
56
+ backgroundColor = defaultColors.white,
57
+ backdropColor = defaultColors.backdrop,
58
+ borderRadius = 15,
41
59
  }) => {
42
60
  const { height } = useWindowDimensions();
43
61
 
44
62
  const [isModalRendered, setIsModalRendered] = useState(visible);
45
63
  const backdropOpacity = useSharedValue(0);
46
64
  const modalTranslateY = useSharedValue(height);
65
+ const keyboardOffset = useSharedValue(0);
66
+
67
+ // Check if modal is positioned at bottom (bottom sheet style)
68
+ const flattenedStyle = style ? StyleSheet.flatten(style) : {};
69
+ const isBottomSheet = (flattenedStyle as any)?.justifyContent === "flex-end";
47
70
 
48
71
  const backdropAnimatedStyle = useAnimatedStyle(() => ({
49
- opacity: backdropOpacity.value,
72
+ opacity: noBackdrop ? 0 : backdropOpacity.value,
50
73
  }));
51
74
 
52
- const modalAnimatedStyle = useAnimatedStyle(() => ({
53
- transform: [{ translateY: modalTranslateY.value }],
54
- }));
75
+ const modalAnimatedStyle = useAnimatedStyle(() => {
76
+ if (isBottomSheet) {
77
+ // For bottom sheets, position at bottom and translate from below
78
+ // Subtract keyboardOffset to move modal up when keyboard appears
79
+ return {
80
+ transform: [
81
+ {
82
+ translateY: modalTranslateY.value - keyboardOffset.value,
83
+ },
84
+ ],
85
+ };
86
+ }
87
+ // For centered modals, use standard transform
88
+ return {
89
+ transform: [{ translateY: modalTranslateY.value - keyboardOffset.value }],
90
+ };
91
+ });
92
+
93
+ // Handle Android back button
94
+ useEffect(() => {
95
+ if (!visible) return;
96
+
97
+ const backHandler = BackHandler.addEventListener(
98
+ "hardwareBackPress",
99
+ () => {
100
+ if (!preventBackgroundTouchEvent) {
101
+ onClose();
102
+ return true; // Prevent default back behavior
103
+ }
104
+ return false; // Allow default back behavior if preventBackgroundTouchEvent is true
105
+ }
106
+ );
107
+
108
+ return () => backHandler.remove();
109
+ }, [visible, preventBackgroundTouchEvent, onClose]);
110
+
111
+ // Handle keyboard events for bottom sheet modals
112
+ useEffect(() => {
113
+ if (!visible || !isBottomSheet) {
114
+ // Reset keyboard offset when modal is not visible or not a bottom sheet
115
+ keyboardOffset.value = 0;
116
+ return;
117
+ }
118
+
119
+ const showEvent =
120
+ Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
121
+ const hideEvent =
122
+ Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
123
+
124
+ const keyboardWillShowListener = Keyboard.addListener(showEvent, (e) => {
125
+ const keyboardHeight = e.endCoordinates.height;
126
+ keyboardOffset.value = withTiming(keyboardHeight, {
127
+ duration: Platform.OS === "ios" ? e.duration || 250 : 250,
128
+ easing: Easing.out(Easing.ease),
129
+ });
130
+ });
131
+
132
+ const keyboardWillHideListener = Keyboard.addListener(hideEvent, () => {
133
+ keyboardOffset.value = withTiming(0, {
134
+ duration: 250,
135
+ easing: Easing.out(Easing.ease),
136
+ });
137
+ });
138
+
139
+ return () => {
140
+ keyboardWillShowListener.remove();
141
+ keyboardWillHideListener.remove();
142
+ };
143
+ }, [visible, isBottomSheet, keyboardOffset]);
55
144
 
56
145
  useEffect(() => {
57
146
  if (visible) {
58
147
  setIsModalRendered(true);
59
- backdropOpacity.value = withTiming(0.5, {
148
+ // Ensure modalTranslateY starts from height for bottom sheets
149
+ if (isBottomSheet) {
150
+ modalTranslateY.value = height;
151
+ }
152
+ backdropOpacity.value = withTiming(noBackdrop ? 0 : 0.5, {
60
153
  duration: MODAL_ANIMATION_DURATION,
61
154
  easing: backdropEasing,
62
155
  });
@@ -69,6 +162,10 @@ const CustomModal: React.FC<CustomModalProps> = ({
69
162
  duration: MODAL_ANIMATION_DURATION,
70
163
  easing: backdropEasing,
71
164
  });
165
+ keyboardOffset.value = withTiming(0, {
166
+ duration: MODAL_ANIMATION_DURATION,
167
+ easing: modalEasing,
168
+ });
72
169
  modalTranslateY.value = withTiming(
73
170
  height,
74
171
  {
@@ -82,23 +179,46 @@ const CustomModal: React.FC<CustomModalProps> = ({
82
179
  }
83
180
  );
84
181
  }
85
- }, [visible, height, backdropOpacity, modalTranslateY]);
182
+ }, [visible, height, isBottomSheet, backdropOpacity, modalTranslateY, keyboardOffset, noBackdrop]);
86
183
 
87
184
  if (!isModalRendered) {
88
185
  return null;
89
186
  }
90
187
 
91
188
  return (
92
- // Apply the custom root 'style' prop here
93
189
  <Animated.View style={[styles.container, style]}>
94
- {!preventBackgroundTouchEvent && (
95
- <TouchableWithoutFeedback onPress={onClose}>
96
- <Animated.View style={[styles.backdrop, backdropAnimatedStyle]} />
190
+ {!noBackdrop && (
191
+ <TouchableWithoutFeedback
192
+ onPress={() => {
193
+ // By default, backdrop touch closes the modal
194
+ // Only prevent if explicitly set to true
195
+ if (!preventBackgroundTouchEvent) {
196
+ onClose();
197
+ }
198
+ }}
199
+ >
200
+ <Animated.View
201
+ style={[
202
+ styles.backdrop,
203
+ { backgroundColor: backdropColor },
204
+ backdropAnimatedStyle,
205
+ ]}
206
+ />
97
207
  </TouchableWithoutFeedback>
98
208
  )}
99
209
 
100
- {/* Apply the custom 'modalStyle' prop here */}
101
- <Animated.View style={[styles.modalView, modalAnimatedStyle, modalStyle]}>
210
+ <Animated.View
211
+ style={[
212
+ styles.modalView,
213
+ {
214
+ backgroundColor,
215
+ borderRadius: isBottomSheet ? borderRadius : borderRadius,
216
+ ...(isBottomSheet && styles.modalViewBottomSheet),
217
+ },
218
+ modalAnimatedStyle,
219
+ modalStyle,
220
+ ]}
221
+ >
102
222
  {children}
103
223
  </Animated.View>
104
224
  </Animated.View>
@@ -112,25 +232,38 @@ const styles = StyleSheet.create({
112
232
  left: 0,
113
233
  right: 0,
114
234
  bottom: 0,
115
- // Changed to 'flex-end' to act like a bottom-sheet
116
235
  justifyContent: "center",
117
236
  alignItems: "center",
118
237
  zIndex: 1000,
119
238
  },
120
239
  backdrop: {
121
240
  ...StyleSheet.absoluteFillObject,
122
- backgroundColor: "rgba(0, 0, 0, 0.8)",
123
241
  },
124
242
  modalView: {
125
- // We keep your default styles
126
- // backgroundColor: 'white', // Good to apply this in the App.tsx
127
- borderTopLeftRadius: 10, // Added top radius for bottom-sheet look
128
- borderTopRightRadius: 10, // Added top radius for bottom-sheet look
129
- padding: 10,
243
+ padding: 20,
244
+ width: "90%",
245
+ maxWidth: 500,
246
+ maxHeight: "80%",
247
+ shadowColor: "#000",
248
+ shadowOffset: {
249
+ width: 0,
250
+ height: 2,
251
+ },
252
+ shadowOpacity: 0.25,
253
+ shadowRadius: 3.84,
254
+ elevation: 5,
255
+ },
256
+ modalViewBottomSheet: {
257
+ position: "absolute",
258
+ bottom: 0,
259
+ left: 0,
260
+ right: 0,
130
261
  width: "100%",
131
- maxHeight: "100%",
132
- // justifyContent: "center",
133
- // alignItems: "center",
262
+ maxWidth: "100%",
263
+ borderTopLeftRadius: 20,
264
+ borderTopRightRadius: 20,
265
+ borderBottomLeftRadius: 0,
266
+ borderBottomRightRadius: 0,
134
267
  },
135
268
  });
136
269
 
@@ -1,7 +1,13 @@
1
- import { ActivityIndicator, View, ViewStyle } from "react-native";
1
+ import React from "react";
2
+ import { ActivityIndicator, View, ViewStyle, Text } from "react-native";
2
3
  import { Image } from "expo-image";
3
- import { colors, size } from "@/constants/theme";
4
- import CustomText from "@/components/ui/CustomText";
4
+
5
+ // Default colors - using black and white as defaults
6
+ const defaultColors = {
7
+ white: "#FFFFFF",
8
+ black: "#000000",
9
+ primary: "#000000", // Default to black
10
+ };
5
11
 
6
12
  interface ProfilePicProps {
7
13
  source?: string;
@@ -24,10 +30,10 @@ const ProfilePic = ({
24
30
  username,
25
31
  width = 50,
26
32
  height = 50,
27
- borderColor = colors.white,
33
+ borderColor = defaultColors.white,
28
34
  borderWidth = 1,
29
35
  borderRadius = 50,
30
- backgroundColor = colors.primary,
36
+ backgroundColor = defaultColors.primary,
31
37
  isLoading = false,
32
38
  style,
33
39
  }: ProfilePicProps) => {
@@ -47,8 +53,7 @@ const ProfilePic = ({
47
53
  return (
48
54
  <View style={[combinedStyle, style]}>
49
55
  <ActivityIndicator
50
- color={colors.white}
51
- // style={{ borderColor: colors.white }}
56
+ color={defaultColors.white}
52
57
  />
53
58
  </View>
54
59
  );
@@ -65,9 +70,15 @@ const ProfilePic = ({
65
70
  transition={1000}
66
71
  />
67
72
  ) : (
68
- <CustomText color={colors.white} fontSize={size.lg}>
73
+ <Text
74
+ style={{
75
+ color: defaultColors.white,
76
+ fontSize: 20,
77
+ }}
78
+ allowFontScaling={false}
79
+ >
69
80
  {username ? username.charAt(0).toUpperCase() : "Z"}
70
- </CustomText>
81
+ </Text>
71
82
  )}
72
83
  </View>
73
84
  );
@@ -1,8 +1,13 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import { Animated, StyleSheet, Text, View } from "react-native";
3
- import BoxView from "@/components/ui/BoxView";
4
- import CustomText from "@/components/ui/CustomText";
5
- import { colors } from "@/constants/theme";
3
+
4
+ // Default colors - using black and white as defaults
5
+ const defaultColors = {
6
+ white: "#FFFFFF",
7
+ black: "#000000",
8
+ darkGray: "#1C1C1E",
9
+ forest_green: "#228B22", // Keep forest_green for progress bar
10
+ };
6
11
 
7
12
  interface CustomProgressBarProps {
8
13
  progress?: number;
@@ -20,7 +25,7 @@ const CustomProgressBar: React.FC<CustomProgressBarProps> = ({
20
25
  progress,
21
26
  width = 300,
22
27
  height = 20,
23
- color = colors.forest_green,
28
+ color = defaultColors.forest_green,
24
29
  backgroundColor = "#e0e0e0",
25
30
  label = "",
26
31
  variant = "normal",
@@ -69,18 +74,33 @@ const CustomProgressBar: React.FC<CustomProgressBarProps> = ({
69
74
  ]}
70
75
  />
71
76
  {variant === "count" && (
72
- <BoxView
73
- flexDirection="row"
74
- justifyContent="space-between"
75
- style={{ position: "absolute", right: 10, left: 10, top: 5 }}
77
+ <View
78
+ style={{
79
+ position: "absolute",
80
+ right: 10,
81
+ left: 10,
82
+ top: 5,
83
+ flexDirection: "row",
84
+ justifyContent: "space-between",
85
+ }}
76
86
  >
77
- <CustomText color={colors.white} fontWeight="700">
87
+ <Text
88
+ style={{
89
+ color: defaultColors.white,
90
+ fontWeight: "700",
91
+ }}
92
+ >
78
93
  {currentCount}
79
- </CustomText>
80
- <CustomText color={colors.darkGray} fontWeight="700">
94
+ </Text>
95
+ <Text
96
+ style={{
97
+ color: defaultColors.darkGray,
98
+ fontWeight: "700",
99
+ }}
100
+ >
81
101
  {count}
82
- </CustomText>
83
- </BoxView>
102
+ </Text>
103
+ </View>
84
104
  )}
85
105
  </View>
86
106
  </View>