expo-app-ui 1.0.1 → 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/README.md +52 -30
- package/bin/expo-app-ui.js +3 -2
- package/package.json +13 -5
- package/src/commands/add.js +138 -11
- package/src/core/dependencyDetector.js +82 -0
- package/src/core/templateProcessor.js +13 -3
- package/src/utils/pathUtils.js +93 -10
- package/src/utils/prompt.js +42 -0
- package/templates/components/ui/button.tsx +4 -5
- package/templates/components/ui/profile-pic.tsx +20 -9
- package/templates/components/ui/progress-bar.tsx +33 -13
package/README.md
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
# Expo UI
|
|
1
|
+
# Expo App UI
|
|
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
|
-
##
|
|
5
|
+
## 📚 Documentation
|
|
6
|
+
|
|
7
|
+
**👉 [View Full Documentation →](https://expo-apps-ui.vercel.app)**
|
|
8
|
+
|
|
9
|
+
For complete documentation, usage examples, API references, and detailed instructions, visit our documentation site:
|
|
10
|
+
|
|
11
|
+
**https://expo-apps-ui.vercel.app**
|
|
12
|
+
|
|
13
|
+
The documentation includes:
|
|
14
|
+
- 📖 Getting Started Guide
|
|
15
|
+
- 🎨 Component Documentation
|
|
16
|
+
- 🛠️ CLI Commands Reference
|
|
17
|
+
- 💡 Usage Examples
|
|
18
|
+
- 🔧 Configuration Guides
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Installation
|
|
6
23
|
|
|
7
24
|
You can use this library directly with npx:
|
|
8
25
|
|
|
@@ -26,14 +43,14 @@ expo-app-ui add <component-name>
|
|
|
26
43
|
|
|
27
44
|
### Adding Components
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
Add a component to your project:
|
|
30
47
|
|
|
31
48
|
```bash
|
|
32
49
|
npx expo-app-ui add custom-text
|
|
33
50
|
```
|
|
34
51
|
|
|
35
52
|
This will:
|
|
36
|
-
- Copy the component template to `components/ui/
|
|
53
|
+
- Copy the component template to `components/ui/custom-text.tsx` in your project
|
|
37
54
|
- Automatically detect and add required dependencies (helpers, constants)
|
|
38
55
|
- Preserve all imports and dependencies
|
|
39
56
|
- Allow you to customize the component as needed
|
|
@@ -44,44 +61,33 @@ npx expo-app-ui add button
|
|
|
44
61
|
# Automatically adds normalizeSize helper and theme constants if needed
|
|
45
62
|
```
|
|
46
63
|
|
|
47
|
-
### Adding Helpers
|
|
48
|
-
|
|
49
|
-
To add a helper utility:
|
|
64
|
+
### Adding Helpers, Constants, and Contexts
|
|
50
65
|
|
|
51
66
|
```bash
|
|
67
|
+
# Add a helper
|
|
52
68
|
npx expo-app-ui add normalizeSize
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
This will copy the helper to `helpers/normalizeSize.ts` in your project.
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
To add constants (like theme):
|
|
60
|
-
|
|
61
|
-
```bash
|
|
70
|
+
# Add constants
|
|
62
71
|
npx expo-app-ui add theme
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
This will copy the constants to `constants/theme.ts` in your project.
|
|
66
72
|
|
|
67
|
-
|
|
73
|
+
# Add contexts (e.g., top loading bar)
|
|
74
|
+
npx expo-app-ui add top-loading-bar
|
|
75
|
+
```
|
|
68
76
|
|
|
69
77
|
### Listing Available Items
|
|
70
78
|
|
|
71
|
-
To see all available components, helpers, and constants:
|
|
72
|
-
|
|
73
79
|
```bash
|
|
74
80
|
npx expo-app-ui list
|
|
75
81
|
```
|
|
76
82
|
|
|
77
83
|
### Overwriting Existing Files
|
|
78
84
|
|
|
79
|
-
To replace an existing component, helper, or constant:
|
|
80
|
-
|
|
81
85
|
```bash
|
|
82
86
|
npx expo-app-ui add custom-text --overwrite
|
|
83
87
|
```
|
|
84
88
|
|
|
89
|
+
> 📖 **For detailed usage instructions, examples, and API documentation, visit [expo-apps-ui.vercel.app](https://expo-apps-ui.vercel.app)**
|
|
90
|
+
|
|
85
91
|
## Project Structure
|
|
86
92
|
|
|
87
93
|
After adding components, your project structure will look like:
|
|
@@ -105,9 +111,12 @@ your-project/
|
|
|
105
111
|
The CLI automatically detects when a component requires:
|
|
106
112
|
- `normalizeSize` helper (from `@/helper/normalizeSize`)
|
|
107
113
|
- `theme` constants (from `@/constants/theme`)
|
|
114
|
+
- Related components or contexts
|
|
108
115
|
|
|
109
116
|
When you add a component that uses these dependencies, they will be automatically added to your project.
|
|
110
117
|
|
|
118
|
+
> 📖 **Learn more about auto-dependency detection in the [documentation](https://expo-apps-ui.vercel.app/docs/cli)**
|
|
119
|
+
|
|
111
120
|
## Component Templates
|
|
112
121
|
|
|
113
122
|
Components are copied directly into your project, so you have full control:
|
|
@@ -119,12 +128,13 @@ Components are copied directly into your project, so you have full control:
|
|
|
119
128
|
|
|
120
129
|
## Path Aliases
|
|
121
130
|
|
|
122
|
-
Components use path aliases like `@/components/ui/
|
|
131
|
+
Components use path aliases like `@/components/ui/custom-text` and `@/constants/theme`. Make sure your Expo project has these configured:
|
|
123
132
|
|
|
124
133
|
**tsconfig.json:**
|
|
125
134
|
```json
|
|
126
135
|
{
|
|
127
136
|
"compilerOptions": {
|
|
137
|
+
"baseUrl": ".",
|
|
128
138
|
"paths": {
|
|
129
139
|
"@/*": ["./*"]
|
|
130
140
|
}
|
|
@@ -149,6 +159,8 @@ module.exports = {
|
|
|
149
159
|
};
|
|
150
160
|
```
|
|
151
161
|
|
|
162
|
+
> 📖 **See the [Getting Started guide](https://expo-apps-ui.vercel.app/docs/getting-started) for detailed setup instructions**
|
|
163
|
+
|
|
152
164
|
## Available Components
|
|
153
165
|
|
|
154
166
|
- `custom-text` - A customizable Text component with font, color, and spacing props
|
|
@@ -159,6 +171,11 @@ module.exports = {
|
|
|
159
171
|
- `progress-bar` - A progress bar component with variants
|
|
160
172
|
- `marquee` - A scrolling marquee component
|
|
161
173
|
- `otp-input` - An OTP input component
|
|
174
|
+
- `loading-bar` - An animated top loading bar component
|
|
175
|
+
|
|
176
|
+
## Available Contexts
|
|
177
|
+
|
|
178
|
+
- `top-loading-bar-context` - React Context for managing top loading bar state
|
|
162
179
|
|
|
163
180
|
## Available Helpers
|
|
164
181
|
|
|
@@ -168,15 +185,20 @@ module.exports = {
|
|
|
168
185
|
|
|
169
186
|
- `theme` - Theme constants including colors, fonts, and sizes
|
|
170
187
|
|
|
188
|
+
> 📖 **View complete documentation, props, examples, and usage for all components at [expo-apps-ui.vercel.app/docs/components](https://expo-apps-ui.vercel.app/docs/components)**
|
|
189
|
+
|
|
171
190
|
## Contributing
|
|
172
191
|
|
|
173
|
-
To add new components, helpers, or
|
|
192
|
+
To add new components, helpers, constants, or contexts:
|
|
193
|
+
|
|
194
|
+
1. **Components**: Create a new `.tsx` file in the `templates/components/ui/` directory
|
|
195
|
+
2. **Contexts**: Create a new `.tsx` file in the `templates/context/` directory
|
|
196
|
+
3. **Helpers**: Create a new `.ts` file in the `templates/helpers/` directory
|
|
197
|
+
4. **Constants**: Create a new `.ts` file in the `templates/constants/` directory
|
|
198
|
+
5. Use kebab-case for filenames (e.g., `my-component.tsx`)
|
|
199
|
+
6. The item will be automatically available via the CLI
|
|
174
200
|
|
|
175
|
-
|
|
176
|
-
2. **Helpers**: Create a new `.ts` file in the `templates/helpers/` directory
|
|
177
|
-
3. **Constants**: Create a new `.ts` file in the `templates/constants/` directory
|
|
178
|
-
4. Use kebab-case for filenames (e.g., `my-component.tsx`)
|
|
179
|
-
5. The item will be automatically available via the CLI
|
|
201
|
+
> 📖 **For contribution guidelines and best practices, visit the [documentation](https://expo-apps-ui.vercel.app)**
|
|
180
202
|
|
|
181
203
|
## License
|
|
182
204
|
|
package/bin/expo-app-ui.js
CHANGED
|
@@ -45,8 +45,9 @@ program
|
|
|
45
45
|
logger.error(`"${name}" not found.`);
|
|
46
46
|
logger.info('Run "npx expo-app-ui list" to see available items.');
|
|
47
47
|
} else if (error instanceof FileExistsError) {
|
|
48
|
-
|
|
49
|
-
logger.
|
|
48
|
+
// User was prompted and chose not to overwrite, or non-interactive mode
|
|
49
|
+
logger.warning(`File already exists: ${error.filePath}`);
|
|
50
|
+
logger.info('Use --overwrite to replace it, or run the command again and choose "y" when prompted.');
|
|
50
51
|
} else if (error instanceof InvalidInputError) {
|
|
51
52
|
logger.error(error.message);
|
|
52
53
|
} else if (error instanceof CLIError) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-app-ui",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "A UI component library for Expo React Native",
|
|
3
|
+
"version": "1.0.3",
|
|
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": {
|
|
7
7
|
"expo-app-ui": "bin/expo-app-ui.js"
|
|
@@ -18,12 +18,20 @@
|
|
|
18
18
|
"keywords": [
|
|
19
19
|
"expo",
|
|
20
20
|
"react-native",
|
|
21
|
+
"android",
|
|
22
|
+
"ios",
|
|
21
23
|
"ui",
|
|
22
24
|
"components",
|
|
23
25
|
"cli",
|
|
24
|
-
"component-library"
|
|
26
|
+
"component-library",
|
|
27
|
+
"expo-ui",
|
|
28
|
+
"react-native-components",
|
|
29
|
+
"mobile-ui",
|
|
30
|
+
"expo-components",
|
|
31
|
+
"ui-library",
|
|
32
|
+
"react-native-ui"
|
|
25
33
|
],
|
|
26
|
-
"author": "",
|
|
34
|
+
"author": "Krish Panchani",
|
|
27
35
|
"license": "MIT",
|
|
28
36
|
"repository": {
|
|
29
37
|
"type": "git",
|
|
@@ -32,7 +40,7 @@
|
|
|
32
40
|
"bugs": {
|
|
33
41
|
"url": "https://github.com/Krish-Panchani/expo-app-ui/issues"
|
|
34
42
|
},
|
|
35
|
-
|
|
43
|
+
"homepage": "https://expo-apps-ui.vercel.app",
|
|
36
44
|
"engines": {
|
|
37
45
|
"node": ">=14.0.0"
|
|
38
46
|
},
|
package/src/commands/add.js
CHANGED
|
@@ -3,9 +3,26 @@ const fs = require('fs-extra');
|
|
|
3
3
|
const { toKebabCase, toPascalCase, getPackageDir } = require('../utils/pathUtils');
|
|
4
4
|
const { TemplateNotFoundError } = require('../utils/errors');
|
|
5
5
|
const { readTemplate, writeFile, listTemplates } = require('../core/templateProcessor');
|
|
6
|
-
const { detectDependencies } = require('../core/dependencyDetector');
|
|
6
|
+
const { detectDependencies, detectRelatedContext, detectRelatedComponent } = require('../core/dependencyDetector');
|
|
7
7
|
const Logger = require('../utils/logger');
|
|
8
8
|
const Config = require('../utils/config');
|
|
9
|
+
const { promptOverwrite } = require('../utils/prompt');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Helper to create a file exists handler with prompt
|
|
13
|
+
*/
|
|
14
|
+
function createFileExistsHandler(options) {
|
|
15
|
+
return async (filePath) => {
|
|
16
|
+
if (options.overwrite) {
|
|
17
|
+
return true; // Already confirmed via --overwrite flag
|
|
18
|
+
}
|
|
19
|
+
// Check if we're in interactive mode (not silent and TTY)
|
|
20
|
+
if (!options.silent && process.stdin.isTTY) {
|
|
21
|
+
return await promptOverwrite(filePath);
|
|
22
|
+
}
|
|
23
|
+
return false; // Non-interactive, don't overwrite
|
|
24
|
+
};
|
|
25
|
+
}
|
|
9
26
|
|
|
10
27
|
/**
|
|
11
28
|
* Add a helper file
|
|
@@ -25,11 +42,13 @@ async function addHelper(helperName, options = {}) {
|
|
|
25
42
|
logger.debug(`Looking for template: ${templatePath}`);
|
|
26
43
|
|
|
27
44
|
const content = await readTemplate(templatePath);
|
|
45
|
+
const handleFileExists = createFileExistsHandler(options);
|
|
28
46
|
const validatedPath = await writeFile(
|
|
29
47
|
targetPath,
|
|
30
48
|
content,
|
|
31
49
|
projectRoot,
|
|
32
|
-
options.overwrite || false
|
|
50
|
+
options.overwrite || false,
|
|
51
|
+
handleFileExists
|
|
33
52
|
);
|
|
34
53
|
|
|
35
54
|
if (!options.silent) {
|
|
@@ -91,11 +110,13 @@ async function addConstant(constantName, options = {}) {
|
|
|
91
110
|
}
|
|
92
111
|
}
|
|
93
112
|
|
|
113
|
+
const handleFileExists = createFileExistsHandler(options);
|
|
94
114
|
const validatedPath = await writeFile(
|
|
95
115
|
targetPath,
|
|
96
116
|
content,
|
|
97
117
|
projectRoot,
|
|
98
|
-
options.overwrite || false
|
|
118
|
+
options.overwrite || false,
|
|
119
|
+
handleFileExists
|
|
99
120
|
);
|
|
100
121
|
|
|
101
122
|
if (!options.silent) {
|
|
@@ -132,7 +153,20 @@ async function addComponent(componentName, options = {}) {
|
|
|
132
153
|
const packageDir = getPackageDir();
|
|
133
154
|
const templatesDir = path.join(packageDir, 'templates');
|
|
134
155
|
|
|
156
|
+
// Track processing to prevent circular dependencies
|
|
157
|
+
const processingSet = options.processingSet || new Set();
|
|
135
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
|
+
|
|
136
170
|
const templatePath = path.join(templatesDir, 'components', 'ui', `${kebabName}.tsx`);
|
|
137
171
|
const targetPath = path.join(config.getComponentsDir(), `${kebabName}.tsx`);
|
|
138
172
|
|
|
@@ -161,6 +195,20 @@ async function addComponent(componentName, options = {}) {
|
|
|
161
195
|
}
|
|
162
196
|
}
|
|
163
197
|
|
|
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
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
164
212
|
// Show summary of what will be added
|
|
165
213
|
if (dependenciesToAdd.length > 0) {
|
|
166
214
|
logger.info(`\nDetected dependencies: ${dependenciesToAdd.join(', ')}`);
|
|
@@ -192,11 +240,30 @@ async function addComponent(componentName, options = {}) {
|
|
|
192
240
|
}
|
|
193
241
|
}
|
|
194
242
|
|
|
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) {
|
|
246
|
+
const contextPath = path.join(projectRoot, 'context', `${relatedContext}.tsx`);
|
|
247
|
+
const contextKey = `context:${relatedContext}`;
|
|
248
|
+
if (!fs.existsSync(contextPath) && !processingSet.has(contextKey)) {
|
|
249
|
+
await addContext(relatedContext, {
|
|
250
|
+
logger,
|
|
251
|
+
config,
|
|
252
|
+
silent: false,
|
|
253
|
+
overwrite: false,
|
|
254
|
+
processingSet,
|
|
255
|
+
skipRelatedCheck: true, // Prevent context from checking for related component again
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const handleFileExists = createFileExistsHandler(options);
|
|
195
261
|
const validatedPath = await writeFile(
|
|
196
262
|
targetPath,
|
|
197
263
|
content,
|
|
198
264
|
projectRoot,
|
|
199
|
-
options.overwrite || false
|
|
265
|
+
options.overwrite || false,
|
|
266
|
+
handleFileExists
|
|
200
267
|
);
|
|
201
268
|
|
|
202
269
|
logger.success(`Added ${componentName} component to ${path.relative(projectRoot, validatedPath)}`);
|
|
@@ -237,20 +304,74 @@ async function addContext(contextName, options = {}) {
|
|
|
237
304
|
const packageDir = getPackageDir();
|
|
238
305
|
const templatesDir = path.join(packageDir, 'templates');
|
|
239
306
|
|
|
307
|
+
// Track processing to prevent circular dependencies
|
|
308
|
+
const processingSet = options.processingSet || new Set();
|
|
240
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
|
+
|
|
241
321
|
const templatePath = path.join(templatesDir, 'context', `${kebabName}.tsx`);
|
|
242
322
|
const contextDir = path.join(projectRoot, 'context');
|
|
243
|
-
const targetPath = path.join(contextDir, `${
|
|
323
|
+
const targetPath = path.join(contextDir, `${kebabName}.tsx`);
|
|
244
324
|
|
|
245
325
|
try {
|
|
246
326
|
logger.debug(`Looking for template: ${templatePath}`);
|
|
247
327
|
|
|
248
328
|
const content = await readTemplate(templatePath);
|
|
329
|
+
|
|
330
|
+
// Detect dependencies including related component
|
|
331
|
+
const dependencies = detectDependencies(content);
|
|
332
|
+
|
|
333
|
+
// Check for related component (but skip if already processing to prevent loops)
|
|
334
|
+
const relatedComponent = detectRelatedComponent(kebabName, templatesDir);
|
|
335
|
+
const dependenciesToAdd = [];
|
|
336
|
+
|
|
337
|
+
if (relatedComponent) {
|
|
338
|
+
const componentPath = path.join(config.getComponentsDir(), `${relatedComponent}.tsx`);
|
|
339
|
+
const componentKey = `component:${relatedComponent}`;
|
|
340
|
+
if (!fs.existsSync(componentPath) && !processingSet.has(componentKey)) {
|
|
341
|
+
dependenciesToAdd.push(`${relatedComponent} component`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Show summary of what will be added
|
|
346
|
+
if (dependenciesToAdd.length > 0) {
|
|
347
|
+
logger.info(`\nDetected dependencies: ${dependenciesToAdd.join(', ')}`);
|
|
348
|
+
logger.debug('Adding required dependencies...\n');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Add related component if needed (pass processingSet to prevent loops)
|
|
352
|
+
// Skip related check to prevent circular detection
|
|
353
|
+
if (relatedComponent) {
|
|
354
|
+
const componentPath = path.join(config.getComponentsDir(), `${relatedComponent}.tsx`);
|
|
355
|
+
const componentKey = `component:${relatedComponent}`;
|
|
356
|
+
if (!fs.existsSync(componentPath) && !processingSet.has(componentKey)) {
|
|
357
|
+
await addComponent(relatedComponent, {
|
|
358
|
+
logger,
|
|
359
|
+
config,
|
|
360
|
+
silent: false,
|
|
361
|
+
overwrite: false,
|
|
362
|
+
processingSet,
|
|
363
|
+
skipRelatedCheck: true, // Prevent component from checking for related context again
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const handleFileExists = createFileExistsHandler(options);
|
|
249
369
|
const validatedPath = await writeFile(
|
|
250
370
|
targetPath,
|
|
251
371
|
content,
|
|
252
372
|
projectRoot,
|
|
253
|
-
options.overwrite || false
|
|
373
|
+
options.overwrite || false,
|
|
374
|
+
handleFileExists
|
|
254
375
|
);
|
|
255
376
|
|
|
256
377
|
if (!options.silent) {
|
|
@@ -277,6 +398,7 @@ async function addTopLoadingBar(options = {}) {
|
|
|
277
398
|
const packageDir = getPackageDir();
|
|
278
399
|
const templatesDir = path.join(packageDir, 'templates');
|
|
279
400
|
const overwrite = options.overwrite || false;
|
|
401
|
+
const handleFileExists = createFileExistsHandler(options);
|
|
280
402
|
|
|
281
403
|
logger.info('Adding Top Loading Bar (context + component)...\n');
|
|
282
404
|
|
|
@@ -291,7 +413,8 @@ async function addTopLoadingBar(options = {}) {
|
|
|
291
413
|
targetComponentPath,
|
|
292
414
|
content,
|
|
293
415
|
projectRoot,
|
|
294
|
-
overwrite
|
|
416
|
+
overwrite,
|
|
417
|
+
handleFileExists
|
|
295
418
|
);
|
|
296
419
|
logger.success(`Added loading-bar component to ${path.relative(projectRoot, validatedPath)}`);
|
|
297
420
|
} else {
|
|
@@ -309,7 +432,8 @@ async function addTopLoadingBar(options = {}) {
|
|
|
309
432
|
targetContextPath,
|
|
310
433
|
content,
|
|
311
434
|
projectRoot,
|
|
312
|
-
overwrite
|
|
435
|
+
overwrite,
|
|
436
|
+
handleFileExists
|
|
313
437
|
);
|
|
314
438
|
logger.success(`Added top-loading-bar-context to ${path.relative(projectRoot, validatedPath)}`);
|
|
315
439
|
} else {
|
|
@@ -372,10 +496,13 @@ async function handleAdd(name, options = {}) {
|
|
|
372
496
|
return await addTopLoadingBar({ logger, config, overwrite });
|
|
373
497
|
}
|
|
374
498
|
|
|
499
|
+
// Initialize processing set to track items and prevent circular dependencies
|
|
500
|
+
const processingSet = new Set();
|
|
501
|
+
|
|
375
502
|
// Check if it's a component (look in components/ui/)
|
|
376
503
|
const componentPath = path.join(templatesDir, 'components', 'ui', `${kebabName}.tsx`);
|
|
377
504
|
if (fs.existsSync(componentPath)) {
|
|
378
|
-
return await addComponent(name, { logger, config, overwrite });
|
|
505
|
+
return await addComponent(name, { logger, config, overwrite, processingSet });
|
|
379
506
|
}
|
|
380
507
|
|
|
381
508
|
// Check if it's a helper
|
|
@@ -394,11 +521,11 @@ async function handleAdd(name, options = {}) {
|
|
|
394
521
|
const contextPathKebab = path.join(templatesDir, 'context', `${kebabName}.tsx`);
|
|
395
522
|
const contextPathPascal = path.join(templatesDir, 'context', `${toPascalCase(kebabName)}.tsx`);
|
|
396
523
|
if (fs.existsSync(contextPathKebab)) {
|
|
397
|
-
return await addContext(name, { logger, config, overwrite });
|
|
524
|
+
return await addContext(name, { logger, config, overwrite, processingSet });
|
|
398
525
|
} else if (fs.existsSync(contextPathPascal)) {
|
|
399
526
|
// Use the actual file name
|
|
400
527
|
const actualName = path.basename(contextPathPascal, '.tsx');
|
|
401
|
-
return await addContext(actualName, { logger, config, overwrite });
|
|
528
|
+
return await addContext(actualName, { logger, config, overwrite, processingSet });
|
|
402
529
|
}
|
|
403
530
|
|
|
404
531
|
// Not found
|
|
@@ -8,12 +8,16 @@ function detectDependencies(content) {
|
|
|
8
8
|
return {
|
|
9
9
|
needsNormalizeSize: false,
|
|
10
10
|
needsTheme: false,
|
|
11
|
+
needsComponent: null,
|
|
12
|
+
needsContext: null,
|
|
11
13
|
};
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
const dependencies = {
|
|
15
17
|
needsNormalizeSize: false,
|
|
16
18
|
needsTheme: false,
|
|
19
|
+
needsComponent: null,
|
|
20
|
+
needsContext: null,
|
|
17
21
|
};
|
|
18
22
|
|
|
19
23
|
// More precise pattern matching
|
|
@@ -23,10 +27,88 @@ function detectDependencies(content) {
|
|
|
23
27
|
dependencies.needsNormalizeSize = normalizeSizePattern.test(content);
|
|
24
28
|
dependencies.needsTheme = themePattern.test(content);
|
|
25
29
|
|
|
30
|
+
// Detect component imports in context files
|
|
31
|
+
const componentImportPattern = /from\s+['"]@\/components\/ui\/([^'"]+)['"]/;
|
|
32
|
+
const componentMatch = content.match(componentImportPattern);
|
|
33
|
+
if (componentMatch) {
|
|
34
|
+
dependencies.needsComponent = componentMatch[1]; // e.g., "loading-bar"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Detect context imports in component files
|
|
38
|
+
const contextImportPattern = /from\s+['"]@\/context\/([^'"]+)['"]/;
|
|
39
|
+
const contextMatch = content.match(contextImportPattern);
|
|
40
|
+
if (contextMatch) {
|
|
41
|
+
dependencies.needsContext = contextMatch[1]; // e.g., "top-loading-bar-context"
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
return dependencies;
|
|
27
45
|
}
|
|
28
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Detect related context for a component
|
|
49
|
+
* @param {string} componentName - Component name in kebab-case
|
|
50
|
+
* @param {string} templatesDir - Templates directory
|
|
51
|
+
* @returns {string|null} Related context name or null
|
|
52
|
+
*/
|
|
53
|
+
function detectRelatedContext(componentName, templatesDir) {
|
|
54
|
+
const fs = require('fs-extra');
|
|
55
|
+
const path = require('path');
|
|
56
|
+
|
|
57
|
+
// Check all context files to see if any imports this component
|
|
58
|
+
const contextDir = path.join(templatesDir, 'context');
|
|
59
|
+
if (!fs.existsSync(contextDir)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const contextFiles = fs.readdirSync(contextDir).filter(file => file.endsWith('.tsx'));
|
|
64
|
+
|
|
65
|
+
for (const contextFile of contextFiles) {
|
|
66
|
+
const contextPath = path.join(contextDir, contextFile);
|
|
67
|
+
const content = fs.readFileSync(contextPath, 'utf-8');
|
|
68
|
+
|
|
69
|
+
// Check if this context imports the component
|
|
70
|
+
// Escape special regex characters in componentName
|
|
71
|
+
const escapedComponentName = componentName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
72
|
+
const componentImportPattern = new RegExp(`@/components/ui/${escapedComponentName}`);
|
|
73
|
+
if (componentImportPattern.test(content)) {
|
|
74
|
+
return path.basename(contextFile, '.tsx');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Detect related component for a context
|
|
83
|
+
* @param {string} contextName - Context name in kebab-case
|
|
84
|
+
* @param {string} templatesDir - Templates directory
|
|
85
|
+
* @returns {string|null} Related component name or null
|
|
86
|
+
*/
|
|
87
|
+
function detectRelatedComponent(contextName, templatesDir) {
|
|
88
|
+
const fs = require('fs-extra');
|
|
89
|
+
const path = require('path');
|
|
90
|
+
|
|
91
|
+
const contextPath = path.join(templatesDir, 'context', `${contextName}.tsx`);
|
|
92
|
+
if (!fs.existsSync(contextPath)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const content = fs.readFileSync(contextPath, 'utf-8');
|
|
97
|
+
|
|
98
|
+
// Extract component name from import
|
|
99
|
+
const componentImportPattern = /from\s+['"]@\/components\/ui\/([^'"]+)['"]/;
|
|
100
|
+
const match = content.match(componentImportPattern);
|
|
101
|
+
|
|
102
|
+
if (match) {
|
|
103
|
+
return match[1]; // e.g., "loading-bar"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
29
109
|
module.exports = {
|
|
30
110
|
detectDependencies,
|
|
111
|
+
detectRelatedContext,
|
|
112
|
+
detectRelatedComponent,
|
|
31
113
|
};
|
|
32
114
|
|
|
@@ -30,16 +30,26 @@ async function readTemplate(templatePath) {
|
|
|
30
30
|
* @param {string} content - File content
|
|
31
31
|
* @param {string} baseDir - Base directory for validation
|
|
32
32
|
* @param {boolean} overwrite - Whether to overwrite existing file
|
|
33
|
-
* @
|
|
33
|
+
* @param {Function} onExists - Optional callback when file exists (returns Promise<boolean>)
|
|
34
|
+
* @throws {FileExistsError} If file exists and overwrite is false and onExists is not provided
|
|
34
35
|
* @throws {CLIError} If path validation fails
|
|
35
36
|
*/
|
|
36
|
-
async function writeFile(targetPath, content, baseDir, overwrite = false) {
|
|
37
|
+
async function writeFile(targetPath, content, baseDir, overwrite = false, onExists = null) {
|
|
37
38
|
// Validate path
|
|
38
39
|
const validatedPath = validatePath(targetPath, baseDir);
|
|
39
40
|
|
|
40
41
|
// Check if file exists
|
|
41
42
|
if (fs.existsSync(validatedPath) && !overwrite) {
|
|
42
|
-
|
|
43
|
+
// If callback is provided, use it to ask user
|
|
44
|
+
if (onExists && typeof onExists === 'function') {
|
|
45
|
+
const shouldOverwrite = await onExists(validatedPath);
|
|
46
|
+
if (!shouldOverwrite) {
|
|
47
|
+
throw new FileExistsError(validatedPath);
|
|
48
|
+
}
|
|
49
|
+
// User confirmed, proceed with overwrite
|
|
50
|
+
} else {
|
|
51
|
+
throw new FileExistsError(validatedPath);
|
|
52
|
+
}
|
|
43
53
|
}
|
|
44
54
|
|
|
45
55
|
// Ensure directory exists
|
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
|
/**
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Prompt user for confirmation
|
|
5
|
+
* @param {string} message - Message to display
|
|
6
|
+
* @returns {Promise<boolean>} User's response (true for yes, false for no)
|
|
7
|
+
*/
|
|
8
|
+
function promptUser(message) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const rl = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
rl.question(`${message} (y/N): `, (answer) => {
|
|
16
|
+
rl.close();
|
|
17
|
+
// Default to 'no' if empty or anything other than 'y'/'Y'/'yes'/'YES'
|
|
18
|
+
const normalized = answer.trim().toLowerCase();
|
|
19
|
+
resolve(normalized === 'y' || normalized === 'yes');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Prompt user to overwrite existing file
|
|
26
|
+
* @param {string} filePath - Path to the file that exists
|
|
27
|
+
* @returns {Promise<boolean>} true if user wants to overwrite, false otherwise
|
|
28
|
+
*/
|
|
29
|
+
async function promptOverwrite(filePath) {
|
|
30
|
+
const path = require('path');
|
|
31
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
32
|
+
|
|
33
|
+
return await promptUser(
|
|
34
|
+
`File "${relativePath}" already exists. Do you want to overwrite it?`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
promptUser,
|
|
40
|
+
promptOverwrite,
|
|
41
|
+
};
|
|
42
|
+
|
|
@@ -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>
|