feat(SW-340): Added scroll to top button

This commit is contained in:
Pontus Dreij
2024-11-08 10:00:53 +01:00
parent 2748120890
commit 5f46844b9b
22 changed files with 238 additions and 99 deletions

View File

@@ -12,6 +12,10 @@
width: 100%; width: 100%;
} }
.card.active {
border: 1px solid var(--Base-Border-Hover);
}
.imageContainer { .imageContainer {
grid-area: image; grid-area: image;
position: relative; position: relative;

View File

@@ -17,12 +17,13 @@ import { hotelCardVariants } from "./variants"
import styles from "./hotelCard.module.css" import styles from "./hotelCard.module.css"
import { HotelCardListingType } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import type { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps" import type { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps"
export default function HotelCard({ export default function HotelCard({
hotel, hotel,
type = HotelCardListingType.PageListing, type = HotelCardListingTypeEnum.PageListing,
state = "default",
}: HotelCardProps) { }: HotelCardProps) {
const intl = useIntl() const intl = useIntl()
@@ -33,6 +34,7 @@ export default function HotelCard({
const classNames = hotelCardVariants({ const classNames = hotelCardVariants({
type, type,
state,
}) })
return ( return (

View File

@@ -8,8 +8,13 @@ export const hotelCardVariants = cva(styles.card, {
pageListing: styles.pageListing, pageListing: styles.pageListing,
mapListing: styles.mapListing, mapListing: styles.mapListing,
}, },
state: {
active: styles.active,
default: styles.default,
},
}, },
defaultVariants: { defaultVariants: {
type: "pageListing", type: "pageListing",
state: "default",
}, },
}) })

View File

@@ -10,30 +10,39 @@
.dialogContainer { .dialogContainer {
border: 1px solid var(--Base-Border-Subtle); border: 1px solid var(--Base-Border-Subtle);
border-radius: var(--Corner-radius-Medium); border-radius: var(--Corner-radius-Medium);
width: 402px; min-width: 334px;
min-height: 227px;
background: var(--Base-Surface-Primary-light-Normal); background: var(--Base-Surface-Primary-light-Normal);
box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1); box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1);
flex-direction: row; flex-direction: row;
display: flex; display: flex;
position: relative;
}
.closeIcon {
position: absolute;
top: 7px;
right: 7px;
} }
.imageContainer { .imageContainer {
position: relative; position: relative;
min-height: 227px; min-width: 177px;
width: 177px;
}
.tripAdvisor {
display: none;
} }
.imageContainer img { .imageContainer img {
object-fit: cover; object-fit: cover;
} }
.tripAdvisor {
position: absolute;
display: block;
left: 7px;
top: 7px;
}
.content { .content {
width: 100%; width: 100%;
min-width: 201px;
padding: var(--Spacing-x-one-and-half); padding: var(--Spacing-x-one-and-half);
gap: var(--Spacing-x1); gap: var(--Spacing-x1);
display: flex; display: flex;
@@ -65,6 +74,13 @@
color: var(--Base-Text-Subtle-light-Normal); color: var(--Base-Text-Subtle-light-Normal);
} }
.button { .content .button {
margin-top: auto; margin-top: auto;
} }
@media (min-width: 768px) {
.facilities,
.memberPrice {
display: none;
}
}

View File

@@ -3,6 +3,7 @@
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data"
import { CloseLargeIcon } from "@/components/Icons"
import TripAdvisorIcon from "@/components/Icons/TripAdvisor" import TripAdvisorIcon from "@/components/Icons/TripAdvisor"
import Image from "@/components/Image" import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
@@ -13,17 +14,19 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./hotelCardDialog.module.css" import styles from "./hotelCardDialog.module.css"
import { HotelPin } from "@/types/components/hotelReservation/selectHotel/map" import { HotelCardDialogProps } from "@/types/components/hotelReservation/selectHotel/map"
export default function HotelCardDialog({ export default function HotelCardDialog({
pin, pin,
isOpen, isOpen,
}: { handleClose,
isOpen: boolean }: HotelCardDialogProps) {
pin: HotelPin
}) {
const intl = useIntl() const intl = useIntl()
if (!pin) {
return null
}
const { const {
name, name,
publicPrice, publicPrice,
@@ -34,15 +37,20 @@ export default function HotelCardDialog({
ratings, ratings,
} = pin } = pin
const firstImage = images[0]?.imageSizes?.small
const altText = images[0]?.metaData?.altText
return ( return (
<dialog open={isOpen} className={styles.dialog}> <dialog open={isOpen} className={styles.dialog}>
<div className={styles.dialogContainer}> <div className={styles.dialogContainer}>
<CloseLargeIcon
onClick={handleClose}
className={styles.closeIcon}
width={16}
height={16}
/>
<div className={styles.imageContainer}> <div className={styles.imageContainer}>
<Image <Image src={firstImage} alt={altText} fill />
src={images[0].imageSizes.small}
alt={images[0].metaData.altText}
fill
/>
<div className={styles.tripAdvisor}> <div className={styles.tripAdvisor}>
<Chip intent="primary" className={styles.tripAdvisor}> <Chip intent="primary" className={styles.tripAdvisor}>
<TripAdvisorIcon color="white" /> <TripAdvisorIcon color="white" />
@@ -58,7 +66,9 @@ export default function HotelCardDialog({
return ( return (
<div className={styles.facilitiesItem} key={facility.id}> <div className={styles.facilitiesItem} key={facility.id}>
{IconComponent && <IconComponent color="grey80" />} {IconComponent && <IconComponent color="grey80" />}
<Caption color="textMediumContrast">{facility.name}</Caption> <Caption color="uiTextMediumContrast">
{facility.name}
</Caption>
</div> </div>
) )
})} })}
@@ -72,7 +82,7 @@ export default function HotelCardDialog({
</Body> </Body>
</Subtitle> </Subtitle>
{memberPrice && ( {memberPrice && (
<Subtitle type="two" color="red"> <Subtitle type="two" color="red" className={styles.memberPrice}>
{memberPrice} {currency} {memberPrice} {currency}
<Body asChild color="red"> <Body asChild color="red">
<span>/{intl.formatMessage({ id: "night" })}</span> <span>/{intl.formatMessage({ id: "night" })}</span>

View File

@@ -9,13 +9,14 @@ import HotelCard from "../HotelCard"
import styles from "./hotelCardListing.module.css" import styles from "./hotelCardListing.module.css"
import { import {
HotelCardListingProps, type HotelCardListingProps,
HotelCardListingType, HotelCardListingTypeEnum,
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
export default function HotelCardListing({ export default function HotelCardListing({
hotelData, hotelData,
type = HotelCardListingType.PageListing, type = HotelCardListingTypeEnum.PageListing,
state = "default",
}: HotelCardListingProps) { }: HotelCardListingProps) {
const searchParams = useSearchParams() const searchParams = useSearchParams()
@@ -36,7 +37,12 @@ export default function HotelCardListing({
<section className={styles.hotelCards}> <section className={styles.hotelCards}>
{hotels?.length ? ( {hotels?.length ? (
hotels.map((hotel) => ( hotels.map((hotel) => (
<HotelCard key={hotel.hotelData.name} hotel={hotel} type={type} /> <HotelCard
key={hotel.hotelData.name}
hotel={hotel}
type={type}
state={state}
/>
)) ))
) : ( ) : (
<Title>No hotels found</Title> <Title>No hotels found</Title>

View File

@@ -5,8 +5,8 @@
@media (min-width: 768px) { @media (min-width: 768px) {
.hotelListing { .hotelListing {
display: block; display: block;
width: 420px; width: 100%;
padding: var(--Spacing-x3) var(--Spacing-x4);
overflow-y: auto; overflow-y: auto;
padding-top: var(--Spacing-x2);
} }
} }

View File

@@ -4,15 +4,19 @@ import HotelCardListing from "@/components/HotelReservation/HotelCardListing"
import styles from "./hotelListing.module.css" import styles from "./hotelListing.module.css"
import { HotelCardListingType } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import type { HotelListingProps } from "@/types/components/hotelReservation/selectHotel/map" import type { HotelListingProps } from "@/types/components/hotelReservation/selectHotel/map"
export default function HotelListing({ hotels }: HotelListingProps) { export default function HotelListing({
hotels,
cardState = "default",
}: HotelListingProps) {
return ( return (
<div className={styles.hotelListing}> <div className={styles.hotelListing}>
<HotelCardListing <HotelCardListing
hotelData={hotels} hotelData={hotels}
type={HotelCardListingType.MapListing} type={HotelCardListingTypeEnum.MapListing}
state={cardState}
/> />
</div> </div>
) )

View File

@@ -1,7 +1,7 @@
"use client" "use client"
import { APIProvider } from "@vis.gl/react-google-maps" import { APIProvider } from "@vis.gl/react-google-maps"
import { useRouter, useSearchParams } from "next/navigation" import { useRouter, useSearchParams } from "next/navigation"
import { useState } from "react" import { useEffect, useState } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { selectHotel } from "@/constants/routes/hotelReservation" import { selectHotel } from "@/constants/routes/hotelReservation"
@@ -30,6 +30,29 @@ export default function SelectHotelMap({
const lang = useLang() const lang = useLang()
const intl = useIntl() const intl = useIntl()
const [activeHotelPin, setActiveHotelPin] = useState<string | null>(null) const [activeHotelPin, setActiveHotelPin] = useState<string | null>(null)
const [showBackToTop, setShowBackToTop] = useState<boolean>(false)
useEffect(() => {
const hotelListingElement = document.querySelector(
`.${styles.listingContainer}`
)
if (!hotelListingElement) return
const handleScroll = () => {
const hasScrolledPast = hotelListingElement.scrollTop > 490
setShowBackToTop(hasScrolledPast)
}
hotelListingElement.addEventListener("scroll", handleScroll)
return () => hotelListingElement.removeEventListener("scroll", handleScroll)
}, [])
function scrollToTop() {
const hotelListingElement = document.querySelector(
`.${styles.listingContainer}`
)
hotelListingElement?.scrollTo({ top: 0, behavior: "smooth" })
}
function handleModalDismiss() { function handleModalDismiss() {
router.back() router.back()
@@ -54,25 +77,41 @@ export default function SelectHotelMap({
return ( return (
<APIProvider apiKey={apiKey}> <APIProvider apiKey={apiKey}>
<div className={styles.container}> <div className={styles.container}>
<div className={styles.filterContainer}> <div className={styles.listingContainer}>
<Button <div className={styles.filterContainer}>
intent="text" <Button
size="small" intent="text"
variant="icon" size="small"
wrapping variant="icon"
onClick={isModal ? handleModalDismiss : handlePageRedirect} wrapping
> onClick={isModal ? handleModalDismiss : handlePageRedirect}
<CloseLargeIcon /> className={styles.filterContainerCloseButton}
</Button> >
<span>Filter and sort</span> <CloseLargeIcon />
{/* TODO: Add filter and sort button */} </Button>
<span>Filter and sort</span>
{/* TODO: Add filter and sort button */}
</div>
<HotelListing
hotels={hotels}
cardState={activeHotelPin ? "active" : "default"}
/>
{showBackToTop && (
<Button
intent="inverted"
size="small"
theme="base"
className={styles.backToTopButton}
onClick={scrollToTop}
>
{intl.formatMessage({ id: "Back to top" })}
</Button>
)}
</div> </div>
<HotelListing hotels={hotels} />
<InteractiveMap <InteractiveMap
closeButton={closeButton} closeButton={closeButton}
coordinates={coordinates} coordinates={coordinates}
hotelPins={hotelPins} hotelPins={hotelPins}
hotels={hotels}
activeHotelPin={activeHotelPin} activeHotelPin={activeHotelPin}
onActiveHotelPinChange={setActiveHotelPin} onActiveHotelPinChange={setActiveHotelPin}
mapId={mapId} mapId={mapId}

View File

@@ -23,16 +23,29 @@
height: 44px; height: 44px;
} }
.filterContainer .closeButton { .backToTopButton {
color: var(--UI-Text-High-Contrast); display: none;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.closeButton { .closeButton {
display: flex !important; display: flex !important;
} }
.filterContainer { .filterContainerCloseButton {
display: none; display: none !important;
}
.backToTopButton {
position: fixed;
bottom: 24px;
left: 32px;
display: flex;
}
.listingContainer {
background-color: var(--Base-Surface-Secondary-light-Normal);
padding: var(--Spacing-x3) var(--Spacing-x4);
overflow-y: auto;
min-width: 420px;
position: relative;
} }
.container { .container {
display: flex; display: flex;

View File

@@ -2,6 +2,7 @@ import {
AdvancedMarker, AdvancedMarker,
AdvancedMarkerAnchorPoint, AdvancedMarkerAnchorPoint,
} from "@vis.gl/react-google-maps" } from "@vis.gl/react-google-maps"
import { useState } from "react"
import HotelCardDialog from "@/components/HotelReservation/HotelCardDialog" import HotelCardDialog from "@/components/HotelReservation/HotelCardDialog"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
@@ -10,59 +11,77 @@ import HotelMarker from "../../Markers/HotelMarker"
import styles from "./hotelListingMapContent.module.css" import styles from "./hotelListingMapContent.module.css"
import { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { HotelListingMapContentProps } from "@/types/components/hotelReservation/selectHotel/map"
import { HotelPin } from "@/types/components/hotelReservation/selectHotel/map"
export default function HotelListingMapContent({ export default function HotelListingMapContent({
activeHotelPin, activeHotelPin,
hotelPins, hotelPins,
hotels,
onActiveHotelPinChange, onActiveHotelPinChange,
}: { }: HotelListingMapContentProps) {
activeHotelPin?: HotelPin["name"] | null const [hoveredHotelPin, setHoveredHotelPin] = useState<string | null>(null)
hotelPins: HotelPin[]
hotels?: HotelData[] function toggleActiveHotelPin(pinName: string | null) {
onActiveHotelPinChange?: (pinName: string | null) => void if (onActiveHotelPinChange) {
}) { onActiveHotelPinChange(activeHotelPin === pinName ? null : pinName)
function toggleActiveHotelPin(pinName: string) { setHoveredHotelPin(null)
onActiveHotelPinChange?.(activeHotelPin === pinName ? null : pinName) }
}
function isPinActiveOrHovered(pinName: string) {
return activeHotelPin === pinName || hoveredHotelPin === pinName
} }
return ( return (
<div> <div>
{hotelPins.map((pin) => ( {hotelPins.map((pin) => {
<AdvancedMarker const isActiveOrHovered = isPinActiveOrHovered(pin.name)
key={pin.name} return (
className={styles.advancedMarker} <AdvancedMarker
position={pin.coordinates} key={pin.name}
anchorPoint={AdvancedMarkerAnchorPoint.CENTER} className={styles.advancedMarker}
zIndex={activeHotelPin === pin.name ? 2 : 0} position={pin.coordinates}
onMouseEnter={() => onActiveHotelPinChange?.(pin.name)} anchorPoint={AdvancedMarkerAnchorPoint.CENTER}
onMouseLeave={() => onActiveHotelPinChange?.(null)} zIndex={isActiveOrHovered ? 2 : 0}
onClick={() => toggleActiveHotelPin(pin.name)} onMouseEnter={() => setHoveredHotelPin(pin.name)}
> onMouseLeave={() => setHoveredHotelPin(null)}
<HotelCardDialog isOpen={activeHotelPin === pin.name} pin={pin} /> onClick={() =>
toggleActiveHotelPin(
<span activeHotelPin === pin.name ? null : pin.name
className={`${styles.pin} ${activeHotelPin === pin.name ? styles.active : ""}`} )
}
> >
<span className={styles.pinIcon}> <HotelCardDialog
<HotelMarker isOpen={isActiveOrHovered}
width={16} handleClose={(event: { stopPropagation: () => void }) => {
color={activeHotelPin === pin.name ? "burgundy" : "white"} event.stopPropagation()
/> if (activeHotelPin === pin.name) {
</span> toggleActiveHotelPin(null)
<Body }
asChild }}
color={activeHotelPin === pin.name ? "white" : "textHighContrast"} pin={pin}
/>
<span
className={`${styles.pin} ${isActiveOrHovered ? styles.active : ""}`}
> >
<span> <span className={styles.pinIcon}>
{pin.memberPrice} {pin.currency} <HotelMarker
width={16}
color={isActiveOrHovered ? "burgundy" : "white"}
/>
</span> </span>
</Body> <Body
</span> asChild
</AdvancedMarker> color={isActiveOrHovered ? "white" : "textHighContrast"}
))} >
<span>
{pin.memberPrice} {pin.currency}
</span>
</Body>
</span>
</AdvancedMarker>
)
})}
</div> </div>
) )
} }

View File

@@ -18,7 +18,6 @@ export default function InteractiveMap({
activePoi, activePoi,
hotelPins, hotelPins,
activeHotelPin, activeHotelPin,
hotels,
mapId, mapId,
closeButton, closeButton,
onActivePoiChange, onActivePoiChange,
@@ -56,7 +55,6 @@ export default function InteractiveMap({
activeHotelPin={activeHotelPin} activeHotelPin={activeHotelPin}
hotelPins={hotelPins} hotelPins={hotelPins}
onActiveHotelPinChange={onActiveHotelPinChange} onActiveHotelPinChange={onActiveHotelPinChange}
hotels={hotels}
/> />
)} )}
{pointsOfInterest && ( {pointsOfInterest && (

View File

@@ -36,6 +36,7 @@
"At the hotel": "På hotellet", "At the hotel": "På hotellet",
"Attractions": "Attraktioner", "Attractions": "Attraktioner",
"Back to scandichotels.com": "Tilbage til scandichotels.com", "Back to scandichotels.com": "Tilbage til scandichotels.com",
"Back to top": "Tilbage til top",
"Bar": "Bar", "Bar": "Bar",
"Based on availability": "Baseret på tilgængelighed", "Based on availability": "Baseret på tilgængelighed",
"Bed type": "Seng type", "Bed type": "Seng type",

View File

@@ -36,6 +36,7 @@
"At the hotel": "Im Hotel", "At the hotel": "Im Hotel",
"Attraction": "Attraktion", "Attraction": "Attraktion",
"Back to scandichotels.com": "Zurück zu scandichotels.com", "Back to scandichotels.com": "Zurück zu scandichotels.com",
"Back to top": "Zurück zur Spitze",
"Bar": "Bar", "Bar": "Bar",
"Based on availability": "Je nach Verfügbarkeit", "Based on availability": "Je nach Verfügbarkeit",
"Bed type": "Bettentyp", "Bed type": "Bettentyp",

View File

@@ -38,6 +38,7 @@
"At the hotel": "At the hotel", "At the hotel": "At the hotel",
"Attractions": "Attractions", "Attractions": "Attractions",
"Back to scandichotels.com": "Back to scandichotels.com", "Back to scandichotels.com": "Back to scandichotels.com",
"Back to top": "Back to top",
"Bar": "Bar", "Bar": "Bar",
"Based on availability": "Based on availability", "Based on availability": "Based on availability",
"Bed": "Bed", "Bed": "Bed",

View File

@@ -36,6 +36,7 @@
"At the hotel": "Hotellissa", "At the hotel": "Hotellissa",
"Attractions": "Nähtävyydet", "Attractions": "Nähtävyydet",
"Back to scandichotels.com": "Takaisin scandichotels.com", "Back to scandichotels.com": "Takaisin scandichotels.com",
"Back to top": "Takaisin ylös",
"Bar": "Bar", "Bar": "Bar",
"Based on availability": "Saatavuuden mukaan", "Based on availability": "Saatavuuden mukaan",
"Bed type": "Vuodetyyppi", "Bed type": "Vuodetyyppi",

View File

@@ -36,6 +36,7 @@
"At the hotel": "På hotellet", "At the hotel": "På hotellet",
"Attractions": "Attraksjoner", "Attractions": "Attraksjoner",
"Back to scandichotels.com": "Tilbake til scandichotels.com", "Back to scandichotels.com": "Tilbake til scandichotels.com",
"Back to top": "Tilbake til toppen",
"Bar": "Bar", "Bar": "Bar",
"Based on availability": "Basert på tilgjengelighet", "Based on availability": "Basert på tilgjengelighet",
"Bed type": "Seng type", "Bed type": "Seng type",

View File

@@ -36,6 +36,7 @@
"At the hotel": "På hotellet", "At the hotel": "På hotellet",
"Attractions": "Sevärdheter", "Attractions": "Sevärdheter",
"Back to scandichotels.com": "Tillbaka till scandichotels.com", "Back to scandichotels.com": "Tillbaka till scandichotels.com",
"Back to top": "Tillbaka till toppen",
"Bar": "Bar", "Bar": "Bar",
"Based on availability": "Baserat på tillgänglighet", "Based on availability": "Baserat på tillgänglighet",
"Bed type": "Sängtyp", "Bed type": "Sängtyp",

View File

@@ -12,7 +12,6 @@ export interface InteractiveMapProps {
activePoi?: PointOfInterest["name"] | null activePoi?: PointOfInterest["name"] | null
hotelPins?: HotelPin[] hotelPins?: HotelPin[]
activeHotelPin?: HotelPin["name"] | null activeHotelPin?: HotelPin["name"] | null
hotels?: HotelData[]
mapId: string mapId: string
closeButton: ReactElement closeButton: ReactElement
onActivePoiChange?: (poi: PointOfInterest["name"] | null) => void onActivePoiChange?: (poi: PointOfInterest["name"] | null) => void

View File

@@ -2,14 +2,15 @@ import { HotelsAvailabilityPrices } from "@/server/routers/hotels/output"
import { Hotel } from "@/types/hotel" import { Hotel } from "@/types/hotel"
export enum HotelCardListingType { export enum HotelCardListingTypeEnum {
MapListing = "mapListing", MapListing = "mapListing",
PageListing = "pageListing", PageListing = "pageListing",
} }
export type HotelCardListingProps = { export type HotelCardListingProps = {
hotelData: HotelData[] hotelData: HotelData[]
type?: HotelCardListingType type?: HotelCardListingTypeEnum
state?: "default" | "active"
} }
export type HotelData = { export type HotelData = {

View File

@@ -1,6 +1,10 @@
import { HotelCardListingType, HotelData } from "./hotelCardListingProps" import {
HotelCardListingTypeEnum,
type HotelData,
} from "./hotelCardListingProps"
export type HotelCardProps = { export type HotelCardProps = {
hotel: HotelData hotel: HotelData
type?: HotelCardListingType type?: HotelCardListingTypeEnum
state?: "default" | "active"
} }

View File

@@ -12,6 +12,7 @@ import type { Coordinates } from "@/types/components/maps/coordinates"
export interface HotelListingProps { export interface HotelListingProps {
hotels: HotelData[] hotels: HotelData[]
cardState?: "default" | "active"
// pointsOfInterest: PointOfInterest[] // pointsOfInterest: PointOfInterest[]
// activePoi: PointOfInterest["name"] | null // activePoi: PointOfInterest["name"] | null
// onActivePoiChange: (poi: PointOfInterest["name"] | null) => void // onActivePoiChange: (poi: PointOfInterest["name"] | null) => void
@@ -42,3 +43,15 @@ export type HotelPin = {
amenities: Filter[] amenities: Filter[]
ratings: number | null ratings: number | null
} }
export interface HotelListingMapContentProps {
activeHotelPin?: HotelPin["name"] | null
hotelPins: HotelPin[]
onActiveHotelPinChange?: (pinName: string | null) => void
}
export interface HotelCardDialogProps {
isOpen: boolean
pin: HotelPin
handleClose: (event: { stopPropagation: () => void }) => void
}