Files
graff.training/src/components/Main/Projects.tsx
T
2024-07-03 20:45:37 +05:00

173 lines
5.8 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';
export function Projects() {
return (
<div className="desktop:py-[70px] desktop:px-10 mobile:py-14 tablet:px-6 mobile:px-4 overflow-hidden select-none">
<Title className="desktop:mb-14 mobile:mb-6">
<span
className="bg-text-gradient bg-gradient-to-r from-[#798FFF] to-[#D375FF]"
style={{
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
}}
>
Большой опыт в работе
</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 desktop:min-w-[624px] tablet:min-w-[520px] mobile:min-w-[328px] duration-1000 select-none desktop:translate-x-[264px]">
<div
className="bg-cover bg-center bg-no-repeat h-[340px] rounded-2xl"
style={{ backgroundImage: `url(${src})` }}
/>
<div className="p-5">
<h1 className="text-[#ffffff] font-medium tablet-figma:text-[clamp(16px,16px+(100vw-768px)/832*2,18px)] tablet-figma:leading-[19.2px,19.2px+(100vw-768px)/832*2.4,21.6px] mobile:max-tablet-figma:text-[clamp(14px,14px+(100vw-360px)/408*2,16px)] mobile:max-tablet-figma:leading-[clamp(16.8px,16.8px+(100vw-360px)/408*2,19.2px)]">
{title}
</h1>
<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] tablet-figma:text-[clamp(12px,12px+(100vw-768px)/832*2,14px)] tablet-figma:leading-[16.8px,16.8px+(100vw-768px)/832*2.8,19.6px] mobile:max-tablet-figma:text-xs mobile:max-tablet-figma:leading-[16.8px]"
>
{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 [touchStart, setTouchStart] = useState(0);
useEffect(() => {
setSliderOffset(-baseOffset);
}, [order, baseOffset]);
return (
<div className="flex flex-col desktop:mt-4 tablet:mt-3 mobile:mt-2">
<div
className="flex gap-2 overflow-visible relative mb-[18px] -mr-10 duration-1000"
style={{
transition: `${+!(sliderOffset === 0 || sliderOffset === -baseOffset * 2)}s`,
transform: `translateX(${sliderOffset}px)`,
}}
onTouchStart={e => {
setTouchStart(e.targetTouches[0].clientX);
}}
onTouchEnd={e => {
if (e.nativeEvent.changedTouches[0].clientX - touchStart > 100) {
dispatch('prev');
setSlide(prev => (prev === 0 ? order.length - 3 : prev - 1));
return;
} else if (
e.nativeEvent.changedTouches[0].clientX - touchStart <
-100
) {
dispatch('next');
setSlide(prev => (prev === order.length - 3 ? 0 : prev + 1));
return;
}
}}
>
{order.map((project, index) => (
<Project key={index} {...project} />
))}
</div>
<div className="flex items-center gap-4 w-[1264px] self-start ml-64">
<button
onClick={() => {
dispatch('prev');
setSlide(prev => (prev === 0 ? order.length - 3 : prev - 1));
}}
className="mobile:max-tablet: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-1000"
style={{ width: `${((slide + 1) / 3) * 100}%` }}
/>
</div>
<button
onClick={() => {
dispatch('next');
setSlide(prev => (prev === order.length - 3 ? 0 : prev + 1));
}}
className="mobile:max-tablet:hidden"
>
<img src="src/assets/right_slide.svg" alt="" />
</button>
</div>
</div>
);
}