refactor: update SidePeek composition
This commit is contained in:
@@ -2,8 +2,8 @@ import { about, amenities } from "@/constants/routes/hotelPageParams"
|
|||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
import SidePeekContainer from "@/components/TempDesignSystem/SidePeek/Container"
|
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||||
import SidePeekContent from "@/components/TempDesignSystem/SidePeek/Content"
|
import SidePeekItem from "@/components/TempDesignSystem/SidePeek/Item"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import AmenitiesList from "./AmenitiesList"
|
import AmenitiesList from "./AmenitiesList"
|
||||||
@@ -20,10 +20,10 @@ export default async function HotelPage() {
|
|||||||
if (!hotelPageIdentifierData) {
|
if (!hotelPageIdentifierData) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
const lang = getLang()
|
||||||
const { attributes, roomCategories } = await serverClient().hotel.getHotel({
|
const { attributes, roomCategories } = await serverClient().hotel.getHotel({
|
||||||
hotelId: hotelPageIdentifierData.hotel_page_id,
|
hotelId: hotelPageIdentifierData.hotel_page_id,
|
||||||
language: getLang(),
|
language: lang,
|
||||||
include: ["RoomCategories"],
|
include: ["RoomCategories"],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -35,29 +35,27 @@ export default async function HotelPage() {
|
|||||||
<main className={styles.mainSection}>
|
<main className={styles.mainSection}>
|
||||||
<div className={styles.introContainer}>
|
<div className={styles.introContainer}>
|
||||||
<IntroSection
|
<IntroSection
|
||||||
// TODO: remove prop drilling once getLang() is merged
|
|
||||||
lang={lang}
|
|
||||||
hotelName={attributes.name}
|
hotelName={attributes.name}
|
||||||
hotelDescription={attributes.hotelContent.texts.descriptions.short}
|
hotelDescription={attributes.hotelContent.texts.descriptions.short}
|
||||||
location={attributes.location}
|
location={attributes.location}
|
||||||
address={attributes.address}
|
address={attributes.address}
|
||||||
tripAdvisor={attributes.ratings.tripAdvisor}
|
tripAdvisor={attributes.ratings.tripAdvisor}
|
||||||
/>
|
/>
|
||||||
<SidePeekContainer>
|
<SidePeek>
|
||||||
<SidePeekContent
|
<SidePeekItem
|
||||||
contentKey={"amenities"}
|
contentKey={amenities[lang]}
|
||||||
title={formatMessage({ id: "Amenities" })}
|
title={formatMessage({ id: "Amenities" })}
|
||||||
>
|
>
|
||||||
{/* TODO: Render amenities as per the design. */}
|
{/* TODO: Render amenities as per the design. */}
|
||||||
Read more about the amenities here
|
Read more about the amenities here
|
||||||
</SidePeekContent>
|
</SidePeekItem>
|
||||||
<SidePeekContent
|
<SidePeekItem
|
||||||
contentKey={"about"}
|
contentKey={about[lang]}
|
||||||
title={formatMessage({ id: "About the hotel" })}
|
title={formatMessage({ id: "About the hotel" })}
|
||||||
>
|
>
|
||||||
Some additional information about the hotel
|
Some additional information about the hotel
|
||||||
</SidePeekContent>
|
</SidePeekItem>
|
||||||
</SidePeekContainer>
|
</SidePeek>
|
||||||
<AmenitiesList
|
<AmenitiesList
|
||||||
detailedFacilities={attributes.detailedFacilities}
|
detailedFacilities={attributes.detailedFacilities}
|
||||||
lang={lang}
|
lang={lang}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Body from "@/components/TempDesignSystem/Text/Body"
|
|||||||
import Preamble from "@/components/TempDesignSystem/Text/Preamble"
|
import Preamble from "@/components/TempDesignSystem/Text/Preamble"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
import { IntroSectionProps } from "./types"
|
import { IntroSectionProps } from "./types"
|
||||||
|
|
||||||
import styles from "./introSection.module.css"
|
import styles from "./introSection.module.css"
|
||||||
@@ -20,8 +20,6 @@ export default async function IntroSection({
|
|||||||
location,
|
location,
|
||||||
address,
|
address,
|
||||||
tripAdvisor,
|
tripAdvisor,
|
||||||
// TODO: remove prop drilling once getLang() is merged
|
|
||||||
lang,
|
|
||||||
}: IntroSectionProps) {
|
}: IntroSectionProps) {
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
const { formatMessage } = intl
|
const { formatMessage } = intl
|
||||||
@@ -31,6 +29,7 @@ export default async function IntroSection({
|
|||||||
{ id: "Distance to city centre" },
|
{ id: "Distance to city centre" },
|
||||||
{ number: distanceToCentre }
|
{ number: distanceToCentre }
|
||||||
)
|
)
|
||||||
|
const lang = getLang()
|
||||||
const formattedLocationText = `${streetAddress}, ${city} (${formattedDistanceText})`
|
const formattedLocationText = `${streetAddress}, ${city} (${formattedDistanceText})`
|
||||||
const formattedTripAdvisorText = formatMessage(
|
const formattedTripAdvisorText = formatMessage(
|
||||||
{ id: "Tripadvisor reviews" },
|
{ id: "Tripadvisor reviews" },
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Lang } from "@/constants/languages"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HotelAddress,
|
HotelAddress,
|
||||||
HotelData,
|
HotelData,
|
||||||
@@ -13,6 +11,4 @@ export type IntroSectionProps = {
|
|||||||
location: HotelLocation
|
location: HotelLocation
|
||||||
address: HotelAddress
|
address: HotelAddress
|
||||||
tripAdvisor: HotelTripAdvisor
|
tripAdvisor: HotelTripAdvisor
|
||||||
// TODO: remove prop drilling once getLang() is merged
|
|
||||||
lang: Lang
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
|
||||||
import React, { Children, cloneElement, useEffect, useState } from "react"
|
|
||||||
|
|
||||||
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
|
||||||
import { SidePeekContentKey } from "@/components/TempDesignSystem/SidePeek/types"
|
|
||||||
|
|
||||||
export default function SidePeekContainer({
|
|
||||||
children,
|
|
||||||
}: React.PropsWithChildren) {
|
|
||||||
const router = useRouter()
|
|
||||||
const pathname = usePathname()
|
|
||||||
const searchParams = useSearchParams()
|
|
||||||
const [activeSidePeek, setActiveSidePeek] =
|
|
||||||
useState<SidePeekContentKey | null>(() => {
|
|
||||||
const sidePeekParam = searchParams.get(
|
|
||||||
"open"
|
|
||||||
) as SidePeekContentKey | null
|
|
||||||
return sidePeekParam || null
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const sidePeekParam = searchParams.get("open") 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("open")
|
|
||||||
|
|
||||||
router.push(`${pathname}?${nextSearchParams}`, { scroll: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const childrenWithIsActive = Children.map(children, (child) => {
|
|
||||||
if (!React.isValidElement(child)) {
|
|
||||||
return child
|
|
||||||
}
|
|
||||||
return cloneElement(child as React.ReactElement, {
|
|
||||||
isActive:
|
|
||||||
(child.props.contentKey as SidePeekContentKey) === activeSidePeek,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidePeek activeContent={activeSidePeek} onClose={handleClose}>
|
|
||||||
{childrenWithIsActive}
|
|
||||||
</SidePeek>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Children, PropsWithChildren } from "react"
|
import { PropsWithChildren } from "react"
|
||||||
|
|
||||||
import { CloseIcon } from "@/components/Icons"
|
import { CloseIcon } from "@/components/Icons"
|
||||||
import { SidePeekContentProps } from "@/components/TempDesignSystem/SidePeek/types"
|
import { SidePeekContentProps } from "@/components/TempDesignSystem/SidePeek/types"
|
||||||
@@ -8,16 +8,16 @@ import Title from "@/components/TempDesignSystem/Text/Title"
|
|||||||
|
|
||||||
import Button from "../../Button"
|
import Button from "../../Button"
|
||||||
|
|
||||||
import styles from "./content.module.css"
|
import styles from "./sidePeekItem.module.css"
|
||||||
|
|
||||||
export default function Content({
|
function SidePeekItem({
|
||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
isActive = false,
|
isActive = false,
|
||||||
onClose,
|
onClose,
|
||||||
}: PropsWithChildren<SidePeekContentProps>) {
|
}: PropsWithChildren<SidePeekContentProps>) {
|
||||||
return isActive ? (
|
return isActive ? (
|
||||||
<aside className={styles.content}>
|
<aside className={styles.sidePeekItem}>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<Title color="burgundy" textTransform="uppercase" level="h2" as="h3">
|
<Title color="burgundy" textTransform="uppercase" level="h2" as="h3">
|
||||||
{title}
|
{title}
|
||||||
@@ -35,3 +35,5 @@ export default function Content({
|
|||||||
</aside>
|
</aside>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default SidePeekItem
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
.content {
|
.sidePeekItem {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: min-content auto;
|
grid-template-rows: min-content auto;
|
||||||
gap: var(--Spacing-x4);
|
gap: var(--Spacing-x4);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content > * {
|
.content>* {
|
||||||
padding: var(--Spacing-x3) var(--Spacing-x2);
|
padding: var(--Spacing-x3) var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media and screen (min-width: 1367px) {
|
@media screen and (min-width: 1367px) {
|
||||||
.content > * {
|
.content>* {
|
||||||
padding: var(--Spacing-x4);
|
padding: var(--Spacing-x4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useIsSSR } from "@react-aria/ssr"
|
import { useIsSSR } from "@react-aria/ssr"
|
||||||
import { Children, cloneElement, PropsWithChildren } from "react"
|
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||||
|
import React, { Children, cloneElement, useEffect, useState } from "react"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
@@ -9,15 +10,54 @@ import {
|
|||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
} from "react-aria-components"
|
} from "react-aria-components"
|
||||||
|
|
||||||
import { SidePeekProps } from "./types"
|
import { SidePeekContentKey } from "@/components/TempDesignSystem/SidePeek/types"
|
||||||
|
|
||||||
import styles from "./sidePeek.module.css"
|
import styles from "./sidePeek.module.css"
|
||||||
|
|
||||||
export default function SidePeek({
|
function SidePeek({ children }: React.PropsWithChildren) {
|
||||||
children,
|
// TODO: to make this more reusable,
|
||||||
onClose,
|
// should the param logic be moved up/out so this component can be
|
||||||
activeContent,
|
// controlled by e.g. URL segments
|
||||||
}: PropsWithChildren<SidePeekProps>) {
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
const [activeSidePeek, setActiveSidePeek] =
|
||||||
|
useState<SidePeekContentKey | null>(() => {
|
||||||
|
const sidePeekParam = searchParams.get(
|
||||||
|
"open"
|
||||||
|
) as SidePeekContentKey | null
|
||||||
|
return sidePeekParam || null
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const sidePeekParam = searchParams.get("open") 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("open")
|
||||||
|
|
||||||
|
router.push(`${pathname}?${nextSearchParams}`, { scroll: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
const isSSR = useIsSSR()
|
||||||
return isSSR ? (
|
return isSSR ? (
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
@@ -25,24 +65,16 @@ export default function SidePeek({
|
|||||||
<DialogTrigger>
|
<DialogTrigger>
|
||||||
<ModalOverlay
|
<ModalOverlay
|
||||||
className={styles.overlay}
|
className={styles.overlay}
|
||||||
isOpen={!!activeContent}
|
isOpen={!!activeSidePeek}
|
||||||
onOpenChange={onClose}
|
onOpenChange={handleClose}
|
||||||
isDismissable
|
isDismissable
|
||||||
>
|
>
|
||||||
<Modal className={styles.sidePeek}>
|
<Modal className={styles.sidePeek}>
|
||||||
<Dialog className={styles.dialog}>
|
<Dialog className={styles.dialog}>{sidePeekChildren}</Dialog>
|
||||||
{({ close }) => (
|
|
||||||
<>
|
|
||||||
{Children.map(children, (child) => {
|
|
||||||
return cloneElement(child as React.ReactElement, {
|
|
||||||
onClose: close,
|
|
||||||
})
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Dialog>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</ModalOverlay>
|
</ModalOverlay>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default SidePeek
|
||||||
Reference in New Issue
Block a user