diff --git a/src/components/AboutDubaiMarina.tsx b/src/components/AboutDubaiMarina.tsx index 1a0fdd4..65f2478 100644 --- a/src/components/AboutDubaiMarina.tsx +++ b/src/components/AboutDubaiMarina.tsx @@ -6,20 +6,14 @@ import { dubaiMarinaDescriptionBadges, sliderBadgesCategory, } from "../data/aboutDubaiMarina"; -import { - AnimatePresence, - motion, - useInView, - useScroll, - useTransform, -} from "motion/react"; +import { AnimatePresence, motion, useScroll, useTransform } from "motion/react"; import useWindowSize from "../hooks/useWindowSize"; import TextBox from "./ui/TextBox"; import SliderMobile from "./SliderMobile"; import clsx from "clsx"; -import Slider from "./Slider"; import PlusIcon from "./icons/map/PlusIcon"; import EqualIcon from "./icons/EqualIcon"; +import AboutSlider from "./AboutSlider/AboutSlider"; // import FullScreenButton from "./FullScreenButton"; function AboutDubaiMarina() { @@ -37,22 +31,12 @@ function AboutDubaiMarina() { }); const firstSectionOpacity = useTransform(scrollYProgress, [0, 0.2], [1, 0]); - const secondSectionY = useTransform( scrollYProgress, [0, 0.4], ["100dvh", "0dvh"] ); - const isSliderInView = useInView(sliderRef, { - once: true, - amount: 0.1, - }); - // const isMapInView = useInView(mapRef, { - // once: true, - // margin: `0px 0px ${-window.innerHeight / 2}px 0px`, - // }); - return (
+

