Files
graff.training/src/components/Main/Projects.tsx
T
2024-07-16 18:52:27 +05:00

163 lines
5.0 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 { useEffect, useMemo, useReducer, useState } from 'react';
import { MiniTitle } from '../../ui/MiniTitle';
import { useWindowWidth } from '../../hooks/useWindowWidth';
import { Title } from '../../ui/Title';
import { useSwipeable } from 'react-swipeable';
export function Projects() {
return (
<div
id="projects"
className="lg:py-[70px] lg:px-10 mobile:py-14 sm:px-6 mobile:px-5 overflow-hidden select-none"
>
<Title className="desktop-figma:mb-[77px] lg:mb-14 mobile:mb-6">
<span className="text-gradient">Большой опыт в работе</span> с
промышленными предприятиями и учебными заведениями
</Title>
<MiniTitle text="реализованные проекты" />
<Slider
projects={[
{
src: 'src/assets/tank.png',
tags: ['Симулятор', 'VR-приложение'],
title: 'Ремонт и обслуживание двигателей спецтехники',
},
{
src: 'src/assets/helicopter.jpg',
tags: ['Симулятор'],
title: 'Сборка-разборка вертолётного двигателя',
},
{
src: 'src/assets/train.png',
tags: ['Симулятор'],
title: 'Симулятор машиниста',
},
]}
/>
</div>
);
}
function Project({
src,
title,
tags,
}: {
src: string;
title: string;
tags: string[];
}) {
return (
<div className="bg-[#3D425C] bg-opacity-50 rounded-2xl box-border lg:min-w-[624px] sm:min-w-[520px] mobile:min-w-[328px] duration-1000 lg:translate-x-[264px] pointer-events-none">
<div
className="bg-cover bg-center bg-no-repeat h-[340px] rounded-2xl"
style={{ backgroundImage: `url(${src})` }}
/>
<div className="p-5">
<h4 className="text-[#ffffff] font-medium h4">{title}</h4>
<div className="flex gap-2 mt-4">
{tags.map(tag => (
<p
key={tag}
className="text-[#ffffff] opacity-80 font-medium rounded-3xl py-3 px-4 border border-[#798FFF] m-text"
>
{tag}
</p>
))}
</div>
</div>
</div>
);
}
function Slider({
projects,
}: {
projects: { src: string; title: string; tags: string[] }[];
}) {
const width = useWindowWidth();
const baseOffset = useMemo(
() => (width >= 1024 ? 640 : width >= 640 ? 536 : 336),
[width],
);
const [sliderOffset, setSliderOffset] = useState(-baseOffset);
const [slide, setSlide] = useState(0);
const [order, dispatch] = useReducer(
(state: typeof projects, action: string) => {
if (action === 'next') {
setSliderOffset(prev => prev + baseOffset);
return [...state.slice(1), state[2]];
}
if (action === 'prev') {
setSliderOffset(-baseOffset * 2);
return [state[state.length - 3], ...state.slice(0, -1)];
}
return state;
},
[projects[projects.length - 1], ...projects, projects[0]],
);
const handlers = useSwipeable({
onSwipedLeft: () => {
setSlide(prev => (prev === order.length - 3 ? 0 : prev + 1));
dispatch('next');
},
onSwipedRight: () => {
setSlide(prev => (prev === 0 ? order.length - 3 : prev - 1));
dispatch('prev');
},
trackMouse: true,
preventScrollOnSwipe: true,
touchEventOptions: { passive: false },
});
useEffect(() => {
setSliderOffset(-baseOffset);
}, [order, baseOffset, slide]);
return (
<div className="flex flex-col lg:mt-4 sm:mt-3 mobile:mt-2">
<div {...handlers}>
<div
className="flex gap-2 overflow-visible relative mb-[18px] -mr-10 select-none"
style={{
transition: `${sliderOffset === 0 || sliderOffset === -baseOffset * 2 ? 0 : 0.5}s`,
transform: `translateX(${sliderOffset}px)`,
}}
>
{order.map((project, index) => (
<Project key={index} {...project} />
))}
</div>
</div>
<div className="flex items-center gap-4 lg:max-desktop-figma:w-[clamp(720px,100vw-465px,1135px)] desktop-figma:w-[70.9vw] mobile:w-full self-start lg:ml-64">
<button
onClick={() => {
setSlide(prev => (prev === 0 ? order.length - 3 : prev - 1));
dispatch('prev');
}}
className="mobile:max-sm:hidden"
>
<img src="src/assets/left_slide.svg" alt="" />
</button>
<div className="h-1 bg-[#3D425C] w-full">
<div
className="bg-[#ffffff] h-1 duration-500"
style={{ width: `${((slide + 1) / 3) * 100}%` }}
/>
</div>
<button
onClick={() => {
setSlide(prev => (prev === order.length - 3 ? 0 : prev + 1));
dispatch('next');
}}
className="mobile:max-sm:hidden"
>
<img src="src/assets/right_slide.svg" alt="" />
</button>
</div>
</div>
);
}