@androbinco/library-cli 0.1.0 → 0.3.0
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 +86 -37
- package/package.json +11 -16
- package/src/commands/add.js +107 -0
- package/src/commands/list.js +51 -0
- package/src/index.js +46 -15
- package/src/templates/carousel/components/navigation-buttons.tsx +1 -0
- package/src/templates/carousel/components/pagination/bullet.pagination.carousel.tsx +69 -0
- package/src/templates/carousel/components/pagination/number.pagination.carousel.tsx +30 -0
- package/src/templates/carousel/components/pagination/progress/progress.pagination.carousel.tsx +99 -0
- package/src/templates/carousel/components/pagination/progress/use-slide-progress.tsx +31 -0
- package/src/templates/carousel/components/pagination.tsx +47 -82
- package/src/templates/faqs-accordion/examples/faqs-showcase.tsx +42 -0
- package/src/templates/faqs-accordion/faqs-accordion.tsx +70 -0
- package/src/templates/faqs-accordion/mock-data.ts +38 -0
- package/src/templates/faqs-accordion/types.ts +18 -0
- package/src/templates/in-view/data.in-view.ts +89 -0
- package/src/templates/in-view/examples/in-view-examples.home.tsx +101 -0
- package/src/templates/in-view/examples/in-view-grid-showcase.tsx +41 -0
- package/src/templates/in-view/in-view-animation.tsx +72 -0
- package/src/templates/in-view/in-view-grid.tsx +81 -0
- package/src/templates/in-view/in-view-hidden-text.tsx +45 -0
- package/src/templates/in-view/in-view-stroke-line.tsx +30 -0
- package/src/templates/lenis/examples/providers.tsx +23 -0
- package/src/templates/lenis/lenis-provider.tsx +46 -0
- package/src/templates/scroll-components/hooks/use-client-dimensions.ts +21 -0
- package/src/templates/scroll-components/parallax/examples/parallax-showcase.tsx +87 -0
- package/src/templates/scroll-components/parallax/parallax.css +36 -0
- package/src/templates/scroll-components/parallax/parallax.tsx +67 -0
- package/src/templates/scroll-components/scale-gallery/components/expanding-element.tsx +40 -0
- package/src/templates/scroll-components/scale-gallery/examples/scale-gallery-showcase.tsx +68 -0
- package/src/templates/scroll-components/scale-gallery/scale-gallery.tsx +57 -0
- package/src/templates/scroll-components/scroll-tracker-provider.tsx +78 -0
- package/src/templates/scroll-components/scroll-tracker-showcase.tsx +44 -0
- package/src/templates/strapi-dynamic-zone/README.md +157 -0
- package/src/templates/strapi-dynamic-zone/dynamic-zone.tsx +113 -0
- package/src/templates/strapi-dynamic-zone/examples/page.tsx +53 -0
- package/src/templates/strapi-dynamic-zone/examples/renderers.tsx +74 -0
- package/src/templates/strapi-dynamic-zone/examples/types.ts +41 -0
- package/src/templates/strapi-dynamic-zone/index.ts +11 -0
- package/src/templates/strapi-dynamic-zone/types.ts +73 -0
- package/src/templates/ticker/css-ticker/css-ticker.tsx +61 -0
- package/src/templates/ticker/css-ticker/ticker.keyframes.css +86 -0
- package/src/templates/ticker/examples/ticker-hover-showcase.home.tsx +57 -0
- package/src/templates/ticker/examples/ticker-static-showcase.home.tsx +56 -0
- package/src/templates/ticker/hooks/use-ticker-clones.tsx +70 -0
- package/src/templates/ticker/hooks/use-ticker-incremental.tsx +72 -0
- package/src/templates/ticker/motion-ticker.tsx +93 -0
- package/src/utils/components.js +587 -54
- package/src/utils/files.js +89 -5
- package/src/templates/button/button.tsx +0 -5
- package/src/templates/card/card.tsx +0 -5
- package/src/templates/example/example.tsx +0 -5
- package/src/templates/hero/hero.tsx +0 -5
package/src/utils/components.js
CHANGED
|
@@ -5,82 +5,615 @@ import {
|
|
|
5
5
|
checkMissingDependencies,
|
|
6
6
|
installDependencies
|
|
7
7
|
} from './dependencies.js';
|
|
8
|
-
import { copyComponent } from './files.js';
|
|
8
|
+
import { copyComponent, checkComponentExists } from './files.js';
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
|
+
import path from 'path';
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
// Component registry
|
|
13
|
+
export const COMPONENTS = [
|
|
14
|
+
{
|
|
15
|
+
name: 'carousel',
|
|
16
|
+
description: 'Carousel component with navigation and pagination',
|
|
17
|
+
sourceDir: 'carousel',
|
|
18
|
+
dependencies: ['embla-carousel-react', 'embla-carousel', 'class-variance-authority']
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'in-view',
|
|
22
|
+
description: 'In view animations',
|
|
23
|
+
sourceDir: 'in-view',
|
|
24
|
+
hasSubmenu: true,
|
|
25
|
+
submenuOptions: [
|
|
26
|
+
{
|
|
27
|
+
value: 'grid',
|
|
28
|
+
label: 'Grid',
|
|
29
|
+
description: 'Grid-based animations',
|
|
30
|
+
hasNestedSubmenu: true,
|
|
31
|
+
nestedSubmenuOptions: [
|
|
32
|
+
{ value: 'in-view-grid', file: 'in-view-grid.tsx', required: true },
|
|
33
|
+
{ value: 'in-view-animation', file: 'in-view-animation.tsx', required: false, checkExists: true },
|
|
34
|
+
{ value: 'data.in-view', file: 'data.in-view.ts', required: true }
|
|
35
|
+
],
|
|
36
|
+
examples: ['in-view-grid-showcase.tsx']
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
value: 'individual',
|
|
40
|
+
label: 'Individual Elements',
|
|
41
|
+
description: 'Individual element animations',
|
|
42
|
+
hasNestedSubmenu: true,
|
|
43
|
+
nestedSubmenuOptions: [
|
|
44
|
+
{ value: 'in-view-hidden-text', file: 'in-view-hidden-text.tsx', required: true },
|
|
45
|
+
{ value: 'in-view-stroke-line', file: 'in-view-stroke-line.tsx', required: true },
|
|
46
|
+
{ value: 'in-view-animation', file: 'in-view-animation.tsx', required: false, checkExists: true },
|
|
47
|
+
{ value: 'data.in-view', file: 'data.in-view.ts', required: true }
|
|
48
|
+
],
|
|
49
|
+
examples: ['in-view-examples.home.tsx']
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
dependencies: ['motion']
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'ticker',
|
|
56
|
+
description: 'Ticker component with hover and non-hover variants',
|
|
57
|
+
sourceDir: 'ticker',
|
|
58
|
+
hasSubmenu: true,
|
|
59
|
+
submenuOptions: [
|
|
60
|
+
{
|
|
61
|
+
value: 'hover',
|
|
62
|
+
label: 'Stop on hover ticker (motion)',
|
|
63
|
+
description: 'MotionTicker with hover stop functionality',
|
|
64
|
+
examples: ['ticker-hover-showcase.home.tsx']
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
value: 'non-hover',
|
|
68
|
+
label: 'Non-stop on hover ticker (css)',
|
|
69
|
+
description: 'TickerStatic with CSS animations',
|
|
70
|
+
examples: ['ticker-static-showcase.home.tsx']
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
dependencies: ['motion']
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'scroll-components',
|
|
77
|
+
description: 'Scroll-driven animation components',
|
|
78
|
+
sourceDir: 'scroll-components',
|
|
79
|
+
hasSubmenu: true,
|
|
80
|
+
submenuOptions: [
|
|
81
|
+
{
|
|
82
|
+
value: 'scroll-tracker',
|
|
83
|
+
label: 'Scroll Tracker Provider',
|
|
84
|
+
description: 'Scroll progress tracking provider for animations',
|
|
85
|
+
examples: ['scroll-tracker-showcase.tsx']
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
value: 'parallax-image',
|
|
89
|
+
label: 'Parallax Image',
|
|
90
|
+
description: 'CSS-based parallax effect using scroll-driven animations',
|
|
91
|
+
examples: ['parallax/examples/parallax-showcase.tsx']
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
value: 'scale-gallery',
|
|
95
|
+
label: 'Scale Gallery',
|
|
96
|
+
description: 'Expanding gallery with scroll-driven scaling',
|
|
97
|
+
examples: ['scale-gallery/examples/scale-gallery-showcase.tsx']
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
dependencies: ['motion']
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'strapi-dynamic-zone',
|
|
104
|
+
description: 'Type-safe renderer for Strapi Dynamic Zones with React',
|
|
105
|
+
sourceDir: 'strapi-dynamic-zone',
|
|
106
|
+
dependencies: [],
|
|
107
|
+
examples: ['page.tsx', 'renderers.tsx', 'types.ts']
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'faqs-accordion',
|
|
111
|
+
description: 'Accessible FAQ accordion component using Radix UI',
|
|
112
|
+
sourceDir: 'faqs-accordion',
|
|
113
|
+
dependencies: ['@radix-ui/react-accordion'],
|
|
114
|
+
examples: ['faqs-showcase.tsx']
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'lenis',
|
|
118
|
+
description: 'Smooth scroll provider with Lenis',
|
|
119
|
+
sourceDir: 'lenis',
|
|
120
|
+
dependencies: ['lenis'],
|
|
121
|
+
examples: ['providers.tsx']
|
|
122
|
+
}
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
// Helper functions for CLI commands
|
|
126
|
+
export function getComponentByName(name) {
|
|
127
|
+
return COMPONENTS.find(c => c.name === name);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function getVariantByName(component, variantName) {
|
|
131
|
+
if (!component.hasSubmenu || !component.submenuOptions) return null;
|
|
132
|
+
return component.submenuOptions.find(v => v.value === variantName);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function getFilesForVariant(component, variant) {
|
|
136
|
+
const files = [];
|
|
137
|
+
|
|
138
|
+
if (component.name === 'ticker') {
|
|
139
|
+
if (variant.value === 'hover') {
|
|
140
|
+
files.push('motion-ticker.tsx');
|
|
141
|
+
files.push('hooks');
|
|
142
|
+
} else if (variant.value === 'non-hover') {
|
|
143
|
+
files.push('css-ticker');
|
|
144
|
+
files.push('hooks');
|
|
145
|
+
}
|
|
146
|
+
} else if (component.name === 'scroll-components') {
|
|
147
|
+
if (variant.value === 'scroll-tracker') {
|
|
148
|
+
files.push('scroll-tracker-provider.tsx');
|
|
149
|
+
} else if (variant.value === 'parallax-image') {
|
|
150
|
+
files.push('parallax/parallax.tsx');
|
|
151
|
+
files.push('parallax/parallax.css');
|
|
152
|
+
} else if (variant.value === 'scale-gallery') {
|
|
153
|
+
files.push('scale-gallery/scale-gallery.tsx');
|
|
154
|
+
files.push('scale-gallery/components/expanding-element.tsx');
|
|
155
|
+
files.push('hooks/use-client-dimensions.ts');
|
|
156
|
+
files.push('scroll-tracker-provider.tsx');
|
|
157
|
+
}
|
|
158
|
+
} else if (component.name === 'in-view' && variant.hasNestedSubmenu) {
|
|
159
|
+
// Copy all files for the variant
|
|
160
|
+
for (const nested of variant.nestedSubmenuOptions) {
|
|
161
|
+
files.push(nested.file);
|
|
162
|
+
}
|
|
24
163
|
}
|
|
25
164
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
165
|
+
return files;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function handleSubmenuFlow(component, templatesDir) {
|
|
169
|
+
if (!component.hasSubmenu || !component.submenuOptions || component.submenuOptions.length === 0) {
|
|
170
|
+
p.log.error(`Component "${component.name}" is not configured for submenu flow.`);
|
|
171
|
+
return { goBack: false };
|
|
29
172
|
}
|
|
30
173
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
174
|
+
while (true) {
|
|
175
|
+
// Show submenu with options and go back
|
|
176
|
+
const submenuOptions = component.submenuOptions.map(option => ({
|
|
177
|
+
value: option.value,
|
|
178
|
+
label: option.label,
|
|
179
|
+
hint: option.description || ''
|
|
180
|
+
}));
|
|
181
|
+
|
|
182
|
+
const selectedOptions = await p.multiselect({
|
|
183
|
+
message: `Select options for ${component.name} (press spacebar to select and return to confirm selection)`,
|
|
184
|
+
options: [
|
|
185
|
+
...submenuOptions,
|
|
186
|
+
{ value: '__go_back__', label: '← Go back', hint: 'Return to component selection' }
|
|
187
|
+
],
|
|
188
|
+
required: true
|
|
43
189
|
});
|
|
44
190
|
|
|
45
|
-
if (p.isCancel(
|
|
46
|
-
p.
|
|
47
|
-
|
|
48
|
-
|
|
191
|
+
if (p.isCancel(selectedOptions)) {
|
|
192
|
+
p.cancel('Operation cancelled.');
|
|
193
|
+
return { goBack: false };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check if user selected go back
|
|
197
|
+
if (selectedOptions.includes('__go_back__')) {
|
|
198
|
+
return { goBack: true };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Remove go back from selected options
|
|
202
|
+
const filteredOptions = selectedOptions.filter(opt => opt !== '__go_back__');
|
|
203
|
+
|
|
204
|
+
if (!filteredOptions.length) {
|
|
205
|
+
p.log.info('No options selected.');
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Process each selected option
|
|
210
|
+
const allComponentsToCopy = new Set();
|
|
211
|
+
const allExamplesToCopy = [];
|
|
212
|
+
const requiredComponents = new Set();
|
|
213
|
+
let shouldRestartSubmenu = false;
|
|
214
|
+
|
|
215
|
+
for (const selectedValue of filteredOptions) {
|
|
216
|
+
const option = component.submenuOptions.find(opt => opt.value === selectedValue);
|
|
217
|
+
if (!option) continue;
|
|
218
|
+
|
|
219
|
+
// Collect examples
|
|
220
|
+
if (option.examples) {
|
|
221
|
+
allExamplesToCopy.push(...option.examples);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Handle nested submenu
|
|
225
|
+
if (option.hasNestedSubmenu && option.nestedSubmenuOptions) {
|
|
226
|
+
// Filter components based on existence check
|
|
227
|
+
const availableComponents = [];
|
|
228
|
+
const optionRequiredComponents = new Set();
|
|
229
|
+
|
|
230
|
+
for (const nestedOption of option.nestedSubmenuOptions) {
|
|
231
|
+
// Check if component should be filtered out
|
|
232
|
+
if (nestedOption.checkExists) {
|
|
233
|
+
const exists = await checkComponentExists(component.name, nestedOption.file);
|
|
234
|
+
if (exists) {
|
|
235
|
+
continue; // Skip if already exists
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Track required components for this option
|
|
240
|
+
if (nestedOption.required) {
|
|
241
|
+
optionRequiredComponents.add(nestedOption.file);
|
|
242
|
+
requiredComponents.add(nestedOption.file);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
availableComponents.push({
|
|
246
|
+
value: nestedOption.value,
|
|
247
|
+
label: nestedOption.file.replace('.tsx', '').replace('.ts', ''),
|
|
248
|
+
hint: nestedOption.required ? 'Required' : 'Optional'
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (availableComponents.length === 0) {
|
|
253
|
+
p.log.info(`All components for "${option.label}" already exist. Skipping.`);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Show nested submenu with go back
|
|
258
|
+
const nestedSelected = await p.multiselect({
|
|
259
|
+
message: `Select components for ${option.label} (press spacebar to select and return to confirm selection)`,
|
|
260
|
+
options: [
|
|
261
|
+
...availableComponents,
|
|
262
|
+
{ value: '__go_back__', label: '← Go back', hint: 'Return to previous menu' }
|
|
263
|
+
],
|
|
264
|
+
required: true
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (p.isCancel(nestedSelected)) {
|
|
268
|
+
p.cancel('Operation cancelled.');
|
|
269
|
+
return { goBack: false };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Check if user selected go back in nested menu
|
|
273
|
+
if (nestedSelected.includes('__go_back__')) {
|
|
274
|
+
shouldRestartSubmenu = true;
|
|
275
|
+
break; // Break out of for loop to restart submenu
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Remove go back and collect selected components
|
|
279
|
+
const filteredNested = nestedSelected.filter(opt => opt !== '__go_back__');
|
|
280
|
+
|
|
281
|
+
for (const nestedValue of filteredNested) {
|
|
282
|
+
const nestedOption = option.nestedSubmenuOptions.find(opt => opt.value === nestedValue);
|
|
283
|
+
if (nestedOption) {
|
|
284
|
+
allComponentsToCopy.add(nestedOption.file);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Add required components for this option
|
|
289
|
+
for (const requiredFile of optionRequiredComponents) {
|
|
290
|
+
allComponentsToCopy.add(requiredFile);
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
// Handle ticker component specifically
|
|
294
|
+
if (component.name === 'ticker') {
|
|
295
|
+
if (selectedValue === 'hover') {
|
|
296
|
+
// Hover option: copy motion-ticker.tsx
|
|
297
|
+
allComponentsToCopy.add('motion-ticker.tsx');
|
|
298
|
+
} else if (selectedValue === 'non-hover') {
|
|
299
|
+
// Non-hover option: copy css-ticker directory
|
|
300
|
+
allComponentsToCopy.add('css-ticker');
|
|
301
|
+
}
|
|
302
|
+
} else if (component.name === 'scroll-components') {
|
|
303
|
+
// Handle scroll-components submenu options
|
|
304
|
+
if (selectedValue === 'scroll-tracker') {
|
|
305
|
+
allComponentsToCopy.add('scroll-tracker-provider.tsx');
|
|
306
|
+
} else if (selectedValue === 'parallax-image') {
|
|
307
|
+
allComponentsToCopy.add('parallax/parallax.tsx');
|
|
308
|
+
allComponentsToCopy.add('parallax/parallax.css');
|
|
309
|
+
} else if (selectedValue === 'scale-gallery') {
|
|
310
|
+
allComponentsToCopy.add('scale-gallery/scale-gallery.tsx');
|
|
311
|
+
allComponentsToCopy.add('scale-gallery/components/expanding-element.tsx');
|
|
312
|
+
allComponentsToCopy.add('hooks/use-client-dimensions.ts');
|
|
313
|
+
|
|
314
|
+
// Check if scroll-tracker-provider exists, if not add it
|
|
315
|
+
const trackerExists = await checkComponentExists(component.name, 'scroll-tracker-provider.tsx');
|
|
316
|
+
if (!trackerExists) {
|
|
317
|
+
allComponentsToCopy.add('scroll-tracker-provider.tsx');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
// No nested submenu - copy all component files (legacy behavior)
|
|
322
|
+
// This maintains backward compatibility
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// For ticker component, automatically add hooks folder if it doesn't exist
|
|
328
|
+
if (component.name === 'ticker' && allComponentsToCopy.size > 0) {
|
|
329
|
+
const hooksDestPath = path.resolve(process.cwd(), 'src', 'components', component.name, 'hooks');
|
|
330
|
+
if (!(await fs.pathExists(hooksDestPath))) {
|
|
331
|
+
allComponentsToCopy.add('hooks');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// If user selected go back from nested menu, restart submenu
|
|
336
|
+
if (shouldRestartSubmenu) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// If we have components to copy (from nested submenu), proceed with selective copying
|
|
341
|
+
if (allComponentsToCopy.size > 0) {
|
|
342
|
+
// Check and install dependencies
|
|
343
|
+
// For ticker hover variant, motion is required
|
|
344
|
+
let dependencies = component.dependencies || [];
|
|
345
|
+
if (component.name === 'ticker') {
|
|
346
|
+
// Check if hover variant is selected
|
|
347
|
+
const hoverSelected = filteredOptions.includes('hover');
|
|
348
|
+
if (hoverSelected) {
|
|
349
|
+
// Motion is required for hover variant
|
|
350
|
+
dependencies = ['motion'];
|
|
351
|
+
} else {
|
|
352
|
+
// No dependencies needed for non-hover variant only
|
|
353
|
+
dependencies = [];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const missingDependencies = await checkMissingDependencies(dependencies);
|
|
49
357
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
358
|
+
// For ticker hover variant, motion is required (not optional)
|
|
359
|
+
if (component.name === 'ticker' && filteredOptions.includes('hover') && missingDependencies.includes('motion')) {
|
|
360
|
+
const packageManager = await detectPackageManager();
|
|
361
|
+
|
|
362
|
+
p.log.info('Motion dependency is required for the hover ticker variant.');
|
|
363
|
+
|
|
364
|
+
const shouldInstall = await p.confirm({
|
|
365
|
+
message: `Install motion dependency using ${packageManager.manager}?`,
|
|
53
366
|
initialValue: true
|
|
54
367
|
});
|
|
368
|
+
|
|
369
|
+
if (p.isCancel(shouldInstall) || !shouldInstall) {
|
|
370
|
+
p.log.error('Motion dependency is required for hover ticker variant. Operation cancelled.');
|
|
371
|
+
return { goBack: false };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const installSuccess = await installDependencies(['motion'], packageManager);
|
|
375
|
+
|
|
376
|
+
if (!installSuccess) {
|
|
377
|
+
const continueAnyway = await p.confirm({
|
|
378
|
+
message: 'Installation failed. Continue with copying component anyway?',
|
|
379
|
+
initialValue: false
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
383
|
+
p.log.info('Operation cancelled.');
|
|
384
|
+
return { goBack: false };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} else if (missingDependencies.length > 0) {
|
|
388
|
+
const packageManager = await detectPackageManager();
|
|
55
389
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
390
|
+
p.log.info(`The following dependencies are missing: ${missingDependencies.join(', ')}`);
|
|
391
|
+
|
|
392
|
+
const shouldInstall = await p.confirm({
|
|
393
|
+
message: `Install missing dependencies using ${packageManager.manager}?`,
|
|
394
|
+
initialValue: true
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
if (p.isCancel(shouldInstall)) {
|
|
398
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
399
|
+
} else if (shouldInstall) {
|
|
400
|
+
const installSuccess = await installDependencies(missingDependencies, packageManager);
|
|
401
|
+
|
|
402
|
+
if (!installSuccess) {
|
|
403
|
+
const continueAnyway = await p.confirm({
|
|
404
|
+
message: 'Installation failed. Continue with copying component anyway?',
|
|
405
|
+
initialValue: true
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
409
|
+
p.log.info('Operation cancelled.');
|
|
410
|
+
return { goBack: false };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
59
415
|
}
|
|
60
416
|
}
|
|
417
|
+
|
|
418
|
+
// Copy selected components
|
|
419
|
+
const spinner = p.spinner();
|
|
420
|
+
spinner.start(`Copying ${component.name} components...`);
|
|
421
|
+
|
|
422
|
+
const componentsArray = Array.from(allComponentsToCopy);
|
|
423
|
+
const success = await copyComponent(component, templatesDir, allExamplesToCopy, componentsArray);
|
|
424
|
+
|
|
425
|
+
if (success) {
|
|
426
|
+
spinner.stop(`✅ Component ${component.name} copied successfully!`);
|
|
427
|
+
p.log.success(`Component files are now available at: src/components/${component.name}/`);
|
|
428
|
+
if (allExamplesToCopy.length > 0) {
|
|
429
|
+
p.log.success(`Examples are available at: src/components/${component.name}/examples/`);
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
spinner.stop(`⚠️ Component ${component.name} was not copied.`);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return { goBack: false };
|
|
61
436
|
} else {
|
|
62
|
-
|
|
437
|
+
// Legacy behavior: copy all files if no nested submenu was used
|
|
438
|
+
// Check and install dependencies
|
|
439
|
+
const dependencies = component.dependencies || [];
|
|
440
|
+
const missingDependencies = await checkMissingDependencies(dependencies);
|
|
441
|
+
|
|
442
|
+
if (missingDependencies.length > 0) {
|
|
443
|
+
const packageManager = await detectPackageManager();
|
|
444
|
+
|
|
445
|
+
p.log.info(`The following dependencies are missing: ${missingDependencies.join(', ')}`);
|
|
446
|
+
|
|
447
|
+
const shouldInstall = await p.confirm({
|
|
448
|
+
message: `Install missing dependencies using ${packageManager.manager}?`,
|
|
449
|
+
initialValue: true
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
if (p.isCancel(shouldInstall)) {
|
|
453
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
454
|
+
} else if (shouldInstall) {
|
|
455
|
+
const installSuccess = await installDependencies(missingDependencies, packageManager);
|
|
456
|
+
|
|
457
|
+
if (!installSuccess) {
|
|
458
|
+
const continueAnyway = await p.confirm({
|
|
459
|
+
message: 'Installation failed. Continue with copying component anyway?',
|
|
460
|
+
initialValue: true
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
464
|
+
p.log.info('Operation cancelled.');
|
|
465
|
+
return { goBack: false };
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Copy component with selected examples
|
|
474
|
+
const spinner = p.spinner();
|
|
475
|
+
spinner.start(`Copying ${component.name} component...`);
|
|
476
|
+
|
|
477
|
+
const success = await copyComponent(component, templatesDir, allExamplesToCopy);
|
|
478
|
+
|
|
479
|
+
if (success) {
|
|
480
|
+
spinner.stop(`✅ Component ${component.name} copied successfully!`);
|
|
481
|
+
p.log.success(`Component files are now available at: src/components/${component.name}/`);
|
|
482
|
+
if (allExamplesToCopy.length > 0) {
|
|
483
|
+
p.log.success(`Examples are available at: src/components/${component.name}/examples/`);
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
spinner.stop(`⚠️ Component ${component.name} was not copied.`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return { goBack: false };
|
|
63
490
|
}
|
|
64
491
|
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export async function handleComponentsFlow(components, templatesDir) {
|
|
495
|
+
let regularComponents = [];
|
|
496
|
+
let componentsWithSubmenu = [];
|
|
65
497
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
498
|
+
while (true) {
|
|
499
|
+
const selectedComponents = await p.multiselect({
|
|
500
|
+
message: 'Please select one or more components to add (press spacebar to select and return to confirm selection)',
|
|
501
|
+
options: [
|
|
502
|
+
...components.map(comp => ({
|
|
503
|
+
value: comp.name,
|
|
504
|
+
label: comp.name,
|
|
505
|
+
hint: comp.description
|
|
506
|
+
})),
|
|
507
|
+
{ value: '__go_back__', label: '← Go back', hint: 'Return to main menu' }
|
|
508
|
+
],
|
|
509
|
+
required: true
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
if (p.isCancel(selectedComponents)) {
|
|
513
|
+
p.cancel('Operation cancelled.');
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Check if user selected go back
|
|
518
|
+
if (selectedComponents.includes('__go_back__')) {
|
|
519
|
+
return { goBack: true };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!selectedComponents.length) {
|
|
523
|
+
p.log.info('No components selected.');
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Separate components with submenu from regular components
|
|
528
|
+
componentsWithSubmenu = [];
|
|
529
|
+
regularComponents = [];
|
|
530
|
+
|
|
531
|
+
for (const componentName of selectedComponents) {
|
|
532
|
+
const component = components.find(c => c.name === componentName);
|
|
533
|
+
if (!component) {
|
|
534
|
+
p.log.warn(`Component "${componentName}" not found. Skipping.`);
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (component.hasSubmenu) {
|
|
539
|
+
componentsWithSubmenu.push(component);
|
|
540
|
+
} else {
|
|
541
|
+
regularComponents.push(component);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Handle components with submenu
|
|
546
|
+
let shouldGoBack = false;
|
|
547
|
+
for (const component of componentsWithSubmenu) {
|
|
548
|
+
const result = await handleSubmenuFlow(component, templatesDir);
|
|
549
|
+
// If user selected go back, break and show component selection again
|
|
550
|
+
if (result && result.goBack) {
|
|
551
|
+
shouldGoBack = true;
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// If user selected go back, continue loop to show component selection again
|
|
557
|
+
if (shouldGoBack) {
|
|
71
558
|
continue;
|
|
72
559
|
}
|
|
73
560
|
|
|
74
|
-
|
|
75
|
-
|
|
561
|
+
// If we processed all components without go back, exit the loop
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
76
564
|
|
|
77
|
-
|
|
565
|
+
// Handle regular components
|
|
566
|
+
if (regularComponents.length > 0) {
|
|
567
|
+
// Check and install dependencies for regular components
|
|
568
|
+
const regularComponentNames = regularComponents.map(c => c.name);
|
|
569
|
+
const allDependencies = collectDependencies(regularComponentNames, components);
|
|
570
|
+
const missingDependencies = await checkMissingDependencies(allDependencies);
|
|
78
571
|
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
572
|
+
if (missingDependencies.length > 0) {
|
|
573
|
+
const packageManager = await detectPackageManager();
|
|
574
|
+
|
|
575
|
+
p.log.info(`The following dependencies are missing: ${missingDependencies.join(', ')}`);
|
|
576
|
+
|
|
577
|
+
const shouldInstall = await p.confirm({
|
|
578
|
+
message: `Install missing dependencies using ${packageManager.manager}?`,
|
|
579
|
+
initialValue: true
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
if (p.isCancel(shouldInstall)) {
|
|
583
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
584
|
+
} else if (shouldInstall) {
|
|
585
|
+
const installSuccess = await installDependencies(missingDependencies, packageManager);
|
|
586
|
+
|
|
587
|
+
if (!installSuccess) {
|
|
588
|
+
const continueAnyway = await p.confirm({
|
|
589
|
+
message: 'Installation failed. Continue with copying components anyway?',
|
|
590
|
+
initialValue: true
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
594
|
+
p.log.info('Operation cancelled.');
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Copy regular components
|
|
604
|
+
for (const component of regularComponents) {
|
|
605
|
+
const spinner = p.spinner();
|
|
606
|
+
spinner.start(`Copying ${component.name} component...`);
|
|
607
|
+
|
|
608
|
+
const examplesToCopy = component.examples || [];
|
|
609
|
+
const success = await copyComponent(component, templatesDir, examplesToCopy);
|
|
610
|
+
|
|
611
|
+
if (success) {
|
|
612
|
+
spinner.stop(`✅ Component ${component.name} copied successfully!`);
|
|
613
|
+
p.log.success(`Component files are now available at: src/components/${component.name}/`);
|
|
614
|
+
} else {
|
|
615
|
+
spinner.stop(`⚠️ Component ${component.name} was not copied.`);
|
|
616
|
+
}
|
|
84
617
|
}
|
|
85
618
|
}
|
|
86
619
|
}
|