Files
graff.event/src/components/Promotion.tsx
T

231 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { AnimatePresence, motion } from 'framer-motion';
import { Title } from './ui/Title';
import { promotionFeatures } from '../consts/promotionFeatures';
import { useEffect, useRef, useState } from 'react';
import { useHover } from 'usehooks-ts';
import { useLocation } from 'react-router-dom';
import { hashes } from '../consts/motivationHashes';
import { ChevronUpIcon } from './icons/ChevronUpIcon';
import { ChevronDownIcon } from './icons/ChevronDownIcon';
import { useWindowWidth } from '../hooks/useWindowWidth';
import { ClassNameWrapper } from '../hocs/ClassNameWrapper';
export function Promotion() {
const width = useWindowWidth();
return (
<div
id="products"
className="space-y-8 lg:space-y-20 sm:space-y-10 lg:-mt-20 sm:-mt-10"
>
<Title className="max-w-[calc(1310/1600*100vw)]">
Повышаем количество посетителей на стенде,
<span className="text-gradient">
{' '}
помогаем продвигать ваш продукт и увеличиваем продажи на выставках
</span>
</Title>
<div>
{promotionFeatures.map((feature, index) =>
width >= 1024 ? (
<DesktopFeature
key={feature.title}
number={index + 1}
{...feature}
/>
) : (
<Feature {...feature} key={feature.title} number={index + 1} />
),
)}
</div>
</div>
);
}
function DesktopFeature({
description,
images,
number,
title,
}: {
number: number;
title: string;
description: string;
images: string[];
}) {
const { hash } = useLocation();
const ref = useRef<HTMLDivElement>(null);
const hovered = useHover(ref);
return (
<div
ref={ref}
id={hashes.get(number)}
className="p-7 border-t border-x border-[#3D425C] last:border-b hover:bg-[url(/src/assets/promotion/Ellipse.png)] bg-no-repeat bg-center bg-cover"
>
<motion.div
className="flex justify-between gap-x-4 [clip-path:polygon(0%_0%,100%_0%,100%_calc(100%+28px),0%_calc(100%+28px))]"
transition={{ duration: 0.7 }}
animate={
hovered || hash.slice(1) === hashes.get(number)
? { height: 424 }
: { height: 140 }
}
>
<div className="max-w-[calc(540/1600*100vw)]">
<div
className={
'mb-6' +
(hovered || hash.slice(1) === hashes.get(number)
? ' space-y-[220px]'
: '')
}
>
<p className="l-text text-[#52587A] font-medium">[0{number}]</p>
<p
className={
'h3 font-medium mt-12 transition-all duration-700' +
(hovered || hash.slice(1) === hashes.get(number)
? ' mt-[220px]'
: '')
}
>
{title}
</p>
</div>
<motion.p
className="font-medium h4 opacity-60"
animate={
hovered || hash.slice(1) === hashes.get(number)
? { opacity: 0.6, transition: { delay: 0.5 } }
: { opacity: 0 }
}
>
{description}
</motion.p>
</div>
<motion.div
className="flex items-start justify-end flex-1 gap-x-2"
transition={{ duration: 1 }}
animate={{
translateX:
hovered || hash.slice(1) === hashes.get(number)
? (images.length - 1) * 240 + (images.length - 2) * 8
: 0,
}}
>
{images.map((image, index) => (
<AnimatePresence key={image}>
<motion.img
key={image}
src={image}
alt=""
transition={{ duration: 0.5 }}
className="transition-[height] h-full"
animate={{
maxHeight:
(hovered || hash.slice(1) === hashes.get(number)) &&
index === 0
? 424
: 140,
}}
/>
</AnimatePresence>
))}
</motion.div>
</motion.div>
</div>
);
}
function Feature({
title,
description,
images,
number,
}: {
title: string;
description: string;
images: string[];
number: number;
}) {
const [expanded, setExpanded] = useState(false);
const [descriptionHeight, setDescriptionHeight] = useState(0);
const root = useRef<HTMLDivElement>(null);
const descriptionRef = useRef<HTMLParagraphElement>(null);
// const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => {
setDescriptionHeight(descriptionRef.current?.clientHeight ?? 0);
}, [expanded, descriptionRef]);
return (
<motion.div
id={hashes.get(number)}
ref={root}
animate={{
height: expanded
? (root.current?.clientHeight ?? 0) + descriptionHeight + 16
: 'auto',
}}
className="sm:py-4 py-2 sm:space-y-8 space-y-4 border-t border-[#3D425C] last:border-b flex flex-col justify-between relative"
onClick={() => setExpanded(prev => !prev)}
>
<div className="flex items-center justify-between max-sm:items-start gap-x-5">
<p className="font-medium h3">{title}</p>
<ClassNameWrapper
className="flex-1"
element={expanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
/>
</div>
<AnimatePresence>
{expanded && (
<motion.p
ref={descriptionRef}
className="absolute font-medium top- l-text"
transition={{ delay: 1 }}
initial={{ opacity: 0 }}
animate={
expanded
? { opacity: 0.6, transition: { delay: 0.5 } }
: { opacity: 0 }
}
>
{description}
</motion.p>
)}
</AnimatePresence>
<div className="flex mb-0 gap-x-2 max-sm:hidden">
{images.map((image, index) => (
<AnimatePresence key={image}>
<motion.div
transition={{ duration: 0.5 }}
animate={
expanded
? index === 0
? { width: '100%' }
: { width: '0%' }
: { width: '33%' }
}
className={index > 0 ? 'max-sm:hidden' : ''}
key={image}
>
<img src={image} alt={title} className="w-full" />
</motion.div>
</AnimatePresence>
))}
</div>
<img
// ref={imgRef}
src={images[0]}
className="object-cover w-full sm:hidden"
alt={title}
/>
</motion.div>
);
}