feat/SW-451-ui-hotel-card (pull request #700)

Feat/SW-451 ui hotel card

* feat(SW-451): initial design

* feat(SW-451): add gallery icon and responsive  design for mobile

* feat(SW-451): refactor name, add routing to sidepeek

* feat(SW-451): add updated design

* feat(SW-451): add tripadvisor chip

* feat(SW-451): fix gallery icon

* feat(SW-451): fix additional falsy value check

* feat(SW-451): fix import type

* feat(SW-451): remove galleryIcon until image data exists

* feat(SW-451): fix css styling

* feat(SW-451): add new design for mobile

* feat(SW-451): add translation

* feat(SW-451): change css to mobile first

* feat(SW-451): change div to article


Approved-by: Matilda Landström
This commit is contained in:
Bianca Widstam
2024-10-22 06:18:40 +00:00
parent b5cb8fe080
commit ea3b86c33e
7 changed files with 231 additions and 7 deletions

View File

@@ -67,7 +67,11 @@ export default async function HotelCard({ hotel }: HotelCardProps) {
)
})}
</div>
<ReadMore hotelId={hotelData.operaId} hotel={hotelData} />
<ReadMore
label={intl.formatMessage({ id: "See hotel details" })}
hotelId={hotelData.operaId}
hotel={hotelData}
/>
</section>
<section className={styles.prices}>
<div>

View File

@@ -34,7 +34,7 @@ function getAmenitiesList(hotel: Hotel) {
return [...detailedAmenities, ...simpleAmenities]
}
export default function ReadMore({ hotel, hotelId }: ReadMoreProps) {
export default function ReadMore({ label, hotel, hotelId }: ReadMoreProps) {
const intl = useIntl()
const [sidePeekOpen, setSidePeekOpen] = useState(false)
@@ -46,11 +46,12 @@ export default function ReadMore({ hotel, hotelId }: ReadMoreProps) {
onPress={() => {
setSidePeekOpen(true)
}}
intent={"text"}
color="burgundy"
intent="text"
theme="base"
wrapping
className={styles.detailsButton}
>
{intl.formatMessage({ id: "See hotel details" })}
{label}
<ChevronRightIcon color="burgundy" />
</Button>
<SidePeek

View File

@@ -0,0 +1,116 @@
.container {
background-color: var(--Base-Surface-Subtle-Normal);
padding: var(--Spacing-x3) var(--Spacing-x2);
}
.wrapper {
display: flex;
margin: 0 auto;
max-width: var(--max-width-navigation);
position: relative;
flex-direction: column;
gap: var(--Spacing-x2);
}
.image {
border-radius: var(--Corner-radius-Medium);
}
.imageWrapper {
position: relative;
overflow: hidden;
height: 200px;
max-width: 360px;
width: 100%;
}
.tripAdvisor {
display: flex;
align-items: center;
gap: var(--Spacing-x-half);
background-color: var(--Base-Surface-Primary-light-Normal);
position: absolute;
left: var(--Spacing-x2);
top: var(--Spacing-x2);
padding: 0 var(--Spacing-x1);
border-radius: var(--Corner-radius-Small);
}
.hotelContent {
display: flex;
flex-direction: column;
}
.hotelInformation {
gap: var(--Spacing-x1);
width: min(607px, 100%);
}
.title {
margin-bottom: var(--Spacing-x1);
}
.body {
margin-top: var(--Spacing-x2);
}
.facilities {
padding: var(--Spacing-x3) 0 var(--Spacing-x-quarter);
gap: var(--Spacing-x-one-and-half);
}
.facilityList {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--Spacing-x-one-and-half);
padding-bottom: var(--Spacing-x1);
}
.facilitiesItem {
display: flex;
align-items: center;
gap: var(--Spacing-x1);
}
.facilityName {
color: var(--UI-Text-Medium-contrast);
}
@media screen and (min-width: 1367px) {
.container {
padding: var(--Spacing-x4) var(--Spacing-x5);
}
.hotelContent {
gap: var(--Spacing-x6);
}
.hotelInformation {
padding-right: var(--Spacing-x3);
}
.wrapper {
gap: var(--Spacing-x3);
flex-direction: row;
}
.facilities {
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x-half);
}
.facilityList {
gap: var(--Spacing-x1);
padding-bottom: var(--Spacing-x-half);
}
.facilityTitle {
display: none;
}
.hotelContent {
flex-direction: row;
align-items: center;
}
.imageWrapper {
align-self: center;
}
}

View File

@@ -0,0 +1,97 @@
"use client"
import { useIntl } from "react-intl"
import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data"
import TripAdvisorIcon from "@/components/Icons/TripAdvisor"
import Image from "@/components/Image"
import Divider from "@/components/TempDesignSystem/Divider"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title"
import ReadMore from "../../ReadMore"
import styles from "./hotelInfoCard.module.css"
import type { HotelInfoCardProps } from "@/types/components/hotelReservation/selectRate/hotelInfoCardProps"
export default function HotelInfoCard({ hotelData }: HotelInfoCardProps) {
const hotelAttributes = hotelData?.data.attributes
const intl = useIntl()
const sortedFacilities = hotelAttributes?.detailedFacilities
.sort((a, b) => b.sortOrder - a.sortOrder)
.slice(0, 5)
return (
<article className={styles.container}>
{hotelAttributes && (
<section className={styles.wrapper}>
<div className={styles.imageWrapper}>
<Image
src={hotelAttributes.hotelContent.images.imageSizes.medium}
alt={hotelAttributes.hotelContent.images.metaData.altText}
className={styles.image}
fill
/>
{hotelAttributes.ratings?.tripAdvisor && (
<div className={styles.tripAdvisor}>
<TripAdvisorIcon color="burgundy" />
<Caption color="burgundy">
{hotelAttributes.ratings.tripAdvisor.rating}
</Caption>
</div>
)}
{/* TODO: gallery icon and image carousel */}
</div>
<div className={styles.hotelContent}>
<div className={styles.hotelInformation}>
<Title
level="h3"
textTransform="uppercase"
className={styles.title}
>
{hotelAttributes.name}
</Title>
<Caption color="uiTextMediumContrast">
{`${hotelAttributes.address.streetAddress}, ${hotelAttributes.address.city}${hotelAttributes.location.distanceToCentre} ${intl.formatMessage({ id: "km to city center" })}`}
</Caption>
<Body color="uiTextHighContrast" className={styles.body}>
{hotelAttributes.hotelContent.texts.descriptions.medium}
</Body>
</div>
<Divider color="subtle" variant="vertical" />
<div className={styles.facilities}>
<div className={styles.facilityList}>
<Body textTransform="bold" className={styles.facilityTitle}>
{intl.formatMessage({ id: "At the hotel" })}
</Body>
{sortedFacilities?.map((facility) => {
const IconComponent = mapFacilityToIcon(facility.name)
return (
<div className={styles.facilitiesItem} key={facility.id}>
{IconComponent && (
<IconComponent
className={styles.facilitiesIcon}
color="grey80"
/>
)}
<Caption className={styles.facilityName}>
{facility.name}
</Caption>
</div>
)
})}
</div>
<ReadMore
label={intl.formatMessage({ id: "Show all amenities" })}
hotelId={hotelAttributes.operaId}
hotel={hotelAttributes}
/>
</div>
</div>
</section>
)}
</article>
)
}