@androbinco/library-cli 0.1.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.
Files changed (53) hide show
  1. package/README.md +86 -37
  2. package/package.json +11 -16
  3. package/src/commands/add.js +107 -0
  4. package/src/commands/list.js +51 -0
  5. package/src/index.js +46 -15
  6. package/src/templates/carousel/components/navigation-buttons.tsx +1 -0
  7. package/src/templates/carousel/components/pagination/bullet.pagination.carousel.tsx +69 -0
  8. package/src/templates/carousel/components/pagination/number.pagination.carousel.tsx +30 -0
  9. package/src/templates/carousel/components/pagination/progress/progress.pagination.carousel.tsx +99 -0
  10. package/src/templates/carousel/components/pagination/progress/use-slide-progress.tsx +31 -0
  11. package/src/templates/carousel/components/pagination.tsx +47 -82
  12. package/src/templates/faqs-accordion/examples/faqs-showcase.tsx +42 -0
  13. package/src/templates/faqs-accordion/faqs-accordion.tsx +70 -0
  14. package/src/templates/faqs-accordion/mock-data.ts +38 -0
  15. package/src/templates/faqs-accordion/types.ts +18 -0
  16. package/src/templates/in-view/data.in-view.ts +89 -0
  17. package/src/templates/in-view/examples/in-view-examples.home.tsx +101 -0
  18. package/src/templates/in-view/examples/in-view-grid-showcase.tsx +41 -0
  19. package/src/templates/in-view/in-view-animation.tsx +72 -0
  20. package/src/templates/in-view/in-view-grid.tsx +81 -0
  21. package/src/templates/in-view/in-view-hidden-text.tsx +45 -0
  22. package/src/templates/in-view/in-view-stroke-line.tsx +30 -0
  23. package/src/templates/lenis/examples/providers.tsx +23 -0
  24. package/src/templates/lenis/lenis-provider.tsx +46 -0
  25. package/src/templates/scroll-components/hooks/use-client-dimensions.ts +21 -0
  26. package/src/templates/scroll-components/parallax/examples/parallax-showcase.tsx +87 -0
  27. package/src/templates/scroll-components/parallax/parallax.css +36 -0
  28. package/src/templates/scroll-components/parallax/parallax.tsx +67 -0
  29. package/src/templates/scroll-components/scale-gallery/components/expanding-element.tsx +40 -0
  30. package/src/templates/scroll-components/scale-gallery/examples/scale-gallery-showcase.tsx +68 -0
  31. package/src/templates/scroll-components/scale-gallery/scale-gallery.tsx +57 -0
  32. package/src/templates/scroll-components/scroll-tracker-provider.tsx +78 -0
  33. package/src/templates/scroll-components/scroll-tracker-showcase.tsx +44 -0
  34. package/src/templates/strapi-dynamic-zone/README.md +157 -0
  35. package/src/templates/strapi-dynamic-zone/dynamic-zone.tsx +113 -0
  36. package/src/templates/strapi-dynamic-zone/examples/page.tsx +53 -0
  37. package/src/templates/strapi-dynamic-zone/examples/renderers.tsx +74 -0
  38. package/src/templates/strapi-dynamic-zone/examples/types.ts +41 -0
  39. package/src/templates/strapi-dynamic-zone/index.ts +11 -0
  40. package/src/templates/strapi-dynamic-zone/types.ts +73 -0
  41. package/src/templates/ticker/css-ticker/css-ticker.tsx +61 -0
  42. package/src/templates/ticker/css-ticker/ticker.keyframes.css +86 -0
  43. package/src/templates/ticker/examples/ticker-hover-showcase.home.tsx +57 -0
  44. package/src/templates/ticker/examples/ticker-static-showcase.home.tsx +56 -0
  45. package/src/templates/ticker/hooks/use-ticker-clones.tsx +70 -0
  46. package/src/templates/ticker/hooks/use-ticker-incremental.tsx +72 -0
  47. package/src/templates/ticker/motion-ticker.tsx +93 -0
  48. package/src/utils/components.js +587 -54
  49. package/src/utils/files.js +89 -5
  50. package/src/templates/button/button.tsx +0 -5
  51. package/src/templates/card/card.tsx +0 -5
  52. package/src/templates/example/example.tsx +0 -5
  53. package/src/templates/hero/hero.tsx +0 -5
