163 lines
5.0 KiB
TypeScript
163 lines
5.0 KiB
TypeScript
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>
|
||
);
|
||
}
|