expo-app-ui 1.0.2 → 1.0.3
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": "expo-app-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
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": {
|
package/src/commands/add.js
CHANGED
|
@@ -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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/src/utils/pathUtils.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
67
|
-
|
|
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.
|
|
74
|
-
|
|
154
|
+
if (pkg.name === 'expo-app-ui') {
|
|
155
|
+
cachedPackageDir = currentDir;
|
|
156
|
+
return cachedPackageDir;
|
|
75
157
|
}
|
|
76
|
-
} catch (
|
|
158
|
+
} catch (err) {
|
|
77
159
|
// Continue searching
|
|
78
160
|
}
|
|
79
161
|
}
|
|
80
162
|
currentDir = path.dirname(currentDir);
|
|
81
163
|
}
|
|
82
164
|
|
|
83
|
-
//
|
|
165
|
+
// Final fallback (shouldn't normally reach here)
|
|
84
166
|
if (__dirname.includes('src')) {
|
|
85
|
-
|
|
167
|
+
cachedPackageDir = path.dirname(path.dirname(__dirname));
|
|
168
|
+
} else {
|
|
169
|
+
cachedPackageDir = path.dirname(__dirname);
|
|
86
170
|
}
|
|
87
171
|
|
|
88
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
},
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ActivityIndicator, View, ViewStyle, Text } from "react-native";
|
|
2
3
|
import { Image } from "expo-image";
|
|
3
|
-
|
|
4
|
-
|
|
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 =
|
|
33
|
+
borderColor = defaultColors.white,
|
|
28
34
|
borderWidth = 1,
|
|
29
35
|
borderRadius = 50,
|
|
30
|
-
backgroundColor =
|
|
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={
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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 =
|
|
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
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
<
|
|
87
|
+
<Text
|
|
88
|
+
style={{
|
|
89
|
+
color: defaultColors.white,
|
|
90
|
+
fontWeight: "700",
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
78
93
|
{currentCount}
|
|
79
|
-
</
|
|
80
|
-
<
|
|
94
|
+
</Text>
|
|
95
|
+
<Text
|
|
96
|
+
style={{
|
|
97
|
+
color: defaultColors.darkGray,
|
|
98
|
+
fontWeight: "700",
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
81
101
|
{count}
|
|
82
|
-
</
|
|
83
|
-
</
|
|
102
|
+
</Text>
|
|
103
|
+
</View>
|
|
84
104
|
)}
|
|
85
105
|
</View>
|
|
86
106
|
</View>
|