From 2849c69c522dcccd4412ea902a9fd0f0fa0fa842 Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Tue, 3 Sep 2024 15:35:06 +0200 Subject: [PATCH] refactor: Move Sidepeek param logic to SidePeekProvider --- .../HotelPage/IntroSection/index.tsx | 1 + .../ContentType/HotelPage/SidePeeks.tsx | 99 ----------------- components/ContentType/HotelPage/index.tsx | 55 ++++++++- components/SidePeekProvider/index.tsx | 45 ++++++++ components/TempDesignSystem/Link/index.tsx | 2 +- .../TempDesignSystem/SidePeek/Item/index.tsx | 39 ------- .../SidePeek/Item/sidePeekItem.module.css | 27 ----- .../TempDesignSystem/SidePeek/index.tsx | 75 +++++++++---- .../SidePeek/sidePeek.module.css | 105 ++++++++++++------ .../TempDesignSystem/SidePeek/sidePeek.ts | 6 +- components/TempDesignSystem/SidePeek/types.ts | 4 +- 11 files changed, 227 insertions(+), 231 deletions(-) delete mode 100644 components/ContentType/HotelPage/SidePeeks.tsx create mode 100644 components/SidePeekProvider/index.tsx delete mode 100644 components/TempDesignSystem/SidePeek/Item/index.tsx delete mode 100644 components/TempDesignSystem/SidePeek/Item/sidePeekItem.module.css diff --git a/components/ContentType/HotelPage/IntroSection/index.tsx b/components/ContentType/HotelPage/IntroSection/index.tsx index d20486c60..cb62ca01c 100644 --- a/components/ContentType/HotelPage/IntroSection/index.tsx +++ b/components/ContentType/HotelPage/IntroSection/index.tsx @@ -73,6 +73,7 @@ export default async function IntroSection({ color="burgundy" variant="icon" href={`?s=${about[lang]}`} + scroll={false} > {intl.formatMessage({ id: "Read more about the hotel" })} diff --git a/components/ContentType/HotelPage/SidePeeks.tsx b/components/ContentType/HotelPage/SidePeeks.tsx deleted file mode 100644 index 2fcf9b46f..000000000 --- a/components/ContentType/HotelPage/SidePeeks.tsx +++ /dev/null @@ -1,99 +0,0 @@ -"use client" - -import { usePathname, useRouter, useSearchParams } from "next/navigation" -import { useEffect, useState } from "react" -import { useIntl } from "react-intl" - -import { - about, - activities, - amenities, - meetingsAndConferences, - restaurantAndBar, - wellnessAndExercise, -} from "@/constants/routes/hotelPageParams" - -import SidePeek from "@/components/TempDesignSystem/SidePeek" -import SidePeekItem from "@/components/TempDesignSystem/SidePeek/Item" -import { SidePeekContentKey } from "@/components/TempDesignSystem/SidePeek/types" -import useLang from "@/hooks/useLang" - -function SidePeekContainer() { - const router = useRouter() - const pathname = usePathname() - const searchParams = useSearchParams() - const [activeSidePeek, setActiveSidePeek] = - useState(() => { - const sidePeekParam = searchParams.get("s") as SidePeekContentKey | null - return sidePeekParam || null - }) - - const lang = useLang() - const intl = useIntl() - - useEffect(() => { - const sidePeekParam = searchParams.get("s") as SidePeekContentKey | null - if (sidePeekParam !== activeSidePeek) { - setActiveSidePeek(sidePeekParam) - } - }, [searchParams, activeSidePeek]) - - function handleClose(isOpen: boolean) { - if (!isOpen) { - setActiveSidePeek(null) - - const nextSearchParams = new URLSearchParams(searchParams.toString()) - nextSearchParams.delete("s") - - router.push(`${pathname}?${nextSearchParams}`, { scroll: false }) - } - } - - return ( - - - {/* TODO: Render amenities as per the design. */} - Read more about the amenities here - - - Some additional information about the hotel - - - {/* TODO */} - Restaurant & Bar - - - {/* TODO */} - Wellness & Exercise - - - {/* TODO */} - Activities - - - {/* TODO */} - Meetings & Conferences - - - ) -} - -export default SidePeekContainer diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index f37750db4..210bf9b7c 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -1,6 +1,12 @@ +import hotelPageParams from "@/constants/routes/hotelPageParams" import { env } from "@/env/server" import { serverClient } from "@/lib/trpc/server" +import SidePeekProvider from "@/components/SidePeekProvider" +import SidePeek from "@/components/TempDesignSystem/SidePeek" +import { getIntl } from "@/i18n" +import { getLang } from "@/i18n/serverContext" + import { MOCK_FACILITIES } from "./Facilities/mockData" import { setActivityCard } from "./Facilities/utils" import DynamicMap from "./Map/DynamicMap" @@ -12,13 +18,14 @@ import Facilities from "./Facilities" import IntroSection from "./IntroSection" import PreviewImages from "./PreviewImages" import { Rooms } from "./Rooms" -import SidePeeks from "./SidePeeks" import TabNavigation from "./TabNavigation" import styles from "./hotelPage.module.css" export default async function HotelPage() { const googleMapsApiKey = env.GOOGLE_STATIC_MAP_KEY + const intl = await getIntl() + const lang = getLang() const hotelData = await serverClient().hotel.get({ include: ["RoomCategories"], }) @@ -61,6 +68,51 @@ export default async function HotelPage() { address={hotelAddress} tripAdvisor={hotelRatings?.tripAdvisor} /> + + {/* eslint-disable import/no-named-as-default-member */} + + {/* TODO: Render amenities as per the design. */} + Read more about the amenities here + + + Some additional information about the hotel + + + {/* TODO */} + Restaurant & Bar + + + {/* TODO */} + Wellness & Exercise + + + {/* TODO */} + Activities + + + {/* TODO */} + Meetings & Conferences + + {/* eslint-enable import/no-named-as-default-member */} + @@ -80,7 +132,6 @@ export default async function HotelPage() { /> ) : null} - ) } diff --git a/components/SidePeekProvider/index.tsx b/components/SidePeekProvider/index.tsx new file mode 100644 index 000000000..dad577b55 --- /dev/null +++ b/components/SidePeekProvider/index.tsx @@ -0,0 +1,45 @@ +"use client" +import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { createContext, useEffect, useState } from "react" + +interface ISidePeekContext { + handleClose: (isOpen: boolean) => void + activeSidePeek: string | null +} + +export const SidePeekContext = createContext(null) + +function SidePeekProvider({ children }: React.PropsWithChildren) { + const router = useRouter() + const pathname = usePathname() + const searchParams = useSearchParams() + const [activeSidePeek, setActiveSidePeek] = useState(() => { + const sidePeekParam = searchParams.get("s") + return sidePeekParam || null + }) + + useEffect(() => { + const sidePeekParam = searchParams.get("s") + if (sidePeekParam !== activeSidePeek) { + setActiveSidePeek(sidePeekParam) + } + }, [searchParams, activeSidePeek]) + + function handleClose(isOpen: boolean) { + if (!isOpen) { + const nextSearchParams = new URLSearchParams(searchParams.toString()) + nextSearchParams.delete("s") + + router.push(`${pathname}?${nextSearchParams}`, { scroll: false }) + setActiveSidePeek(null) + } + } + + return ( + + {children} + + ) +} + +export default SidePeekProvider diff --git a/components/TempDesignSystem/Link/index.tsx b/components/TempDesignSystem/Link/index.tsx index 6856f87aa..23cf15e46 100644 --- a/components/TempDesignSystem/Link/index.tsx +++ b/components/TempDesignSystem/Link/index.tsx @@ -75,7 +75,7 @@ export default function Link({ trackPageViewStart() startTransition(() => { startRouterTransition() - router.push(href) + router.push(href, { scroll }) }) }} href={href} diff --git a/components/TempDesignSystem/SidePeek/Item/index.tsx b/components/TempDesignSystem/SidePeek/Item/index.tsx deleted file mode 100644 index fa897aae9..000000000 --- a/components/TempDesignSystem/SidePeek/Item/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -"use client" - -import { PropsWithChildren } from "react" - -import { CloseIcon } from "@/components/Icons" -import { SidePeekContentProps } from "@/components/TempDesignSystem/SidePeek/types" -import Title from "@/components/TempDesignSystem/Text/Title" - -import Button from "../../Button" - -import styles from "./sidePeekItem.module.css" - -function SidePeekItem({ - title, - children, - isActive = false, - onClose, -}: PropsWithChildren) { - return isActive ? ( - - ) : null -} - -export default SidePeekItem \ No newline at end of file diff --git a/components/TempDesignSystem/SidePeek/Item/sidePeekItem.module.css b/components/TempDesignSystem/SidePeek/Item/sidePeekItem.module.css deleted file mode 100644 index eb90ed60b..000000000 --- a/components/TempDesignSystem/SidePeek/Item/sidePeekItem.module.css +++ /dev/null @@ -1,27 +0,0 @@ -.sidePeekItem { - display: grid; - grid-template-rows: min-content auto; - gap: var(--Spacing-x4); - height: 100%; -} - -.content>* { - padding: var(--Spacing-x3) var(--Spacing-x2); -} - -.header { - display: flex; - justify-content: flex-end; - border-bottom: 1px solid var(--Base-Border-Subtle); - align-items: center; -} - -.header:has(> h2) { - justify-content: space-between; -} - -@media screen and (min-width: 1367px) { - .content>* { - padding: var(--Spacing-x4); - } -} \ No newline at end of file diff --git a/components/TempDesignSystem/SidePeek/index.tsx b/components/TempDesignSystem/SidePeek/index.tsx index 271ad30e5..df0b8dc44 100644 --- a/components/TempDesignSystem/SidePeek/index.tsx +++ b/components/TempDesignSystem/SidePeek/index.tsx @@ -1,15 +1,20 @@ "use client" import { useIsSSR } from "@react-aria/ssr" -import React, { Children, cloneElement } from "react" +import { useContext } from "react" import { Dialog, DialogTrigger, Modal, ModalOverlay, } from "react-aria-components" +import { useIntl } from "react-intl" -import { SidePeekContentKey } from "@/components/TempDesignSystem/SidePeek/types" +import { CloseIcon } from "@/components/Icons" +import { SidePeekContext } from "@/components/SidePeekProvider" + +import Button from "../Button" +import Title from "../Text/Title" import styles from "./sidePeek.module.css" @@ -17,33 +22,61 @@ import type { SidePeekProps } from "./sidePeek" function SidePeek({ children, + title, + contentKey, handleClose, - activeSidePeek, + isOpen, }: React.PropsWithChildren) { - const sidePeekChildren = Children.map(children, (child) => { - if (!React.isValidElement(child)) { - return child - } - return cloneElement(child as React.ReactElement, { - isActive: - (child.props.contentKey as SidePeekContentKey) === activeSidePeek, - onClose: handleClose, - }) - }) - const isSSR = useIsSSR() - return isSSR ? ( -
{children}
- ) : ( + const intl = useIntl() + const context = useContext(SidePeekContext) + function onClose() { + const closeHandler = handleClose || context?.handleClose + closeHandler && closeHandler(false) + } + + if (isSSR) { + return ( +
+

{title}

+ {children} +
+ ) + } + return ( - - {sidePeekChildren} + + + + diff --git a/components/TempDesignSystem/SidePeek/sidePeek.module.css b/components/TempDesignSystem/SidePeek/sidePeek.module.css index 0ed9304c5..2d6de4f00 100644 --- a/components/TempDesignSystem/SidePeek/sidePeek.module.css +++ b/components/TempDesignSystem/SidePeek/sidePeek.module.css @@ -1,38 +1,9 @@ -.sidePeek { - position: fixed; - top: var(--current-mobile-site-header-height); - right: auto; - bottom: 0; - width: 100%; - height: calc(100vh - var(--current-mobile-site-header-height)); - background-color: var(--Base-Background-Primary-Normal); - z-index: 100; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.85); -} - -.sidePeek[data-entering] { - animation: slide-up 300ms; -} - -.sidePeek[data-exiting] { - animation: slide-up 300ms reverse; -} - -.dialog { - height: 100%; -} - -.overlay { - position: absolute; - top: var(--current-mobile-site-header-height); - bottom: 0; - left: 0; - right: 0; - z-index: 99; +.modal { + --sidepeek-desktop-width: 600px; } @keyframes slide-in { from { - right: -600px; + right: calc(-1 * var(--sidepeek-desktop-width)); } to { @@ -46,24 +17,84 @@ } to { - top: var(--current-mobile-site-header-height); + top: 0; } } +.overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 99; +} + +.modal { + position: fixed; + top: 0; + right: auto; + bottom: 0; + width: 100%; + height: 100vh; + background-color: var(--Base-Background-Primary-Normal); + z-index: 100; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.85); +} + +.modal[data-entering] { + animation: slide-up 300ms; +} + +.modal[data-exiting] { + animation: slide-up 300ms reverse; +} + +.dialog { + height: 100%; +} + +.sidePeek { + display: grid; + grid-template-rows: min-content auto; + height: 100%; +} + +.header { + display: flex; + justify-content: flex-end; + border-bottom: 1px solid var(--Base-Border-Subtle); + align-items: center; + padding: var(--Spacing-x4); +} + +.header:has(> h2) { + justify-content: space-between; +} + +.closeButton { + padding: 0; +} + +.sidePeekContent { + padding: var(--Spacing-x4); +} @media screen and (min-width: 1367px) { - .sidePeek { + .modal { top: 0; right: 0px; - width: 600px; + width: var(--sidepeek-desktop-width); height: 100vh; } - .sidePeek[data-entering] { + + .modal[data-entering] { animation: slide-in 250ms; } - .sidePeek[data-exiting] { + .modal[data-exiting] { animation: slide-in 250ms reverse; } + .overlay { top: 0; } diff --git a/components/TempDesignSystem/SidePeek/sidePeek.ts b/components/TempDesignSystem/SidePeek/sidePeek.ts index 626fc640c..e1781f137 100644 --- a/components/TempDesignSystem/SidePeek/sidePeek.ts +++ b/components/TempDesignSystem/SidePeek/sidePeek.ts @@ -1,4 +1,6 @@ export interface SidePeekProps { - handleClose: (isOpen: boolean) => void - activeSidePeek: string | null + contentKey: string + title: string + isOpen?: boolean + handleClose?: (isOpen: boolean) => void } diff --git a/components/TempDesignSystem/SidePeek/types.ts b/components/TempDesignSystem/SidePeek/types.ts index f506fc6bf..e37554aff 100644 --- a/components/TempDesignSystem/SidePeek/types.ts +++ b/components/TempDesignSystem/SidePeek/types.ts @@ -1,5 +1,3 @@ -export type SidePeekContentKey = string - export type SidePeekProps = { activeContent: string | null onClose: (isOpen: boolean) => void @@ -7,7 +5,7 @@ export type SidePeekProps = { export type SidePeekContentProps = { title?: string - contentKey: SidePeekContentKey + contentKey: string isActive?: boolean onClose?: () => void }