@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,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { FaqsAccordion, MOCK_FAQS_DATA } from '../faqs-accordion';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Example showcase for FaqsAccordion component
|
|
7
|
+
*
|
|
8
|
+
* This simulates a Strapi API call with mock data.
|
|
9
|
+
* In production, replace MOCK_FAQS_DATA with:
|
|
10
|
+
*
|
|
11
|
+
* const response = await fetch('your-strapi-url/api/faqs?populate=questions');
|
|
12
|
+
* const faqs = await response.json();
|
|
13
|
+
*/
|
|
14
|
+
export default function FaqsShowcase() {
|
|
15
|
+
// Simulating Strapi response with mock data
|
|
16
|
+
const faqsData = MOCK_FAQS_DATA;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="min-h-screen bg-gray-50 py-12">
|
|
20
|
+
<div className="container mx-auto px-4">
|
|
21
|
+
<h1 className="mb-12 text-center text-4xl font-bold">
|
|
22
|
+
FAQs Accordion Example
|
|
23
|
+
</h1>
|
|
24
|
+
|
|
25
|
+
<FaqsAccordion faqs={faqsData} />
|
|
26
|
+
|
|
27
|
+
{/* Example with custom styling */}
|
|
28
|
+
<div className="mt-16">
|
|
29
|
+
<h2 className="mb-8 text-center text-3xl font-bold">
|
|
30
|
+
Custom Styled Version
|
|
31
|
+
</h2>
|
|
32
|
+
<FaqsAccordion
|
|
33
|
+
faqs={faqsData}
|
|
34
|
+
className="rounded-lg bg-white p-8 shadow-lg"
|
|
35
|
+
questionClassName="text-blue-600"
|
|
36
|
+
answerClassName="text-gray-700"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as Accordion from '@radix-ui/react-accordion';
|
|
4
|
+
import { FaqsAccordionProps } from './types';
|
|
5
|
+
|
|
6
|
+
const ChevronDownIcon = ({ className }: { className?: string }) => (
|
|
7
|
+
<svg
|
|
8
|
+
width="24"
|
|
9
|
+
height="24"
|
|
10
|
+
viewBox="0 0 24 24"
|
|
11
|
+
fill="none"
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
+
className={className}
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
d="M6 9L12 15L18 9"
|
|
17
|
+
stroke="currentColor"
|
|
18
|
+
strokeWidth="2"
|
|
19
|
+
strokeLinecap="round"
|
|
20
|
+
strokeLinejoin="round"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const FaqsAccordion = ({
|
|
26
|
+
faqs,
|
|
27
|
+
className = '',
|
|
28
|
+
questionClassName = '',
|
|
29
|
+
answerClassName = '',
|
|
30
|
+
}: FaqsAccordionProps) => {
|
|
31
|
+
return (
|
|
32
|
+
<div className={`mx-auto w-full max-w-[910px] ${className}`}>
|
|
33
|
+
<h2 className="mb-8 text-3xl font-bold">{faqs.title}</h2>
|
|
34
|
+
|
|
35
|
+
<Accordion.Root type="single" collapsible className="w-full">
|
|
36
|
+
{faqs.questions.map((faq, index) => (
|
|
37
|
+
<div key={faq.id}>
|
|
38
|
+
<Accordion.Item value={`item-${faq.id}`} className="border-b">
|
|
39
|
+
<Accordion.Header>
|
|
40
|
+
<Accordion.Trigger
|
|
41
|
+
className={`group flex w-full items-center justify-between py-6 text-left transition-all hover:opacity-80 ${questionClassName}`}
|
|
42
|
+
>
|
|
43
|
+
<span className="text-lg font-semibold">{faq.question}</span>
|
|
44
|
+
<ChevronDownIcon className="transition-transform duration-300 group-data-[state=open]:rotate-180" />
|
|
45
|
+
</Accordion.Trigger>
|
|
46
|
+
</Accordion.Header>
|
|
47
|
+
|
|
48
|
+
<Accordion.Content
|
|
49
|
+
className={`overflow-hidden transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down ${answerClassName}`}
|
|
50
|
+
>
|
|
51
|
+
<div className="pb-6 text-base text-gray-600">
|
|
52
|
+
{faq.answer}
|
|
53
|
+
</div>
|
|
54
|
+
</Accordion.Content>
|
|
55
|
+
</Accordion.Item>
|
|
56
|
+
</div>
|
|
57
|
+
))}
|
|
58
|
+
</Accordion.Root>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default FaqsAccordion;
|
|
64
|
+
|
|
65
|
+
export type {
|
|
66
|
+
FaqsAccordionProps,
|
|
67
|
+
FaqsResponse,
|
|
68
|
+
FaqQuestion,
|
|
69
|
+
} from './types';
|
|
70
|
+
export { MOCK_FAQS_DATA } from './mock-data';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { FaqsResponse } from './types';
|
|
2
|
+
|
|
3
|
+
export const MOCK_FAQS_DATA: FaqsResponse = {
|
|
4
|
+
id: 1,
|
|
5
|
+
title: 'Frequently Asked Questions',
|
|
6
|
+
questions: [
|
|
7
|
+
{
|
|
8
|
+
id: 1,
|
|
9
|
+
question: 'What is the return policy?',
|
|
10
|
+
answer:
|
|
11
|
+
'You can return any item within 30 days of purchase for a full refund. Items must be in original condition with tags attached.',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 2,
|
|
15
|
+
question: 'How long does shipping take?',
|
|
16
|
+
answer:
|
|
17
|
+
'Standard shipping takes 5-7 business days. Express shipping is available and takes 2-3 business days.',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 3,
|
|
21
|
+
question: 'Do you ship internationally?',
|
|
22
|
+
answer:
|
|
23
|
+
'Yes, we ship to over 50 countries worldwide. International shipping times vary by location.',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 4,
|
|
27
|
+
question: 'How do I track my order?',
|
|
28
|
+
answer:
|
|
29
|
+
'Once your order ships, you\'ll receive a tracking number via email. You can use this to track your package on our website.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 5,
|
|
33
|
+
question: 'What payment methods do you accept?',
|
|
34
|
+
answer:
|
|
35
|
+
'We accept all major credit cards, PayPal, Apple Pay, and Google Pay for your convenience.',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type FaqQuestion = {
|
|
2
|
+
id: number;
|
|
3
|
+
question: string;
|
|
4
|
+
answer: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type FaqsResponse = {
|
|
8
|
+
id: number;
|
|
9
|
+
title: string;
|
|
10
|
+
questions: FaqQuestion[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type FaqsAccordionProps = {
|
|
14
|
+
faqs: FaqsResponse;
|
|
15
|
+
className?: string;
|
|
16
|
+
questionClassName?: string;
|
|
17
|
+
answerClassName?: string;
|
|
18
|
+
};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
import { FC, ReactNode } from
|
|
1
|
+
'use client';
|
|
2
|
+
import { FC, ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
motion,
|
|
6
6
|
MotionProps,
|
|
7
7
|
TargetAndTransition,
|
|
8
8
|
Transition,
|
|
9
|
-
} from
|
|
9
|
+
} from 'motion/react';
|
|
10
10
|
|
|
11
|
-
import IN_VIEW_ANIMATIONS from
|
|
11
|
+
import IN_VIEW_ANIMATIONS from './data.in-view';
|
|
12
12
|
|
|
13
13
|
const transitionsDefault: Transition = {
|
|
14
14
|
duration: 0.6,
|
|
15
15
|
delay: 0,
|
|
16
|
-
ease:
|
|
16
|
+
ease: 'easeInOut',
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
export type InViewAnimationProps = {
|
|
@@ -33,11 +33,11 @@ export type InViewAnimationProps = {
|
|
|
33
33
|
React.HTMLAttributes<HTMLDivElement>;
|
|
34
34
|
|
|
35
35
|
const InViewAnimation: FC<InViewAnimationProps> = ({
|
|
36
|
-
margin =
|
|
36
|
+
margin = '-10% 0%',
|
|
37
37
|
execute = true,
|
|
38
38
|
transition = {},
|
|
39
39
|
customEffect,
|
|
40
|
-
effect =
|
|
40
|
+
effect = 'fadeInUp',
|
|
41
41
|
once = true,
|
|
42
42
|
style,
|
|
43
43
|
className,
|
|
@@ -1,62 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
import { Children, useEffect, useState } from
|
|
1
|
+
'use client';
|
|
2
|
+
import { Children, useEffect, useState } from 'react';
|
|
3
3
|
|
|
4
|
-
import { motion, Transition } from
|
|
4
|
+
import { motion, Transition } from 'motion/react';
|
|
5
5
|
|
|
6
|
-
import { cn } from
|
|
6
|
+
import { cn } from '@/common/utils/classname-builder';
|
|
7
7
|
|
|
8
|
-
import IN_VIEW_ANIMATIONS from
|
|
8
|
+
import IN_VIEW_ANIMATIONS from './data.in-view';
|
|
9
9
|
|
|
10
10
|
type GridInViewProps = {
|
|
11
|
+
breakpoint?: number;
|
|
11
12
|
children: React.ReactNode;
|
|
12
13
|
className?: string;
|
|
13
14
|
effect?: keyof typeof IN_VIEW_ANIMATIONS;
|
|
14
15
|
columns?: number | { desktop: number; mobile: number };
|
|
15
16
|
delayDelta?: number;
|
|
16
17
|
delayByColumn?: boolean;
|
|
17
|
-
transition?: Omit<Transition,
|
|
18
|
+
transition?: Omit<Transition, 'delay'>;
|
|
18
19
|
columnsMobile?: number;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
export const GridInView = ({
|
|
22
23
|
children,
|
|
23
24
|
className,
|
|
24
|
-
effect =
|
|
25
|
-
delayDelta = 0.
|
|
25
|
+
effect = 'fadeInUp',
|
|
26
|
+
delayDelta = 0.25,
|
|
26
27
|
delayByColumn = true,
|
|
27
28
|
transition = {
|
|
28
29
|
duration: 0.5,
|
|
29
|
-
ease:
|
|
30
|
+
ease: 'easeOut',
|
|
30
31
|
},
|
|
32
|
+
breakpoint = 1024,
|
|
31
33
|
columns = 5,
|
|
32
34
|
}: GridInViewProps) => {
|
|
33
35
|
const childrenArray = Children.toArray(children);
|
|
34
|
-
const isColumnsObject = typeof columns ===
|
|
36
|
+
const isColumnsObject = typeof columns === 'object';
|
|
35
37
|
const [columnsValue, setColumnsValue] = useState(
|
|
36
38
|
isColumnsObject ? columns.desktop : columns
|
|
37
39
|
);
|
|
38
40
|
|
|
39
41
|
useEffect(() => {
|
|
40
42
|
if (!delayByColumn) return;
|
|
41
|
-
if (typeof window ===
|
|
42
|
-
if (window.innerWidth <
|
|
43
|
+
if (typeof window === 'undefined' || !isColumnsObject) return;
|
|
44
|
+
if (window.innerWidth < breakpoint) setColumnsValue(columns.mobile);
|
|
43
45
|
else setColumnsValue(columns.desktop);
|
|
44
|
-
}, [columns, isColumnsObject, delayByColumn]);
|
|
46
|
+
}, [columns, isColumnsObject, delayByColumn, breakpoint]);
|
|
45
47
|
const delayCount = (index: number) =>
|
|
46
48
|
delayByColumn ? index % columnsValue : index;
|
|
47
49
|
|
|
48
50
|
return (
|
|
49
51
|
<div
|
|
50
52
|
className={cn(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
className
|
|
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,
|
|
55
57
|
)}
|
|
56
58
|
style={
|
|
57
59
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
'--columns': isColumnsObject ? columns.desktop : columns,
|
|
61
|
+
'--columns-mobile': isColumnsObject ? columns.mobile : columns,
|
|
60
62
|
} as React.CSSProperties
|
|
61
63
|
}
|
|
62
64
|
>
|
|
@@ -68,7 +70,7 @@ export const GridInView = ({
|
|
|
68
70
|
...transition,
|
|
69
71
|
delay: delayCount(index) * delayDelta,
|
|
70
72
|
}}
|
|
71
|
-
viewport={{ once: true, margin:
|
|
73
|
+
viewport={{ once: true, margin: '-10% 0%' }}
|
|
72
74
|
whileInView={IN_VIEW_ANIMATIONS[effect].visible}
|
|
73
75
|
>
|
|
74
76
|
{item}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { Transition } from
|
|
1
|
+
import { Transition } from 'motion/react';
|
|
2
2
|
|
|
3
|
-
import { cn } from
|
|
3
|
+
import { cn } from '@/common/utils/classname-builder';
|
|
4
4
|
|
|
5
|
-
import InViewAnimation, { InViewAnimationProps } from
|
|
5
|
+
import InViewAnimation, { InViewAnimationProps } from './in-view-animation';
|
|
6
6
|
|
|
7
7
|
export const InViewHiddenText = ({
|
|
8
8
|
children,
|
|
9
9
|
className,
|
|
10
10
|
transition = {
|
|
11
11
|
duration: 0.6,
|
|
12
|
-
ease:
|
|
12
|
+
ease: 'easeOut',
|
|
13
13
|
},
|
|
14
14
|
...props
|
|
15
15
|
}: {
|
|
@@ -19,12 +19,12 @@ export const InViewHiddenText = ({
|
|
|
19
19
|
props?: InViewAnimationProps;
|
|
20
20
|
}) => {
|
|
21
21
|
return (
|
|
22
|
-
<div className={cn(
|
|
22
|
+
<div className={cn('overflow-hidden', className)}>
|
|
23
23
|
<InViewAnimation
|
|
24
|
-
className={cn(
|
|
24
|
+
className={cn('origin-left overflow-hidden', className)}
|
|
25
25
|
customEffect={{
|
|
26
26
|
hidden: {
|
|
27
|
-
y:
|
|
27
|
+
y: '99%',
|
|
28
28
|
opacity: 0,
|
|
29
29
|
rotate: 5,
|
|
30
30
|
},
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Transition } from
|
|
1
|
+
import { Transition } from 'motion/react';
|
|
2
2
|
|
|
3
|
-
import { cn } from
|
|
3
|
+
import { cn } from '@/common/utils/classname-builder';
|
|
4
4
|
|
|
5
|
-
import InViewAnimation, { InViewAnimationProps } from
|
|
5
|
+
import InViewAnimation, { InViewAnimationProps } from './in-view-animation';
|
|
6
6
|
|
|
7
7
|
export const InViewStrokeLine = ({
|
|
8
8
|
className,
|
|
@@ -15,12 +15,13 @@ export const InViewStrokeLine = ({
|
|
|
15
15
|
<InViewAnimation
|
|
16
16
|
aria-label="separator"
|
|
17
17
|
aria-roledescription="separator"
|
|
18
|
-
className={cn(
|
|
18
|
+
className={cn('h-px w-full bg-red-950', className)}
|
|
19
19
|
effect="strokeLine"
|
|
20
|
+
margin="0%"
|
|
20
21
|
role="separator"
|
|
21
22
|
transition={{
|
|
22
23
|
duration: 0.6,
|
|
23
|
-
ease:
|
|
24
|
+
ease: 'easeInOut',
|
|
24
25
|
...transition,
|
|
25
26
|
}}
|
|
26
27
|
{...props}
|
|
@@ -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
|
+
};
|