@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.
- package/README.md +86 -37
- package/package.json +11 -16
- package/src/commands/add.js +107 -0
- package/src/commands/list.js +51 -0
- package/src/index.js +46 -15
- 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 +89 -0
- package/src/templates/in-view/examples/in-view-examples.home.tsx +101 -0
- package/src/templates/in-view/examples/in-view-grid-showcase.tsx +41 -0
- package/src/templates/in-view/in-view-animation.tsx +72 -0
- package/src/templates/in-view/in-view-grid.tsx +81 -0
- package/src/templates/in-view/in-view-hidden-text.tsx +45 -0
- package/src/templates/in-view/in-view-stroke-line.tsx +30 -0
- 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-provider.tsx +78 -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/templates/ticker/css-ticker/css-ticker.tsx +61 -0
- package/src/templates/ticker/css-ticker/ticker.keyframes.css +86 -0
- package/src/templates/ticker/examples/ticker-hover-showcase.home.tsx +57 -0
- package/src/templates/ticker/examples/ticker-static-showcase.home.tsx +56 -0
- package/src/templates/ticker/hooks/use-ticker-clones.tsx +70 -0
- package/src/templates/ticker/hooks/use-ticker-incremental.tsx +72 -0
- package/src/templates/ticker/motion-ticker.tsx +93 -0
- package/src/utils/components.js +587 -54
- package/src/utils/files.js +89 -5
- package/src/templates/button/button.tsx +0 -5
- package/src/templates/card/card.tsx +0 -5
- package/src/templates/example/example.tsx +0 -5
- 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
|
+
};
|