finished slider, todo: controls for showreel, fixes
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 }),
|
||||
}));
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user