finished slider, todo: controls for showreel, fixes

This commit is contained in:
2024-08-16 14:51:22 +05:00
parent 775e01253f
commit 42fc749d02
13 changed files with 118 additions and 181 deletions
+2 -2
View File
@@ -47,7 +47,7 @@ export function Header() {
</div>
<div className="flex">
<Button
onClick={() => setModal(<ModalWithForm />)}
onClick={() => setModal(<ModalWithForm />, 'form')}
className="rounded-none btn-text font-bold max-sm:hidden px-10 outline-none"
>
Оставить заявку
@@ -77,7 +77,7 @@ export function Header() {
<Button
onClick={() => {
setMenuOpen(false);
setModal(<ModalWithForm />);
setModal(<ModalWithForm />, 'form');
}}
width="full"
className="sm:hidden font-semibold btn-text rounded-none"
+3 -3
View File
@@ -8,9 +8,9 @@ export function ModalContainer() {
return (
modal && (
<div className="fixed top-0 left-0 z-10 w-full h-full flex justify-center items-start bg-black bg-opacity-40 transition-opacity">
{/* <div onClick={e => e.stopPropagation()} className="cursor-default"> */}
{modal}
{/* </div> */}
<div onClick={e => e.stopPropagation()} className="cursor-default">
{modal}
</div>
</div>
)
);
+16 -11
View File
@@ -16,6 +16,7 @@ interface IProduct {
export function ModalWithProducts() {
const { setModal } = useModalStore();
const [show, setShow] = useState(true);
const ref = useRef<HTMLDivElement>(null);
useOnClickOutside(ref, () => setShow(false));
@@ -29,30 +30,29 @@ export function ModalWithProducts() {
}, []);
return (
<AnimatePresence onExitComplete={() => setModal(null)}>
<AnimatePresence onExitComplete={() => setModal(null, '')}>
{show && (
<motion.div
ref={ref}
key={'products'}
initial={{
opacity: 0,
// scaleY: 0,
// originY: 0,
}}
animate={{
opacity: 1,
// scaleY: 1,
// originY: 0,
}}
transition={{ duration: 0.75, type: 'spring' }}
exit={{
opacity: 0,
// scaleY: 0,
// originY: 0,
}}
className="pt-10 p-6 bg-[#14161F] bg-opacity-90 -z-20 aspect-[1600/720] top-[76px] relative space-y-6 transition-all backdrop-blur-3xl"
>
<h4 className="h4 font-medium flex justify-between items-center">
<motion.h4
initial={{ y: -76 }}
animate={{ y: 0 }}
transition={{ duration: 0.8 }}
className="h4 font-medium flex justify-between items-center"
>
GRAFF.estate
<button
onClick={() => setShow(false)}
@@ -60,8 +60,13 @@ export function ModalWithProducts() {
>
<CloseIcon />
</button>
</h4>
<div className="grid grid-col-3 grid-rows-2 mt-px ml-px">
</motion.h4>
<motion.div
initial={{ y: -76 }}
animate={{ y: 0 }}
transition={{ duration: 0.8 }}
className="grid grid-col-3 grid-rows-2 mt-px ml-px"
>
{Products.map((product, index) => (
<ProductItem
key={product.id}
@@ -69,7 +74,7 @@ export function ModalWithProducts() {
className={`col-start-${(index % 3) + 1} row-start-${index < 3 ? 1 : 2}`}
/>
))}
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
+7 -11
View File
@@ -1,32 +1,28 @@
'use client';
import { useModalStore } from '@/stores/useModalStore';
import { useEffect, useState } from 'react';
import { ChevronDownIcon } from '../icons/ChevronDownIcon';
import { ChevronUpIcon } from '../icons/ChevronUpIcon';
import { ModalWithProducts } from './ModalWithProducts';
export function ProductsList() {
const { setModal, modal } = useModalStore();
const [show, setShow] = useState(false);
useEffect(() => {
setShow(!!modal);
}, [modal]);
const { setModal, modal, name } = useModalStore();
return (
<button
onClick={() => {
setModal(modal ? null : <ModalWithProducts />);
setShow(prev => !prev);
setModal(
modal ? null : <ModalWithProducts />,
name === 'products' ? '' : 'products',
);
}}
className={
'btn-text font-medium px-8 py-6 flex gap-x-2 items-center border-r border-[#3D425C] hover:bg-[#3D425C] outline-none' +
(show ? ' relative z-[101]' : '')
(name === 'products' ? ' relative z-[101]' : '')
}
>
Продукты
{modal ? <ChevronUpIcon /> : <ChevronDownIcon />}
{name === 'products' ? <ChevronUpIcon /> : <ChevronDownIcon />}
</button>
);
}
+4 -5
View File
@@ -9,20 +9,19 @@ export function VideoModal({ link }: VideoModalProps) {
const { setModal } = useModalStore();
const handleOnCloseClick = () => {
setModal(null);
console.log('aboba');
setModal(null, '');
};
return (
<div className="w-screen h-screen absolute top-0 left-0 overflow-hidden flex justify-center items-center">
<div className="w-screen h-screen absolute z-[110] top-0 left-0 overflow-hidden flex justify-center items-center">
<div className="aspect-video w-full flex items-center justify-center">
<button
className="p-4 rounded-full border border-[#3D425C] absolute z-100 top-4 right-4 cursor-pointer"
className="p-4 rounded-full border border-[#3D425C] absolute top-4 right-4 z-[99] cursor-pointer"
onClick={handleOnCloseClick}
>
<CloseIcon />
</button>
<video src={link} className="aspect-video h-full" />
<video src={link} className="aspect-video h-full" autoPlay loop />
</div>
</div>
);
@@ -1,48 +1,17 @@
import { motion } from 'framer-motion';
import { forwardRef } from 'react';
export interface IAvailable {
title: string;
text: string;
img: string;
}
export const AvailableItem = forwardRef<HTMLDivElement, IAvailable>(
({ img, text, title }, ref) => (
<div className="pointer-events-none min-h-[35.5vw] flex flex-col justify-end">
<motion.div
ref={ref}
key={title}
initial={{
minWidth: '31.6vw',
minHeight: '17.6vw',
backgroundImage: `url(${img})`,
}}
animate={
!!ref
? { minWidth: '64.3vw', minHeight: '35.5vw' }
: {
minWidth: '31.6vw',
minHeight: '17.6vw',
}
}
transition={{ duration: 1, type: 'just', delay: 0.5 }}
exit={{
minWidth: '31.6vw',
minHeight: '17.6vw',
transition: { duration: 1, type: 'just' },
}}
className="bg-no-repeat bg-cover flex flex-col justify-end p-6"
>
{!!ref && (
<>
<h3 className="h3 font-medium mb-4">{title}</h3>
<p className="m-text max-w-[49%]">{text}</p>
</>
)}
</motion.div>
export function AvailableItem({ text, title, img }: IAvailable) {
return (
<div
className="bg-no-repeat bg-cover flex flex-col justify-end gap-y-4 p-6 container"
style={{ backgroundImage: `url(${img})` }}
>
<h3 className="h3 font-medium">{title}</h3>
<p className="m-text max-w-[49%]">{text}</p>
</div>
),
);
AvailableItem.displayName = 'AvailableItem';
);
}
@@ -20,8 +20,9 @@ export function AvailablesSlider({ availables }: { availables: IAvailable[] }) {
slides={availables}
title="Функциональные возможности"
alignItems="end"
className="relative"
slideBaseSizes={['31.6vw', '17.6vw']}
className="min-h-[44vw] justify-between"
slideSizes={['31.6vw', '17.6vw', '64.3vw', '35.5vw']}
childClassName="flex justify-stretch container h-full w-full"
controlsPosition="top"
/>
</div>
@@ -1,6 +1,4 @@
import { AnimatePresence, motion } from 'framer-motion';
import Image from 'next/image';
import { forwardRef } from 'react';
export interface IIntegration {
img: string;
@@ -9,66 +7,26 @@ export interface IIntegration {
company: string;
}
export const IntegrationItem = forwardRef<
HTMLDivElement,
IIntegration & { index: number }
>(({ img, title, year, company, index }, ref) => {
// const transitionStyles: Map<TransitionStatus, CSSProperties> = new Map([
// ['entering', { minWidth: '48vw', minHeight: '48vw' }],
// ['entered', { minWidth: '48vw', minHeight: '48vw' }],
// ['exiting', { minWidth: '31.6vw', minHeight: '31.8vw' }],
// ['exited', { minWidth: '31.6vw', minHeight: '31.8vw' }],
// ]);
export function IntegrationItem({ img, title, year, company }: IIntegration) {
return (
// <Transition
// nodeRef={ref}
// timeout={500}
// in={!!ref}
// exit={!ref}
// appear
// onEnter={() => console.log('enter')}
// onEntering={() => console.log('entering')}
// onEntered={() => console.log('entered')}
// onExit={() => console.log('exit')}
// onExiting={() => console.log('exiting')}
// onExited={() => console.log('exited')}
// >
// {state => (
<AnimatePresence onExitComplete={() => console.log('exit')}>
<motion.div
ref={ref}
key={title + (index < 1 ? '123' : index > 3 ? '456' : '')}
initial={{ minWidth: '31.6vw', minHeight: '31.8vw' }}
transition={{ duration: 0.5, type: 'just', delay: 0.5 }}
animate={!!ref && { minWidth: '48vw', minHeight: '48vw' }}
// exit={{ minWidth: '31.6vw', minHeight: '31.8vw' }}
className={'flex flex-col relative transition-all pointer-events-none'}
// style={{
// transition: 'all 0.5s ease-out',
// ...transitionStyles.get(state),
// }}
>
<>
<div className="flex-1 relative">
<Image
src={img}
alt={''}
fill
priority
sizes="auto"
className="!relative flex-1 object-cover"
sizes="100% 100%"
className="object-cover"
/>
<div className="mt-4">
<div className={`flex justify-between mb-${!ref ? 1 : 2}`}>
<h4 className="h4 font-medium">{title}</h4>
<h4 className="h4 font-medium">{year}</h4>
</div>
<p className="m-caption font-medium text-[#737AA1]">{company}</p>
</div>
<div className="mt-4 space-y-1">
<div className={'flex justify-between'}>
<h4 className="h4 font-medium">{title}</h4>
<h4 className="h4 font-medium">{year}</h4>
</div>
</motion.div>
</AnimatePresence>
// )}
// </Transition>
<p className="m-caption font-medium text-[#737AA1]">{company}</p>
</div>
</>
);
});
IntegrationItem.displayName = 'IntegrationItem';
}
@@ -13,8 +13,9 @@ export function IntegrationsSlider({
slides={integrations}
SlideElement={IntegrationItem}
title="Интеграции в офисы продаж"
className="pt-[120px] pb-14 border-b border-[#3D425C] min-h-[calc(48vw+248px)] "
slideBaseSizes={['31.6vw', '31.8vw']}
className="pt-[120px] pb-14 border-b border-[#3D425C] min-h-[calc(48vw+250px)]"
childClassName="flex flex-col"
slideSizes={['31.6vw', '31.8vw', '48vw', '48vw']}
controlsPosition="bottom"
/>
);
+5 -14
View File
@@ -1,13 +1,12 @@
'use client';
import { LoadingIcon } from '@/components/icons/LoadingIcon';
import { PlayIcon } from '@/components/icons/PlayIcon';
import { VideoModal } from '@/components/Layout/VideoModal';
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
import { useModalStore } from '@/stores/useModalStore';
export function Showreel() {
const { modal, setModal } = useModalStore();
const { setModal } = useModalStore();
return (
<div className="lg:mb-[200px]">
@@ -15,23 +14,15 @@ export function Showreel() {
<button
className="absolute z-10 mx-auto p-8 rounded-full border"
onClick={() =>
setModal(<VideoModal link={'/videos/pages/home/showreel.mp4'} />)
setModal(
<VideoModal link={'/videos/pages/home/showreel.mp4'} />,
'video modal',
)
}
>
<ClassNameWrapper className="w-10 h-10" element={<PlayIcon />} />
</button>
</div>
<div className="absolute aspect-video w-full h-full flex justify-center items-center bg-black bg-opacity-50 transition-opacity">
<div className="flex gap-4 items-center">
<span>
<ClassNameWrapper
className="animate-spin w-5 h-5"
element={<LoadingIcon />}
/>
</span>
<span>Загружаем шоурил...</span>
</div>
</div>
</div>
);
}
+1 -1
View File
@@ -32,7 +32,7 @@ export function Technology() {
презентовать объект покупателю из любой точки мира
</p>
<div className="col-start-4 col-span-6 rounded-lg [box-shadow:1.04px_1.04px_4.18px_0px_rgba(45,79,0,0.3)] relative">
<video src="/img/pages/home/technology/content.mp4" autoPlay loop />
<video src="/videos/pages/home/technology.mp4" autoPlay loop />
</div>
<div className="flex justify-between items-start col-start-4 col-span-6 mt-10">
<h4 className="h4 font-medium w-2/3">
+4 -2
View File
@@ -3,10 +3,12 @@ import { create } from 'zustand';
interface IModalState {
modal: ReactNode | null;
setModal: (modal: ReactNode) => void;
setModal: (modal: ReactNode, modalName: string) => void;
name: string;
}
export const useModalStore = create<IModalState>(set => ({
modal: null,
setModal: modal => set({ modal }),
name: '',
setModal: (modal: ReactNode, name: string) => set({ modal, name }),
}));
+46 -31
View File
@@ -1,44 +1,40 @@
'use client';
import { useWindowWidth } from '@/hooks/useWindowWidth';
import {
ForwardRefExoticComponent,
RefAttributes,
useEffect,
useReducer,
useRef,
useState,
} from 'react';
import { motion } from 'framer-motion';
import { ReactNode, useEffect, useReducer, useState } from 'react';
import { useSwipeable } from 'react-swipeable';
import { SliderControls } from './SliderControls';
export function SliderWithScaling<T>({
export function SliderWithScaling<T extends { title: string }>({
slides,
SlideElement,
className = '',
childClassName = '',
alignItems = 'start',
title,
slideBaseSizes: [minWidth, minHeight],
slideSizes: [minWidth, minHeight, minWidthScaled, minHeightScaled],
controlsPosition,
}: {
slides: T[];
SlideElement: ForwardRefExoticComponent<
T & { index: number } & RefAttributes<HTMLDivElement>
>;
SlideElement: (_: T) => ReactNode;
className?: string;
childClassName?: string;
alignItems?: 'start' | 'center' | 'end';
title: string;
slideBaseSizes: [string, string];
slideSizes: [string, string, string, string];
controlsPosition: 'top' | 'bottom';
}) {
const width = useWindowWidth();
const baseoffset = (-width / 1600) * 507 + 8;
const itemRef = useRef<HTMLDivElement>(null);
const [slide, setSlide] = useState(0);
const [sliderOffset, setSliderOffset] = useState(baseoffset);
const [currentSliding, setCurrentSliding] = useState<
'prev' | 'next' | null
>();
const [order, dispatch] = useReducer(
(state: typeof slides, action: 'prev' | 'next') => {
switch (action) {
@@ -58,17 +54,19 @@ export function SliderWithScaling<T>({
);
const nextSlide = () => {
setCurrentSliding('next');
dispatch('next');
};
const prevSlide = () => {
setCurrentSliding('prev');
dispatch('prev');
};
useEffect(() => {
setSliderOffset(baseoffset);
}, [baseoffset, order, slide]);
const prevSlide = () => {
dispatch('prev');
};
const handlers = useSwipeable({
onSwipedLeft: nextSlide,
onSwipedRight: prevSlide,
@@ -89,19 +87,36 @@ export function SliderWithScaling<T>({
style={{
transform: `translateX(${sliderOffset}px)`,
transitionDuration: `${sliderOffset !== 0 && sliderOffset !== 2 * baseoffset ? 1 : 0}s`,
// transitionDelay: `${sliderOffset !== 0 && sliderOffset !== 2 * baseoffset ? 1 : 0}s`,
}}
>
{order.map((slide, index) => {
return (
<SlideElement
ref={index === 1 ? itemRef : null}
index={index}
key={index}
{...slide}
/>
);
})}
{order.map((slide, index) => (
<motion.div
key={slide.title + (index < 1 ? '123' : index > 3 ? '456' : '')}
initial={
currentSliding === 'next' && index === 0
? { minWidth: minWidthScaled, minHeight: minHeightScaled }
: index === 3
? { minWidth, minHeight }
: {}
}
transition={{ duration: 1, type: 'just' }}
animate={
index === 1
? {
minWidth: minWidthScaled,
minHeight: minHeightScaled,
}
: {
minWidth,
minHeight,
transition: { duration: index === 3 ? 0.0001 : 1 },
}
}
className={'pointer-events-none ' + childClassName}
>
<SlideElement {...slide} />
</motion.div>
))}
</div>
</div>
</div>