@@ -0,0 +1,81 @@
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
+ breakpoint?: number;
12
+ children: React.ReactNode;
13
+ className?: string;
14
+ effect?: keyof typeof IN_VIEW_ANIMATIONS;
15
+ columns?: number | { desktop: number; mobile: number };
16
+ delayDelta?: number;
17
+ delayByColumn?: boolean;
18
+ transition?: Omit<Transition, 'delay'>;
19
+ columnsMobile?: number;
20
+ };
21
+
22
+ export const GridInView = ({
23
+ children,
24
+ className,
25
+ effect = 'fadeInUp',
26
+ delayDelta = 0.25,
27
+ delayByColumn = true,
28
+ transition = {
29
+ duration: 0.5,
30
+ ease: 'easeOut',
31
+ },
32
+ breakpoint = 1024,
33
+ columns = 5,
34
+ }: GridInViewProps) => {
35
+ const childrenArray = Children.toArray(children);
36
+ const isColumnsObject = typeof columns === 'object';
37
+ const [columnsValue, setColumnsValue] = useState(
38
+ isColumnsObject ? columns.desktop : columns
39
+ );
40
+
41
+ useEffect(() => {
42
+ if (!delayByColumn) return;
43
+ if (typeof window === 'undefined' || !isColumnsObject) return;
44
+ if (window.innerWidth < breakpoint) setColumnsValue(columns.mobile);
45
+ else setColumnsValue(columns.desktop);
46
+ }, [columns, isColumnsObject, delayByColumn, breakpoint]);
47
+ const delayCount = (index: number) =>
48
+ delayByColumn ? index % columnsValue : index;
49
+
50
+ return (
51
+ <div
52
+ className={cn(
53
+ 'grid gap-4',
54
+ '[grid-template-columns:repeat(var(--columns),1fr)]',
55
+ 'max-lg:[grid-template-columns:repeat(var(--columns-mobile),1fr)]',
56
+ className,
57
+ )}
58
+ style={
59
+ {
60
+ '--columns': isColumnsObject ? columns.desktop : columns,
61
+ '--columns-mobile': isColumnsObject ? columns.mobile : columns,
62
+ } as React.CSSProperties
63
+ }
64
+ >
65
+ {childrenArray.map((item, index) => (
66
+ <motion.div
67
+ key={index}
68
+ initial={IN_VIEW_ANIMATIONS[effect].hidden}
69
+ transition={{
70
+ ...transition,
71
+ delay: delayCount(index) * delayDelta,
72
+ }}
73
+ viewport={{ once: true, margin: '-10% 0%' }}
74
+ whileInView={IN_VIEW_ANIMATIONS[effect].visible}
75
+ >
76
+ {item}
77
+ </motion.div>
78
+ ))}
79
+ </div>
80
+ );
81
+ };
@@ -0,0 +1,45 @@
1
+ import { Transition } from 'motion/react';
2
+
3
+ import { cn } from '@/common/utils/classname-builder';
4
+
5
+ import InViewAnimation, { InViewAnimationProps } from './in-view-animation';
6
+
7
+ export const InViewHiddenText = ({
8
+ children,
9
+ className,
10
+ transition = {
11
+ duration: 0.6,
12
+ ease: 'easeOut',
13
+ },
14
+ ...props
15
+ }: {
16
+ children: React.ReactNode;
17
+ className?: string;
18
+ transition?: Transition;
19
+ props?: InViewAnimationProps;
20
+ }) => {
21
+ return (
22
+ <div className={cn('overflow-hidden', className)}>
23
+ <InViewAnimation
24
+ className={cn('origin-left overflow-hidden', className)}
25
+ customEffect={{
26
+ hidden: {
27
+ y: '99%',
28
+ opacity: 0,
29
+ rotate: 5,
30
+ },
31
+ visible: {
32
+ y: 0,
33
+ opacity: 1,
34
+ rotate: 0,
35
+ },
36
+ }}
37
+ margin="0%"
38
+ transition={transition}
39
+ {...props}
40
+ >
41
+ {children}
42
+ </InViewAnimation>
43
+ </div>
44
+ );
45
+ };
@@ -0,0 +1,30 @@
1
+ import { Transition } from 'motion/react';
2
+
3
+ import { cn } from '@/common/utils/classname-builder';
4
+
5
+ import InViewAnimation, { InViewAnimationProps } from './in-view-animation';
6
+
7
+ export const InViewStrokeLine = ({
8
+ className,
9
+ transition,
10
+ ...props
11
+ }: {
12
+ className?: string;
13
+ } & InViewAnimationProps) => {
14
+ return (
15
+ <InViewAnimation
16
+ aria-label="separator"
17
+ aria-roledescription="separator"
18
+ className={cn('h-px w-full bg-red-950', className)}
19
+ effect="strokeLine"
20
+ margin="0%"
21
+ role="separator"
22
+ transition={{
23
+ duration: 0.6,
24
+ ease: 'easeInOut',
25
+ ...transition,
26
+ }}
27
+ {...props}
28
+ />
29
+ );
30
+ };
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { QueryClientProvider } from '@tanstack/react-query';
6
+
7
+ import { LenisProvider } from '@/common/animations/smooth-scroll/lenis.provider';
8
+ import { getQueryClient } from '@/common/hooks/get-query-client';
9
+
10
+ import { ThemeProvider } from './theme-provider';
11
+
12
+ export default function Providers({ children }: { children: React.ReactNode }) {
13
+ const queryClient = getQueryClient();
14
+
15
+ return (
16
+ <QueryClientProvider client={queryClient}>
17
+ <ThemeProvider disableTransitionOnChange enableSystem attribute="class" defaultTheme="system">
18
+ <LenisProvider>{children}</LenisProvider>
19
+ {/* <ReactQueryDevtools /> */}
20
+ </ThemeProvider>
21
+ </QueryClientProvider>
22
+ );
23
+ }
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+
3
+ import { createContext, useEffect, useState } from 'react';
4
+
5
+ import { usePathname } from 'next/navigation';
6
+
7
+ import Lenis from 'lenis';
8
+
9
+ type LenisContextType = {
10
+ lenis: Lenis | null;
11
+ };
12
+
13
+ const LenisContext = createContext<LenisContextType | undefined>(undefined);
14
+
15
+ export const LenisProvider = ({ children }: { children: React.ReactNode }) => {
16
+ const [lenis, setLenis] = useState<Lenis | null>(null);
17
+ const pathname = usePathname();
18
+
19
+ useEffect(() => {
20
+ lenis?.scrollTo(0, { immediate: true });
21
+ }, [pathname, lenis]);
22
+
23
+ useEffect(() => {
24
+ if (typeof window === 'undefined') return;
25
+ const lenisInstance = new Lenis({
26
+ lerp: 0.1,
27
+ smoothWheel: true,
28
+ });
29
+
30
+ setLenis(lenisInstance);
31
+ const raf = (time: number) => {
32
+ lenisInstance.raf(time);
33
+ requestAnimationFrame(raf);
34
+ };
35
+
36
+ const requestId = requestAnimationFrame(raf);
37
+
38
+ return () => {
39
+ lenisInstance.destroy();
40
+ cancelAnimationFrame(requestId);
41
+ setLenis(null);
42
+ };
43
+ }, []);
44
+
45
+ return <LenisContext.Provider value={{ lenis }}>{children}</LenisContext.Provider>;
46
+ };
@@ -0,0 +1,21 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ export const useClientDimensions = () => {
4
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
5
+
6
+ useEffect(() => {
7
+ if (typeof window === 'undefined') return;
8
+ setDimensions({ width: window.innerWidth, height: window.innerHeight });
9
+ window.addEventListener('resize', () => {
10
+ setDimensions({ width: window.innerWidth, height: window.innerHeight });
11
+ });
12
+
13
+ return () => {
14
+ window.removeEventListener('resize', () => {
15
+ setDimensions({ width: window.innerWidth, height: window.innerHeight });
16
+ });
17
+ };
18
+ }, []);
19
+
20
+ return dimensions;
21
+ };
@@ -0,0 +1,87 @@
1
+ "use client";
2
+ import { Parallax } from "../parallax/parallax";
3
+ import { Text } from "@/common/ui/text/text";
4
+
5
+ export const ParallaxShowcase = () => {
6
+ return (
7
+ <div className="grid gap-10">
8
+ <div className="flex flex-col justify-center gap-2 py-3">
9
+ <Text className="px-8 max-lg:px-4" variant="title.6">
10
+ Scroll Parallax
11
+ </Text>
12
+ </div>
13
+ <div className="flex flex-col gap-0">
14
+ <Parallax.Root className="relative h-screen w-full">
15
+ <Parallax.Image
16
+ alt="parallax-image"
17
+ imageClassName="h-screen w-full object-cover"
18
+ src="https://storage.googleapis.com/testing-mets-bucket/Players_Family_Lounge_21537_d8a8b086cb/Players_Family_Lounge_21537_d8a8b086cb.jpg"
19
+ />
20
+ </Parallax.Root>
21
+ <Parallax.Root className="relative h-[90vh] w-full">
22
+ <Parallax.Image
23
+ alt="parallax-image"
24
+ imageClassName="w-full object-cover"
25
+ src="https://storage.googleapis.com/testing-mets-bucket/Players_Family_Lounge_21564_3de5bc0bed/Players_Family_Lounge_21564_3de5bc0bed.jpg"
26
+ />
27
+ </Parallax.Root>
28
+ <Parallax.Root className="relative h-[90vh] w-full">
29
+ <Parallax.Image
30
+ alt="parallax-image"
31
+ imageClassName="w-full object-cover"
32
+ src="https://storage.googleapis.com/testing-mets-bucket/Delta_Sky360_Club_21192_2cee5eec25/Delta_Sky360_Club_21192_2cee5eec25.jpg"
33
+ />
34
+ </Parallax.Root>
35
+ </div>
36
+ <div className="mx-auto w-full max-w-container">
37
+ <div className="flex flex-row flex-wrap items-center justify-between gap-4">
38
+ <div className="flex flex-col items-center gap-2">
39
+ <Parallax.Root
40
+ easing="ease-in"
41
+ offset="200px"
42
+ className="aspect-[9/16] max-h-150 max-w-100 rounded-2xl overflow-hidden"
43
+ >
44
+ <Parallax.Image
45
+ alt="parallax-image"
46
+ imageClassName="h-full w-full object-cover"
47
+ easing="ease-in"
48
+ src="/img/gallery/gallery-example3.jpg"
49
+ />
50
+ </Parallax.Root>
51
+ <Text>Image text</Text>
52
+ </div>
53
+ <div className="flex flex-col items-center gap-2">
54
+ <Parallax.Root
55
+ className="aspect-[9/16] max-h-150 max-w-100 rounded-2xl overflow-hidden"
56
+ easing="ease-in-out"
57
+ offset="20%"
58
+ >
59
+ <Parallax.Image
60
+ alt="parallax-image"
61
+ imageClassName="h-full w-full object-cover"
62
+ easing="ease-in-out"
63
+ src="/img/gallery/gallery-example2.jpg"
64
+ />
65
+ </Parallax.Root>
66
+ <Text>Image text</Text>
67
+ </div>
68
+ <div className="flex flex-col items-center gap-2">
69
+ <Parallax.Root
70
+ className="aspect-[9/16] max-h-150 max-w-100 rounded-2xl overflow-hidden"
71
+ easing="ease-out"
72
+ offset="10%"
73
+ >
74
+ <Parallax.Image
75
+ alt="parallax-image"
76
+ imageClassName="h-full w-full object-cover"
77
+ easing="ease-out"
78
+ src="/img/gallery/gallery-example1.jpg"
79
+ />
80
+ </Parallax.Root>
81
+ <Text>Image text</Text>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ );
87
+ };
@@ -0,0 +1,36 @@
1
+ .parallax-root {
2
+ position: relative;
3
+ --parallax-offset: 20%;
4
+ view-timeline-name: --parallax-tracking;
5
+ }
6
+
7
+ @keyframes parallax-image {
8
+ from {
9
+ transform: translateY(calc((var(--parallax-offset) / 2) * -1));
10
+ }
11
+ to {
12
+ transform: translateY(calc(var(--parallax-offset) / 2));
13
+ }
14
+ }
15
+ .parallax-container {
16
+ height: 100%;
17
+ position: relative;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .parallax-img {
22
+ width: 100%;
23
+ display: block;
24
+ --calc-height: calc(100% + var(--parallax-offset));
25
+ height: var(--calc-height);
26
+ position: relative;
27
+ animation-timeline: --parallax-tracking;
28
+ animation-timing-function: linear;
29
+ animation-range: entry calc(100vh - 100%) exit calc(100vh + (100vh - 100%));
30
+ top: calc((var(--parallax-offset) / 2) * -1);
31
+ /* top:0%; */
32
+ animation-name: parallax-image;
33
+ animation-fill-mode: both;
34
+ animation-duration: 1ms;
35
+ object-fit: cover;
36
+ }
@@ -0,0 +1,67 @@
1
+ import { cn } from "@/common/utils/classname-builder";
2
+ import "./parallax.css";
3
+
4
+ type Easing = "linear" | "ease-in-out" | "ease-in" | "ease-out";
5
+ const ParallaxRoot = ({
6
+ className,
7
+ children,
8
+ offset = "200px",
9
+ easing = "linear",
10
+ }: {
11
+ className?: string;
12
+ children: React.ReactNode;
13
+ easing?: Easing;
14
+ offset?:
15
+ | `${number}px`
16
+ | `${number}%`
17
+ | `${number}vh`
18
+ | `${number}vw`
19
+ | `${number}em`
20
+ | `${number}rem`
21
+ | `${number}ex`
22
+ | `${number}ch`;
23
+ }) => {
24
+ return (
25
+ <div
26
+ className={cn("parallax-root h-max w-max", className)}
27
+ style={
28
+ {
29
+ "--easing": easing,
30
+ "--parallax-offset": offset,
31
+ } as React.CSSProperties
32
+ }
33
+ >
34
+ {children}
35
+ </div>
36
+ );
37
+ };
38
+
39
+ const ParallaxImage = ({
40
+ className,
41
+ src,
42
+ imageClassName,
43
+ alt,
44
+ easing = "linear",
45
+ }: {
46
+ className?: string;
47
+ src: string;
48
+ imageClassName?: string;
49
+ easing?: Easing;
50
+ alt: string;
51
+ }) => {
52
+ return (
53
+ <div className={cn("parallax-container h-full w-full", className)}>
54
+ <img
55
+ alt={alt}
56
+ className={cn("parallax-img", imageClassName)}
57
+ src={src}
58
+ style={{ "--easing": easing } as React.CSSProperties}
59
+ />
60
+ </div>
61
+ );
62
+ };
63
+
64
+ export const Parallax = {
65
+ Root: ParallaxRoot,
66
+ Image: ParallaxImage,
67
+ };
@@ -0,0 +1,40 @@
1
+ import { motion, useTransform } from 'motion/react';
2
+
3
+ import { cn } from '@/common/utils/classname-builder';
4
+ import { useScrollTrackerContext } from '../../scroll-tracker-provider';
5
+
6
+ export const ExpandingElement = ({
7
+ scaleOffset,
8
+ height,
9
+ children,
10
+ widthOffset,
11
+ className,
12
+ }: {
13
+ scaleOffset: [number, number, number];
14
+ widthOffset: [number, number, number];
15
+ height: number | string;
16
+ className?: string;
17
+ children?: React.ReactNode;
18
+ }) => {
19
+ const { scrollYProgress } = useScrollTrackerContext();
20
+
21
+ const width = useTransform(scrollYProgress, scaleOffset, widthOffset);
22
+
23
+ return (
24
+ <div className="absolute inset-1/2 -translate-x-1/2 -translate-y-1/2" style={{ height }}>
25
+ <motion.div
26
+ className="absolute inset-1/2 flex h-full -translate-x-1/2 -translate-y-1/2 items-center justify-center overflow-hidden"
27
+ style={{ width, height }}
28
+ >
29
+ <div
30
+ className={cn(
31
+ 'absolute inset-1/2 h-full w-full min-w-100 -translate-x-1/2 -translate-y-1/2 object-cover',
32
+ className,
33
+ )}
34
+ >
35
+ {children}
36
+ </div>
37
+ </motion.div>
38
+ </div>
39
+ );
40
+ };
@@ -0,0 +1,68 @@
1
+ "use client";
2
+ import { Text } from "@/common/ui/text/text";
3
+
4
+ import { ScrollTrackerProvider } from "../scroll-tracker-provider";
5
+ import { ExpandingElement } from "../scale-gallery/components/expanding-element";
6
+ import { useClientDimensions } from "../hooks/use-client-dimensions";
7
+
8
+ export const ScaleGalleryShowcase = () => {
9
+ const { width, height } = useClientDimensions();
10
+
11
+ return (
12
+ <div className="space-y-12">
13
+ <div className="h-screen flex items-center justify-center bg-bg-primary">
14
+ <Text variant="title.4">Scale Gallery Example</Text>
15
+ </div>
16
+
17
+ <ScrollTrackerProvider height={500} offset={["0 0", "1 1"]}>
18
+ <Text className="max-lg:px-4 px-8 mb-8" variant="title.6">
19
+ Gallery with Scale Animation
20
+ </Text>
21
+ <div className="flex h-full w-full flex-col items-center justify-center">
22
+ <ExpandingElement
23
+ className="flex flex-col items-center justify-between gap-2"
24
+ height={height * 0.5}
25
+ scaleOffset={[0.05, 0.2, 0.4]}
26
+ widthOffset={[0, width / 2, width * 0.8]}
27
+ >
28
+ <img
29
+ alt="gallery-1"
30
+ className="h-full w-full object-cover rounded-lg"
31
+ src="https://images.pexels.com/photos/34533069/pexels-photo-34533069.jpeg"
32
+ />
33
+ </ExpandingElement>
34
+
35
+ <ExpandingElement
36
+ className="flex items-center justify-center"
37
+ height={height * 0.5}
38
+ scaleOffset={[0.3, 0.5, 0.7]}
39
+ widthOffset={[0, width / 2, width * 0.8]}
40
+ >
41
+ <img
42
+ alt="gallery-2"
43
+ className="h-full w-full object-cover rounded-lg"
44
+ src="https://images.pexels.com/photos/34712722/pexels-photo-34712722.jpeg"
45
+ />
46
+ </ExpandingElement>
47
+
48
+ <ExpandingElement
49
+ className="flex items-center justify-center"
50
+ height={height * 0.5}
51
+ scaleOffset={[0.5, 0.8, 0.97]}
52
+ widthOffset={[0, width / 2, width * 0.8]}
53
+ >
54
+ <img
55
+ alt="gallery-3"
56
+ className="h-full w-full object-cover rounded-lg"
57
+ src="https://images.pexels.com/photos/3833517/pexels-photo-3833517.jpeg"
58
+ />
59
+ </ExpandingElement>
60
+ </div>
61
+ </ScrollTrackerProvider>
62
+
63
+ <div className="h-screen flex items-center justify-center bg-bg-primary">
64
+ <Text variant="title.4">Gallery ends here</Text>
65
+ </div>
66
+ </div>
67
+ );
68
+ };
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+ import { Text } from '@/common/ui/text/text';
3
+
4
+ import { ScrollTrackerProvider } from '../scroll-tracker-provider';
5
+ import { useClientDimensions } from '../hooks/use-client-dimensions';
6
+
7
+ import { ExpandingElement } from './components/expanding-element';
8
+
9
+ export const ScaleGallery = () => {
10
+ const { width, height } = useClientDimensions();
11
+
12
+ return (
13
+ <ScrollTrackerProvider height={500} offset={['0 0', '1 1']}>
14
+ <Text className="max-lg:px-4 px-8" variant="title.6">
15
+ Scale Gallery
16
+ </Text>
17
+ <div className='flex h-full w-full flex-col items-center justify-center'>
18
+ <ExpandingElement
19
+ className="flex flex-col items-center justify-between gap-2"
20
+ height={height * 0.5}
21
+ scaleOffset={[0.05, 0.2, 0.4]}
22
+ widthOffset={[0, width / 2, width * 0.8]}
23
+ >
24
+ <img
25
+ alt="gallery-image-1"
26
+ className="h-full w-full object-cover"
27
+ src="/img/gallery/gallery-example1.jpg"
28
+ />
29
+ </ExpandingElement>
30
+ <ExpandingElement
31
+ className="flex items-center justify-center"
32
+ height={height * 0.5}
33
+ scaleOffset={[0.3, 0.5, 0.7]}
34
+ widthOffset={[0, width / 2, width * 0.8]}
35
+ >
36
+ <img
37
+ alt="gallery-image-2"
38
+ className="h-full w-full object-cover"
39
+ src="https://images.pexels.com/photos/34533069/pexels-photo-34533069.jpeg"
40
+ />
41
+ </ExpandingElement>
42
+ <ExpandingElement
43
+ className="flex items-center justify-center"
44
+ height={height * 0.5}
45
+ scaleOffset={[0.5, 0.8, 0.97]}
46
+ widthOffset={[0, width / 2, width * 0.8]}
47
+ >
48
+ <img
49
+ alt="gallery-image-3"
50
+ className="h-full w-full object-cover"
51
+ src="https://images.pexels.com/photos/34712722/pexels-photo-34712722.jpeg"
52
+ />
53
+ </ExpandingElement>
54
+ </div>
55
+ </ScrollTrackerProvider>
56
+ );
57
+ };
@@ -0,0 +1,78 @@
1
+ "use client";
2
+
3
+ import { createContext, useContext, useRef } from "react";
4
+
5
+ import { MotionValue, useScroll } from "motion/react";
6
+
7
+ import { cn } from "@/common/utils/classname-builder";
8
+
9
+ type Offset =
10
+ | [number, number]
11
+ | [`${number} ${number}`, `${number} ${number}`]
12
+ | [`${number}`, `${number}`];
13
+
14
+ type ScrollProviderProps = {
15
+ height?: number;
16
+ mobileHeight?: string;
17
+ children: React.ReactNode;
18
+ offset?: Offset;
19
+ className?: string;
20
+ containerClassName?: string;
21
+ };
22
+ type ScrollTrackerContextType = {
23
+ scrollYProgress: MotionValue<number>;
24
+ };
25
+ const ScrollTrackerContext = createContext<ScrollTrackerContextType>({
26
+ scrollYProgress: {} as MotionValue<number>,
27
+ });
28
+
29
+ export const ScrollTrackerProvider = ({
30
+ height = 500,
31
+ mobileHeight,
32
+ children,
33
+ className,
34
+ containerClassName,
35
+ offset = ["0 0", "1 1"],
36
+ }: ScrollProviderProps) => {
37
+ const ref = useRef<HTMLDivElement>(null);
38
+ const { scrollYProgress } = useScroll({ target: ref, offset });
39
+
40
+ return (
41
+ <ScrollTrackerContext.Provider value={{ scrollYProgress }}>
42
+ <div
43
+ ref={ref}
44
+ className={cn(
45
+ "h-[var(--tracker-mobile-height)] w-full lg:h-[var(--tracker-height)]",
46
+ containerClassName
47
+ )}
48
+ style={
49
+ {
50
+ "--tracker-height": `${height}vh`,
51
+ "--tracker-mobile-height": `${mobileHeight ?? height}vh`,
52
+ } as React.CSSProperties
53
+ }
54
+ >
55
+ <div
56
+ className={cn(
57
+ "sticky top-0 right-0 left-0 h-screen w-full",
58
+ className
59
+ )}
60
+ >
61
+ {children}
62
+ </div>
63
+ </div>
64
+ </ScrollTrackerContext.Provider>
65
+ );
66
+ };
67
+
68
+ export const useScrollTrackerContext = () => {
69
+ const context = useContext(ScrollTrackerContext);
70
+
71
+ if (!context) {
72
+ throw new Error(
73
+ "useScrollTracker must be used within a ScrollTrackerProvider"
74
+ );
75
+ }
76
+
77
+ return context;
78
+ };