@androbinco/library-cli 0.1.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 +262 -0
- package/package.json +40 -0
- package/src/index.js +79 -0
- package/src/templates/button/button.tsx +5 -0
- package/src/templates/card/card.tsx +5 -0
- package/src/templates/carousel/carousel.barrel.tsx +108 -0
- package/src/templates/carousel/components/navigation-buttons.tsx +100 -0
- package/src/templates/carousel/components/pagination.tsx +89 -0
- package/src/templates/carousel/components/provider/carousel.provider.tsx +72 -0
- package/src/templates/carousel/components/provider/use-navigation.tsx +52 -0
- package/src/templates/carousel/components/provider/use-pagination.tsx +56 -0
- package/src/templates/carousel/examples/base-cards.carousel.tsx +60 -0
- package/src/templates/carousel/examples/gallery-carousel.tsx +53 -0
- package/src/templates/carousel/examples/loop-carousel.tsx +43 -0
- package/src/templates/carousel/hooks/use-slide-active.tsx +9 -0
- package/src/templates/carousel/styles/embla.css +63 -0
- package/src/templates/example/example.tsx +5 -0
- package/src/templates/hero/hero.tsx +5 -0
- package/src/utils/components.js +87 -0
- package/src/utils/dependencies.js +134 -0
- package/src/utils/files.js +68 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { EmblaCarouselType, EmblaOptionsType } from 'embla-carousel';
|
|
6
|
+
|
|
7
|
+
import { NavigationProps, useNavigation } from './use-navigation';
|
|
8
|
+
import { PaginationProps, usePagination } from './use-pagination';
|
|
9
|
+
|
|
10
|
+
type CarouselContextType = {
|
|
11
|
+
options: EmblaOptionsType;
|
|
12
|
+
emblaApi: EmblaCarouselType | undefined;
|
|
13
|
+
setEmblaApi: (emblaApi: EmblaCarouselType | undefined) => void;
|
|
14
|
+
} & PaginationProps &
|
|
15
|
+
NavigationProps;
|
|
16
|
+
|
|
17
|
+
export const CarouselContext = createContext<CarouselContextType>({
|
|
18
|
+
selectedIndex: 0,
|
|
19
|
+
scrollSnaps: [],
|
|
20
|
+
onDotButtonClick: () => {},
|
|
21
|
+
|
|
22
|
+
prevBtnDisabled: true,
|
|
23
|
+
nextBtnDisabled: true,
|
|
24
|
+
options: {},
|
|
25
|
+
emblaApi: undefined,
|
|
26
|
+
setEmblaApi: () => {},
|
|
27
|
+
onPrevButtonClick: () => {},
|
|
28
|
+
onNextButtonClick: () => {},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const CarouselProvider = ({
|
|
32
|
+
children,
|
|
33
|
+
options,
|
|
34
|
+
}: {
|
|
35
|
+
children: React.ReactNode;
|
|
36
|
+
options: EmblaOptionsType;
|
|
37
|
+
}) => {
|
|
38
|
+
const [emblaApi, setEmblaApi] = useState<EmblaCarouselType | undefined>(undefined);
|
|
39
|
+
|
|
40
|
+
const { selectedIndex, scrollSnaps, onDotButtonClick } = usePagination(emblaApi);
|
|
41
|
+
const { prevBtnDisabled, nextBtnDisabled, onPrevButtonClick, onNextButtonClick } =
|
|
42
|
+
useNavigation(emblaApi);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<CarouselContext.Provider
|
|
46
|
+
value={{
|
|
47
|
+
selectedIndex,
|
|
48
|
+
scrollSnaps,
|
|
49
|
+
onDotButtonClick,
|
|
50
|
+
options,
|
|
51
|
+
emblaApi,
|
|
52
|
+
setEmblaApi,
|
|
53
|
+
prevBtnDisabled,
|
|
54
|
+
nextBtnDisabled,
|
|
55
|
+
onPrevButtonClick,
|
|
56
|
+
onNextButtonClick,
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</CarouselContext.Provider>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const useCarouselContext = () => {
|
|
65
|
+
const context = useContext(CarouselContext);
|
|
66
|
+
|
|
67
|
+
if (!context) {
|
|
68
|
+
throw new Error('useCarousel must be used within a CarouselProvider');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return context;
|
|
72
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { EmblaCarouselType } from 'embla-carousel';
|
|
4
|
+
|
|
5
|
+
export type NavigationProps = {
|
|
6
|
+
prevBtnDisabled: boolean;
|
|
7
|
+
nextBtnDisabled: boolean;
|
|
8
|
+
onPrevButtonClick: () => void;
|
|
9
|
+
onNextButtonClick: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useNavigation = (embla: EmblaCarouselType | undefined): NavigationProps => {
|
|
13
|
+
const [prevBtnDisabled, setPrevBtnDisabled] = useState(true);
|
|
14
|
+
const [nextBtnDisabled, setNextBtnDisabled] = useState(true);
|
|
15
|
+
|
|
16
|
+
const onPrevButtonClick = useCallback(() => {
|
|
17
|
+
if (!embla) return;
|
|
18
|
+
embla.scrollPrev();
|
|
19
|
+
}, [embla]);
|
|
20
|
+
|
|
21
|
+
const onNextButtonClick = useCallback(() => {
|
|
22
|
+
if (!embla) return;
|
|
23
|
+
embla.scrollNext();
|
|
24
|
+
}, [embla]);
|
|
25
|
+
|
|
26
|
+
const onSelect = useCallback(
|
|
27
|
+
(emblaApi: EmblaCarouselType) => {
|
|
28
|
+
setPrevBtnDisabled(!emblaApi.canScrollPrev());
|
|
29
|
+
setNextBtnDisabled(!emblaApi.canScrollNext());
|
|
30
|
+
},
|
|
31
|
+
[embla],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!embla) return;
|
|
36
|
+
|
|
37
|
+
onSelect(embla);
|
|
38
|
+
embla.on('reInit', onSelect).on('select', onSelect);
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
embla.off('reInit', onSelect);
|
|
42
|
+
embla.off('select', onSelect);
|
|
43
|
+
};
|
|
44
|
+
}, [embla, onSelect]);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
prevBtnDisabled,
|
|
48
|
+
nextBtnDisabled,
|
|
49
|
+
onPrevButtonClick,
|
|
50
|
+
onNextButtonClick,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { EmblaCarouselType } from 'embla-carousel';
|
|
4
|
+
|
|
5
|
+
export type PaginationProps = {
|
|
6
|
+
selectedIndex: number;
|
|
7
|
+
scrollSnaps: number[];
|
|
8
|
+
onDotButtonClick: (index: number) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const usePagination = (emblaApi: EmblaCarouselType | undefined): PaginationProps => {
|
|
12
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
13
|
+
const [scrollSnaps, setScrollSnaps] = useState<number[]>([]);
|
|
14
|
+
|
|
15
|
+
const onDotButtonClick = useCallback(
|
|
16
|
+
(index: number) => {
|
|
17
|
+
if (!emblaApi) return;
|
|
18
|
+
emblaApi.scrollTo(index);
|
|
19
|
+
},
|
|
20
|
+
[emblaApi],
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const onInit = useCallback(
|
|
24
|
+
(emblaApi: EmblaCarouselType) => {
|
|
25
|
+
setScrollSnaps(emblaApi.scrollSnapList());
|
|
26
|
+
},
|
|
27
|
+
[emblaApi],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const onSelect = useCallback(
|
|
31
|
+
(emblaApi: EmblaCarouselType) => {
|
|
32
|
+
setSelectedIndex(emblaApi.selectedScrollSnap());
|
|
33
|
+
},
|
|
34
|
+
[emblaApi],
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!emblaApi) return;
|
|
39
|
+
|
|
40
|
+
onInit(emblaApi);
|
|
41
|
+
onSelect(emblaApi);
|
|
42
|
+
emblaApi.on('reInit', onInit).on('reInit', onSelect).on('select', onSelect);
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
emblaApi.off('reInit', onInit);
|
|
46
|
+
emblaApi.off('reInit', onSelect);
|
|
47
|
+
emblaApi.off('select', onSelect);
|
|
48
|
+
};
|
|
49
|
+
}, [emblaApi, onInit, onSelect]);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
selectedIndex,
|
|
53
|
+
scrollSnaps,
|
|
54
|
+
onDotButtonClick,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import useSlideActive from "../hooks/use-slide-active";
|
|
4
|
+
import { cn } from "@/common/utils/classname-builder";
|
|
5
|
+
|
|
6
|
+
export const BasicImageCard = ({
|
|
7
|
+
index,
|
|
8
|
+
className,
|
|
9
|
+
}: {
|
|
10
|
+
index: number;
|
|
11
|
+
className?: string;
|
|
12
|
+
}) => (
|
|
13
|
+
<div
|
|
14
|
+
className={cn(
|
|
15
|
+
"flex items-center justify-center relative !max-h-screen overflow-hidden rounded-2xl max-lg:aspect-[9/16] max-lg:min-w-10 lg:aspect-video",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
>
|
|
19
|
+
<div
|
|
20
|
+
className={cn(
|
|
21
|
+
"relative flex !max-h-screen items-center justify-center overflow-hidden rounded-2xl max-lg:aspect-[9/16] max-lg:min-w-10 lg:aspect-video",
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
<div className="absolute inset-0 grid h-full w-full place-items-center bg-red-600">
|
|
26
|
+
<p className="text-body-2 text-grey-50-15">{index}</p>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<p className="text-body-2 text-grey-50-15">{index + 1}</p>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export const BasicCard = ({
|
|
34
|
+
index,
|
|
35
|
+
className,
|
|
36
|
+
}: {
|
|
37
|
+
index: number;
|
|
38
|
+
className?: string;
|
|
39
|
+
}) => (
|
|
40
|
+
<div
|
|
41
|
+
className={cn(
|
|
42
|
+
"flex items-center justify-center !max-h-screen max-lg:aspect-[9/16] max-lg:min-w-10 lg:aspect-video",
|
|
43
|
+
"h-full w-full rounded-4xl border-2 border-fill-brand-primary bg-bg-primary-inverse transition-all duration-500 ease-out max-lg:scale-y-100",
|
|
44
|
+
className
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
<p className="text-body-2 text-grey-50-15">{index + 1}</p>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
export const CarouselActiveCard = ({ index }: { index: number }) => {
|
|
52
|
+
const isActive = useSlideActive({ index });
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<BasicCard
|
|
56
|
+
className={isActive ? "scale-y-100 opacity-100" : "scale-y-80 opacity-40"}
|
|
57
|
+
index={index}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Carousel } from "../carousel.barrel";
|
|
4
|
+
|
|
5
|
+
import { BasicImageCard } from "./base-cards.carousel";
|
|
6
|
+
|
|
7
|
+
export const CarouselWithNumberPagination = ({
|
|
8
|
+
slides,
|
|
9
|
+
}: {
|
|
10
|
+
slides: number[];
|
|
11
|
+
}) => {
|
|
12
|
+
return (
|
|
13
|
+
<section>
|
|
14
|
+
<Carousel.Root
|
|
15
|
+
className="flex flex-col gap-8"
|
|
16
|
+
options={{
|
|
17
|
+
duration: 35,
|
|
18
|
+
slidesToScroll: 2,
|
|
19
|
+
breakpoints: {
|
|
20
|
+
"(max-width: 1024px)": {
|
|
21
|
+
slidesToScroll: 1,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
align: "start",
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<div className="ml-auto flex w-max flex-row gap-2 px-4 max-lg:px-2">
|
|
28
|
+
<Carousel.PrevButton variant="primary" />
|
|
29
|
+
<Carousel.NextButton variant="primary" />
|
|
30
|
+
</div>
|
|
31
|
+
<div className="relative w-full">
|
|
32
|
+
<Carousel.Slides
|
|
33
|
+
className="w-full gap-4 lg:gap-8"
|
|
34
|
+
containerClassName="pl-5 pr-7 lg:px-4"
|
|
35
|
+
>
|
|
36
|
+
{slides.map((_, index) => (
|
|
37
|
+
<Carousel.Slide
|
|
38
|
+
key={index}
|
|
39
|
+
className="max-lg:[--view-size:90%] lg:[--view-size:40%]"
|
|
40
|
+
>
|
|
41
|
+
<BasicImageCard index={index + 1} />
|
|
42
|
+
</Carousel.Slide>
|
|
43
|
+
))}
|
|
44
|
+
</Carousel.Slides>
|
|
45
|
+
</div>
|
|
46
|
+
<div className="flex flex-col gap-2">
|
|
47
|
+
<Carousel.Pagination type="progress" />
|
|
48
|
+
<Carousel.Pagination type="number" />
|
|
49
|
+
</div>
|
|
50
|
+
</Carousel.Root>
|
|
51
|
+
</section>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Carousel } from "../carousel.barrel";
|
|
4
|
+
|
|
5
|
+
import { CarouselActiveCard } from "./base-cards.carousel";
|
|
6
|
+
|
|
7
|
+
export const LoopCenterCarousel = ({ slides }: { slides: number[] }) => {
|
|
8
|
+
return (
|
|
9
|
+
<section>
|
|
10
|
+
<Carousel.Root
|
|
11
|
+
className="flex flex-col gap-8"
|
|
12
|
+
options={{
|
|
13
|
+
loop: true,
|
|
14
|
+
duration: 35,
|
|
15
|
+
align: "center",
|
|
16
|
+
breakpoints: {
|
|
17
|
+
"(max-width: 1024px)": {
|
|
18
|
+
align: "start",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
<div className="relative w-full">
|
|
24
|
+
<Carousel.Slides
|
|
25
|
+
className="w-full gap-8 px-8"
|
|
26
|
+
containerClassName="px-2"
|
|
27
|
+
>
|
|
28
|
+
{slides.map((_, index) => (
|
|
29
|
+
<Carousel.Slide
|
|
30
|
+
key={index}
|
|
31
|
+
className="max-lg:[--view-size:90%] lg:[--view-size:70%]"
|
|
32
|
+
>
|
|
33
|
+
<CarouselActiveCard index={index} />
|
|
34
|
+
</Carousel.Slide>
|
|
35
|
+
))}
|
|
36
|
+
</Carousel.Slides>
|
|
37
|
+
<Carousel.Navigation className="max-lg:hidden" />
|
|
38
|
+
</div>
|
|
39
|
+
<Carousel.Pagination />
|
|
40
|
+
</Carousel.Root>
|
|
41
|
+
</section>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useCarouselContext } from '../components/provider/carousel.provider';
|
|
2
|
+
|
|
3
|
+
const useSlideActive = ({ index }: { index: number }) => {
|
|
4
|
+
const { selectedIndex } = useCarouselContext();
|
|
5
|
+
|
|
6
|
+
return selectedIndex === index;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default useSlideActive;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
.embla {
|
|
2
|
+
--view-size: 100%;
|
|
3
|
+
}
|
|
4
|
+
.embla-slot {
|
|
5
|
+
flex: 0 0 var(--view-size);
|
|
6
|
+
}
|
|
7
|
+
.view-size-10 {
|
|
8
|
+
--view-size: 10%;
|
|
9
|
+
}
|
|
10
|
+
.view-size-15 {
|
|
11
|
+
--view-size: 15%;
|
|
12
|
+
}
|
|
13
|
+
.view-size-20 {
|
|
14
|
+
--view-size: 20%;
|
|
15
|
+
}
|
|
16
|
+
.view-size-25 {
|
|
17
|
+
--view-size: 25%;
|
|
18
|
+
}
|
|
19
|
+
.view-size-30 {
|
|
20
|
+
--view-size: 30%;
|
|
21
|
+
}
|
|
22
|
+
.view-size-35 {
|
|
23
|
+
--view-size: 35%;
|
|
24
|
+
}
|
|
25
|
+
.view-size-40 {
|
|
26
|
+
--view-size: 40%;
|
|
27
|
+
}
|
|
28
|
+
.view-size-45 {
|
|
29
|
+
--view-size: 45%;
|
|
30
|
+
}
|
|
31
|
+
.view-size-50 {
|
|
32
|
+
--view-size: 50%;
|
|
33
|
+
}
|
|
34
|
+
.view-size-55 {
|
|
35
|
+
--view-size: 55%;
|
|
36
|
+
}
|
|
37
|
+
.view-size-60 {
|
|
38
|
+
--view-size: 60%;
|
|
39
|
+
}
|
|
40
|
+
.view-size-65 {
|
|
41
|
+
--view-size: 65%;
|
|
42
|
+
}
|
|
43
|
+
.view-size-70 {
|
|
44
|
+
--view-size: 70%;
|
|
45
|
+
}
|
|
46
|
+
.view-size-75 {
|
|
47
|
+
--view-size: 75%;
|
|
48
|
+
}
|
|
49
|
+
.view-size-80 {
|
|
50
|
+
--view-size: 80%;
|
|
51
|
+
}
|
|
52
|
+
.view-size-85 {
|
|
53
|
+
--view-size: 85%;
|
|
54
|
+
}
|
|
55
|
+
.view-size-90 {
|
|
56
|
+
--view-size: 90%;
|
|
57
|
+
}
|
|
58
|
+
.view-size-95 {
|
|
59
|
+
--view-size: 95%;
|
|
60
|
+
}
|
|
61
|
+
.view-size-100 {
|
|
62
|
+
--view-size: 100%;
|
|
63
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import {
|
|
3
|
+
detectPackageManager,
|
|
4
|
+
collectDependencies,
|
|
5
|
+
checkMissingDependencies,
|
|
6
|
+
installDependencies
|
|
7
|
+
} from './dependencies.js';
|
|
8
|
+
import { copyComponent } from './files.js';
|
|
9
|
+
|
|
10
|
+
export async function handleComponentsFlow(components, templatesDir) {
|
|
11
|
+
const selectedComponents = await p.multiselect({
|
|
12
|
+
message: 'Please select one or more components to add (press spacebar to select and return to confirm selection)',
|
|
13
|
+
options: components.map(comp => ({
|
|
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;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!selectedComponents.length) {
|
|
27
|
+
p.log.info('No components selected.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check and install dependencies
|
|
32
|
+
const allDependencies = collectDependencies(selectedComponents, components);
|
|
33
|
+
const missingDependencies = await checkMissingDependencies(allDependencies);
|
|
34
|
+
|
|
35
|
+
if (missingDependencies.length > 0) {
|
|
36
|
+
const packageManager = await detectPackageManager();
|
|
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
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (p.isCancel(shouldInstall)) {
|
|
46
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
47
|
+
} else if (shouldInstall) {
|
|
48
|
+
const installSuccess = await installDependencies(missingDependencies, packageManager);
|
|
49
|
+
|
|
50
|
+
if (!installSuccess) {
|
|
51
|
+
const continueAnyway = await p.confirm({
|
|
52
|
+
message: 'Installation failed. Continue with copying components anyway?',
|
|
53
|
+
initialValue: true
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
57
|
+
p.log.info('Operation cancelled.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
p.log.info('Skipping dependency installation. You can install them manually later.');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Copy components
|
|
67
|
+
for (const componentName of selectedComponents) {
|
|
68
|
+
const component = components.find(c => c.name === componentName);
|
|
69
|
+
if (!component) {
|
|
70
|
+
p.log.warn(`Component "${componentName}" not found. Skipping.`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const spinner = p.spinner();
|
|
75
|
+
spinner.start(`Copying ${component.name} component...`);
|
|
76
|
+
|
|
77
|
+
const success = await copyComponent(component, templatesDir);
|
|
78
|
+
|
|
79
|
+
if (success) {
|
|
80
|
+
spinner.stop(`✅ Component ${component.name} copied successfully!`);
|
|
81
|
+
p.log.success(`Component files are now available at: src/components/${component.name}/`);
|
|
82
|
+
} else {
|
|
83
|
+
spinner.stop(`⚠️ Component ${component.name} was not copied.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
|
|
9
|
+
export async function detectPackageManager() {
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
|
|
12
|
+
if (await fs.pathExists(path.join(cwd, 'pnpm-lock.yaml'))) {
|
|
13
|
+
return { manager: 'pnpm', command: 'pnpm' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (await fs.pathExists(path.join(cwd, 'yarn.lock'))) {
|
|
17
|
+
return { manager: 'yarn', command: 'yarn' };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (await fs.pathExists(path.join(cwd, 'package-lock.json'))) {
|
|
21
|
+
return { manager: 'npm', command: 'npm' };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Default to pnpm if no lock file found
|
|
25
|
+
return { manager: 'pnpm', command: 'pnpm' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function collectDependencies(selectedComponents, components) {
|
|
29
|
+
const allDeps = new Set();
|
|
30
|
+
|
|
31
|
+
for (const componentName of selectedComponents) {
|
|
32
|
+
const component = components.find(c => c.name === componentName);
|
|
33
|
+
if (component && component.dependencies) {
|
|
34
|
+
component.dependencies.forEach(dep => allDeps.add(dep));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return Array.from(allDeps);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function checkMissingDependencies(dependencies) {
|
|
42
|
+
if (dependencies.length === 0) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
47
|
+
|
|
48
|
+
if (!(await fs.pathExists(packageJsonPath))) {
|
|
49
|
+
// If no package.json, all dependencies are "missing" but we'll warn
|
|
50
|
+
return dependencies;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
55
|
+
const installedDeps = {
|
|
56
|
+
...packageJson.dependencies,
|
|
57
|
+
...packageJson.devDependencies
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const missing = dependencies.filter(dep => !installedDeps[dep]);
|
|
61
|
+
return missing;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// If we can't read package.json, assume all are missing
|
|
64
|
+
return dependencies;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function installDependencies(packages, packageManager) {
|
|
69
|
+
if (packages.length === 0) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { manager, command } = packageManager;
|
|
74
|
+
|
|
75
|
+
// Build install command based on package manager
|
|
76
|
+
let installCmd;
|
|
77
|
+
if (manager === 'pnpm') {
|
|
78
|
+
installCmd = `pnpm add ${packages.join(' ')}`;
|
|
79
|
+
} else if (manager === 'yarn') {
|
|
80
|
+
installCmd = `yarn add ${packages.join(' ')}`;
|
|
81
|
+
} else {
|
|
82
|
+
installCmd = `npm install ${packages.join(' ')}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const spinner = p.spinner();
|
|
86
|
+
spinner.start(`Installing dependencies with ${manager}...`);
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const { stdout, stderr } = await execAsync(installCmd, {
|
|
90
|
+
cwd: process.cwd(),
|
|
91
|
+
maxBuffer: 1024 * 1024 * 10 // 10MB buffer
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
spinner.stop(`✅ Dependencies installed successfully!`);
|
|
95
|
+
|
|
96
|
+
if (stderr && !stderr.includes('warning')) {
|
|
97
|
+
p.log.warn(stderr);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
spinner.stop(`❌ Failed to install dependencies`);
|
|
103
|
+
p.log.error(`Installation error: ${error.message}`);
|
|
104
|
+
|
|
105
|
+
// Show the command that failed
|
|
106
|
+
p.log.info(`You can manually install with: ${installCmd}`);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function checkReactDependency() {
|
|
112
|
+
try {
|
|
113
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
114
|
+
|
|
115
|
+
if (!(await fs.pathExists(packageJsonPath))) {
|
|
116
|
+
p.log.warn('No package.json found in current directory. Make sure React is installed before using these components.');
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
121
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
122
|
+
|
|
123
|
+
if (!deps.react) {
|
|
124
|
+
p.log.warn('React dependency not found in package.json. These components require React to work properly.');
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return true;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
p.log.warn('Could not check for React dependency. Make sure React is installed before using these components.');
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|