feat(SW-1957): Mapview on hotel pages is now activated by search params
Approved-by: Matilda Landström
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
"use client"
|
||||
import { APIProvider } from "@vis.gl/react-google-maps"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { Dialog, Modal } from "react-aria-components"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import useHotelPageStore from "@/stores/hotel-page"
|
||||
|
||||
import CloseLargeIcon from "@/components/Icons/CloseLarge"
|
||||
import InteractiveMap from "@/components/Maps/InteractiveMap"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||
import { debounce } from "@/utils/debounce"
|
||||
|
||||
import Sidebar from "./Sidebar"
|
||||
@@ -26,18 +24,17 @@ export default function DynamicMap({
|
||||
mapId,
|
||||
}: DynamicMapProps) {
|
||||
const intl = useIntl()
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const isMapView = useMemo(
|
||||
() => searchParams.get("view") === "map",
|
||||
[searchParams]
|
||||
)
|
||||
const rootDiv = useRef<HTMLDivElement | null>(null)
|
||||
const [mapHeight, setMapHeight] = useState("0px")
|
||||
const { isDynamicMapOpen, closeDynamicMap } = useHotelPageStore()
|
||||
const [scrollHeightWhenOpened, setScrollHeightWhenOpened] = useState(0)
|
||||
const [activePoi, setActivePoi] = useState<string | null>(null)
|
||||
|
||||
useHandleKeyUp((event: KeyboardEvent) => {
|
||||
if (event.key === "Escape" && isDynamicMapOpen) {
|
||||
closeDynamicMap()
|
||||
}
|
||||
})
|
||||
|
||||
// Calculate the height of the map based on the viewport height from the start-point (below the header and booking widget)
|
||||
const handleMapHeight = useCallback(() => {
|
||||
const topPosition = rootDiv.current?.getBoundingClientRect().top ?? 0
|
||||
@@ -45,6 +42,12 @@ export default function DynamicMap({
|
||||
setMapHeight(`calc(100dvh - ${topPosition + scrollY}px)`)
|
||||
}, [])
|
||||
|
||||
function handleClose() {
|
||||
const url = new URL(window.location.href)
|
||||
url.searchParams.delete("view")
|
||||
router.push(url.toString())
|
||||
}
|
||||
|
||||
// Making sure the map is always opened at the top of the page,
|
||||
// just below the header and booking widget as these should stay visible.
|
||||
// When closing, the page should scroll back to the position it was before opening the map.
|
||||
@@ -54,15 +57,15 @@ export default function DynamicMap({
|
||||
return
|
||||
}
|
||||
|
||||
if (isDynamicMapOpen && scrollHeightWhenOpened === 0) {
|
||||
if (isMapView && scrollHeightWhenOpened === 0) {
|
||||
const scrollY = window.scrollY
|
||||
setScrollHeightWhenOpened(scrollY)
|
||||
window.scrollTo({ top: 0, behavior: "instant" })
|
||||
} else if (!isDynamicMapOpen && scrollHeightWhenOpened !== 0) {
|
||||
} else if (!isMapView && scrollHeightWhenOpened !== 0) {
|
||||
window.scrollTo({ top: scrollHeightWhenOpened, behavior: "instant" })
|
||||
setScrollHeightWhenOpened(0)
|
||||
}
|
||||
}, [isDynamicMapOpen, scrollHeightWhenOpened, rootDiv])
|
||||
}, [isMapView, scrollHeightWhenOpened, rootDiv])
|
||||
|
||||
useEffect(() => {
|
||||
const debouncedResizeHandler = debounce(function () {
|
||||
@@ -78,7 +81,7 @@ export default function DynamicMap({
|
||||
observer.unobserve(document.documentElement)
|
||||
}
|
||||
}
|
||||
}, [rootDiv, isDynamicMapOpen, handleMapHeight])
|
||||
}, [rootDiv, isMapView, handleMapHeight])
|
||||
|
||||
const closeButton = (
|
||||
<Button
|
||||
@@ -87,7 +90,7 @@ export default function DynamicMap({
|
||||
variant="icon"
|
||||
size="small"
|
||||
className={styles.closeButton}
|
||||
onClick={closeDynamicMap}
|
||||
onClick={handleClose}
|
||||
>
|
||||
<CloseLargeIcon color="burgundy" />
|
||||
<span>{intl.formatMessage({ id: "Close the map" })}</span>
|
||||
@@ -98,7 +101,7 @@ export default function DynamicMap({
|
||||
<APIProvider apiKey={apiKey}>
|
||||
<div className={styles.wrapper} ref={rootDiv}>
|
||||
<Modal
|
||||
isOpen={isDynamicMapOpen}
|
||||
isOpen={isMapView}
|
||||
UNSTABLE_portalContainer={rootDiv.current || undefined}
|
||||
>
|
||||
<Dialog
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { useParams } from "next/navigation"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import useHotelPageStore from "@/stores/hotel-page"
|
||||
|
||||
import PoiMarker from "@/components/Maps/Markers/Poi"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
@@ -17,12 +18,14 @@ import type { MapCardProps } from "@/types/components/hotelPage/map/mapCard"
|
||||
|
||||
export default function MapCard({ hotelName, pois }: MapCardProps) {
|
||||
const intl = useIntl()
|
||||
const { openDynamicMap } = useHotelPageStore()
|
||||
const params = useParams()
|
||||
const [mapUrl, setMapUrl] = useState<string | null>(null)
|
||||
|
||||
function handleOpenMapClick() {
|
||||
openDynamicMap()
|
||||
trackHotelMapClick()
|
||||
}
|
||||
useEffect(() => {
|
||||
const url = new URL(window.location.href)
|
||||
url.searchParams.set("view", "map")
|
||||
setMapUrl(url.toString())
|
||||
}, [params])
|
||||
|
||||
return (
|
||||
<div className={styles.mapCard}>
|
||||
@@ -62,15 +65,13 @@ export default function MapCard({ hotelName, pois }: MapCardProps) {
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<Button
|
||||
theme="base"
|
||||
intent="secondary"
|
||||
size="small"
|
||||
fullWidth
|
||||
onClick={handleOpenMapClick}
|
||||
>
|
||||
{intl.formatMessage({ id: "Explore nearby" })}
|
||||
</Button>
|
||||
{mapUrl ? (
|
||||
<Button theme="base" intent="secondary" size="small" fullWidth asChild>
|
||||
<Link href={mapUrl} scroll={true} onClick={trackHotelMapClick}>
|
||||
{intl.formatMessage({ id: "Explore nearby" })}
|
||||
</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"use client"
|
||||
import Link from "next/link"
|
||||
import { useParams, useSearchParams } from "next/navigation"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import useHotelPageStore from "@/stores/hotel-page"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { HouseIcon, MapIcon } from "@/components/Icons"
|
||||
import { trackHotelMapClick } from "@/utils/tracking"
|
||||
@@ -10,40 +13,53 @@ import styles from "./mobileToggle.module.css"
|
||||
|
||||
export default function MobileMapToggle() {
|
||||
const intl = useIntl()
|
||||
const { isDynamicMapOpen, openDynamicMap, closeDynamicMap } =
|
||||
useHotelPageStore()
|
||||
const searchParams = useSearchParams()
|
||||
const params = useParams()
|
||||
const isMapView = useMemo(
|
||||
() => searchParams.get("view") === "map",
|
||||
[searchParams]
|
||||
)
|
||||
const [mapUrl, setMapUrl] = useState<string | null>(null)
|
||||
|
||||
function handleOpenMapClick() {
|
||||
openDynamicMap()
|
||||
trackHotelMapClick()
|
||||
useEffect(() => {
|
||||
const url = new URL(window.location.href)
|
||||
url.searchParams.set("view", "map")
|
||||
setMapUrl(url.toString())
|
||||
}, [params])
|
||||
|
||||
if (!mapUrl) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.mobileToggle}>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.button} ${!isDynamicMapOpen ? styles.active : ""}`}
|
||||
onClick={closeDynamicMap}
|
||||
<span
|
||||
className={`${styles.iconWrapper} ${!isMapView ? styles.active : ""}`}
|
||||
>
|
||||
<HouseIcon
|
||||
color={!isDynamicMapOpen ? "white" : "red"}
|
||||
color={!isMapView ? "white" : "red"}
|
||||
height={24}
|
||||
width={24}
|
||||
/>
|
||||
<span>{intl.formatMessage({ id: "Hotel" })}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.button} ${isDynamicMapOpen ? styles.active : ""}`}
|
||||
onClick={handleOpenMapClick}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{intl.formatMessage({ id: "Hotel" })}</span>
|
||||
</Typography>
|
||||
</span>
|
||||
<span
|
||||
className={`${styles.iconWrapper} ${isMapView ? styles.active : ""}`}
|
||||
>
|
||||
<MapIcon
|
||||
color={isDynamicMapOpen ? "white" : "red"}
|
||||
height={24}
|
||||
width={24}
|
||||
/>
|
||||
<span>{intl.formatMessage({ id: "Map" })}</span>
|
||||
</button>
|
||||
<Link
|
||||
className={styles.link}
|
||||
href={mapUrl}
|
||||
scroll={true}
|
||||
onClick={trackHotelMapClick}
|
||||
>
|
||||
<MapIcon color={isMapView ? "white" : "red"} height={24} width={24} />
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{intl.formatMessage({ id: "Map" })}</span>
|
||||
</Typography>
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
padding: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.button {
|
||||
.iconWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -24,22 +24,24 @@
|
||||
cursor: pointer;
|
||||
border-radius: 2.5rem;
|
||||
color: var(--Base-Text-Accent);
|
||||
font-family: var(--typography-Caption-Bold-Desktop-fontFamily);
|
||||
font-size: var(--typography-Caption-Bold-Desktop-fontSize);
|
||||
font-weight: var(--typography-Caption-Bold-Desktop-fontWeight);
|
||||
}
|
||||
.button:hover {
|
||||
.iconWrapper:hover {
|
||||
background-color: var(--Base-Surface-Primary-light-Hover);
|
||||
}
|
||||
|
||||
.button.active {
|
||||
.iconWrapper.active {
|
||||
background-color: var(--Primary-Strong-Surface-Normal);
|
||||
color: var(--Base-Text-Inverted);
|
||||
}
|
||||
.button.active:hover {
|
||||
.iconWrapper.active:hover {
|
||||
background-color: var(--Primary-Strong-Surface-Hover);
|
||||
}
|
||||
|
||||
.link {
|
||||
display: contents;
|
||||
color: var(--Base-Text-Accent);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.mobileToggle {
|
||||
display: none;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { create } from "zustand"
|
||||
|
||||
interface HotelPageState {
|
||||
isDynamicMapOpen: boolean
|
||||
openDynamicMap: () => void
|
||||
closeDynamicMap: () => void
|
||||
}
|
||||
|
||||
const useHotelPageStore = create<HotelPageState>((set) => ({
|
||||
isDynamicMapOpen: false,
|
||||
openDynamicMap: () => set({ isDynamicMapOpen: true }),
|
||||
closeDynamicMap: () => set({ isDynamicMapOpen: false }),
|
||||
}))
|
||||
|
||||
export default useHotelPageStore
|
||||
Reference in New Issue
Block a user