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
113 lines
3.0 KiB
TypeScript
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>
|
|
)
|
|
}
|