react-native-varia 0.2.2 → 0.2.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.
Files changed (40) hide show
  1. package/bin/cli.js +316 -139
  2. package/lib/components/Accordion.tsx +113 -0
  3. package/lib/components/Button.tsx +19 -8
  4. package/lib/components/CircleProgress.tsx +46 -28
  5. package/lib/components/Divider.tsx +18 -15
  6. package/lib/components/Drawer.tsx +496 -0
  7. package/lib/components/Field.tsx +24 -39
  8. package/lib/components/GradientBackground.tsx +25 -7
  9. package/lib/components/GradientText.tsx +38 -11
  10. package/lib/components/IconWrapper.tsx +20 -14
  11. package/lib/components/Input.tsx +106 -25
  12. package/lib/components/NumberInput.tsx +88 -19
  13. package/lib/components/OldSlider.tsx +327 -0
  14. package/lib/components/RadioGroup.tsx +55 -17
  15. package/lib/components/ReText.tsx +1 -1
  16. package/lib/components/Select.tsx +58 -22
  17. package/lib/components/Slider.tsx +176 -115
  18. package/lib/components/Slideshow.tsx +68 -69
  19. package/lib/components/SlidingDrawer.tsx +72 -29
  20. package/lib/components/Spinner.tsx +6 -2
  21. package/lib/components/Toast.tsx +89 -0
  22. package/lib/components/context/Field.tsx +27 -0
  23. package/lib/icons/Minus.tsx +24 -0
  24. package/lib/icons/Plus.tsx +23 -0
  25. package/lib/theme/Button.recipe.tsx +11 -1
  26. package/lib/theme/CircleProgress.recipe.tsx +3 -3
  27. package/lib/theme/Field.recipe.tsx +17 -2
  28. package/lib/theme/Input.recipe.tsx +12 -3
  29. package/lib/theme/NumberInput.recipe.tsx +9 -4
  30. package/lib/theme/RadioGroup.recipe.tsx +7 -1
  31. package/lib/theme/Select.recipe.tsx +7 -7
  32. package/lib/theme/Slider.recipe.tsx +366 -22
  33. package/lib/theme/Slideshow.recipe.tsx +1 -1
  34. package/lib/theme/SlidingDrawer.recipe.tsx +58 -4
  35. package/lib/theme/Toast.recipe.tsx +71 -0
  36. package/package.json +3 -2
  37. package/lib/components/NewSelect.tsx +0 -202
  38. package/lib/components/index.tsx +0 -83
  39. package/lib/components/layoutTest.tsx +0 -74
  40. package/lib/theme/Button.recipe-old.tsx +0 -67
package/bin/cli.js CHANGED
@@ -1,32 +1,71 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const { program } = require("commander");
4
+ const inquirer = require("inquirer");
4
5
  const path = require("path");
5
6
  const fs = require("fs-extra");
6
7
 
7
- // Carpetas base
8
+ const COMPONENT_DEPENDENCIES = {
9
+ Button: ["Spinner", "IconWrapper"],
10
+ Field: ["Text"],
11
+ Link: ["Text"],
12
+ Modal: ["Text"],
13
+ ReText: ["Text"],
14
+ Select: ["Text"],
15
+ };
16
+
17
+ const ICON_DEPENDENCIES = {
18
+ NumberInput: ["Plus", "Minus"],
19
+ };
20
+
21
+ const CONTEXT_DEPENDENCIES = {
22
+ Field: true
23
+ };
24
+
25
+ function copyContextComponentIfNeeded(component, destComponents, overwrite = true) {
26
+ if (!CONTEXT_DEPENDENCIES[component]) return;
27
+
28
+ const contextSrc = path.join(COMPONENTS_DIR, "context", `${component}.tsx`);
29
+ const contextDest = path.join(process.cwd(), destComponents, "context", `${component}.tsx`);
30
+ copyFile(contextSrc, contextDest, `Context de "${component}"`, overwrite);
31
+ }
32
+
33
+ function resolveDependencies(component, seen = new Set()) {
34
+ if (seen.has(component)) return [];
35
+ seen.add(component);
36
+
37
+ const deps = COMPONENT_DEPENDENCIES[component] || [];
38
+ let allDeps = [...deps];
39
+
40
+ for (const dep of deps) {
41
+ allDeps = [...allDeps, ...resolveDependencies(dep, seen)];
42
+ }
43
+
44
+ return [...new Set(allDeps)];
45
+ }
46
+
8
47
  const COMPONENTS_DIR = path.join(__dirname, "../lib/components");
