Files
graff.event/src/components/Devices.tsx
T
2024-09-25 16:47:51 +05:00

178 lines
5.4 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 { devices } from '../consts/devices';
import { IDevice } from '../types/IDevice';
import { Title } from './ui/Title';
import { useEffect, useRef, useState } from 'react';
import { useHover, useOnClickOutside } from 'usehooks-ts';
import { ChevronUpIcon } from './icons/ChevronUpIcon';
import { ChevronDownIcon } from './icons/ChevronDownIcon';
export function Devices() {
return (
<div
id="devices"
className="space-y-8 scroll-m-14 lg:scroll-m-20 sm:space-y-10 lg:space-y-20 lg:pt-[180px] sm:pt-[140px] pt-20"
>
<Title>
Работаем с
<span className="text-gradient"> любыми типами оборудования</span>
<br /> и предложим лучшее мультимедийное оснащение
</Title>
<div className="max-lg:hidden">
{devices.map((device, index) => (
<DesktopDevice key={device.title} {...device} number={index + 1} />
))}
</div>
<div className="lg:hidden">
{devices.map(device => (
<Device key={device.title} {...device} />
))}
</div>
</div>
);
}
function DesktopDevice({
title,
description,
img,
number,
}: IDevice & {
number: number;
}) {
const root = useRef<HTMLDivElement>(null);
const descriptionRef = useRef<HTMLParagraphElement>(null);
const [descriptionHeight, setDescriptionHeight] = useState(0);
const hovered = useHover(root);
useEffect(() => {
setDescriptionHeight(descriptionRef.current?.clientHeight ?? 0);
}, [descriptionRef, hovered]);
return (
<motion.div
ref={root}
className="py-10 border-b border-[#3D425C] flex justify-between items-start relative [clip-path:polygon(0%_-50%,100%_-50%,100%_100%,-50%_100%)]"
style={{ maxHeight: 112 + descriptionHeight + 21 }}
animate={{
height: hovered
? (root.current?.clientHeight ?? 0) + descriptionHeight + 24
: 112,
}}
transition={{ delay: 0.3 }}
>
<div className="space-y-6">
<p className="font-medium h3">{title}</p>
<AnimatePresence>
{hovered && (
<motion.div
ref={descriptionRef}
initial={{ opacity: 0 }}
animate={{ opacity: 0.6 }}
exit={{ opacity: 0 }}
transition={{ delay: 0.3 }}
className="l-text space-y-4 max-w-[calc(600/1552*100%)] absolute"
>
{description.map(paragraph => (
<p key={paragraph}>
{paragraph}
<br />
</p>
))}
</motion.div>
)}
</AnimatePresence>
</div>
<AnimatePresence>
{hovered && (
<motion.img
src={img}
alt={title}
className="bottom-0 right-[calc(144/1552*100%)] w-[calc(560/1552*100%)] max-w-[560px] absolute"
initial={{ opacity: 0, y: 500 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 100 }}
transition={{ duration: 0.5, delay: 0.3 }}
/>
)}
</AnimatePresence>
<p className="m-text text-[#52587A] font-medium">[0{number}]</p>
</motion.div>
);
}
function Device({ title, description, img }: IDevice) {
const [expanded, setExpanded] = useState(false);
const [descriptionHeight, setDescriptionHeight] = useState(0);
const [imgHeight, setImgHeight] = useState(0);
const root = useRef<HTMLDivElement>(null);
const descriptionRef = useRef<HTMLParagraphElement>(null);
const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => {
if (!imgRef.current) return;
imgRef.current!.onload = () =>
setImgHeight(imgRef.current?.clientHeight ?? 0);
}, [imgRef, expanded]);
useEffect(() => {
setDescriptionHeight(descriptionRef.current?.clientHeight ?? 0);
}, [descriptionRef, expanded]);
useOnClickOutside(root, () => setExpanded(false), 'mouseup');
return (
<motion.div
animate={{
height: expanded
? (root.current?.clientHeight ?? 0) +
descriptionHeight +
imgHeight +
56
: 72,
}}
transition={{ duration: 0.3 }}
ref={root}
onClick={() => setExpanded(prev => !prev)}
className="py-4 space-y-6 border-t last:border-b border-[#3D425C] relative select-none"
>
<div className="flex items-center justify-between py-2">
<p className="font-medium h3">{title}</p>
{expanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
</div>
<AnimatePresence>
{expanded && (
<motion.p
ref={descriptionRef}
initial={{ opacity: 0 }}
animate={{ opacity: +expanded, transition: { delay: 0.3 } }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
className="mb-4 space-y-4 h4"
>
{description.map(paragraph => (
<p key={paragraph}>{paragraph}</p>
))}
</motion.p>
)}
</AnimatePresence>
<AnimatePresence>
{expanded && (
<motion.img
ref={imgRef}
initial={{ opacity: 0 }}
animate={{ opacity: +expanded, transition: { delay: 0.3 } }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
src={img}
alt={title}
/>
)}
</AnimatePresence>
</motion.div>
);
}