Files
web/apps/scandic-web/components/Maps/InteractiveMap/index.tsx
Tobias Johansson 9a9789e736 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
2025-04-15 13:23:23 +00:00

113 lines
3.0 KiB
TypeScript

"use client"
import { Map, type MapProps, useMap } from "@vis.gl/react-google-maps"
import { useEffect, useState } from "react"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Button from "@/components/TempDesignSystem/Button"
import HotelListingMapContent from "./HotelListingMapContent"
import PoiMapMarkers from "./PoiMapMarkers"
import styles from "./interactiveMap.module.css"
import type { InteractiveMapProps } from "@/types/components/hotelPage/map/interactiveMap"
export default function InteractiveMap({
coordinates,
pointsOfInterest,
activePoi,
hotelPins,
mapId,
closeButton,
fitBounds = true,
onTilesLoaded,
onActivePoiChange,
}: InteractiveMapProps) {
const intl = useIntl()
const map = useMap()
const [hasFittedBounds, setHasFittedBounds] = useState(false)
const mapOptions: MapProps = {
defaultZoom: 14,
defaultCenter: coordinates,
disableDefaultUI: true,
clickableIcons: false,
mapId,
gestureHandling: "greedy",
}
function zoomIn() {
const currentZoom = map && map.getZoom()
if (currentZoom) {
map.setZoom(currentZoom + 1)
}
}
function zoomOut() {
const currentZoom = map && map.getZoom()
if (currentZoom) {
map.setZoom(currentZoom - 1)
}
}
useEffect(() => {
if (map && fitBounds && hotelPins?.length && !hasFittedBounds) {
const bounds = new google.maps.LatLngBounds()
hotelPins.forEach((marker) => {
bounds.extend(marker.coordinates)
})
map.fitBounds(bounds, 100)
setHasFittedBounds(true)
}
}, [map, fitBounds, hotelPins, hasFittedBounds])
return (
<div className={styles.mapContainer}>
<Map {...mapOptions} onTilesLoaded={onTilesLoaded}>
{hotelPins && <HotelListingMapContent hotelPins={hotelPins} />}
{pointsOfInterest && (
<PoiMapMarkers
coordinates={coordinates}
pointsOfInterest={pointsOfInterest}
onActivePoiChange={onActivePoiChange}
activePoi={activePoi}
/>
)}
</Map>
<div className={styles.ctaButtons}>
{closeButton}
<div className={styles.zoomButtons}>
<Button
theme="base"
intent="inverted"
variant="icon"
size="small"
className={styles.zoomButton}
onClick={zoomOut}
aria-label={intl.formatMessage({
defaultMessage: "Zoom in",
})}
>
<MaterialIcon icon="remove" color="CurrentColor" size={20} />
</Button>
<Button
theme="base"
intent="inverted"
variant="icon"
size="small"
className={styles.zoomButton}
onClick={zoomIn}
aria-label={intl.formatMessage({
defaultMessage: "Zoom out",
})}
>
<MaterialIcon icon="add" color="CurrentColor" size={20} />
</Button>
</div>
</div>
</div>
)
}