Dubai, within reach @@ -199,19 +184,11 @@ function AboutDubaiMarina() { ref={sliderRef} className="flex flex-col gap-8 max-md:hidden" > - - - +
+ +

+

Dubai's first-ever combinable Apartments diff --git a/src/components/AboutHQ.tsx b/src/components/AboutHQ.tsx index 4a13d0f..e30b8d9 100644 --- a/src/components/AboutHQ.tsx +++ b/src/components/AboutHQ.tsx @@ -1,338 +1,318 @@ -import { useRef, useState } from "react" -import { AnimatePresence, motion, useInView, useScroll } from "motion/react" -import AboutHQScrollSlider from "./AboutHQ/AboutHQSlider" -import { marasiDriveMapCards } from "../data/aboutMarasiDrive" +import { useRef, useState } from "react"; +import { useScroll } from "motion/react"; +import AboutHQScrollSlider from "./AboutHQ/AboutHQSlider"; +import { marasiDriveMapCards } from "../data/aboutMarasiDrive"; -import clsx from "clsx" -import EqualIcon from "./icons/EqualIcon" -import PlusIcon from "./icons/map/PlusIcon" -import MarasiDriveMapMobile from "./MarasiDriveMapMobile" -import MarasiDriveMapCard from "./MarasiDriveMapCard" -import BrochureButton from "./ui/BrochureButton" -import AboutHQSliderTablet from "./AboutHQ/AboutHQSliderTablet" -import AboutHQSliderMobile from "./AboutHQ/AboutHQSliderMobile" -import { hqDescription, hqSlider } from "../data/aboutHQ" -import UnitTypesSlider from "./AboutHQ/UnitTypesSlider" -import UnitTypesSliderMobile from "./AboutHQ/UnitTypesSliderMobile" +import clsx from "clsx"; +import EqualIcon from "./icons/EqualIcon"; +import PlusIcon from "./icons/map/PlusIcon"; +import MarasiDriveMapMobile from "./MarasiDriveMapMobile"; +import MarasiDriveMapCard from "./MarasiDriveMapCard"; +import BrochureButton from "./ui/BrochureButton"; +import AboutHQSliderTablet from "./AboutHQ/AboutHQSliderTablet"; +import AboutHQSliderMobile from "./AboutHQ/AboutHQSliderMobile"; +import { hqDescription, hqSlider } from "../data/aboutHQ"; +import AboutSlider from "./AboutSlider/AboutSlider"; +import AboutSliderMobile from "./AboutSlider/AboutSliderMobile"; +import { HQTabs } from "../types/HQ"; export default function AboutHQ() { - const target = useRef(null); - const { scrollYProgress } = useScroll({ - target, - }); - const [selectedCategorySlider, setSelectedCategorySlider] = - useState("Modular Office Collection"); + const target = useRef(null); + const { scrollYProgress } = useScroll({ + target, + }); + const [selectedCategorySlider, setSelectedCategorySlider] = useState( + "Modular Office Collection" + ); - const sliderRef = useRef(null); - const isSliderInView = useInView(sliderRef, { - once: true, - amount: 0.1, - }); - - return ( + return (
+ {/* Hero */} +
+
+
+ - {/* Hero */} -
-
- -
- - -
-

- {`Rove Home HQ — +
+

+ {`Rove Home HQ — Work looks different here`} -

-
- -
- -

- {`Welcome to the office you - actually want to show up for`} -

-

- HQ by Rove was born out of a question: what if the - office could feel alive again? Now, the first - ever hospitality-branded office building in Dubai is here to - answer it. Starting in Marasi Bay -

-
- - -

- -
-
-

- {`Welcome to the office you - actually want to show up for`} -

-

- HQ by Rove was born out of a question: what if the - office could feel alive again? Now, the first - ever hospitality-branded office building in Dubai is here to - answer it. Starting in Marasi Bay -

-
-
-
- - {/* Slider */} -
-
-
-
-

- More than an office,
a lifestyle. -

-

- {`Living rooms became boardrooms, kitchens became creative hubs. - But as the world returned, the office didn’t keep up. HQ by Rove is the - answer - an office with a living touch.`} -

-
- - - -
-
-
- - {/* Features */} -
-

- Dubai, within reach

- -
- {hqDescription.map((descriptionItem) => ( -
- setSelectedCategorySlider(descriptionItem.title ) - } - > -
-
- {descriptionItem.title} -
-

- {descriptionItem.description} -

-
- ))} -
- -
- - - -
-
-
+
- {/* Apartments */} -
-

- {`Modular Office Collection:`} -

- Combinable units -

-

-

- {`Enjoy the option to combine 2 offices and create a - larger space and configuration.`} -

-
-
-

- Studio -

-
-
- - - -
-
-

- Studio -

-
-
- - - -
-
-

- Deluxe -

-
-
-
- or -
-
-
-

- Studio -

-
-
- - - -
-
-

- Deluxe -

-
-
- - - -
-
-

- Executive -

-
-
-
- - {/* Map */} -
-
-

- Seamlessly connected -

-

- {`From the Dubai Canal at your doorstep to Sheikh Zayed Road in minutes, with - Downtown, DIFC and d3 all close by, you’re never far from the city’s pulse.`} +

+ +

+ {`Welcome to the office you + actually want to show up for`} +

+

+ HQ by Rove was born out of a question: what if the office could + feel alive again? Now, the first ever hospitality-branded office + building in Dubai is here to answer it. Starting in Marasi Bay

-
- {marasiDriveMapCards.map((card) => ( - - ))} -
- +
+ +
+
+

+ {`Welcome to the office you + actually want to show up for`} +

+

+ HQ by Rove was born out of a question: what if the office could + feel alive again? Now, the first ever hospitality-branded office + building in Dubai is here to answer it. Starting in Marasi Bay +

+
+
+
+ + {/* Slider */} +
+
+
+
+

+ More than an office,{" "} +
a lifestyle. +

+

+ {`Living rooms became boardrooms, kitchens became creative hubs. + But as the world returned, the office didn’t keep up. HQ by Rove is the + answer - an office with a living touch.`} +

+
+ + + +
+
+
+ + {/* Features */} +
+

+ Dubai, within reach +

+ + + +
+ {hqDescription.map((descriptionItem) => ( +
setSelectedCategorySlider(descriptionItem.title)} + > +
+
+ {descriptionItem.title} +
+

+ {descriptionItem.description} +

+
+ ))} +
+
+ +
+
+ + {/* Apartments */} +
+

+ {`Modular Office Collection:`} +

Combinable units

+

+

+ {`Enjoy the option to combine 2 offices and create a + larger space and configuration.`} +

+
+
+

+ Studio +

+
+
+ + + +
+
+

+ Studio +

+
+
+ + + +
+
+

+ Deluxe +

+
+
+
or
+
+
+

+ Studio +

+
+
+ + + +
+
+

+ Deluxe +

+
+
+ + + +
+
+

+ Executive +

+
+
+
+ + {/* Map */} +
+
+

Seamlessly connected

+

+ {`From the Dubai Canal at your doorstep to Sheikh Zayed Road in minutes, with + Downtown, DIFC and d3 all close by, you’re never far from the city’s pulse.`} +

+
+
+ {marasiDriveMapCards.map((card) => ( + + ))} +
+ +
+
+ +
+ + {/* Brochures */} +
+
+ +
+
+
+

+ {`Work looks different here`} +

+

+ {`HQ by Rove sets its own standard. A place designed with people at + its heart, where hospitality meets productivity, and work finds its flow.`} +

+
+
+
+ Download our brochures +
+
+ +
- -
- - {/* Brochures */} -
-
- -
-
-
-

- {`Work looks different here`} -

-

- {`HQ by Rove sets its own standard. A place designed with people at - its heart, where hospitality meets productivity, and work finds its flow.`} -

-
-
-
- Download our brochures -
-
- - -
-
-
-
+
+

- ) -} \ No newline at end of file + ); +} diff --git a/src/components/AboutHQ/UnitTypesSlider.tsx b/src/components/AboutHQ/UnitTypesSlider.tsx deleted file mode 100644 index 2bb7088..0000000 --- a/src/components/AboutHQ/UnitTypesSlider.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { useEffect, useState } from "react"; -import ArrowLeftIcon from "../icons/ArrowLeftIcon"; -import ArrowRightIcon from "../icons/ArrowRightIcon"; -import { hqSlider } from "../../data/aboutHQ"; -import { AnimatePresence, motion } from "motion/react"; -import { useSwipeable } from "react-swipeable"; -import Button from "../ui/Button"; - -function UnitTypesSlider({ - categoryName, -}: { - categoryName: "Modular Office Collection" | "A La Carte" | "Loft Office Collection"; -}) { - const [currentSlide, setCurrentSlide] = useState(0); - const [direction, setDirection] = useState(-1); - - const handleNextSlide = () => { - if (currentSlide < hqSlider[categoryName].length - 1) { - setDirection(1); - setCurrentSlide(currentSlide + 1); - } - }; - - const handlePreviousSlide = () => { - if (currentSlide > 0) { - setDirection(-1); - setCurrentSlide(currentSlide - 1); - } - }; - - const handlers = useSwipeable({ - onSwipedLeft: handleNextSlide, - onSwipedRight: handlePreviousSlide, - preventScrollOnSwipe: true, - touchEventOptions: { - passive: false, - }, - trackMouse: true, - }); - - useEffect(() => { - setCurrentSlide(0); - }, [categoryName]); - - useEffect(() => { - if(currentSlide === 0) { - setDirection(1); - } - else if(currentSlide === hqSlider[categoryName].length - 1) { - setDirection(-1); - } - }, [currentSlide]); - - return ( -
- - 0 ? "100%" : "-100%" }} - animate={{ x: 0, transition: { duration: 0.5, ease: "easeInOut" } }} - exit={{ - x: direction < 0 ? "100%" : "-100%", - transition: { duration: 0.8, ease: "easeInOut" }, - }} - /> - -
-
- - - {hqSlider[categoryName][currentSlide].title} - - - - - {hqSlider[categoryName][currentSlide].description} - - -
-
- -
- {currentSlide + 1}/{hqSlider[categoryName].length} -
- -
-
-
- ); -} - -export default UnitTypesSlider; \ No newline at end of file diff --git a/src/components/AboutHQ/UnitTypesSliderMobile.tsx b/src/components/AboutHQ/UnitTypesSliderMobile.tsx deleted file mode 100644 index f7498e7..0000000 --- a/src/components/AboutHQ/UnitTypesSliderMobile.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useScroll, useTransform, motion } from 'motion/react'; -import { useRef } from 'react' -import UnitTypesSlider from './UnitTypesSlider'; - -export default function UnitTypesSliderMobile() { - const sliderMobileRef = useRef(null); - const { scrollYProgress } = useScroll({ - target: sliderMobileRef, - offset: ["start start", "end end"], - }); - - const opacityFirstSlide = useTransform( - scrollYProgress, - [0, 1 / 3], - [1, 0] - ); - const opacitySecondSlide = useTransform( - scrollYProgress, - [ 2 / 3, 1], - [ 1, 0] - ); - const opacityThirdSlide = useTransform( - scrollYProgress, - [ 2 / 3, 1], - [ 1, 1] - ) - - return ( -
- -
-
- Modular Office Collection -
-

- Spaces that easily expand and adapt -

- -
- -
-
- A La Carte -
-

- Bespoke offices designed around you -

- -
- -
-
- Loft Office Collection -
-

- Double-height ceilings and the warm atmosphere of home -

- -
- {/*
*/} -
- ); - } diff --git a/src/components/AboutSlider/AboutSlider.tsx b/src/components/AboutSlider/AboutSlider.tsx new file mode 100644 index 0000000..9ba586d --- /dev/null +++ b/src/components/AboutSlider/AboutSlider.tsx @@ -0,0 +1,167 @@ +import { AboutSliderProps, ControlsProps } from "../../types/AboutSlider"; +import { ReactNode, useEffect, useRef, useState } from "react"; +import { AnimatePresence, motion } from "motion/react"; +import ArrowRightIcon from "../icons/ArrowRightIcon"; +import ArrowLeftIcon from "../icons/ArrowLeftIcon"; +import { useSwipeable } from "react-swipeable"; +import clsx from "clsx"; + +export default function AboutSlider({ items, className }: AboutSliderProps) { + const totalSlides = items.length || 0; + const containerRef = useRef(null); + const [currentSlide, setCurrentSlide] = useState(0); + const [containerWidth, setContainerWidth] = useState(0); + + function handleNext() { + if (currentSlide === totalSlides - 1) return; + setCurrentSlide((currentSlide + 1) % totalSlides); + scroll(1); + } + + function handlePrevious() { + if (currentSlide === 0) return; + setCurrentSlide((currentSlide - 1 + totalSlides) % totalSlides); + scroll(-1); + } + + function scroll(direction: 1 | -1) { + if (!containerRef.current) return; + containerRef.current.scrollLeft += direction * containerWidth; + } + + const swipeHandlers = useSwipeable({ + onSwipedLeft: handleNext, + onSwipedRight: handlePrevious, + preventScrollOnSwipe: true, + touchEventOptions: { + passive: false, + }, + trackMouse: true, + }); + + function refCallback(el: HTMLDivElement | null) { + if (!el) return; + containerRef.current = el; + setContainerWidth(el.clientWidth || 0); + + // Передаем элемент в ref от useSwipeable + if (swipeHandlers.ref && typeof swipeHandlers.ref === "function") { + swipeHandlers.ref(el); + } + } + + useEffect(() => { + setCurrentSlide(0); + if (!containerRef.current) return; + containerRef.current.scrollTo({ left: 0, behavior: "instant" }); + }, [items]); + + return ( + + + +

+ {items[currentSlide] && items[currentSlide].title} +

+

+ {items[currentSlide] && items[currentSlide].description} +

+
+ +
+ {items.map((item, index) => ( +
+ {item.title} +
+ ))} +
+ +
+ +
+ + {/* Затенение */} +
+ + + ); +} + +function Controls({ + handleNext, + handlePrevious, + currentSlide, + totalSlides, +}: ControlsProps) { + function ControlButton({ + children, + onClick, + disabled, + }: { + children: ReactNode; + onClick: () => void; + disabled: boolean; + }) { + return ( + + ); + } + + return ( +
+ + + + + + {currentSlide + 1} / {totalSlides} + + + + + +
+ ); +} diff --git a/src/components/AboutSlider/AboutSliderMobile.tsx b/src/components/AboutSlider/AboutSliderMobile.tsx new file mode 100644 index 0000000..ce4f952 --- /dev/null +++ b/src/components/AboutSlider/AboutSliderMobile.tsx @@ -0,0 +1,58 @@ +import { motion } from "motion/react"; +import { useRef } from "react"; +import { hqSlider } from "../../data/aboutHQ"; +import AboutSlider from "./AboutSlider"; +import { AboutSliderMobileProps } from "../../types/AboutSlider"; +import { dubaiMarinaSlider } from "../../data/aboutDubaiMarina"; + +export default function AboutSliderMobile({ + description, +}: AboutSliderMobileProps) { + const sliderMobileRef = useRef(null); + // const { scrollYProgress } = useScroll({ + // target: sliderMobileRef, + // offset: ["start start", "end end"], + // }); + const data = + typeof description === typeof hqSlider ? hqSlider : dubaiMarinaSlider; + + // const slidesCount = description.length; + + // const transforms = Array.from({ length: slidesCount }, (_, index) => + // useTransform( + // scrollYProgress, + // [index / slidesCount, (index + 1) / slidesCount], + // [1, 0] + // ) + // ); + + return ( +
+ {description.map((item, index) => ( + +
+
+ {item.title} +
+

+ {item.description} +

+ +
+ +
+
+ ))} +
+ ); +} diff --git a/src/components/Slider.tsx b/src/components/Slider.tsx deleted file mode 100644 index afcb45b..0000000 --- a/src/components/Slider.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { useEffect, useState } from "react"; -import ArrowLeftIcon from "./icons/ArrowLeftIcon"; -import ArrowRightIcon from "./icons/ArrowRightIcon"; -import { dubaiMarinaSlider } from "../data/aboutDubaiMarina"; -import { AnimatePresence, motion } from "motion/react"; -import { useSwipeable } from "react-swipeable"; -import Button from "./ui/Button"; - -function Slider({ - categoryName, -}: { - categoryName: "Wellness" | "Fitness" | "Community" | "Convenience"; -}) { - const [currentSlide, setCurrentSlide] = useState(0); - const [direction, setDirection] = useState(-1); - - const handleNextSlide = () => { - if (currentSlide < dubaiMarinaSlider[categoryName].length - 1) { - setDirection(1); - setCurrentSlide(currentSlide + 1); - } - }; - - const handlePreviousSlide = () => { - if (currentSlide > 0) { - setDirection(-1); - setCurrentSlide(currentSlide - 1); - } - }; - - const handlers = useSwipeable({ - onSwipedLeft: handleNextSlide, - onSwipedRight: handlePreviousSlide, - preventScrollOnSwipe: true, - touchEventOptions: { - passive: false, - }, - trackMouse: true, - }); - - useEffect(() => { - setCurrentSlide(0); - }, [categoryName]); - - return ( -
- - 0 ? "100%" : "-100%" }} - animate={{ x: 0, transition: { duration: 0.5, ease: "easeInOut" } }} - exit={{ - x: direction > 0 ? "100%" : "-100%", - transition: { duration: 0.8, ease: "easeInOut" }, - }} - /> - -
-
- - - {dubaiMarinaSlider[categoryName][currentSlide].title} - - - - - {dubaiMarinaSlider[categoryName][currentSlide].description} - - -
-
- -
- {currentSlide + 1}/{dubaiMarinaSlider[categoryName].length} -
- -
-
-
- ); -} - -export default Slider; diff --git a/src/components/SliderMobile.tsx b/src/components/SliderMobile.tsx index 0803fe8..8269cb4 100644 --- a/src/components/SliderMobile.tsx +++ b/src/components/SliderMobile.tsx @@ -1,6 +1,8 @@ import { motion, useScroll, useTransform } from "framer-motion"; -import Slider from "./Slider"; import { useRef } from "react"; +import AboutSlider from "./AboutSlider/AboutSlider"; +import { dubaiMarinaSlider } from "../data/aboutDubaiMarina"; + function SliderMobile() { const sliderMobileRef = useRef(null); const { scrollYProgress } = useScroll({ @@ -45,7 +47,9 @@ function SliderMobile() {

Unlock your inner zen in our wellness playground

- +
+ +
Cancel all your membership. Your new home has it all

- +
+ +
Connect. Engage. Thrive.

- +
+ +
Your smart living hub

- + +
+ +
diff --git a/src/data/aboutDubaiMarina.ts b/src/data/aboutDubaiMarina.ts index dc87620..c25ed5f 100644 --- a/src/data/aboutDubaiMarina.ts +++ b/src/data/aboutDubaiMarina.ts @@ -1,3 +1,5 @@ +import { DMDescription, DMSlider, DMBadges } from "../types/DubaiMarina"; + export const dubaiMarinaDescriptionBadges = [ "Fullyfurnished apartments", "Vibrant art installations", @@ -27,7 +29,7 @@ export const dubaiMarinaFeatures = [ }, ] as const; -export const dubaiMarinaDescription = [ +export const dubaiMarinaDescription: DMDescription = [ { title: "Wellness", description: "Unlock your inner zen in our wellness playground", @@ -44,9 +46,9 @@ export const dubaiMarinaDescription = [ title: "Convenience", description: "Your smart living hub", }, -] as const; +]; -export const dubaiMarinaSlider = { +export const dubaiMarinaSlider: DMSlider = { Wellness: [ { title: "Indoor Infinity Pool", @@ -102,9 +104,9 @@ export const dubaiMarinaSlider = { image: "/images/about-complex/dubai-marina/design.jpg", }, ], -} as const; +}; -export const sliderBadgesCategory = { +export const sliderBadgesCategory: DMBadges = { Wellness: [ "Vitality Pool", "Reflexology Pool", diff --git a/src/data/aboutHQ.ts b/src/data/aboutHQ.ts index 056bb2a..27f0966 100644 --- a/src/data/aboutHQ.ts +++ b/src/data/aboutHQ.ts @@ -1,3 +1,5 @@ +import { HQDescription, HQSlider } from "../types/HQ"; + export const hqFeatures = [ { name: "Reception Lounge", @@ -33,7 +35,7 @@ export const hqFeatures = [ }, ] as const; -export const hqDescription = [ +export const hqDescription: HQDescription = [ { title: "Modular Office Collection", description: "Spaces that easily expand and adapt", @@ -46,9 +48,9 @@ export const hqDescription = [ title: "Loft Office Collection", description: "Double-height ceilings and the warm atmosphere of home", }, -] as const; +]; -export const hqSlider = { +export const hqSlider: HQSlider = { "Modular Office Collection": [ { title: "Studio", diff --git a/src/types/AboutSlider.ts b/src/types/AboutSlider.ts new file mode 100644 index 0000000..acfb378 --- /dev/null +++ b/src/types/AboutSlider.ts @@ -0,0 +1,26 @@ +import { DMDescription } from "./DubaiMarina"; +import { HQDescription } from "./HQ"; + +export type AboutSliderItem = { + title: string; + description: string; + image: string; +}; + +export interface ControlsProps { + handleNext: () => void; + handlePrevious: () => void; + currentSlide: number; + totalSlides: number; +} + +export interface AboutSliderProps { + items: AboutSliderItem[]; + className?: string; +} + +export interface AboutSliderMobileProps { + description: DMDescription | HQDescription; +} + + \ No newline at end of file diff --git a/src/types/DubaiMarina.ts b/src/types/DubaiMarina.ts new file mode 100644 index 0000000..b87fc82 --- /dev/null +++ b/src/types/DubaiMarina.ts @@ -0,0 +1,10 @@ +import { AboutSliderItem } from "./AboutSlider"; + +export type DMTabs = "Wellness" | "Fitness" | "Community" | "Convenience" +export type DMDescription = { + title: DMTabs, + description: string +}[] + +export type DMSlider = Record +export type DMBadges = Record \ No newline at end of file diff --git a/src/types/HQ.ts b/src/types/HQ.ts new file mode 100644 index 0000000..5336059 --- /dev/null +++ b/src/types/HQ.ts @@ -0,0 +1,9 @@ +import { AboutSliderItem } from "./AboutSlider"; + +export type HQTabs = "Modular Office Collection" | "A La Carte" | "Loft Office Collection" +export type HQDescription = { + title: HQTabs, + description: string +}[] + +export type HQSlider = Record \ No newline at end of file