@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.
@@ -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,5 @@
1
+ // @ts-nocheck
2
+
3
+ export default function Example() {
4
+ return <div>Hello World</div>;
5
+ }
@@ -0,0 +1,5 @@
1
+ // @ts-nocheck
2
+
3
+ export default function Hero() {
4
+ return <section>Hero section</section>;
5
+ }
@@ -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
+