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