@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 CHANGED
@@ -59,18 +59,20 @@ robin-library-cli
59
59
  ```
60
60
 
61
61
  The CLI will:
62
- 1. Display a welcome message
62
+ 1. Display a welcome message with a link to view examples online
63
63
  2. Check for React dependency in your `package.json` (warns if not found)
64
64
  3. Show an interactive menu of available components
65
- 4. Copy the selected component to `src/components/{component-name}/`
65
+ 4. For components with variants, show a submenu to select specific configurations
66
+ 5. Copy the selected component to `src/components/{component-name}/`
66
67
 
67
68
  ### Component Structure & Flow
68
69
 
69
70
  Flow:
70
- 1. Main menu (for now: Components or Exit)
71
- 2. Components: multiselect prompt — Please select one or more components
72
- 3. Copy selected components (one run can copy many)
73
- 4. Post-action prompt: “Do something else?” (loop back to menu) or Exit
71
+ 1. Main menu (Components or Exit)
72
+ 2. Components: multiselect prompt — "Please select one or more components"
73
+ 3. **Submenu Selection** (for components with variants): Some components offer submenus to select specific variants or configurations
74
+ 4. Copy selected components (one run can copy many)
75
+ 5. Post-action prompt: "Do something else?" (loop back to menu) or Exit
74
76
 
75
77
  Components are copied to the following structure:
76
78
 
@@ -97,33 +99,77 @@ Additional safety:
97
99
 
98
100
  ## Available Components
99
101
 
100
- ### Example Component
101
-
102
- - **Name**: `example`
103
- - **Description**: A simple example component with Hello World
104
- - **Files**: `example.tsx`
105
- - **Location**: `src/components/example/example.tsx`
106
-
107
- ### Button Component
108
-
109
- - **Name**: `button`
110
- - **Description**: Minimal button component
111
- - **Files**: `button.tsx`
112
- - **Location**: `src/components/button/button.tsx`
113
-
114
- ### Card Component
115
-
116
- - **Name**: `card`
117
- - **Description**: Minimal card wrapper
118
- - **Files**: `card.tsx`
119
- - **Location**: `src/components/card/card.tsx`
120
-
121
- ### Hero Component
122
-
123
- - **Name**: `hero`
124
- - **Description**: Minimal hero section
125
- - **Files**: `hero.tsx`
126
- - **Location**: `src/components/hero/hero.tsx`
102
+ ### Carousel Component
103
+
104
+ - **Name**: `carousel`
105
+ - **Description**: Carousel component with navigation and pagination
106
+ - **Dependencies**: `embla-carousel-react`, `embla-carousel`, `class-variance-authority`
107
+ - **Files**:
108
+ - `carousel.barrel.tsx` (main component)
109
+ - `components/` (navigation buttons, pagination, provider)
110
+ - `hooks/` (use-slide-active hook)
111
+ - `styles/` (embla.css styles)
112
+ - `examples/` (example implementations)
113
+ - **Location**: `src/components/carousel/`
114
+ - **Features**:
115
+ - Navigation buttons (prev/next)
116
+ - Multiple pagination types (dots, numbers, progress bar)
117
+ - Responsive breakpoints
118
+ - Loop support
119
+ - Slide active state tracking
120
+
121
+ ### In-View Component
122
+
123
+ - **Name**: `in-view`
124
+ - **Description**: In view animations for scroll-triggered effects
125
+ - **Dependencies**: `motion` (Framer Motion)
126
+ - **Location**: `src/components/in-view/`
127
+ - **Variants** (selectable via submenu):
128
+ - **Grid**: Grid-based animations
129
+ - Files: `in-view-grid.tsx`, `in-view-animation.tsx`, `data.in-view.ts`
130
+ - Example: `in-view-grid-showcase.tsx`
131
+ - **Individual Elements**: Individual element animations
132
+ - Files: `in-view-hidden-text.tsx`, `in-view-stroke-line.tsx`, `in-view-animation.tsx`, `data.in-view.ts`
133
+ - Example: `in-view-examples.home.tsx`
134
+ - **Features**:
135
+ - Scroll-triggered animations
136
+ - Grid-based and individual element animations
137
+ - Reusable animation utilities
138
+ - Type-safe data attributes
139
+
140
+ ### Ticker Component
141
+
142
+ - **Name**: `ticker`
143
+ - **Description**: Ticker component with hover and non-hover variants
144
+ - **Dependencies**: `motion` (Framer Motion)
145
+ - **Location**: `src/components/ticker/`
146
+ - **Variants** (selectable via submenu):
147
+ - **Stop on hover ticker (motion)**: MotionTicker with hover stop functionality
148
+ - Files: `motion-ticker.tsx`, `hooks/` (use-ticker-clones, use-ticker-incremental)
149
+ - Example: `ticker-hover-showcase.home.tsx`
150
+ - **Non-stop on hover ticker (css)**: TickerStatic with CSS animations
151
+ - Files: `css-ticker/css-ticker.tsx`, `css-ticker/ticker.keyframes.css`
152
+ - Example: `ticker-static-showcase.home.tsx`
153
+ - **Features**:
154
+ - Infinite scrolling ticker
155
+ - Hover to pause (motion variant)
156
+ - CSS-based animation option
157
+ - Customizable speed and direction
158
+
159
+ ### Scroll Tracker Component
160
+
161
+ - **Name**: `scroll-tracker`
162
+ - **Description**: Scroll tracker provider for scroll-driven animations
163
+ - **Dependencies**: `motion` (Framer Motion)
164
+ - **Files**:
165
+ - `scroll-tracker-provider.tsx` (main provider component)
166
+ - `examples/scroll-tracker-showcase.tsx` (example implementation)
167
+ - **Location**: `src/components/scroll-tracker/`
168
+ - **Features**:
169
+ - Scroll progress tracking
170
+ - Context-based scroll state management
171
+ - Enables scroll-driven animations throughout your app
172
+ - Works seamlessly with Framer Motion
127
173
 
128
174
  More components will be added in future updates.
129
175
 
@@ -132,6 +178,7 @@ More components will be added in future updates.
132
178
  - ✨ Beautiful, modern UI with Astro-like aesthetics
133
179
  - 🎯 Interactive component selection
134
180
  - 🔍 Automatic React dependency detection
181
+ - 📦 Automatic dependency installation (detects and installs missing npm packages)
135
182
  - 🛡️ Safe overwrite handling (checks for empty directories)
136
183
  - 📁 Automatic directory creation
137
184
  - ⚠️ Helpful warnings and error messages
@@ -141,13 +188,15 @@ More components will be added in future updates.
141
188
 
142
189
  1. **Dependency Check**: The CLI checks your `package.json` for React. If not found, it displays a warning but still allows you to proceed (you might add React later).
143
190
 
144
- 2. **Component Selection**: An interactive menu displays all available components with descriptions.
191
+ 2. **Component Selection**: An interactive menu displays all available components with descriptions. You can select multiple components at once. Some components offer submenus to select specific variants or configurations (e.g., Grid vs Individual Elements for in-view animations).
192
+
193
+ 3. **Dependency Installation**: The CLI automatically detects missing dependencies required by selected components and offers to install them using your project's package manager (npm, pnpm, or yarn).
145
194
 
146
- 3. **Directory Check**: Before copying, the CLI checks if the destination directory exists and whether it's empty.
195
+ 4. **Directory Check**: Before copying, the CLI checks if the destination directory exists and whether it's empty.
147
196
 
148
- 4. **Copy Operation**: All files from the component template are copied to `src/components/{component-name}/`, creating directories as needed.
197
+ 5. **Copy Operation**: All files from the component template are copied to `src/components/{component-name}/`, creating directories as needed.
149
198
 
150
- 5. **Completion**: A success message confirms the component has been added to your project.
199
+ 6. **Completion**: A success message confirms the component has been added to your project.
151
200
 
152
201
  ## Common Use Cases
153
202
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@androbinco/library-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Interactive CLI tool to add Robin library components to your project",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -11,13 +11,14 @@
11
11
  "src/",
12
12
  "README.md"
13
13
  ],
14
- "keywords": [
15
- "cli",
16
- "robin",
17
- "library",
18
- "components",
19
- "react"
20
- ],
14
+ "scripts": {
15
+ "start": "node ./src/index.js",
16
+ "dev": "node ./src/index.js",
17
+ "test": "echo \"Error: no test specified for robin-library-cli\" && exit 1",
18
+ "build": "echo \"robin-library-cli: No build step required.\"",
19
+ "lint": "echo \"robin-library-cli: Lint not configured.\""
20
+ },
21
+ "keywords": ["cli", "robin", "library", "components", "react"],
21
22
  "author": "androbinco",
22
23
  "license": "MIT",
23
24
  "dependencies": {
@@ -29,12 +30,6 @@
29
30
  },
30
31
  "publishConfig": {
31
32
  "access": "public"
32
- },
33
- "scripts": {
34
- "start": "node ./src/index.js",
35
- "dev": "node ./src/index.js",
36
- "test": "echo \"Error: no test specified for robin-library-cli\" && exit 1",
37
- "build": "echo \"robin-library-cli: No build step required.\"",
38
- "lint": "echo \"robin-library-cli: Lint not configured.\""
39
33
  }
40
- }
34
+ }
35
+
package/src/index.js CHANGED
@@ -12,15 +12,73 @@ const TEMPLATES_DIR = path.join(__dirname, 'templates');
12
12
 
13
13
  // Component registry
14
14
  const COMPONENTS = [
15
- { name: 'example', description: 'Hello World example', sourceDir: 'example', dependencies: [] },
16
- { name: 'button', description: 'Minimal button component', sourceDir: 'button', dependencies: [] },
17
- { name: 'card', description: 'Minimal card wrapper', sourceDir: 'card', dependencies: [] },
18
- { name: 'hero', description: 'Minimal hero section', sourceDir: 'hero', dependencies: [] },
19
15
  {
20
16
  name: 'carousel',
21
17
  description: 'Carousel component with navigation and pagination',
22
18
  sourceDir: 'carousel',
23
19
  dependencies: ['embla-carousel-react', 'embla-carousel', 'class-variance-authority']
20
+ },
21
+ {
22
+ name: 'in-view',
23
+ description: 'In view animations',
24
+ sourceDir: 'in-view',
25
+ hasSubmenu: true,
26
+ submenuOptions: [
27
+ {
28
+ value: 'grid',
29
+ label: 'Grid',
30
+ description: 'Grid-based animations',
31
+ hasNestedSubmenu: true,
32
+ nestedSubmenuOptions: [
33
+ { value: 'in-view-grid', file: 'in-view-grid.tsx', required: true },
34
+ { value: 'in-view-animation', file: 'in-view-animation.tsx', required: false, checkExists: true },
35
+ { value: 'data.in-view', file: 'data.in-view.ts', required: true }
36
+ ],
37
+ examples: ['in-view-grid-showcase.tsx']
38
+ },
39
+ {
40
+ value: 'individual',
41
+ label: 'Individual Elements',
42
+ description: 'Individual element animations',
43
+ hasNestedSubmenu: true,
44
+ nestedSubmenuOptions: [
45
+ { value: 'in-view-hidden-text', file: 'in-view-hidden-text.tsx', required: true },
46
+ { value: 'in-view-stroke-line', file: 'in-view-stroke-line.tsx', required: true },
47
+ { value: 'in-view-animation', file: 'in-view-animation.tsx', required: false, checkExists: true },
48
+ { value: 'data.in-view', file: 'data.in-view.ts', required: true }
49
+ ],
50
+ examples: ['in-view-examples.home.tsx']
51
+ }
52
+ ],
53
+ dependencies: ['motion']
54
+ },
55
+ {
56
+ name: 'ticker',
57
+ description: 'Ticker component with hover and non-hover variants',
58
+ sourceDir: 'ticker',
59
+ hasSubmenu: true,
60
+ submenuOptions: [
61
+ {
62
+ value: 'hover',
63
+ label: 'Stop on hover ticker (motion)',
64
+ description: 'MotionTicker with hover stop functionality',
65
+ examples: ['ticker-hover-showcase.home.tsx']
66
+ },
67
+ {
68
+ value: 'non-hover',
69
+ label: 'Non-stop on hover ticker (css)',
70
+ description: 'TickerStatic with CSS animations',
71
+ examples: ['ticker-static-showcase.home.tsx']
72
+ }
73
+ ],
74
+ dependencies: ['motion']
75
+ },
76
+ {
77
+ name: 'scroll-tracker',
78
+ description: 'Scroll tracker provider for scroll-driven animations',
79
+ sourceDir: 'scroll-tracker',
80
+ dependencies: ['motion'],
81
+ examples: ['scroll-tracker-showcase.tsx']
24
82
  }
25
83
  ];
26
84
 
@@ -40,6 +98,14 @@ async function main() {
40
98
  // Beautiful intro
41
99
  p.intro('✨ Welcome to Robin Library CLI');
42
100
 
101
+ // Add clickable link using ANSI escape codes for hyperlinks
102
+ const websiteUrl = 'https://robin-animation-library-620346143712.us-east1.run.app/';
103
+ const linkText = 'https://robin-animation-library-620346143712.us-east1.run.app/';
104
+ // ANSI escape code format: \u001b]8;;URL\u0007TEXT\u001b]8;;\u0007
105
+ const clickableLink = `\u001b]8;;${websiteUrl}\u0007${linkText}\u001b]8;;\u0007`;
106
+ p.log.info(`You can check examples in ${clickableLink}`);
107
+
108
+
43
109
  // Check React dependency
44
110
  await checkReactDependency();
45
111
 
@@ -0,0 +1,89 @@
1
+ const IN_VIEW_ANIMATIONS = {
2
+ fadeInUp: {
3
+ hidden: {
4
+ y: '100px',
5
+ opacity: 0,
6
+ },
7
+ visible: {
8
+ opacity: 1,
9
+ y: '0%',
10
+ },
11
+ },
12
+ fadeInLeft: {
13
+ hidden: { opacity: 0, transform: `translateX(-100px)` },
14
+ visible: { opacity: 1, transform: `translateX(0px)` },
15
+ },
16
+ fadeInDown: {
17
+ hidden: { opacity: 0, transform: `translateY(-50px)` },
18
+ visible: { opacity: 1, transform: `translateY(0px)` },
19
+ },
20
+ fadeInRight: {
21
+ hidden: { opacity: 0, transform: `translateX(100px)` },
22
+ visible: { opacity: 1, transform: `translateX(0px)` },
23
+ },
24
+ opacity: {
25
+ hidden: { opacity: 0 },
26
+ visible: { opacity: 1 },
27
+ },
28
+ imgBlur: {
29
+ hidden: {
30
+ filter: 'blur(10px)',
31
+ backdropFilter: 'blur(10px)',
32
+ WebkitBackdropFilter: 'blur(10px)',
33
+ overflow: 'hidden',
34
+ },
35
+ visible: {
36
+ overflow: 'hidden',
37
+ filter: 'blur(0px)',
38
+ backdropFilter: 'blur(0px)',
39
+ WebkitBackdropFilter: 'blur(0)',
40
+ },
41
+ },
42
+ scaleX: {
43
+ hidden: {
44
+ scaleX: 0,
45
+ },
46
+ visible: {
47
+ scaleX: 1,
48
+ },
49
+ },
50
+ strokeLine: {
51
+ hidden: {
52
+ scaleX: 0,
53
+ transformOrigin: 'left center',
54
+ },
55
+ visible: {
56
+ scaleX: 1,
57
+ transformOrigin: 'left center',
58
+ },
59
+ },
60
+ slideInUp: {
61
+ hidden: {
62
+ y: '85%',
63
+ },
64
+ visible: {
65
+ y: '0%',
66
+ },
67
+ },
68
+ slideInLeftFull: {
69
+ hidden: {
70
+ x: '-100%',
71
+ },
72
+ visible: {
73
+ x: '0%',
74
+ },
75
+ },
76
+ fadeInRightFull: {
77
+ hidden: {
78
+ opacity: 0,
79
+ x: '100%',
80
+ },
81
+ visible: {
82
+ opacity: 1,
83
+ x: '0%',
84
+ },
85
+ },
86
+ };
87
+
88
+ export default IN_VIEW_ANIMATIONS;
89
+
@@ -0,0 +1,101 @@
1
+ import { Fragment } from "react";
2
+
3
+ import InViewAnimation from "../in-view-animation";
4
+ import { InViewHiddenText } from "../in-view/in-view-hidden-text";
5
+ import { InViewStrokeLine } from "../in-view/in-view-stroke-line";
6
+ import { Text } from "@/common/ui/text/text";
7
+
8
+ const InViewExampleCard = ({ text }: { text?: string }) => {
9
+ return (
10
+ <div className="flex flex-col items-center justify-center h-50 w-full rounded-2xl border-2 border-fill-brand-primary bg-bg-primary-inverse p-2">
11
+ <Text
12
+ className="text-text-interactive-neutral-primary-inverse"
13
+ variant="title.6"
14
+ >
15
+ {text}
16
+ </Text>
17
+ </div>
18
+ );
19
+ };
20
+
21
+ export const InViewExamplesHome = () => {
22
+ return (
23
+ <div className="container mx-auto max-w-480">
24
+ <Text className="pb-15" variant="title.6">
25
+ In View Examples {"(Individual Elements)"}
26
+ </Text>
27
+ <div className="grid w-full gap-2 gap-y-2 overflow-hidden max-lg:grid-cols-1 lg:grid-cols-3 lg:gap-y-10">
28
+ <InViewAnimation effect="fadeInLeft">
29
+ <InViewExampleCard text="Fade Left Effect" />
30
+ </InViewAnimation>
31
+ <InViewAnimation
32
+ effect="opacity"
33
+ transition={{
34
+ delay: 0.2,
35
+ }}
36
+ >
37
+ <InViewExampleCard text="Opacity Effect" />
38
+ </InViewAnimation>
39
+ <InViewAnimation effect="fadeInRight">
40
+ <InViewExampleCard text="Fade Right Effect" />
41
+ </InViewAnimation>
42
+ <div className="flex w-full flex-col flex-wrap justify-end gap-5 lg:col-span-2">
43
+ <InViewStrokeLine
44
+ className="bg-fill-brand-primary opacity-40"
45
+ transition={{
46
+ duration: 0.8,
47
+ }}
48
+ />
49
+ <InViewHiddenText>
50
+ <Text className="py-1 text-grey-100-80">
51
+ Hidden text effect and stroke line
52
+ </Text>
53
+ </InViewHiddenText>
54
+
55
+ {Array.from({ length: 4 }).map((_, index) => (
56
+ <Fragment key={index}>
57
+ <InViewStrokeLine
58
+ className="bg-fill-brand-primary opacity-40"
59
+ transition={{
60
+ duration: 0.8,
61
+ delay: 0.1 * (index + 1),
62
+ }}
63
+ />
64
+ <InViewHiddenText
65
+ transition={{
66
+ delay: 0.2 * (index + 1),
67
+ }}
68
+ >
69
+ <Text className="text-grey-100-80">
70
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
71
+ </Text>
72
+ </InViewHiddenText>
73
+ </Fragment>
74
+ ))}
75
+ <InViewStrokeLine
76
+ className="bg-fill-brand-primary opacity-40"
77
+ transition={{
78
+ duration: 0.8,
79
+ delay: 0.4,
80
+ }}
81
+ />
82
+ </div>
83
+ <InViewAnimation
84
+ className="lg:col-start-3"
85
+ effect="imgBlur"
86
+ once={false}
87
+ >
88
+ <Center className="relative h-102 w-full overflow-hidden rounded-2xl bg-fill-brand-primary">
89
+ <img alt="Placeholder" src="/img/racti-sireno.jpeg" />
90
+ <Text
91
+ className="absolute bottom-1/4 rounded-2xl bg-grey-50-30 p-2 text-text-primary"
92
+ variant="title.6"
93
+ >
94
+ Blur Effect
95
+ </Text>
96
+ </Center>
97
+ </InViewAnimation>
98
+ </div>
99
+ </div>
100
+ );
101
+ };
@@ -0,0 +1,41 @@
1
+ import { GridInView } from "@/common/animations/in-view/in-view-grid";
2
+ import { Center } from "@/common/ui/containers/center";
3
+ import { Text } from "@/common/ui/text/text";
4
+ import { cn } from "@/common/utils/classname-builder";
5
+
6
+ const CardComponent = ({
7
+ pepito,
8
+ className,
9
+ }: {
10
+ pepito: number;
11
+ className?: string;
12
+ }) => {
13
+ return (
14
+ <div className="flex flex-col items-center justify-center h-50 w-full rounded-2xl border-2 border-fill-brand-primary bg-bg-primary-inverse p-2">
15
+ <Text>{pepito}</Text>
16
+ </div>
17
+ );
18
+ };
19
+
20
+ export const InViewGridShowcase = () => {
21
+ return (
22
+ <>
23
+ <div>
24
+ <h1>In View Grid:</h1>
25
+ <GridInView columns={5} delayByColumn={false}>
26
+ {Array.from({ length: 10 }, (_, index) => index).map((item) => (
27
+ <CardComponent key={item} pepito={item + 1} />
28
+ ))}
29
+ </GridInView>
30
+ </div>
31
+ <div>
32
+ <h1>In View Grid with delay by column:</h1>
33
+ <GridInView columns={{ desktop: 4, mobile: 2 }}>
34
+ {Array.from({ length: 12 }, (_, index) => index).map((item) => (
35
+ <CardComponent key={item} className="h-100" pepito={item + 1} />
36
+ ))}
37
+ </GridInView>
38
+ </div>
39
+ </>
40
+ );
41
+ };
@@ -0,0 +1,72 @@
1
+ "use client";
2
+ import { FC, ReactNode } from "react";
3
+
4
+ import {
5
+ motion,
6
+ MotionProps,
7
+ TargetAndTransition,
8
+ Transition,
9
+ } from "motion/react";
10
+
11
+ import IN_VIEW_ANIMATIONS from "./data.in-view";
12
+
13
+ const transitionsDefault: Transition = {
14
+ duration: 0.6,
15
+ delay: 0,
16
+ ease: "easeInOut",
17
+ };
18
+
19
+ export type InViewAnimationProps = {
20
+ margin?: string;
21
+ transition?: Transition;
22
+ effect?: keyof typeof IN_VIEW_ANIMATIONS;
23
+ customEffect?: {
24
+ hidden: TargetAndTransition;
25
+ visible: TargetAndTransition;
26
+ };
27
+ execute?: boolean;
28
+ once?: boolean;
29
+ style?: React.CSSProperties;
30
+ className?: string;
31
+ children?: ReactNode;
32
+ } & MotionProps &
33
+ React.HTMLAttributes<HTMLDivElement>;
34
+
35
+ const InViewAnimation: FC<InViewAnimationProps> = ({
36
+ margin = "-10% 0%",
37
+ execute = true,
38
+ transition = {},
39
+ customEffect,
40
+ effect = "fadeInUp",
41
+ once = true,
42
+ style,
43
+ className,
44
+ children,
45
+ ...props
46
+ }) => (
47
+ <motion.div
48
+ className={className}
49
+ initial={customEffect?.hidden || IN_VIEW_ANIMATIONS[effect].hidden}
50
+ style={style}
51
+ transition={{
52
+ ...transitionsDefault,
53
+ ...transition,
54
+ }}
55
+ viewport={
56
+ execute
57
+ ? {
58
+ once,
59
+ margin,
60
+ }
61
+ : {}
62
+ }
63
+ whileInView={
64
+ execute ? customEffect?.visible || IN_VIEW_ANIMATIONS[effect].visible : {}
65
+ }
66
+ {...props}
67
+ >
68
+ {children}
69
+ </motion.div>
70
+ );
71
+
72
+ export default InViewAnimation;
@@ -0,0 +1,79 @@
1
+ "use client";
2
+ import { Children, useEffect, useState } from "react";
3
+
4
+ import { motion, Transition } from "motion/react";
5
+
6
+ import { cn } from "@/common/utils/classname-builder";
7
+
8
+ import IN_VIEW_ANIMATIONS from "./data.in-view";
9
+
10
+ type GridInViewProps = {
11
+ children: React.ReactNode;
12
+ className?: string;
13
+ effect?: keyof typeof IN_VIEW_ANIMATIONS;
14
+ columns?: number | { desktop: number; mobile: number };
15
+ delayDelta?: number;
16
+ delayByColumn?: boolean;
17
+ transition?: Omit<Transition, "delay">;
18
+ columnsMobile?: number;
19
+ };
20
+
21
+ export const GridInView = ({
22
+ children,
23
+ className,
24
+ effect = "fadeInUp",
25
+ delayDelta = 0.3,
26
+ delayByColumn = true,
27
+ transition = {
28
+ duration: 0.5,
29
+ ease: "easeOut",
30
+ },
31
+ columns = 5,
32
+ }: GridInViewProps) => {
33
+ const childrenArray = Children.toArray(children);
34
+ const isColumnsObject = typeof columns === "object";
35
+ const [columnsValue, setColumnsValue] = useState(
36
+ isColumnsObject ? columns.desktop : columns
37
+ );
38
+
39
+ useEffect(() => {
40
+ if (!delayByColumn) return;
41
+ if (typeof window === "undefined" || !isColumnsObject) return;
42
+ if (window.innerWidth < 1024) setColumnsValue(columns.mobile);
43
+ else setColumnsValue(columns.desktop);
44
+ }, [columns, isColumnsObject, delayByColumn]);
45
+ const delayCount = (index: number) =>
46
+ delayByColumn ? index % columnsValue : index;
47
+
48
+ return (
49
+ <div
50
+ className={cn(
51
+ "grid gap-4",
52
+ "[grid-template-columns:repeat(var(--columns),1fr)]",
53
+ "max-lg:[grid-template-columns:repeat(var(--columns-mobile),1fr)]",
54
+ className
55
+ )}
56
+ style={
57
+ {
58
+ "--columns": isColumnsObject ? columns.desktop : columns,
59
+ "--columns-mobile": isColumnsObject ? columns.mobile : columns,
60
+ } as React.CSSProperties
61
+ }
62
+ >
63
+ {childrenArray.map((item, index) => (
64
+ <motion.div
65
+ key={index}
66
+ initial={IN_VIEW_ANIMATIONS[effect].hidden}
67
+ transition={{
68
+ ...transition,
69
+ delay: delayCount(index) * delayDelta,
70
+ }}
71
+ viewport={{ once: true, margin: "-10% 0%" }}
72
+ whileInView={IN_VIEW_ANIMATIONS[effect].visible}
73
+ >
74
+ {item}
75
+ </motion.div>
76
+ ))}
77
+ </div>
78
+ );
79
+ };