9
48
  const THEME_DIR = path.join(__dirname, "../lib/theme");
10
49
 
11
- /**
12
- * Obtiene la lista de todos los componentes disponibles en la librería.
13
- */
14
50
  function getAvailableComponents() {
15
51
  if (!fs.existsSync(COMPONENTS_DIR)) return [];
16
52
  return fs.readdirSync(COMPONENTS_DIR).map((name) => path.parse(name).name);
17
53
  }
18
54
 
19
- /**
20
- * Copia un archivo desde origen a destino.
21
- */
22
- function copyFile(srcPath, destPath, label) {
55
+ function copyFile(srcPath, destPath, label, overwrite = true) {
23
56
  if (!fs.existsSync(srcPath)) {
24
57
  console.warn(`⚠️ ${label} no encontrado: ${srcPath}`);
25
58
  return false;
26
59
  }
27
60
  try {
28
- fs.ensureDirSync(path.dirname(destPath)); // crea carpeta destino si no existe
29
- fs.copySync(srcPath, destPath, { overwrite: true, errorOnExist: false });
61
+ fs.ensureDirSync(path.dirname(destPath));
62
+
63
+ if (!overwrite && fs.existsSync(destPath)) {
64
+ console.log(`⏭️ Saltando ${label}, ya existe en ${destPath}`);
65
+ return false;
66
+ }
67
+
68
+ fs.copySync(srcPath, destPath, { overwrite: true });
30
69
  console.log(`✅ ${label} copiado a: ${destPath}`);
31
70
  return true;
32
71
  } catch (err) {
@@ -35,17 +74,28 @@ function copyFile(srcPath, destPath, label) {
35
74
  }
36
75
  }
37
76
 
38
- /**
39
- * Copia un componente y su recipe.
40
- */
41
- function copyComponentAndRecipe(component, destComponents, destTheme) {
77
+ function copyComponentAndRecipe(component, destComponents, destTheme, overwrite = true) {
42
78
  const componentSrc = path.join(COMPONENTS_DIR, `${component}.tsx`);
43
79
  const componentDest = path.join(process.cwd(), destComponents, `${component}.tsx`);
44
- copyFile(componentSrc, componentDest, `Componente "${component}"`);
80
+ copyFile(componentSrc, componentDest, `Componente "${component}"`, overwrite);
45
81
 
46
82
  const recipeSrc = path.join(THEME_DIR, `${component}.recipe.tsx`);
47
83
  const recipeDest = path.join(process.cwd(), destTheme, `${component}.recipe.tsx`);
48
- copyFile(recipeSrc, recipeDest, `Recipe de "${component}"`);
84
+ copyFile(recipeSrc, recipeDest, `Recipe de "${component}"`, overwrite);
85
+ }
86
+
87
+ async function confirmOverwrite(existingComponents) {
88
+ const { overwrite } = await inquirer.prompt([
89
+ {
90
+ type: "confirm",
91
+ name: "overwrite",
92
+ message: `Los siguientes componentes ya existen: ${existingComponents.join(
93
+ ", "
94
+ )}\n¿Quieres sobrescribirlos?`,
95
+ default: false,
96
+ },
97
+ ]);
98
+ return overwrite;
49
99
  }
50
100
 
51
101
  program
@@ -53,74 +103,141 @@ program
53
103
  .description("CLI para instalar componentes de react-native-varia")
54
104
  .version("1.0.0");
55
105
 
56
- // program
57
- // .command('setup')
58
- // .description('Copia mixins y utils a src/styles')
59
- // .action(() => {
60
- // const sourceDir = path.join(__dirname, '../lib');
61
- // const destDir = path.join(process.cwd(), 'src/style');
62
-
63
- // const filesToCopy = ['mixins', 'utils', 'types'];
64
-
65
- // filesToCopy.forEach((file) => {
66
- // const srcPath = path.join(sourceDir, `${file}.ts`);
67
- // const destPath = path.join(destDir, `${file}.ts`);
68
-
69
- // try {
70
- // fs.ensureDirSync(destDir); // Asegura que el directorio de destino exista
71
- // fs.copySync(srcPath, destPath, { overwrite: true });
72
- // console.log(`✅ ${file}.tsx copiado a ${destPath}`);
73
- // } catch (err) {
74
- // console.error(`❌ Error al copiar ${file}.tsx: ${err.message}`);
75
- // }
76
- // });
77
- // });
78
-
79
106
  program
80
- .command('setup')
81
- .description('Copia la carpeta varia a src/styles/varia')
107
+ .command("setup")
108
+ .description("Copia la carpeta varia y patterns al proyecto (src/style/varia y src/patterns)")
82
109
  .action(() => {
83
- const sourceDir = path.join(__dirname, '../lib/varia'); // carpeta a copiar
84
- const destParentDir = path.join(process.cwd(), 'src/style'); // carpeta donde irá "varia"
85
- const destDir = path.join(destParentDir, 'varia'); // ruta final completa
110
+ const variaSrc = path.join(__dirname, "../lib/varia");
111
+ const variaDest = path.join(process.cwd(), "src/style/varia");
112
+
113
+ const patternsSrc = path.join(__dirname, "../lib/patterns");
114
+ const patternsDest = path.join(process.cwd(), "src/patterns");
86
115
 
87
116
  try {
88
- fs.ensureDirSync(destParentDir); // asegúrate de que exista src/style
89
- fs.copySync(sourceDir, destDir, { overwrite: true });
90
- console.log(`✅ Carpeta "varia" copiada a ${destDir}`);
117
+ fs.ensureDirSync(path.dirname(variaDest));
118
+ fs.copySync(variaSrc, variaDest, { overwrite: true });
119
+ console.log(`✅ Carpeta "varia" copiada a ${variaDest}`);
120
+
121
+ fs.ensureDirSync(path.dirname(patternsDest));
122
+ fs.copySync(patternsSrc, patternsDest, { overwrite: true });
123
+ console.log(`✅ Carpeta "patterns" copiada a ${patternsDest}`);
91
124
  } catch (err) {
92
- console.error(`❌ Error al copiar la carpeta "varia": ${err.message}`);
125
+ console.error(`❌ Error durante el setup: ${err.message}`);
93
126
  }
94
127
  });
95
128
 
96
- program
97
- .command('add-patterns')
98
- .description('Copia la carpeta lib/patterns a src/patterns')
129
+
130
+ program
131
+ .command("add-patterns")
132
+ .description("Copia la carpeta lib/patterns a src/patterns")
99
133
  .action(() => {
100
- const sourceDir = path.join(__dirname, '../lib/patterns');
101
- const destDir = path.join(process.cwd(), 'src/patterns');
134
+ const sourceDir = path.join(__dirname, "../lib/patterns");
135
+ const destDir = path.join(process.cwd(), "src/patterns");
102
136
 
103
137
  try {
104
- fs.ensureDirSync(destDir); // Asegura que el directorio de destino exista
138
+ fs.ensureDirSync(destDir);
105
139
  fs.copySync(sourceDir, destDir, { overwrite: true });
106
- console.log('✅ Carpeta patterns copiada a src/patterns');
140
+ console.log("✅ Carpeta patterns copiada a src/patterns");
107
141
  } catch (err) {
108
142
  console.error(`❌ Error al copiar patterns: ${err.message}`);
109
143
  }
110
144
  });
111
145
 
146
+ // program
147
+ // .command("add <components...>")
148
+ // .description("Copia uno o más componentes y sus recipes desde la librería a tu proyecto")
149
+ // .option("-d, --dest <path>", "Ruta de destino de componentes", "src/components")
150
+ // .option("-t, --theme <path>", "Ruta de destino de recipes", "src/theme")
151
+ // .action(async (components, options) => {
152
+ // const available = getAvailableComponents();
153
+ // const componentsCapitalized = components.map(
154
+ // (c) => c.charAt(0).toUpperCase() + c.slice(1)
155
+ // );
156
+
157
+ // // 🔹 Obtener dependencias totales
158
+ // const allComponents = new Set();
159
+ // const componentToDeps = {}; // para saber qué depende de qué
160
+ // for (const c of componentsCapitalized) {
161
+ // const deps = resolveDependencies(c);
162
+ // componentToDeps[c] = deps;
163
+ // allComponents.add(c);
164
+ // deps.forEach((d) => allComponents.add(d));
165
+ // }
166
+
167
+ // const allComponentsArray = Array.from(allComponents);
168
+
169
+ // // 🔹 Validar existencia
170
+ // const notFound = allComponentsArray.filter((c) => !available.includes(c));
171
+ // if (notFound.length > 0) {
172
+ // console.error(`❌ Los siguientes componentes no existen: ${notFound.join(", ")}`);
173
+ // console.log("\n📦 Componentes disponibles:");
174
+ // available.forEach((name) => console.log(` - ${name}`));
175
+ // process.exit(1);
176
+ // }
177
+
178
+ // // 🔹 Preparar instalación
179
+ // for (const mainComponent of componentsCapitalized) {
180
+ // const componentPath = path.join(process.cwd(), options.dest, `${mainComponent}.tsx`);
181
+ // let overwriteMain = true;
182
+
183
+ // // Preguntar solo si el componente principal existe
184
+ // if (fs.existsSync(componentPath)) {
185
+ // const { overwrite } = await inquirer.prompt([
186
+ // {
187
+ // type: "confirm",
188
+ // name: "overwrite",
189
+ // message: `El componente "${mainComponent}" ya existe. ¿Deseas sobrescribirlo?`,
190
+ // default: false,
191
+ // },
192
+ // ]);
193
+
194
+ // overwriteMain = overwrite;
195
+ // if (!overwriteMain) {
196
+ // console.log(`⏭️ Componente "${mainComponent}" no sobrescrito.`);
197
+ // }
198
+ // }
199
+
200
+ // // 🔹 Copiar componente principal
201
+ // copyComponentAndRecipe(mainComponent, options.dest, options.theme, overwriteMain);
202
+
203
+ // // 🔹 Copiar dependencias (sin sobrescribir si existen)
204
+ // const deps = componentToDeps[mainComponent] || [];
205
+ // for (const dep of deps) {
206
+ // const depPath = path.join(process.cwd(), options.dest, `${dep}.tsx`);
207
+ // const exists = fs.existsSync(depPath);
208
+ // if (exists) {
209
+ // console.log(`⏭️ Dependencia "${dep}" ya existe. No se sobrescribe.`);
210
+ // continue;
211
+ // }
212
+ // copyComponentAndRecipe(dep, options.dest, options.theme, true);
213
+ // }
214
+ // }
215
+ // });
216
+
112
217
  program
113
218
  .command("add <components...>")
114
219
  .description("Copia uno o más componentes y sus recipes desde la librería a tu proyecto")
115
220
  .option("-d, --dest <path>", "Ruta de destino de componentes", "src/components")
116
221
  .option("-t, --theme <path>", "Ruta de destino de recipes", "src/theme")
117
- .action((components, options) => {
222
+ .option("-i, --icons <path>", "Ruta de destino de íconos", "src/icons")
223
+ .action(async (components, options) => {
118
224
  const available = getAvailableComponents();
119
225
  const componentsCapitalized = components.map(
120
226
  (c) => c.charAt(0).toUpperCase() + c.slice(1)
121
227
  );
122
228
 
123
- const notFound = componentsCapitalized.filter((c) => !available.includes(c));
229
+ const allComponents = new Set();
230
+ const componentToDeps = {};
231
+ for (const c of componentsCapitalized) {
232
+ const deps = resolveDependencies(c);
233
+ componentToDeps[c] = deps;
234
+ allComponents.add(c);
235
+ deps.forEach((d) => allComponents.add(d));
236
+ }
237
+
238
+ const allComponentsArray = Array.from(allComponents);
239
+
240
+ const notFound = allComponentsArray.filter((c) => !available.includes(c));
124
241
  if (notFound.length > 0) {
125
242
  console.error(`❌ Los siguientes componentes no existen: ${notFound.join(", ")}`);
126
243
  console.log("\n📦 Componentes disponibles:");
@@ -128,90 +245,150 @@ program
128
245
  process.exit(1);
129
246
  }
130
247
 
131
- componentsCapitalized.forEach((component) => {
132
- copyComponentAndRecipe(component, options.dest, options.theme);
133
- });
134
- });
248
+ function copyContextComponentIfNeeded(component, destComponents, overwrite = true) {
249
+ if (!CONTEXT_DEPENDENCIES[component]) return;
135
250
 
136
-
137
- function copyIconTemplate(iconName, dest) {
138
- const srcPath = path.join(COMPONENTS_DIR, "Icon.tsx"); // plantilla
139
- const destPath = path.join(process.cwd(), dest, `${iconName}.tsx`); // nombre final
140
-
141
- if (!fs.existsSync(srcPath)) {
142
- console.error(`❌ Plantilla de icono no encontrada en: ${srcPath}`);
143
- return false;
251
+ const contextSrc = path.join(COMPONENTS_DIR, "context", `${component}.tsx`);
252
+ const contextDest = path.join(process.cwd(), destComponents, "context", `${component}.tsx`);
253
+ copyFile(contextSrc, contextDest, `Context de "${component}"`, overwrite);
144
254
  }
145
-
146
- try {
147
- // Leemos el contenido de la plantilla
148
- let content = fs.readFileSync(srcPath, "utf-8");
149
-
150
- // Reemplazamos el nombre del componente (IconName → el nombre deseado)
151
- content = content.replace(/\bIconName\b/g, iconName);
152
-
153
- fs.ensureDirSync(path.dirname(destPath)); // crear carpeta si no existe
154
- fs.writeFileSync(destPath, content);
155
-
156
- console.log(`✅ Icono "${iconName}" copiado a: ${destPath}`);
157
- return true;
158
- } catch (err) {
159
- console.error(`❌ No se pudo copiar el icono "${iconName}": ${err.message}`);
160
- return false;
255
+
256
+ for (const mainComponent of componentsCapitalized) {
257
+ const componentPath = path.join(process.cwd(), options.dest, `${mainComponent}.tsx`);
258
+ let overwriteMain = true;
259
+
260
+ if (fs.existsSync(componentPath)) {
261
+ const { overwrite } = await inquirer.prompt([
262
+ {
263
+ type: "confirm",
264
+ name: "overwrite",
265
+ message: `El componente "${mainComponent}" ya existe. ¿Deseas sobrescribirlo?`,
266
+ default: false,
267
+ },
268
+ ]);
269
+
270
+ overwriteMain = overwrite;
271
+ if (!overwriteMain) {
272
+ console.log(`⏭️ Componente "${mainComponent}" no sobrescrito.`);
273
+ }
274
+ }
275
+
276
+ copyComponentAndRecipe(mainComponent, options.dest, options.theme, overwriteMain);
277
+
278
+ copyContextComponentIfNeeded(mainComponent, options.dest, overwriteMain);
279
+
280
+ const deps = componentToDeps[mainComponent] || [];
281
+ for (const dep of deps) {
282
+ const depPath = path.join(process.cwd(), options.dest, `${dep}.tsx`);
283
+ const exists = fs.existsSync(depPath);
284
+ if (exists) {
285
+ console.log(`⏭️ Dependencia "${dep}" ya existe. No se sobrescribe.`);
286
+ continue;
287
+ }
288
+ copyComponentAndRecipe(dep, options.dest, options.theme, true);
289
+ }
290
+
291
+ const iconDeps = ICON_DEPENDENCIES[mainComponent] || [];
292
+ if (iconDeps.length > 0) {
293
+ console.log(`🎨 Añadiendo íconos requeridos por "${mainComponent}": ${iconDeps.join(", ")}`);
294
+
295
+ for (const iconName of iconDeps) {
296
+ const destIconPath = path.join(process.cwd(), options.icons, `${iconName}.tsx`);
297
+ if (fs.existsSync(destIconPath)) {
298
+ console.log(`⏭️ Icono "${iconName}" ya existe. No se sobrescribe.`);
299
+ continue;
300
+ }
301
+ copyIconTemplate(iconName, options.icons);
302
+ }
303
+
304
+ ensureIconWrapper(options.dest, options.theme);
305
+ }
161
306
  }
307
+ });
308
+
309
+
310
+ function copyIconTemplate(iconName, dest) {
311
+ const srcPath = path.join(COMPONENTS_DIR, "Icon.tsx");
312
+ const destPath = path.join(process.cwd(), dest, `${iconName}.tsx`);
313
+
314
+ if (!fs.existsSync(srcPath)) {
315
+ console.error(`❌ Plantilla de icono no encontrada en: ${srcPath}`);
316
+ return false;
162
317
  }
163
-
164
- /**
165
- * Copia IconWrapper si no existe en la app
166
- */
167
- function ensureIconWrapper(destComponents, destTheme) {
168
- const wrapperComponentDest = path.join(process.cwd(), destComponents, "IconWrapper.tsx");
169
- const wrapperRecipeDest = path.join(process.cwd(), destTheme, "IconWrapper.recipe.tsx");
170
-
171
- if (fs.existsSync(wrapperComponentDest)) {
172
- return; // ya existe
173
- }
174
-
175
- const wrapperComponentSrc = path.join(COMPONENTS_DIR, "IconWrapper.tsx");
176
- const wrapperRecipeSrc = path.join(THEME_DIR, "IconWrapper.recipe.tsx");
177
-
178
- // Copiar componente y recipe
179
- if (fs.existsSync(wrapperComponentSrc)) {
180
- fs.ensureDirSync(path.dirname(wrapperComponentDest));
181
- fs.copySync(wrapperComponentSrc, wrapperComponentDest, { overwrite: true, errorOnExist: false });
182
- console.log(`✅ IconWrapper copiado a: ${wrapperComponentDest}`);
183
- }
184
-
185
- if (fs.existsSync(wrapperRecipeSrc)) {
186
- fs.ensureDirSync(path.dirname(wrapperRecipeDest));
187
- fs.copySync(wrapperRecipeSrc, wrapperRecipeDest, { overwrite: true, errorOnExist: false });
188
- console.log(`✅ IconWrapper recipe copiado a: ${wrapperRecipeDest}`);
189
- }
318
+
319
+ try {
320
+ let content = fs.readFileSync(srcPath, "utf-8");
321
+ content = content.replace(/\bIconName\b/g, iconName);
322
+
323
+ fs.ensureDirSync(path.dirname(destPath));
324
+ fs.writeFileSync(destPath, content);
325
+
326
+ console.log(`✅ Icono "${iconName}" copiado a: ${destPath}`);
327
+ return true;
328
+ } catch (err) {
329
+ console.error(`❌ No se pudo copiar el icono "${iconName}": ${err.message}`);
330
+ return false;
331
+ }
332
+ }
333
+
334
+ function ensureIconWrapper(destComponents, destTheme) {
335
+ const wrapperComponentDest = path.join(process.cwd(), destComponents, "IconWrapper.tsx");
336
+ const wrapperRecipeDest = path.join(process.cwd(), destTheme, "IconWrapper.recipe.tsx");
337
+
338
+ if (fs.existsSync(wrapperComponentDest)) {
339
+ return;
340
+ }
341
+
342
+ const wrapperComponentSrc = path.join(COMPONENTS_DIR, "IconWrapper.tsx");
343
+ const wrapperRecipeSrc = path.join(THEME_DIR, "IconWrapper.recipe.tsx");
344
+
345
+ if (fs.existsSync(wrapperComponentSrc)) {
346
+ fs.ensureDirSync(path.dirname(wrapperComponentDest));
347
+ fs.copySync(wrapperComponentSrc, wrapperComponentDest, { overwrite: true });
348
+ console.log(`✅ IconWrapper copiado a: ${wrapperComponentDest}`);
349
+ }
350
+
351
+ if (fs.existsSync(wrapperRecipeSrc)) {
352
+ fs.ensureDirSync(path.dirname(wrapperRecipeDest));
353
+ fs.copySync(wrapperRecipeSrc, wrapperRecipeDest, { overwrite: true });
354
+ console.log(`✅ IconWrapper recipe copiado a: ${wrapperRecipeDest}`);
190
355
  }
191
-
192
- // Comando CLI
193
- program
194
- .command("add-icon [iconName]")
195
- .description("Copia un icono basado en la plantilla Icon.tsx")
196
- .option("-n, --name <iconName>", "Nombre del icono")
197
- .option("-d, --dest <path>", "Ruta de destino de iconos", "src/icons")
198
- .option("-c, --components <path>", "Ruta de destino de componentes", "src/components")
199
- .option("-t, --theme <path>", "Ruta de destino de recipes", "src/theme")
200
- .action((iconNameArg, options) => {
201
- const rawName = iconNameArg || options.name;
202
- if (!rawName) {
203
- console.error("❌ Debes indicar el nombre del icono como argumento o con --name");
204
- process.exit(1);
356
+ }
357
+
358
+ program
359
+ .command("add-icon [iconName]")
360
+ .description("Copia un icono basado en la plantilla Icon.tsx")
361
+ .option("-n, --name <iconName>", "Nombre del icono")
362
+ .option("-d, --dest <path>", "Ruta de destino de iconos", "src/icons")
363
+ .option("-c, --components <path>", "Ruta de destino de componentes", "src/components")
364
+ .option("-t, --theme <path>", "Ruta de destino de recipes", "src/theme")
365
+ .action(async (iconNameArg, options) => {
366
+ const rawName = iconNameArg || options.name;
367
+ if (!rawName) {
368
+ console.error("❌ Debes indicar el nombre del icono como argumento o con --name");
369
+ process.exit(1);
370
+ }
371
+
372
+ const finalName = rawName.charAt(0).toUpperCase() + rawName.slice(1);
373
+ const destPath = path.join(process.cwd(), options.dest, `${finalName}.tsx`);
374
+
375
+ if (fs.existsSync(destPath)) {
376
+ const { overwrite } = await inquirer.prompt([
377
+ {
378
+ type: "confirm",
379
+ name: "overwrite",
380
+ message: `El icono "${finalName}" ya existe. ¿Quieres sobrescribirlo?`,
381
+ default: false,
382
+ },
383
+ ]);
384
+ if (!overwrite) {
385
+ console.log("⏭️ Icono no sobrescrito.");
386
+ return;
205
387
  }
206
-
207
- // Capitalizamos primera letra
208
- const finalName = rawName.charAt(0).toUpperCase() + rawName.slice(1);
209
-
210
- copyIconTemplate(finalName, options.dest);
211
-
212
- // Aseguramos IconWrapper
213
- ensureIconWrapper(options.components, options.theme);
214
- });
215
-
216
-
217
- program.parse(process.argv);
388
+ }
389
+
390
+ copyIconTemplate(finalName, options.dest);
391
+ ensureIconWrapper(options.components, options.theme);
392
+ });
393
+
394
+ program.parse(process.argv);
@@ -0,0 +1,113 @@
1
+ import React, {ReactNode, useState} from 'react'
2
+ import {View, Pressable} from 'react-native'
3
+ import Animated from 'react-native-reanimated'
4
+ import {LinearTransition} from 'react-native-reanimated'
5
+ import {StyleSheet} from 'react-native-unistyles'
6
+ import Text from './Text'
7
+
8
+ type AccordionItemProps = {
9
+ title: string
10
+ itemKey: string
11
+ children: ReactNode
12
+ isOpen?: boolean
13
+ onToggle?: () => void
14
+ scrollViewRef?: any
15
+ }
16
+
17
+ type AccordionGroupRootProps = {
18
+ children: ReactNode
19
+ defaultOpenKeys?: string[]
20
+ allowMultiple?: boolean
21
+ }
22
+
23
+ export const AccordionGroup = {
24
+ Root: AccordionGroupRoot,
25
+ Item: AccordionGroupItem,
26
+ }
27
+
28
+ function AccordionGroupRoot({
29
+ children,
30
+ defaultOpenKeys = [],
31
+ allowMultiple = false,
32
+ }: AccordionGroupRootProps) {
33
+ const [openKeys, setOpenKeys] = useState<Set<string>>(
34
+ () => new Set(defaultOpenKeys),
35
+ )
36
+
37
+ const toggleItem = (key: string) => {
38
+ setOpenKeys(prev => {
39
+ const newSet = new Set(prev)
40
+ if (newSet.has(key)) {
41
+ newSet.delete(key)
42
+ } else {
43
+ if (allowMultiple) {
44
+ newSet.add(key)
45
+ } else {
46
+ return new Set([key])
47
+ }
48
+ }
49
+ return newSet
50
+ })
51
+ }
52
+
53
+ const items = React.Children.map(children, child => {
54
+ if (!React.isValidElement(child)) return child
55
+ const cp = child.props as AccordionItemProps
56
+ const key = cp.itemKey
57
+ const isOpen = openKeys.has(key)
58
+ return React.cloneElement(child, {
59
+ isOpen,
60
+ onToggle: () => toggleItem(key),
61
+ })
62
+ })
63
+
64
+ return <View style={styles.groupContainer}>{items}</View>
65
+ }
66
+
67
+ function AccordionGroupItem({
68
+ title,
69
+ children,
70
+ isOpen = false,
71
+ onToggle,
72
+ }: AccordionItemProps) {
73
+ return (
74
+ <Animated.View
75
+ // Aquí aplicas la transición de layout
76
+ layout={LinearTransition.duration(150)}
77
+ style={styles.itemContainer}>
78
+ <Pressable onPress={onToggle} style={styles.header}>
79
+ <Text style={styles.headerText}>{title}</Text>
80
+ </Pressable>
81
+
82
+ {isOpen && (
83
+ <View style={styles.contentContainer}>
84
+ <View style={styles.innerContent}>{children}</View>
85
+ </View>
86
+ )}
87
+ </Animated.View>
88
+ )
89
+ }
90
+
91
+ const styles = StyleSheet.create({
92
+ groupContainer: {},
93
+ itemContainer: {
94
+ borderWidth: 1,
95
+ borderColor: '#ddd',
96
+ borderRadius: 6,
97
+ marginVertical: 8,
98
+ overflow: 'hidden', // importante para que no se vea contenido fuera
99
+ },
100
+ header: {
101
+ padding: 12,
102
+ backgroundColor: '#f0f0f0',
103
+ },
104
+ headerText: {
105
+ fontSize: 16,
106
+ },
107
+ contentContainer: {
108
+ // no necesitamos animar altura manual, lo hace el layout
109
+ },
110
+ innerContent: {
111
+ padding: 12,
112
+ },
113
+ })