Merged in feat/SW-1549-map-improvements (pull request #1783)

Feat/SW-1549 map improvements

* fix: imported new icon

* refactor: rename component and set map handling to 'greedy'

* fix: show cards for 3s after hover

* refactor: update styles and added HotelPin component

* fix: change from close to back icon

* refactor: update to only use 1 state value for active pin and card

* fix: add click handler when dialog is opened

* fix: performance fixes for the dialog carousel

* fix: added border

* fix: clear timeout on mouseenter

* fix: changed to absolute import

* fix: moved hover state into the store

* fix: renamed store actions


Approved-by: Michael Zetterberg
This commit is contained in:
Tobias Johansson
2025-04-15 13:23:23 +00:00
parent 57cd2f6a7f
commit 9a9789e736
27 changed files with 288 additions and 261 deletions

View File

@@ -2,62 +2,39 @@ import {
AdvancedMarker,
AdvancedMarkerAnchorPoint,
} from "@vis.gl/react-google-maps"
import { useCallback, useState } from "react"
import { useIntl } from "react-intl"
import { useCallback } from "react"
import { useHotelsMapStore } from "@/stores/hotels-map"
import HotelCardDialog from "@/components/HotelReservation/HotelCardDialog"
import Body from "@/components/TempDesignSystem/Text/Body"
import { formatPrice } from "@/utils/numberFormatting"
import HotelMarker from "../../Markers/HotelMarker"
import HotelPin from "./HotelPin"
import styles from "./hotelListingMapContent.module.css"
import type { HotelListingMapContentProps } from "@/types/components/hotelReservation/selectHotel/map"
function HotelListingMapContent({ hotelPins }: HotelListingMapContentProps) {
const intl = useIntl()
const [hoveredHotelPin, setHoveredHotelPin] = useState<string | null>(null)
const { activeHotelPin, setActiveHotelPin, setActiveHotelCard } =
const { activeHotel, hoveredHotel, activate, deactivate, engage, disengage } =
useHotelsMapStore()
const toggleActiveHotelPin = useCallback(
(pinName: string | null) => {
if (setActiveHotelPin) {
const newActivePin = activeHotelPin === pinName ? null : pinName
setActiveHotelPin(newActivePin)
setActiveHotelCard(newActivePin)
if (newActivePin === null) {
setHoveredHotelPin(null)
setActiveHotelCard(null)
}
if (activeHotel === pinName || pinName === null) {
deactivate()
return
}
},
[activeHotelPin, setActiveHotelPin, setActiveHotelCard]
)
const handleHover = useCallback(
(pinName: string | null) => {
if (pinName !== null && activeHotelPin !== pinName) {
setHoveredHotelPin(pinName)
if (activeHotelPin && setActiveHotelPin) {
setActiveHotelPin(null)
setActiveHotelCard(null)
}
} else if (pinName === null) {
setHoveredHotelPin(null)
}
activate(pinName)
},
[activeHotelPin, setActiveHotelPin, setActiveHotelCard]
[activeHotel, activate, deactivate]
)
return (
<div>
{hotelPins.map((pin) => {
const isActiveOrHovered =
activeHotelPin === pin.name || hoveredHotelPin === pin.name
activeHotel === pin.name || hoveredHotel === pin.name
const hotelPrice =
pin.memberPrice ?? pin.publicPrice ?? pin.redemptionPrice
return (
@@ -67,8 +44,8 @@ function HotelListingMapContent({ hotelPins }: HotelListingMapContentProps) {
position={pin.coordinates}
anchorPoint={AdvancedMarkerAnchorPoint.CENTER}
zIndex={isActiveOrHovered ? 2 : 0}
onMouseEnter={() => handleHover(pin.name)}
onMouseLeave={() => handleHover(null)}
onMouseEnter={() => engage(pin.name)}
onMouseLeave={() => disengage()}
onClick={() => toggleActiveHotelPin(pin.name)}
>
<div className={styles.dialogContainer}>
@@ -76,37 +53,17 @@ function HotelListingMapContent({ hotelPins }: HotelListingMapContentProps) {
isOpen={isActiveOrHovered}
handleClose={(event: { stopPropagation: () => void }) => {
event.stopPropagation()
if (activeHotelPin === pin.name) {
toggleActiveHotelPin(null)
}
deactivate()
disengage()
}}
data={pin}
/>
</div>
<span
className={`${styles.pin} ${isActiveOrHovered ? styles.active : ""}`}
>
<span className={styles.pinIcon}>
<HotelMarker
width={16}
color={isActiveOrHovered ? "burgundy" : "white"}
/>
</span>
<Body
asChild
color={isActiveOrHovered ? "white" : "baseTextHighContrast"}
>
<span>
{/* TODO: Handle when no price is available */}
{hotelPrice
? formatPrice(intl, hotelPrice, pin.currency)
: intl.formatMessage({
defaultMessage: "N/A",
})}
</span>
</Body>
</span>
<HotelPin
isActive={isActiveOrHovered}
hotelPrice={hotelPrice}
currency={pin.currency}
/>
</AdvancedMarker>
)
})}