Merged in fix/SW-2165-map-navigate-hotel-card (pull request #2246)
fix(SW-2165): map navigate on enter press * fix(SW-2165): navigate on enter press and refactor * fix(SW-2165): responsive design * fix(SW-2165): replace spacing variables * fix(SW-2165): resolve pr comment * fix(SW-2165): remove isOpen, hide/show logic already handled * fix(SW-2165): remove dialog * fix(SW-2165): use buttonicon * fix(SW-2165): do not focus on close button without tab * fix(SW-2165): remove unneccessary css Approved-by: Christian Andolf
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
"use client"
|
||||
import { useSession } from "next-auth/react"
|
||||
import { useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { IconButton } from "@scandic-hotels/design-system/IconButton"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { selectRate } from "@/constants/routes/hotelReservation"
|
||||
@@ -11,31 +14,31 @@ import Button from "@/components/TempDesignSystem/Button"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { isValidClientSession } from "@/utils/clientSession"
|
||||
|
||||
import HotelPointsRow from "../../HotelCard/HotelPointsRow"
|
||||
import NoPriceAvailableCard from "../../HotelCard/NoPriceAvailableCard"
|
||||
import HotelCardDialogImage from "../HotelCardDialogImage"
|
||||
|
||||
import styles from "../hotelCardDialog.module.css"
|
||||
import styles from "./listingHotelCardDialog.module.css"
|
||||
|
||||
import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
interface ListingHotelCardProps {
|
||||
data: HotelPin
|
||||
lang: Lang
|
||||
imageError: boolean
|
||||
setImageError: (error: boolean) => void
|
||||
handleClose: () => void
|
||||
}
|
||||
|
||||
export default function ListingHotelCardDialog({
|
||||
data,
|
||||
lang,
|
||||
imageError,
|
||||
setImageError,
|
||||
handleClose,
|
||||
}: ListingHotelCardProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
|
||||
const [imageError, setImageError] = useState(false)
|
||||
|
||||
const { data: session } = useSession()
|
||||
const isUserLoggedIn = isValidClientSession(session)
|
||||
const {
|
||||
@@ -57,161 +60,174 @@ export default function ListingHotelCardDialog({
|
||||
const altText = images[0]?.metaData?.altText
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.header}>
|
||||
<HotelCardDialogImage
|
||||
firstImage={firstImage}
|
||||
altText={altText}
|
||||
rating={ratings}
|
||||
imageError={imageError}
|
||||
setImageError={setImageError}
|
||||
position="top"
|
||||
/>
|
||||
<div>
|
||||
<div className={styles.name}>
|
||||
<Subtitle type="two">{name}</Subtitle>
|
||||
</div>
|
||||
<div className={styles.facilities}>
|
||||
{amenities.map((facility) => (
|
||||
<div className={styles.facilitiesItem} key={facility.id}>
|
||||
<FacilityToIcon
|
||||
id={facility.id}
|
||||
size={20}
|
||||
color="Icon/Default"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{publicPrice ||
|
||||
memberPrice ||
|
||||
redemptionPrice ||
|
||||
voucherPrice ||
|
||||
chequePrice ? (
|
||||
<div className={styles.bottomContainer}>
|
||||
<div className={styles.pricesContainer}>
|
||||
{redemptionPrice ? (
|
||||
<Caption color="uiTextHighContrast">
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Available rates",
|
||||
})}
|
||||
</Caption>
|
||||
) : (
|
||||
<Caption color="uiTextHighContrast">
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Per night from",
|
||||
})}
|
||||
</Caption>
|
||||
)}
|
||||
<div className={styles.listingPrices}>
|
||||
{publicPrice && !isUserLoggedIn && memberPrice ? (
|
||||
<>
|
||||
<Subtitle type="two">
|
||||
{publicPrice} {currency}
|
||||
</Subtitle>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
{memberPrice && <Caption>/</Caption>}
|
||||
</>
|
||||
) : (
|
||||
bookingCode &&
|
||||
publicPrice && (
|
||||
<Subtitle type="two" color="red">
|
||||
{publicPrice} {currency}
|
||||
</Subtitle>
|
||||
)
|
||||
)}
|
||||
{memberPrice && (
|
||||
<Subtitle type="two" color="red">
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{price} {currency}",
|
||||
},
|
||||
{
|
||||
price: memberPrice,
|
||||
currency,
|
||||
}
|
||||
)}
|
||||
</Subtitle>
|
||||
)}
|
||||
{redemptionPrice && (
|
||||
<HotelPointsRow pointsPerStay={redemptionPrice} />
|
||||
)}
|
||||
{chequePrice && (
|
||||
<Subtitle type="two">
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{price} {currency}",
|
||||
},
|
||||
{
|
||||
price: chequePrice.numberOfCheques,
|
||||
currency: "CC",
|
||||
}
|
||||
)}
|
||||
{chequePrice.additionalPricePerStay > 0
|
||||
? // eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
||||
" + " +
|
||||
intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{price} {currency}",
|
||||
},
|
||||
{
|
||||
price: chequePrice.additionalPricePerStay,
|
||||
currency: chequePrice.currency,
|
||||
}
|
||||
)
|
||||
: null}
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<span>
|
||||
/
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "night",
|
||||
})}
|
||||
</span>
|
||||
</Typography>
|
||||
</Subtitle>
|
||||
)}
|
||||
{voucherPrice && (
|
||||
<Subtitle type="two">
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{price} {currency}",
|
||||
},
|
||||
{
|
||||
price: voucherPrice,
|
||||
currency,
|
||||
}
|
||||
)}
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<span>
|
||||
/
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "night",
|
||||
})}
|
||||
</span>
|
||||
</Typography>
|
||||
</Subtitle>
|
||||
)}
|
||||
<div className={styles.container}>
|
||||
<IconButton
|
||||
theme="Black"
|
||||
style="Muted"
|
||||
className={styles.closeButton}
|
||||
onPress={handleClose}
|
||||
aria-label={intl.formatMessage({
|
||||
defaultMessage: "Close",
|
||||
})}
|
||||
>
|
||||
<MaterialIcon icon="close" size={22} color="CurrentColor" />
|
||||
</IconButton>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.header}>
|
||||
<HotelCardDialogImage
|
||||
firstImage={firstImage}
|
||||
altText={altText}
|
||||
rating={ratings}
|
||||
imageError={imageError}
|
||||
setImageError={setImageError}
|
||||
position="top"
|
||||
/>
|
||||
<div>
|
||||
<div className={styles.name}>
|
||||
<Subtitle type="two">{name}</Subtitle>
|
||||
</div>
|
||||
<div className={styles.facilities}>
|
||||
{amenities.map((facility) => (
|
||||
<div key={facility.id}>
|
||||
<FacilityToIcon
|
||||
id={facility.id}
|
||||
size={20}
|
||||
color="Icon/Default"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Button asChild theme="base" size="small" className={styles.button}>
|
||||
<Link
|
||||
href={`${selectRate(lang)}?hotel=${operaId}`}
|
||||
color="none"
|
||||
keepSearchParams
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "See rooms",
|
||||
})}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<NoPriceAvailableCard />
|
||||
)}
|
||||
|
||||
{publicPrice ||
|
||||
memberPrice ||
|
||||
redemptionPrice ||
|
||||
voucherPrice ||
|
||||
chequePrice ? (
|
||||
<div className={styles.bottomContainer}>
|
||||
<div className={styles.pricesContainer}>
|
||||
{redemptionPrice ? (
|
||||
<Caption color="uiTextHighContrast">
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Available rates",
|
||||
})}
|
||||
</Caption>
|
||||
) : (
|
||||
<Caption color="uiTextHighContrast">
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Per night from",
|
||||
})}
|
||||
</Caption>
|
||||
)}
|
||||
<div className={styles.listingPrices}>
|
||||
{publicPrice && !isUserLoggedIn && memberPrice ? (
|
||||
<>
|
||||
<Subtitle type="two">
|
||||
{publicPrice} {currency}
|
||||
</Subtitle>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
{memberPrice && <Caption>/</Caption>}
|
||||
</>
|
||||
) : (
|
||||
bookingCode &&
|
||||
publicPrice && (
|
||||
<Subtitle type="two" color="red">
|
||||
{publicPrice} {currency}
|
||||
</Subtitle>
|
||||
)
|
||||
)}
|
||||
{memberPrice && (
|
||||
<Subtitle type="two" color="red">
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{price} {currency}",
|
||||
},
|
||||
{
|
||||
price: memberPrice,
|
||||
currency,
|
||||
}
|
||||
)}
|
||||
</Subtitle>
|
||||
)}
|
||||
{redemptionPrice && (
|
||||
<HotelPointsRow pointsPerStay={redemptionPrice} />
|
||||
)}
|
||||
{chequePrice && (
|
||||
<Subtitle type="two">
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{price} {currency}",
|
||||
},
|
||||
{
|
||||
price: chequePrice.numberOfCheques,
|
||||
currency: "CC",
|
||||
}
|
||||
)}
|
||||
{chequePrice.additionalPricePerStay > 0
|
||||
? // eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
||||
" + " +
|
||||
intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{price} {currency}",
|
||||
},
|
||||
{
|
||||
price: chequePrice.additionalPricePerStay,
|
||||
currency: chequePrice.currency,
|
||||
}
|
||||
)
|
||||
: null}
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<span>
|
||||
/
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "night",
|
||||
})}
|
||||
</span>
|
||||
</Typography>
|
||||
</Subtitle>
|
||||
)}
|
||||
{voucherPrice && (
|
||||
<Subtitle type="two">
|
||||
{intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "{price} {currency}",
|
||||
},
|
||||
{
|
||||
price: voucherPrice,
|
||||
currency,
|
||||
}
|
||||
)}
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<span>
|
||||
/
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "night",
|
||||
})}
|
||||
</span>
|
||||
</Typography>
|
||||
</Subtitle>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button asChild theme="base" size="small" className={styles.button}>
|
||||
<Link
|
||||
href={`${selectRate(lang)}?hotel=${operaId}`}
|
||||
color="none"
|
||||
keepSearchParams
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "See rooms",
|
||||
})}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<NoPriceAvailableCard />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
.container {
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
border-radius: var(--Corner-radius-md);
|
||||
min-width: 358px;
|
||||
background: var(--Base-Surface-Primary-light-Normal);
|
||||
box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--Space-x15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x15);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
gap: var(--Space-x15);
|
||||
}
|
||||
|
||||
.name {
|
||||
height: 48px;
|
||||
max-width: 180px;
|
||||
margin-bottom: var(--Space-x05);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.facilities {
|
||||
display: flex;
|
||||
gap: 0 var(--Space-x15);
|
||||
}
|
||||
|
||||
.priceCard {
|
||||
border-radius: var(--Corner-radius-md);
|
||||
padding: var(--Space-x05) var(--Space-x1);
|
||||
background: var(--Base-Surface-Secondary-light-Normal);
|
||||
margin-top: var(--Space-x1);
|
||||
}
|
||||
|
||||
.prices {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x1);
|
||||
justify-content: space-between;
|
||||
}
|
||||
.bottomContainer {
|
||||
display: flex;
|
||||
border-top: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
padding-top: var(--Space-x2);
|
||||
padding-bottom: var(--Space-x05);
|
||||
}
|
||||
|
||||
.pricesContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.listingPrices {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Space-x1);
|
||||
}
|
||||
|
||||
.content .button {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
"use client"
|
||||
import { useSession } from "next-auth/react"
|
||||
import { useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { IconButton } from "@scandic-hotels/design-system/IconButton"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
|
||||
import { selectRate } from "@/constants/routes/hotelReservation"
|
||||
|
||||
import { FacilityToIcon } from "@/components/ContentType/HotelPage/data"
|
||||
@@ -11,6 +15,7 @@ import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { isValidClientSession } from "@/utils/clientSession"
|
||||
import { trackEvent } from "@/utils/tracking/base"
|
||||
|
||||
@@ -18,25 +23,22 @@ import HotelPointsRow from "../../HotelCard/HotelPointsRow"
|
||||
import NoPriceAvailableCard from "../../HotelCard/NoPriceAvailableCard"
|
||||
import HotelCardDialogImage from "../HotelCardDialogImage"
|
||||
|
||||
import styles from "../hotelCardDialog.module.css"
|
||||
import styles from "./standaloneHotelCardDialog.module.css"
|
||||
|
||||
import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
interface StandaloneHotelCardProps {
|
||||
data: HotelPin
|
||||
lang: Lang
|
||||
imageError: boolean
|
||||
setImageError: (error: boolean) => void
|
||||
handleClose: () => void
|
||||
}
|
||||
|
||||
export default function StandaloneHotelCardDialog({
|
||||
data,
|
||||
lang,
|
||||
imageError,
|
||||
setImageError,
|
||||
handleClose,
|
||||
}: StandaloneHotelCardProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const [imageError, setImageError] = useState(false)
|
||||
const { data: session } = useSession()
|
||||
const isUserLoggedIn = isValidClientSession(session)
|
||||
const {
|
||||
@@ -57,7 +59,18 @@ export default function StandaloneHotelCardDialog({
|
||||
const altText = images[0]?.metaData?.altText
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.container}>
|
||||
<IconButton
|
||||
theme="Black"
|
||||
style="Muted"
|
||||
className={styles.closeButton}
|
||||
onPress={handleClose}
|
||||
aria-label={intl.formatMessage({
|
||||
defaultMessage: "Close",
|
||||
})}
|
||||
>
|
||||
<MaterialIcon icon="close" size={22} color="CurrentColor" />
|
||||
</IconButton>
|
||||
<HotelCardDialogImage
|
||||
firstImage={firstImage}
|
||||
altText={altText}
|
||||
@@ -67,10 +80,8 @@ export default function StandaloneHotelCardDialog({
|
||||
position="left"
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.name}>
|
||||
<Body textTransform="bold">{name}</Body>
|
||||
</div>
|
||||
<div className={styles.name}>
|
||||
<Body textTransform="bold">{name}</Body>
|
||||
</div>
|
||||
<div className={styles.facilities}>
|
||||
{amenities.slice(0, 3).map((facility) => {
|
||||
@@ -246,6 +257,6 @@ export default function StandaloneHotelCardDialog({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
.container {
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
position: relative;
|
||||
background: var(--Base-Surface-Primary-light-Normal);
|
||||
box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: 220px;
|
||||
padding: var(--Space-x15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.name {
|
||||
height: 48px;
|
||||
max-width: 180px;
|
||||
margin-bottom: var(--Space-x05);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: var(--Space-x1);
|
||||
}
|
||||
|
||||
.facilities {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 var(--Space-x1);
|
||||
}
|
||||
|
||||
.facilitiesItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Space-x05);
|
||||
}
|
||||
|
||||
.prices {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x1);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.priceCard {
|
||||
border-radius: var(--Corner-radius-md);
|
||||
padding: var(--Space-x05) var(--Space-x1);
|
||||
background: var(--Base-Surface-Secondary-light-Normal);
|
||||
margin-top: var(--Space-x1);
|
||||
}
|
||||
|
||||
.pricesContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x1);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.content .button {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
.dialog {
|
||||
padding-bottom: var(--Spacing-x1);
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dialogContainer {
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
border-radius: var(--Corner-radius-md);
|
||||
min-width: 402px;
|
||||
background: var(--Base-Surface-Primary-light-Normal);
|
||||
box-shadow: 0px 0px 8px 3px rgba(0, 0, 0, 0.1);
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dialogContainer[data-type="listing"] {
|
||||
min-width: 358px;
|
||||
}
|
||||
|
||||
.dialogContainer[data-type="listing"] .header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.name {
|
||||
height: 48px;
|
||||
max-width: 180px;
|
||||
margin-bottom: var(--Spacing-x-half);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.closeIcon {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
min-width: 220px;
|
||||
padding: var(--Spacing-x-one-and-half);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dialogContainer[data-type="listing"] .content {
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.facilities {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.dialogContainer[data-type="listing"] .facilities {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
padding-right: 20px;
|
||||
gap: 0 var(--Spacing-x-one-and-half);
|
||||
max-width: 242px;
|
||||
}
|
||||
.dialogContainer[data-type="listing"] .facilities::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
to left,
|
||||
rgba(255, 255, 255, 1),
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.facilitiesItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.dialogContainer[data-type="listing"] .facilitiesItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.priceCard {
|
||||
border-radius: var(--Corner-radius-md);
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
background: var(--Base-Surface-Secondary-light-Normal);
|
||||
margin-top: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.prices {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.bottomContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-top: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
padding-top: var(--Spacing-x2);
|
||||
padding-bottom: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.pricesContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dialogContainer[data-type="listing"] .pricesContainer {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
gap: 0;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.listingPrices {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.perNight {
|
||||
color: var(--Base-Text-Subtle-light-Normal);
|
||||
}
|
||||
|
||||
.content .button {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.dialog {
|
||||
bottom: 32px;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useParams } from "next/navigation"
|
||||
import { useState } from "react"
|
||||
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
|
||||
import ListingHotelCardDialog from "./ListingHotelCardDialog"
|
||||
import StandaloneHotelCardDialog from "./StandaloneHotelCardDialog"
|
||||
|
||||
import styles from "./hotelCardDialog.module.css"
|
||||
|
||||
import type { HotelCardDialogProps } from "@/types/components/hotelReservation/selectHotel/map"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
export default function HotelCardDialog({
|
||||
data,
|
||||
isOpen,
|
||||
type = "standalone",
|
||||
handleClose,
|
||||
}: HotelCardDialogProps) {
|
||||
const params = useParams()
|
||||
const lang = params.lang as Lang
|
||||
const [imageError, setImageError] = useState(false)
|
||||
|
||||
if (!data) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<dialog open={isOpen} className={styles.dialog}>
|
||||
<div className={styles.dialogContainer} data-type={type}>
|
||||
<div onClick={handleClose}>
|
||||
<MaterialIcon icon="close" className={styles.closeIcon} size={22} />
|
||||
</div>
|
||||
|
||||
{type === "standalone" ? (
|
||||
<StandaloneHotelCardDialog
|
||||
data={data}
|
||||
lang={lang}
|
||||
imageError={imageError}
|
||||
setImageError={setImageError}
|
||||
/>
|
||||
) : (
|
||||
<ListingHotelCardDialog
|
||||
data={data}
|
||||
lang={lang}
|
||||
imageError={imageError}
|
||||
setImageError={setImageError}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</dialog>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
.hotelCardDialogListing {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x1);
|
||||
align-items: flex-end;
|
||||
overflow-x: scroll;
|
||||
@@ -17,13 +16,7 @@
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.hotelCardDialogListing > div {
|
||||
.hotelCard {
|
||||
height: 100%;
|
||||
scroll-snap-align: center;
|
||||
}
|
||||
|
||||
.hotelCardDialogListing dialog {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useIntl } from "react-intl"
|
||||
|
||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
||||
|
||||
import HotelCardDialog from "../HotelCardDialog"
|
||||
import ListingHotelCardDialog from "../HotelCardDialog/ListingHotelCardDialog"
|
||||
import { getHotelPins } from "./utils"
|
||||
|
||||
import styles from "./hotelCardDialogListing.module.css"
|
||||
@@ -127,13 +127,9 @@ export default function HotelCardDialogListing({
|
||||
key={data.name}
|
||||
ref={isActive ? activeCardRef : null}
|
||||
data-name={data.name}
|
||||
className={styles.hotelCard}
|
||||
>
|
||||
<HotelCardDialog
|
||||
data={data}
|
||||
isOpen={!!activeHotel}
|
||||
handleClose={deactivate}
|
||||
type="listing"
|
||||
/>
|
||||
<ListingHotelCardDialog data={data} handleClose={deactivate} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1,28 +1,3 @@
|
||||
.advancedMarker {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.dialogContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 32px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 402px;
|
||||
height: 181px;
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
}
|
||||
|
||||
.card.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.dialogContainer {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import {
|
||||
AdvancedMarker,
|
||||
AdvancedMarkerAnchorPoint,
|
||||
InfoWindow,
|
||||
} from "@vis.gl/react-google-maps"
|
||||
import { useCallback } from "react"
|
||||
import { useMediaQuery } from "usehooks-ts"
|
||||
|
||||
import { useHotelsMapStore } from "@/stores/hotels-map"
|
||||
|
||||
import HotelCardDialog from "@/components/HotelReservation/HotelCardDialog"
|
||||
import StandaloneHotelCardDialog from "@/components/HotelReservation/HotelCardDialog/StandaloneHotelCardDialog"
|
||||
import { trackEvent } from "@/utils/tracking/base"
|
||||
|
||||
import HotelPin from "./HotelPin"
|
||||
@@ -18,6 +20,7 @@ import type { HotelListingMapContentProps } from "@/types/components/hotelReserv
|
||||
function HotelListingMapContent({ hotelPins }: HotelListingMapContentProps) {
|
||||
const { activeHotel, hoveredHotel, activate, deactivate, engage, disengage } =
|
||||
useHotelsMapStore()
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)")
|
||||
|
||||
const toggleActiveHotelPin = useCallback(
|
||||
(pinName: string | null, hotelId: string) => {
|
||||
@@ -69,17 +72,22 @@ function HotelListingMapContent({ hotelPins }: HotelListingMapContentProps) {
|
||||
onMouseLeave={() => disengage()}
|
||||
onClick={() => toggleActiveHotelPin(pin.name, pin.operaId)}
|
||||
>
|
||||
<div className={styles.dialogContainer}>
|
||||
<HotelCardDialog
|
||||
isOpen={isActiveOrHovered}
|
||||
handleClose={(event: { stopPropagation: () => void }) => {
|
||||
event.stopPropagation()
|
||||
deactivate()
|
||||
disengage()
|
||||
}}
|
||||
data={pin}
|
||||
/>
|
||||
</div>
|
||||
{isActiveOrHovered && isDesktop && (
|
||||
<InfoWindow
|
||||
position={pin.coordinates}
|
||||
pixelOffset={[0, -24]}
|
||||
headerDisabled={true}
|
||||
shouldFocus={false}
|
||||
>
|
||||
<StandaloneHotelCardDialog
|
||||
data={pin}
|
||||
handleClose={() => {
|
||||
deactivate()
|
||||
disengage()
|
||||
}}
|
||||
/>
|
||||
</InfoWindow>
|
||||
)}
|
||||
<HotelPin
|
||||
isActive={isActiveOrHovered}
|
||||
hotelPrice={hotelPrice}
|
||||
|
||||
@@ -6,6 +6,20 @@
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.mapContainer :global(.gm-style .gm-style-iw-d) {
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
max-height: none !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
.mapContainer :global(.gm-style .gm-style-iw-c) {
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
max-height: none !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
.mapContainer::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user