231 lines
6.7 KiB
TypeScript
231 lines
6.7 KiB
TypeScript
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>
|
||
);
|
||
}
|