@androbinco/library-cli 0.2.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/package.json +1 -1
- package/src/commands/add.js +107 -0
- package/src/commands/list.js +51 -0
- package/src/index.js +38 -73
- 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 +1 -1
- package/src/templates/in-view/in-view-animation.tsx +7 -7
- package/src/templates/in-view/in-view-grid.tsx +22 -20
- package/src/templates/in-view/in-view-hidden-text.tsx +7 -7
- package/src/templates/in-view/in-view-stroke-line.tsx +6 -5
- 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-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/utils/components.js +187 -5
- package/src/utils/files.js +13 -12
- package/src/templates/scroll-tracker/examples/scroll-tracker-showcase.tsx +0 -90
- /package/src/templates/{scroll-tracker → scroll-components}/scroll-tracker-provider.tsx +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strapi Dynamic Zone Type Utilities
|
|
3
|
+
*
|
|
4
|
+
* These types provide type-safe rendering for Strapi Dynamic Zones.
|
|
5
|
+
* Define your component union type with `__component` discriminator,
|
|
6
|
+
* then use these utilities for type-safe prop extraction.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base constraint for Strapi dynamic zone components.
|
|
11
|
+
* All components must have a `__component` string identifier.
|
|
12
|
+
*/
|
|
13
|
+
export type StrapiComponent = {
|
|
14
|
+
__component: string;
|
|
15
|
+
id?: number | string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extracts the props for a specific component type from a union,
|
|
20
|
+
* omitting the `__component` discriminator.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* type HeroProps = ExtractComponentProps<PageComponent, 'hero.hero'>;
|
|
24
|
+
* // Results in: { title: string; subtitle: string; image: ImageType }
|
|
25
|
+
*/
|
|
26
|
+
export type ExtractComponentProps<
|
|
27
|
+
TUnion extends StrapiComponent,
|
|
28
|
+
TComponentKey extends TUnion['__component']
|
|
29
|
+
> = Omit<Extract<TUnion, { __component: TComponentKey }>, '__component'>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a type-safe renderer map from a component union.
|
|
33
|
+
* Each key is a `__component` value, each value is a render function.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const renderers: ComponentRendererMap<PageComponent> = {
|
|
37
|
+
* 'hero.hero': (props) => <Hero {...props} />,
|
|
38
|
+
* 'cta.cta': (props) => <CTA {...props} />,
|
|
39
|
+
* };
|
|
40
|
+
*/
|
|
41
|
+
export type ComponentRendererMap<
|
|
42
|
+
TUnion extends StrapiComponent,
|
|
43
|
+
TExtra = object
|
|
44
|
+
> = {
|
|
45
|
+
[K in TUnion['__component']]: (
|
|
46
|
+
props: ExtractComponentProps<TUnion, K> & TExtra
|
|
47
|
+
) => React.ReactElement | null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Utility type to get all component keys from a union.
|
|
52
|
+
*/
|
|
53
|
+
export type ComponentKeys<TUnion extends StrapiComponent> =
|
|
54
|
+
TUnion['__component'];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Props for the DynamicZone component (renders multiple components).
|
|
58
|
+
*/
|
|
59
|
+
export type DynamicZoneProps<
|
|
60
|
+
TUnion extends StrapiComponent,
|
|
61
|
+
TExtra = object
|
|
62
|
+
> = {
|
|
63
|
+
/** Array of components from Strapi Dynamic Zone */
|
|
64
|
+
components: TUnion[];
|
|
65
|
+
/** Map of component renderers */
|
|
66
|
+
renderers: ComponentRendererMap<TUnion, TExtra>;
|
|
67
|
+
/** Function to generate extra props per component (receives component and index) */
|
|
68
|
+
getExtraProps?: (component: TUnion, index: number) => TExtra;
|
|
69
|
+
/** Optional fallback for unknown components */
|
|
70
|
+
fallback?: (component: TUnion) => React.ReactElement | null;
|
|
71
|
+
/** Optional key generator (defaults to component.id or index) */
|
|
72
|
+
getKey?: (component: TUnion, index: number) => string | number;
|
|
73
|
+
};
|
package/src/utils/components.js
CHANGED
|
@@ -9,6 +9,162 @@ import { copyComponent, checkComponentExists } from './files.js';
|
|
|
9
9
|
import fs from 'fs-extra';
|
|
10
10
|
import path from 'path';
|
|
11
11
|
|
|
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
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return files;
|
|
166
|
+
}
|
|
167
|
+
|
|
12
168
|
export async function handleSubmenuFlow(component, templatesDir) {
|
|
13
169
|
if (!component.hasSubmenu || !component.submenuOptions || component.submenuOptions.length === 0) {
|
|
14
170
|
p.log.error(`Component "${component.name}" is not configured for submenu flow.`);
|
|
@@ -143,6 +299,24 @@ export async function handleSubmenuFlow(component, templatesDir) {
|
|
|
143
299
|
// Non-hover option: copy css-ticker directory
|
|
144
300
|
allComponentsToCopy.add('css-ticker');
|
|
145
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
|
+
}
|
|
146
320
|
} else {
|
|
147
321
|
// No nested submenu - copy all component files (legacy behavior)
|
|
148
322
|
// This maintains backward compatibility
|
|
@@ -324,11 +498,14 @@ export async function handleComponentsFlow(components, templatesDir) {
|
|
|
324
498
|
while (true) {
|
|
325
499
|
const selectedComponents = await p.multiselect({
|
|
326
500
|
message: 'Please select one or more components to add (press spacebar to select and return to confirm selection)',
|
|
327
|
-
options:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
+
],
|
|
332
509
|
required: true
|
|
333
510
|
});
|
|
334
511
|
|
|
@@ -337,6 +514,11 @@ export async function handleComponentsFlow(components, templatesDir) {
|
|
|
337
514
|
return;
|
|
338
515
|
}
|
|
339
516
|
|
|
517
|
+
// Check if user selected go back
|
|
518
|
+
if (selectedComponents.includes('__go_back__')) {
|
|
519
|
+
return { goBack: true };
|
|
520
|
+
}
|
|
521
|
+
|
|
340
522
|
if (!selectedComponents.length) {
|
|
341
523
|
p.log.info('No components selected.');
|
|
342
524
|
return;
|
package/src/utils/files.js
CHANGED
|
@@ -31,24 +31,25 @@ export async function copyExamples(component, templatesDir, examplesToCopy) {
|
|
|
31
31
|
return true;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
if (!(await fs.pathExists(examplesSourcePath))) {
|
|
38
|
-
p.log.warn(`Examples directory not found at ${examplesSourcePath}. Skipping examples.`);
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
34
|
+
const componentSourcePath = path.join(templatesDir, component.sourceDir);
|
|
35
|
+
const componentDestPath = path.resolve(process.cwd(), 'src', 'components', component.name);
|
|
41
36
|
|
|
42
37
|
try {
|
|
43
|
-
// Ensure destination directory exists
|
|
44
|
-
await fs.ensureDir(examplesDestPath);
|
|
45
|
-
|
|
46
38
|
// Copy selected example files
|
|
47
39
|
for (const exampleFile of examplesToCopy) {
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
// Support both nested paths (e.g., 'parallax/examples/parallax-showcase.tsx')
|
|
41
|
+
// and flat paths (e.g., 'scroll-tracker-showcase.tsx')
|
|
42
|
+
const sourceFile = path.join(componentSourcePath, exampleFile);
|
|
43
|
+
|
|
44
|
+
// For nested paths like 'parallax/examples/parallax-showcase.tsx',
|
|
45
|
+
// copy to 'examples/parallax-showcase.tsx' in destination
|
|
46
|
+
// For flat paths like 'scroll-tracker-showcase.tsx',
|
|
47
|
+
// copy to 'examples/scroll-tracker-showcase.tsx' in destination
|
|
48
|
+
const filename = path.basename(exampleFile);
|
|
49
|
+
const destFile = path.join(componentDestPath, 'examples', filename);
|
|
50
50
|
|
|
51
51
|
if (await fs.pathExists(sourceFile)) {
|
|
52
|
+
await fs.ensureDir(path.dirname(destFile));
|
|
52
53
|
await fs.copy(sourceFile, destFile, { overwrite: true });
|
|
53
54
|
} else {
|
|
54
55
|
p.log.warn(`Example file "${exampleFile}" not found. Skipping.`);
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { motion, MotionValue, useSpring, useTransform } from "motion/react";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
ScrollTrackerProvider,
|
|
6
|
-
useScrollTrackerContext,
|
|
7
|
-
} from "../scroll-tracker-provider";
|
|
8
|
-
import { Center } from "@/common/ui/containers/center";
|
|
9
|
-
import { Text } from "@/common/ui/text/text";
|
|
10
|
-
|
|
11
|
-
/*
|
|
12
|
-
This is a scroll tracker component that provides scroll-driven animations.
|
|
13
|
-
It uses the motion library to track scroll progress and provides a context
|
|
14
|
-
for child components to access the scroll progress.
|
|
15
|
-
|
|
16
|
-
To test it you can just use it like this in any page:
|
|
17
|
-
<ScrollTrackerShowcase />
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
const AnimatedText = ({
|
|
21
|
-
text,
|
|
22
|
-
motionValue,
|
|
23
|
-
offsetIntro,
|
|
24
|
-
opacityOffset = [0, 0.5, 1],
|
|
25
|
-
yOffset = [100, -20, 0],
|
|
26
|
-
}: {
|
|
27
|
-
text: string;
|
|
28
|
-
motionValue: MotionValue<number>;
|
|
29
|
-
offsetIntro: [number, number] | [number, number, number];
|
|
30
|
-
opacityOffset?: [number, number] | [number, number, number];
|
|
31
|
-
yOffset?: [number, number] | [number, number, number];
|
|
32
|
-
}) => {
|
|
33
|
-
const animateEntry = useTransform(motionValue, offsetIntro, opacityOffset);
|
|
34
|
-
const y = useSpring(useTransform(motionValue, offsetIntro, yOffset));
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<motion.div style={{ opacity: animateEntry, y }}>
|
|
38
|
-
<Text className="text-white">{text}</Text>
|
|
39
|
-
</motion.div>
|
|
40
|
-
);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const ScrollSection = () => {
|
|
44
|
-
const { scrollYProgress } = useScrollTrackerContext();
|
|
45
|
-
const text = "SCROLL DRIVEN ANIMATION".split("");
|
|
46
|
-
const showImage = useTransform(scrollYProgress, [0.3, 1 * 0.9], [0, 1]);
|
|
47
|
-
const rotateImage = useTransform(scrollYProgress, [0.3, 1 * 0.9], [0, 360]);
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<div className="relative flex h-full w-full flex-col items-center justify-center rounded-2xl border-2 border-fill-brand-primary bg-bg-primary-inverse p-2">
|
|
51
|
-
<motion.div
|
|
52
|
-
className="absolute top-0 h-2 w-full bg-red-500"
|
|
53
|
-
style={{ scaleX: scrollYProgress, transformOrigin: "left center" }}
|
|
54
|
-
/>
|
|
55
|
-
<div className="flex flex-col gap-2">
|
|
56
|
-
<div className="flex flex-row gap-0.5">
|
|
57
|
-
{text.map((char, index) => (
|
|
58
|
-
<AnimatedText
|
|
59
|
-
key={index}
|
|
60
|
-
motionValue={scrollYProgress}
|
|
61
|
-
offsetIntro={[
|
|
62
|
-
((index + 1) / text.length) * 0.3,
|
|
63
|
-
((index + 1) / text.length) * 0.5,
|
|
64
|
-
((index + 1) / text.length) * 0.9,
|
|
65
|
-
]}
|
|
66
|
-
text={char}
|
|
67
|
-
/>
|
|
68
|
-
))}
|
|
69
|
-
</div>
|
|
70
|
-
<motion.img
|
|
71
|
-
alt="scroll-tracker-showcase"
|
|
72
|
-
className="mx-auto size-40 object-contain"
|
|
73
|
-
src="/img/good-emoji.png"
|
|
74
|
-
style={{ opacity: showImage, rotate: rotateImage }}
|
|
75
|
-
/>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export const ScrollTrackerShowcase = () => {
|
|
82
|
-
return (
|
|
83
|
-
<div className="w-full">
|
|
84
|
-
<Text className="mb-4">Scroll Tracker</Text>
|
|
85
|
-
<ScrollTrackerProvider height={200} offset={["0 0", "1 1"]}>
|
|
86
|
-
<ScrollSection />
|
|
87
|
-
</ScrollTrackerProvider>
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
};
|
|
File without changes
|