@androbinco/library-cli 0.1.0 → 0.2.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/index.js +70 -4
- 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 +79 -0
- package/src/templates/in-view/in-view-hidden-text.tsx +45 -0
- package/src/templates/in-view/in-view-stroke-line.tsx +29 -0
- package/src/templates/scroll-tracker/examples/scroll-tracker-showcase.tsx +90 -0
- package/src/templates/scroll-tracker/scroll-tracker-provider.tsx +78 -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 +406 -55
- package/src/utils/files.js +88 -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
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { animate, motion, useInView } from 'motion/react';
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/common/utils/classname-builder';
|
|
7
|
+
|
|
8
|
+
import { useTickerClones } from './hooks/use-ticker-clones';
|
|
9
|
+
import { useTickerIncremental } from './hooks/use-ticker-incremental';
|
|
10
|
+
type MotionTickerProps = {
|
|
11
|
+
direction?: 'left' | 'right';
|
|
12
|
+
speed?: number;
|
|
13
|
+
delta?: number;
|
|
14
|
+
className?: string;
|
|
15
|
+
wrapperClassName?: string;
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
hoverStop?: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const MotionTicker = ({
|
|
21
|
+
direction = 'right',
|
|
22
|
+
speed = 1,
|
|
23
|
+
delta = 0.05,
|
|
24
|
+
className,
|
|
25
|
+
children,
|
|
26
|
+
wrapperClassName,
|
|
27
|
+
hoverStop = true,
|
|
28
|
+
}: MotionTickerProps) => {
|
|
29
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
30
|
+
const tickerClone = useRef<HTMLDivElement>(null);
|
|
31
|
+
const cardWrapper = useRef<HTMLDivElement>(null);
|
|
32
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
33
|
+
const isInView = useInView(containerRef, {
|
|
34
|
+
once: false,
|
|
35
|
+
margin: '50% 0%',
|
|
36
|
+
});
|
|
37
|
+
const { clonesNeeded } = useTickerClones({ tickerCard: cardWrapper, tickerClone, direction });
|
|
38
|
+
const calculateProportionalDuration = (distance: number, fps?: number) => {
|
|
39
|
+
const distancePerFrame = speed * delta;
|
|
40
|
+
const framesNeeded = distance / distancePerFrame;
|
|
41
|
+
|
|
42
|
+
return framesNeeded / (fps ?? 60); // calc is based on 60fps
|
|
43
|
+
};
|
|
44
|
+
const { x1, x2, fps } = useTickerIncremental({
|
|
45
|
+
speed,
|
|
46
|
+
direction,
|
|
47
|
+
delta,
|
|
48
|
+
stopIncrement: isHovering || !isInView,
|
|
49
|
+
onStop: async (motionValue) => {
|
|
50
|
+
if (!isInView) return;
|
|
51
|
+
const distance = 3;
|
|
52
|
+
const EASE_OUT_COMPENSATION = 1.15;
|
|
53
|
+
const duration =
|
|
54
|
+
calculateProportionalDuration(distance, fps?.current) * EASE_OUT_COMPENSATION;
|
|
55
|
+
|
|
56
|
+
return await animate(motionValue, motionValue.get() + distance, {
|
|
57
|
+
duration,
|
|
58
|
+
ease: 'easeOut',
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
ref={containerRef}
|
|
66
|
+
className={cn('group relative w-full overflow-hidden', wrapperClassName)}
|
|
67
|
+
role="group"
|
|
68
|
+
onMouseEnter={() => {
|
|
69
|
+
if (!hoverStop) return;
|
|
70
|
+
setIsHovering(true);
|
|
71
|
+
}}
|
|
72
|
+
onMouseLeave={() => {
|
|
73
|
+
if (!hoverStop) return;
|
|
74
|
+
setIsHovering(false);
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<motion.div
|
|
78
|
+
ref={cardWrapper}
|
|
79
|
+
className={cn('flex w-max flex-row', className)}
|
|
80
|
+
style={{ x: x1 }}
|
|
81
|
+
>
|
|
82
|
+
{Array.from({ length: clonesNeeded }).map((_, index) => {
|
|
83
|
+
return <Fragment key={index}>{children}</Fragment>;
|
|
84
|
+
})}
|
|
85
|
+
</motion.div>
|
|
86
|
+
<motion.div
|
|
87
|
+
ref={tickerClone}
|
|
88
|
+
className={cn('absolute top-0 h-full w-max', className)}
|
|
89
|
+
style={{ x: x2 }}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
package/src/utils/components.js
CHANGED
|
@@ -5,82 +5,433 @@ 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
|
-
export async function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
value: comp.name,
|
|
15
|
-
label: comp.name,
|
|
16
|
-
hint: comp.description
|
|
17
|
-
})),
|
|
18
|
-
required: true
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
if (p.isCancel(selectedComponents)) {
|
|
22
|
-
p.cancel('Operation cancelled.');
|
|
23
|
-
return;
|
|
12
|
+
export async function handleSubmenuFlow(component, templatesDir) {
|
|
13
|
+
if (!component.hasSubmenu || !component.submenuOptions || component.submenuOptions.length === 0) {
|
|
14
|
+
p.log.error(`Component "${component.name}" is not configured for submenu flow.`);
|
|
15
|
+
return { goBack: false };
|
|
24
16
|
}
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
while (true) {
|
|
19
|
+
// Show submenu with options and go back
|
|
20
|
+
const submenuOptions = component.submenuOptions.map(option => ({
|
|
21
|
+
value: option.value,
|
|
22
|
+
label: option.label,
|
|
23
|
+
hint: option.description || ''
|
|
24
|
+
}));
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
p.log.info(`The following dependencies are missing: ${missingDependencies.join(', ')}`);
|
|
39
|
-
|
|
40
|
-
const shouldInstall = await p.confirm({
|
|
41
|
-
message: `Install missing dependencies using ${packageManager.manager}?`,
|
|
42
|
-
initialValue: true
|
|
26
|
+
const selectedOptions = await p.multiselect({
|
|
27
|
+
message: `Select options for ${component.name} (press spacebar to select and return to confirm selection)`,
|
|
28
|
+
options: [
|
|
29
|
+
...submenuOptions,
|
|
30
|
+
{ value: '__go_back__', label: '← Go back', hint: 'Return to component selection' }
|
|
31
|
+
],
|
|
32
|
+
required: true
|
|
43
33
|
});
|
|
44
34
|
|
|
45
|
-
if (p.isCancel(
|
|
46
|
-
p.
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
if (p.isCancel(selectedOptions)) {
|
|
36
|
+
p.cancel('Operation cancelled.');
|
|
37
|
+
return { goBack: false };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if user selected go back
|
|
41
|
+
if (selectedOptions.includes('__go_back__')) {
|
|
42
|
+
return { goBack: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Remove go back from selected options
|
|
46
|
+
const filteredOptions = selectedOptions.filter(opt => opt !== '__go_back__');
|
|
47
|
+
|
|
48
|
+
if (!filteredOptions.length) {
|
|
49
|
+
p.log.info('No options selected.');
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Process each selected option
|
|
54
|
+
const allComponentsToCopy = new Set();
|
|
55
|
+
const allExamplesToCopy = [];
|
|
56
|
+
const requiredComponents = new Set();
|
|
57
|
+
let shouldRestartSubmenu = false;
|
|
58
|
+
|
|
59
|
+
for (const selectedValue of filteredOptions) {
|
|
60
|
+
const option = component.submenuOptions.find(opt => opt.value === selectedValue);
|
|
61
|
+
if (!option) continue;
|
|
62
|
+
|
|
63
|
+
// Collect examples
|
|
64
|
+
if (option.examples) {
|
|
65
|
+
allExamplesToCopy.push(...option.examples);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Handle nested submenu
|
|
69
|
+
if (option.hasNestedSubmenu && option.nestedSubmenuOptions) {
|
|
70
|
+
// Filter components based on existence check
|
|
71
|
+
const availableComponents = [];
|
|
72
|
+
const optionRequiredComponents = new Set();
|
|
73
|
+
|
|
74
|
+
for (const nestedOption of option.nestedSubmenuOptions) {
|
|
75
|
+
// Check if component should be filtered out
|
|
76
|
+
if (nestedOption.checkExists) {
|
|
77
|
+
const exists = await checkComponentExists(component.name, nestedOption.file);
|
|
78
|
+
if (exists) {
|
|
79
|
+
continue; // Skip if already exists
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Track required components for this option
|
|
84
|
+
if (nestedOption.required) {
|
|
85
|
+
optionRequiredComponents.add(nestedOption.file);
|
|
86
|
+
requiredComponents.add(nestedOption.file);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
availableComponents.push({
|
|
90
|
+
value: nestedOption.value,
|
|
91
|
+
label: nestedOption.file.replace('.tsx', '').replace('.ts', ''),
|
|
92
|
+
hint: nestedOption.required ? 'Required' : 'Optional'
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (availableComponents.length === 0) {
|
|
97
|
+
p.log.info(`All components for "${option.label}" already exist. Skipping.`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Show nested submenu with go back
|
|
102
|
+
const nestedSelected = await p.multiselect({
|
|
103
|
+
message: `Select components for ${option.label} (press spacebar to select and return to confirm selection)`,
|
|
104
|
+
options: [
|
|
105
|
+
...availableComponents,
|
|
106
|
+
{ value: '__go_back__', label: '← Go back', hint: 'Return to previous menu' }
|
|
107
|
+
],
|
|
108
|
+
required: true
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (p.isCancel(nestedSelected)) {
|
|
112
|
+
p.cancel('Operation cancelled.');
|
|
113
|
+
return { goBack: false };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check if user selected go back in nested menu
|
|
117
|
+
if (nestedSelected.includes('__go_back__')) {
|
|
118
|
+
shouldRestartSubmenu = true;
|
|
119
|
+
break; // Break out of for loop to restart submenu
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Remove go back and collect selected components
|
|
123
|
+
const filteredNested = nestedSelected.filter(opt => opt !== '__go_back__');
|
|
124
|
+
|
|
125
|
+
for (const nestedValue of filteredNested) {
|
|
126
|
+
const nestedOption = option.nestedSubmenuOptions.find(opt => opt.value === nestedValue);
|
|
127
|
+
if (nestedOption) {
|
|
128
|
+
allComponentsToCopy.add(nestedOption.file);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Add required components for this option
|
|
133
|
+
for (const requiredFile of optionRequiredComponents) {
|
|
134
|
+
allComponentsToCopy.add(requiredFile);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
// Handle ticker component specifically
|
|
138
|
+
if (component.name === 'ticker') {
|
|
139
|
+
if (selectedValue === 'hover') {
|
|
140
|
+
// Hover option: copy motion-ticker.tsx
|
|
141
|
+
allComponentsToCopy.add('motion-ticker.tsx');
|
|
142
|
+
} else if (selectedValue === 'non-hover') {
|
|
143
|
+
// Non-hover option: copy css-ticker directory
|
|
144
|
+
allComponentsToCopy.add('css-ticker');
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
// No nested submenu - copy all component files (legacy behavior)
|
|
148
|
+
// This maintains backward compatibility
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// For ticker component, automatically add hooks folder if it doesn't exist
|
|
154
|
+
if (component.name === 'ticker' && allComponentsToCopy.size > 0) {
|
|
155
|
+
const hooksDestPath = path.resolve(process.cwd(), 'src', 'components', component.name, 'hooks');
|
|
156
|
+
if (!(await fs.pathExists(hooksDestPath))) {
|
|
157
|
+
allComponentsToCopy.add('hooks');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// If user selected go back from nested menu, restart submenu
|
|
162
|
+
if (shouldRestartSubmenu) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// If we have components to copy (from nested submenu), proceed with selective copying
|
|
167
|
+
if (allComponentsToCopy.size > 0) {
|
|
168
|
+
// Check and install dependencies
|
|
169
|
+
// For ticker hover variant, motion is required
|
|
170
|
+
let dependencies = component.dependencies || [];
|
|
171
|
+
if (component.name === 'ticker') {
|
|
172
|
+
// Check if hover variant is selected
|
|
173
|
+
const hoverSelected = filteredOptions.includes('hover');
|
|
174
|
+
if (hoverSelected) {
|
|
175
|
+
// Motion is required for hover variant
|
|
176
|
+
dependencies = ['motion'];
|
|
177
|
+
} else {
|
|
178
|
+
// No dependencies needed for non-hover variant only
|
|
179
|
+
dependencies = [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const missingDependencies = await checkMissingDependencies(dependencies);
|
|
49
183
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
184
|
+
// For ticker hover variant, motion is required (not optional)
|
|
185
|
+
if (component.name === 'ticker' && filteredOptions.includes('hover') && missingDependencies.includes('motion')) {
|
|
186
|
+
const packageManager = await detectPackageManager();
|
|
187
|
+
|
|
188
|
+
p.log.info('Motion dependency is required for the hover ticker variant.');
|
|
189
|
+
|
|
190
|
+
const shouldInstall = await p.confirm({
|
|
191
|
+
message: `Install motion dependency using ${packageManager.manager}?`,
|
|
53
192
|
initialValue: true
|
|
54
193
|
});
|
|
194
|
+
|
|
195
|
+
if (p.isCancel(shouldInstall) || !shouldInstall) {
|
|
196
|
+
p.log.error('Motion dependency is required for hover ticker variant. Operation cancelled.');
|
|
197
|
+
return { goBack: false };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const installSuccess = await installDependencies(['motion'], packageManager);
|
|
55
201
|
|
|
56
|
-
if (
|
|
57
|
-
p.
|
|
58
|
-
|
|
202
|
+
if (!installSuccess) {
|
|
203
|
+
const continueAnyway = await p.confirm({
|
|
204
|
+
message: 'Installation failed. Continue with copying component anyway?',
|
|
205
|
+
initialValue: false
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
209
|
+
p.log.info('Operation cancelled.');
|
|
210
|
+
return { goBack: false };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else if (missingDependencies.length > 0) {
|
|
214
|
+
const packageManager = await detectPackageManager();
|
|
215
|
+
|
|
216
|
+
p.log.info(`The following dependencies are missing: ${missingDependencies.join(', ')}`);
|
|
217
|
+
|
|
218
|
+
const shouldInstall = await p.confirm({
|
|
219
|
+
message: `Install missing dependencies using ${packageManager.manager}?`,
|
|
220
|
+
initialValue: true
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (p.isCancel(shouldInstall)) {
|
|
224
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
225
|
+
} else if (shouldInstall) {
|
|
226
|
+
const installSuccess = await installDependencies(missingDependencies, packageManager);
|
|
227
|
+
|
|
228
|
+
if (!installSuccess) {
|
|
229
|
+
const continueAnyway = await p.confirm({
|
|
230
|
+
message: 'Installation failed. Continue with copying component anyway?',
|
|
231
|
+
initialValue: true
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
235
|
+
p.log.info('Operation cancelled.');
|
|
236
|
+
return { goBack: false };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
59
241
|
}
|
|
60
242
|
}
|
|
243
|
+
|
|
244
|
+
// Copy selected components
|
|
245
|
+
const spinner = p.spinner();
|
|
246
|
+
spinner.start(`Copying ${component.name} components...`);
|
|
247
|
+
|
|
248
|
+
const componentsArray = Array.from(allComponentsToCopy);
|
|
249
|
+
const success = await copyComponent(component, templatesDir, allExamplesToCopy, componentsArray);
|
|
250
|
+
|
|
251
|
+
if (success) {
|
|
252
|
+
spinner.stop(`✅ Component ${component.name} copied successfully!`);
|
|
253
|
+
p.log.success(`Component files are now available at: src/components/${component.name}/`);
|
|
254
|
+
if (allExamplesToCopy.length > 0) {
|
|
255
|
+
p.log.success(`Examples are available at: src/components/${component.name}/examples/`);
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
spinner.stop(`⚠️ Component ${component.name} was not copied.`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return { goBack: false };
|
|
61
262
|
} else {
|
|
62
|
-
|
|
263
|
+
// Legacy behavior: copy all files if no nested submenu was used
|
|
264
|
+
// Check and install dependencies
|
|
265
|
+
const dependencies = component.dependencies || [];
|
|
266
|
+
const missingDependencies = await checkMissingDependencies(dependencies);
|
|
267
|
+
|
|
268
|
+
if (missingDependencies.length > 0) {
|
|
269
|
+
const packageManager = await detectPackageManager();
|
|
270
|
+
|
|
271
|
+
p.log.info(`The following dependencies are missing: ${missingDependencies.join(', ')}`);
|
|
272
|
+
|
|
273
|
+
const shouldInstall = await p.confirm({
|
|
274
|
+
message: `Install missing dependencies using ${packageManager.manager}?`,
|
|
275
|
+
initialValue: true
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (p.isCancel(shouldInstall)) {
|
|
279
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
280
|
+
} else if (shouldInstall) {
|
|
281
|
+
const installSuccess = await installDependencies(missingDependencies, packageManager);
|
|
282
|
+
|
|
283
|
+
if (!installSuccess) {
|
|
284
|
+
const continueAnyway = await p.confirm({
|
|
285
|
+
message: 'Installation failed. Continue with copying component anyway?',
|
|
286
|
+
initialValue: true
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
290
|
+
p.log.info('Operation cancelled.');
|
|
291
|
+
return { goBack: false };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Copy component with selected examples
|
|
300
|
+
const spinner = p.spinner();
|
|
301
|
+
spinner.start(`Copying ${component.name} component...`);
|
|
302
|
+
|
|
303
|
+
const success = await copyComponent(component, templatesDir, allExamplesToCopy);
|
|
304
|
+
|
|
305
|
+
if (success) {
|
|
306
|
+
spinner.stop(`✅ Component ${component.name} copied successfully!`);
|
|
307
|
+
p.log.success(`Component files are now available at: src/components/${component.name}/`);
|
|
308
|
+
if (allExamplesToCopy.length > 0) {
|
|
309
|
+
p.log.success(`Examples are available at: src/components/${component.name}/examples/`);
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
spinner.stop(`⚠️ Component ${component.name} was not copied.`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return { goBack: false };
|
|
63
316
|
}
|
|
64
317
|
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function handleComponentsFlow(components, templatesDir) {
|
|
321
|
+
let regularComponents = [];
|
|
322
|
+
let componentsWithSubmenu = [];
|
|
323
|
+
|
|
324
|
+
while (true) {
|
|
325
|
+
const selectedComponents = await p.multiselect({
|
|
326
|
+
message: 'Please select one or more components to add (press spacebar to select and return to confirm selection)',
|
|
327
|
+
options: components.map(comp => ({
|
|
328
|
+
value: comp.name,
|
|
329
|
+
label: comp.name,
|
|
330
|
+
hint: comp.description
|
|
331
|
+
})),
|
|
332
|
+
required: true
|
|
333
|
+
});
|
|
65
334
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
335
|
+
if (p.isCancel(selectedComponents)) {
|
|
336
|
+
p.cancel('Operation cancelled.');
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!selectedComponents.length) {
|
|
341
|
+
p.log.info('No components selected.');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Separate components with submenu from regular components
|
|
346
|
+
componentsWithSubmenu = [];
|
|
347
|
+
regularComponents = [];
|
|
348
|
+
|
|
349
|
+
for (const componentName of selectedComponents) {
|
|
350
|
+
const component = components.find(c => c.name === componentName);
|
|
351
|
+
if (!component) {
|
|
352
|
+
p.log.warn(`Component "${componentName}" not found. Skipping.`);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (component.hasSubmenu) {
|
|
357
|
+
componentsWithSubmenu.push(component);
|
|
358
|
+
} else {
|
|
359
|
+
regularComponents.push(component);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Handle components with submenu
|
|
364
|
+
let shouldGoBack = false;
|
|
365
|
+
for (const component of componentsWithSubmenu) {
|
|
366
|
+
const result = await handleSubmenuFlow(component, templatesDir);
|
|
367
|
+
// If user selected go back, break and show component selection again
|
|
368
|
+
if (result && result.goBack) {
|
|
369
|
+
shouldGoBack = true;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// If user selected go back, continue loop to show component selection again
|
|
375
|
+
if (shouldGoBack) {
|
|
71
376
|
continue;
|
|
72
377
|
}
|
|
73
378
|
|
|
74
|
-
|
|
75
|
-
|
|
379
|
+
// If we processed all components without go back, exit the loop
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
76
382
|
|
|
77
|
-
|
|
383
|
+
// Handle regular components
|
|
384
|
+
if (regularComponents.length > 0) {
|
|
385
|
+
// Check and install dependencies for regular components
|
|
386
|
+
const regularComponentNames = regularComponents.map(c => c.name);
|
|
387
|
+
const allDependencies = collectDependencies(regularComponentNames, components);
|
|
388
|
+
const missingDependencies = await checkMissingDependencies(allDependencies);
|
|
78
389
|
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
390
|
+
if (missingDependencies.length > 0) {
|
|
391
|
+
const packageManager = await detectPackageManager();
|
|
392
|
+
|
|
393
|
+
p.log.info(`The following dependencies are missing: ${missingDependencies.join(', ')}`);
|
|
394
|
+
|
|
395
|
+
const shouldInstall = await p.confirm({
|
|
396
|
+
message: `Install missing dependencies using ${packageManager.manager}?`,
|
|
397
|
+
initialValue: true
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
if (p.isCancel(shouldInstall)) {
|
|
401
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
402
|
+
} else if (shouldInstall) {
|
|
403
|
+
const installSuccess = await installDependencies(missingDependencies, packageManager);
|
|
404
|
+
|
|
405
|
+
if (!installSuccess) {
|
|
406
|
+
const continueAnyway = await p.confirm({
|
|
407
|
+
message: 'Installation failed. Continue with copying components anyway?',
|
|
408
|
+
initialValue: true
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
412
|
+
p.log.info('Operation cancelled.');
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Copy regular components
|
|
422
|
+
for (const component of regularComponents) {
|
|
423
|
+
const spinner = p.spinner();
|
|
424
|
+
spinner.start(`Copying ${component.name} component...`);
|
|
425
|
+
|
|
426
|
+
const examplesToCopy = component.examples || [];
|
|
427
|
+
const success = await copyComponent(component, templatesDir, examplesToCopy);
|
|
428
|
+
|
|
429
|
+
if (success) {
|
|
430
|
+
spinner.stop(`✅ Component ${component.name} copied successfully!`);
|
|
431
|
+
p.log.success(`Component files are now available at: src/components/${component.name}/`);
|
|
432
|
+
} else {
|
|
433
|
+
spinner.stop(`⚠️ Component ${component.name} was not copied.`);
|
|
434
|
+
}
|
|
84
435
|
}
|
|
85
436
|
}
|
|
86
437
|
}
|