Merged in feat/SW-1936-ui-room-card (pull request #2268)
feat(SW-1936): update room card ui * feat(SW-1936): update room card ui Approved-by: Linus Flood
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import type { RoomSizeProps } from "@/types/components/hotelReservation/selectRate/roomListItem"
|
import type { RoomSizeProps } from "@/types/components/hotelReservation/selectRate/roomListItem"
|
||||||
|
|
||||||
@@ -15,34 +15,42 @@ export default function RoomSize({ roomSize }: RoomSizeProps) {
|
|||||||
if (roomSize.min === roomSize.max) {
|
if (roomSize.min === roomSize.max) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
<Caption color="uiTextMediumContrast">∙</Caption>
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||||
<Caption color="uiTextMediumContrast">
|
<p>∙</p>
|
||||||
{intl.formatMessage(
|
</Typography>
|
||||||
{
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
defaultMessage: "{roomSize} m²",
|
<h4>
|
||||||
},
|
{intl.formatMessage(
|
||||||
{ roomSize: roomSize.min }
|
{
|
||||||
)}
|
defaultMessage: "{roomSize} m²",
|
||||||
</Caption>
|
},
|
||||||
|
{ roomSize: roomSize.min }
|
||||||
|
)}
|
||||||
|
</h4>
|
||||||
|
</Typography>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
<Caption color="uiTextMediumContrast">∙</Caption>
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||||
<Caption color="uiTextMediumContrast">
|
<p>∙</p>
|
||||||
{intl.formatMessage(
|
</Typography>
|
||||||
{
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
defaultMessage: "{roomSizeMin} - {roomSizeMax} m²",
|
<h4>
|
||||||
},
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
roomSizeMin: roomSize.min,
|
defaultMessage: "{roomSizeMin} - {roomSizeMax} m²",
|
||||||
roomSizeMax: roomSize.max,
|
},
|
||||||
}
|
{
|
||||||
)}
|
roomSizeMin: roomSize.min,
|
||||||
</Caption>
|
roomSizeMax: roomSize.max,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</h4>
|
||||||
|
</Typography>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|||||||
|
|
||||||
import useSidePeekStore from "@/stores/sidepeek"
|
import useSidePeekStore from "@/stores/sidepeek"
|
||||||
|
|
||||||
|
import styles from "./details.module.css"
|
||||||
|
|
||||||
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
|
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
|
||||||
import type { ToggleSidePeekProps } from "@/types/components/hotelReservation/toggleSidePeekProps"
|
import type { ToggleSidePeekProps } from "@/types/components/hotelReservation/toggleSidePeekProps"
|
||||||
|
|
||||||
@@ -30,8 +32,10 @@ export default function ToggleSidePeek({
|
|||||||
variant="Text"
|
variant="Text"
|
||||||
wrapping
|
wrapping
|
||||||
typography="Body/Supporting text (caption)/smBold"
|
typography="Body/Supporting text (caption)/smBold"
|
||||||
|
color="Inverted"
|
||||||
|
className={styles.sidePeekButton}
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ defaultMessage: "Room details" })}
|
{intl.formatMessage({ defaultMessage: "View room details" })}
|
||||||
<MaterialIcon icon="chevron_right" size={20} color="CurrentColor" />
|
<MaterialIcon icon="chevron_right" size={20} color="CurrentColor" />
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,27 +1,18 @@
|
|||||||
.specification {
|
.specification {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
justify-content: center;
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Space-x1);
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggleSidePeek {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.specification .toggleSidePeek button {
|
|
||||||
padding: 0;
|
|
||||||
text-align: start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomDetails {
|
.roomDetails {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x1);
|
text-align: center;
|
||||||
padding-bottom: var(--Spacing-x-half);
|
gap: var(--Space-x1);
|
||||||
|
padding-bottom: var(--Space-x05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.sidePeekButton {
|
||||||
display: inline-block;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useRatesStore } from "@/stores/select-rate"
|
import { useRatesStore } from "@/stores/select-rate"
|
||||||
|
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|
||||||
|
|
||||||
import RoomSize from "./RoomSize"
|
import RoomSize from "./RoomSize"
|
||||||
import ToggleSidePeek from "./ToggleSidePeek"
|
|
||||||
|
|
||||||
import styles from "./details.module.css"
|
import styles from "./details.module.css"
|
||||||
|
|
||||||
export default function Details({ roomTypeCode }: { roomTypeCode: string }) {
|
export default function Details({ roomTypeCode }: { roomTypeCode: string }) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { hotelId, roomCategories } = useRatesStore((state) => ({
|
const roomCategories = useRatesStore((state) => state.roomCategories)
|
||||||
hotelId: state.booking.hotelId,
|
|
||||||
roomCategories: state.roomCategories,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const selectedRoom = roomCategories.find((roomCategory) =>
|
const selectedRoom = roomCategories.find((roomCategory) =>
|
||||||
roomCategory.roomTypes.find((roomType) => roomType.code === roomTypeCode)
|
roomCategory.roomTypes.find((roomType) => roomType.code === roomTypeCode)
|
||||||
@@ -28,40 +23,34 @@ export default function Details({ roomTypeCode }: { roomTypeCode: string }) {
|
|||||||
<>
|
<>
|
||||||
<div className={styles.specification}>
|
<div className={styles.specification}>
|
||||||
{occupancy && (
|
{occupancy && (
|
||||||
<Caption color="uiTextMediumContrast">
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
{occupancy.max === occupancy.min
|
<h4>
|
||||||
? intl.formatMessage(
|
{occupancy.max === occupancy.min
|
||||||
{
|
? intl.formatMessage(
|
||||||
defaultMessage:
|
{
|
||||||
"{guests, plural, one {# guest} other {# guests}}",
|
defaultMessage:
|
||||||
},
|
"{guests, plural, one {# guest} other {# guests}}",
|
||||||
{ guests: occupancy.max }
|
},
|
||||||
)
|
{ guests: occupancy.max }
|
||||||
: intl.formatMessage(
|
)
|
||||||
{
|
: intl.formatMessage(
|
||||||
defaultMessage: "{min}-{max} guests",
|
{
|
||||||
},
|
defaultMessage: "{min}-{max} guests",
|
||||||
{
|
},
|
||||||
min: occupancy.min,
|
{
|
||||||
max: occupancy.max,
|
min: occupancy.min,
|
||||||
}
|
max: occupancy.max,
|
||||||
)}
|
}
|
||||||
</Caption>
|
)}
|
||||||
|
</h4>
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<RoomSize roomSize={roomSize} />
|
<RoomSize roomSize={roomSize} />
|
||||||
<div className={styles.toggleSidePeek}>
|
|
||||||
{roomTypeCode && (
|
|
||||||
<ToggleSidePeek hotelId={hotelId} roomTypeCode={roomTypeCode} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.roomDetails}>
|
<div className={styles.roomDetails}>
|
||||||
<Subtitle className={styles.name} type="two">
|
<Typography variant="Title/Subtitle/lg">
|
||||||
{name}
|
<h2>{name}</h2>
|
||||||
</Subtitle>
|
</Typography>
|
||||||
{/* Out of scope for now
|
|
||||||
<Body>{descriptions?.short}</Body>
|
|
||||||
*/}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
.message {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: var(--Space-x1);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
@@ -2,14 +2,18 @@
|
|||||||
import { useSession } from "next-auth/react"
|
import { useSession } from "next-auth/react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useRatesStore } from "@/stores/select-rate"
|
import { useRatesStore } from "@/stores/select-rate"
|
||||||
|
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
||||||
import { isValidClientSession } from "@/utils/clientSession"
|
import { isValidClientSession } from "@/utils/clientSession"
|
||||||
|
|
||||||
import { getBreakfastMessage } from "./getBreakfastMessage"
|
import { getBreakfastMessage } from "./getBreakfastMessage"
|
||||||
|
|
||||||
|
import styles from "./breakfastMessage.module.css"
|
||||||
|
|
||||||
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
|
import { BookingCodeFilterEnum } from "@/types/enums/bookingCodeFilter"
|
||||||
|
|
||||||
export default function BreakfastMessage({
|
export default function BreakfastMessage({
|
||||||
@@ -61,8 +65,12 @@ export default function BreakfastMessage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<div className={styles.message}>
|
||||||
<Caption color="uiTextHighContrast">{breakfastMessage}</Caption>
|
<Divider color="borderDividerSubtle" />
|
||||||
</span>
|
<Typography variant={"Body/Supporting text (caption)/smRegular"}>
|
||||||
|
<p>{breakfastMessage}</p>
|
||||||
|
</Typography>
|
||||||
|
<Divider color="borderDividerSubtle" />
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,3 +30,19 @@ div[data-multiroom="true"] .imageContainer {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggleSidePeek {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
color: var(--Component-Button-Brand-Secondary-On-fill-Inverted);
|
||||||
|
background-color: var(--Surface-Brand-Primary-1-OnSurface-Default);
|
||||||
|
height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory {
|
||||||
|
color: var(--Text-Interactive-Default);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useRatesStore } from "@/stores/select-rate"
|
import { useRatesStore } from "@/stores/select-rate"
|
||||||
|
|
||||||
import { IconForFeatureCode } from "@/components/HotelReservation/utils"
|
import { IconForFeatureCode } from "@/components/HotelReservation/utils"
|
||||||
import ImageGallery from "@/components/ImageGallery"
|
import ImageGallery from "@/components/ImageGallery"
|
||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
|
||||||
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
import { useRoomContext } from "@/contexts/SelectRate/Room"
|
||||||
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
|
||||||
|
|
||||||
|
import ToggleSidePeek from "../Details/ToggleSidePeek"
|
||||||
|
|
||||||
import styles from "./image.module.css"
|
import styles from "./image.module.css"
|
||||||
|
|
||||||
import type { RoomListItemImageProps } from "@/types/components/hotelReservation/selectRate/roomListItem"
|
import type { RoomListItemImageProps } from "@/types/components/hotelReservation/selectRate/roomListItem"
|
||||||
@@ -21,7 +24,10 @@ export default function RoomImage({
|
|||||||
}: RoomListItemImageProps) {
|
}: RoomListItemImageProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { selectedPackages } = useRoomContext()
|
const { selectedPackages } = useRoomContext()
|
||||||
const roomCategories = useRatesStore((state) => state.roomCategories)
|
const { roomCategories, hotelId } = useRatesStore((state) => ({
|
||||||
|
roomCategories: state.roomCategories,
|
||||||
|
hotelId: state.booking.hotelId,
|
||||||
|
}))
|
||||||
|
|
||||||
const showLowInventory = roomsLeft > 0 && roomsLeft < 5
|
const showLowInventory = roomsLeft > 0 && roomsLeft < 5
|
||||||
|
|
||||||
@@ -36,14 +42,16 @@ export default function RoomImage({
|
|||||||
<div className={styles.chipContainer}>
|
<div className={styles.chipContainer}>
|
||||||
{showLowInventory ? (
|
{showLowInventory ? (
|
||||||
<span className={styles.chip}>
|
<span className={styles.chip}>
|
||||||
<Footnote color="burgundy" textTransform="uppercase">
|
<Typography variant="Tag/sm">
|
||||||
{intl.formatMessage(
|
<p className={styles.inventory}>
|
||||||
{
|
{intl.formatMessage(
|
||||||
defaultMessage: "{amount, number} left",
|
{
|
||||||
},
|
defaultMessage: "{amount, number} left",
|
||||||
{ amount: roomsLeft }
|
},
|
||||||
)}
|
{ amount: roomsLeft }
|
||||||
</Footnote>
|
)}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
{roomPackages
|
{roomPackages
|
||||||
@@ -56,7 +64,17 @@ export default function RoomImage({
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<ImageGallery images={galleryImages} title={roomType} fill />
|
<ImageGallery
|
||||||
|
images={galleryImages}
|
||||||
|
title={roomType}
|
||||||
|
fill
|
||||||
|
imageCountPosition="top"
|
||||||
|
/>
|
||||||
|
<div className={styles.toggleSidePeek}>
|
||||||
|
{roomTypeCode && (
|
||||||
|
<ToggleSidePeek hotelId={hotelId} roomTypeCode={roomTypeCode} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,16 @@
|
|||||||
color: var(--Text-Inverted);
|
color: var(--Text-Inverted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.imageCountBottom {
|
||||||
|
bottom: var(--Space-x2);
|
||||||
|
top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageCountTop {
|
||||||
|
top: var(--Space-x2);
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.triggerArea {
|
.triggerArea {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ function ImageGallery({
|
|||||||
height = 280,
|
height = 280,
|
||||||
sizes,
|
sizes,
|
||||||
hideLabel,
|
hideLabel,
|
||||||
|
imageCountPosition = "bottom",
|
||||||
}: ImageGalleryProps) {
|
}: ImageGalleryProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
@@ -44,7 +45,13 @@ function ImageGallery({
|
|||||||
{...imageProps}
|
{...imageProps}
|
||||||
/>
|
/>
|
||||||
<Typography variant={"Body/Supporting text (caption)/smRegular"}>
|
<Typography variant={"Body/Supporting text (caption)/smRegular"}>
|
||||||
<span className={styles.imageCount}>
|
<span
|
||||||
|
className={`${styles.imageCount} ${
|
||||||
|
imageCountPosition === "top"
|
||||||
|
? styles.imageCountTop
|
||||||
|
: styles.imageCountBottom
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<MaterialIcon icon="filter" color="Icon/Inverted" size={16} />
|
<MaterialIcon icon="filter" color="Icon/Inverted" size={16} />
|
||||||
<span>{images.length}</span>
|
<span>{images.length}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ export type ImageGalleryProps = {
|
|||||||
height?: number
|
height?: number
|
||||||
sizes?: string
|
sizes?: string
|
||||||
hideLabel?: boolean
|
hideLabel?: boolean
|
||||||
|
imageCountPosition?: "top" | "bottom"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user