fix(SW-2721): Facilities only uses 2 columns on viewports >= 768px

Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-05-13 13:35:06 +00:00
parent 5c40e5b0de
commit 74c7c5ccdf
5 changed files with 147 additions and 60 deletions

View File

@@ -17,7 +17,7 @@
transform: rotate(180deg);
}
.grid:not(.allVisible) :nth-child(n + 4) {
.grid:not(.allVisible) > :nth-child(n + 4) {
display: none;
}

View File

@@ -0,0 +1,94 @@
"use client"
import { cx } from "class-variance-authority"
import { useRef, useState } from "react"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { FacilityIcon } from "@/components/SidePeeks/RoomSidePeek/facilityIcon"
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
import styles from "./roomFacilities.module.css"
import type { Room } from "@/types/hotel"
interface RoomFacilitiesProps {
roomFacilities: Room["roomFacilities"]
}
export default function RoomFacilities({
roomFacilities,
}: RoomFacilitiesProps) {
const intl = useIntl()
const showMobileToggleButton = roomFacilities.length > 10
const [allVisibleOnMobile, setAllVisibleOnMobile] = useState(
!showMobileToggleButton
)
const scrollRef = useRef<HTMLHeadingElement>(null)
const mappedFacilities = roomFacilities
.sort((a, b) => a.sortOrder - b.sortOrder)
.map((facility) => {
const facilityName = facility.availableInAllRooms
? facility.name
: intl.formatMessage(
{
defaultMessage: "{facility} (available in some rooms)",
},
{
facility: facility.name,
}
)
return {
...facility,
name: facilityName,
}
})
function handleShowMore() {
if (scrollRef.current && allVisibleOnMobile) {
scrollRef.current.scrollIntoView({ behavior: "smooth" })
}
setAllVisibleOnMobile((state) => !state)
}
return (
<>
<Typography variant="Title/Subtitle/md">
<h3 ref={scrollRef} className={styles.heading}>
{intl.formatMessage({
defaultMessage: "Room amenities",
})}
</h3>
</Typography>
<ul
className={cx(styles.facilities, {
[styles.allVisibleMobile]: allVisibleOnMobile,
})}
>
{mappedFacilities.map((facility) => (
<li className={styles.item} key={facility.name}>
<FacilityIcon
name={facility.icon}
size={24}
color="CurrentColor"
className={styles.icon}
/>
<Typography variant="Body/Paragraph/mdRegular">
<span>{facility.name}</span>
</Typography>
</li>
))}
</ul>
{showMobileToggleButton ? (
<div className={styles.ctaWrapper}>
<ShowMoreButton
loadMoreData={handleShowMore}
showLess={allVisibleOnMobile}
/>
</div>
) : null}
</>
)
}

View File

@@ -0,0 +1,38 @@
.heading {
/* Custom value to make the heading visible with a bit of margin when scrolled programmatically */
scroll-margin-top: 16px;
}
.item {
display: flex;
gap: var(--Spacing-x1);
margin-bottom: var(--Spacing-x-half);
align-items: self-start;
justify-content: flex-start;
color: var(--Text-Secondary);
}
.icon {
flex-shrink: 0;
}
.ctaWrapper {
width: max-content;
}
@media screen and (max-width: 767px) {
.facilities:not(.allVisibleMobile) > :nth-child(n + 11) {
display: none;
}
}
@media screen and (min-width: 768px) {
.facilities {
column-count: 2;
column-gap: var(--Spacing-x2);
}
.ctaWrapper {
display: none;
}
}

View File

@@ -1,4 +1,3 @@
import { cx } from "class-variance-authority"
import Link from "next/link"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
@@ -9,7 +8,6 @@ import { dt } from "@/lib/dt"
import ImageGallery from "@/components/ImageGallery"
import { getBedIconName } from "@/components/SidePeeks/RoomSidePeek/bedIcon"
import { FacilityIcon } from "@/components/SidePeeks/RoomSidePeek/facilityIcon"
import Button from "@/components/TempDesignSystem/Button"
import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
@@ -17,6 +15,7 @@ import { getLang } from "@/i18n/serverContext"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
import { getRoomNameAsParam } from "../../utils"
import RoomFacilities from "./RoomFacilities"
import styles from "./room.module.css"
@@ -95,45 +94,7 @@ export default async function RoomSidePeek({
</div>
<div className={styles.innerContent}>
<Typography variant="Title/Subtitle/md">
<h3>
{intl.formatMessage({
defaultMessage: "Room amenities",
})}
</h3>
</Typography>
<ul className={styles.facilityList}>
{room.roomFacilities
.sort((a, b) => a.sortOrder - b.sortOrder)
.map((facility) => {
const facilityName = facility.availableInAllRooms
? facility.name
: intl.formatMessage(
{
defaultMessage: "{facility} (available in some rooms)",
},
{
facility: facility.name,
}
)
return (
<li className={styles.listItem} key={facility.name}>
<FacilityIcon
name={facility.icon}
size={24}
color="Icon/Default"
/>
<Typography
variant="Body/Paragraph/mdRegular"
className={styles.iconText}
>
<span>{facilityName}</span>
</Typography>
</li>
)
})}
</ul>
<RoomFacilities roomFacilities={room.roomFacilities} />
</div>
<div className={styles.innerContent}>
@@ -155,16 +116,15 @@ export default async function RoomSidePeek({
</div>
<ul className={styles.bedOptions}>
{room.roomTypes.map((roomType) => {
const bedIcon = getBedIconName(roomType.mainBed.type)
const iconName = getBedIconName(roomType.mainBed.type)
return (
<li className={styles.listItem} key={roomType.code}>
<MaterialIcon color="Icon/Default" icon={bedIcon} />
<Typography
variant="Body/Paragraph/mdRegular"
className={cx(styles.iconText, {
[styles.noIcon]: !bedIcon,
})}
>
<MaterialIcon
color="CurrentColor"
icon={iconName}
className={styles.icon}
/>
<Typography variant="Body/Paragraph/mdRegular">
<span>{roomType.mainBed.description}</span>
</Typography>
</li>

View File

@@ -26,11 +26,6 @@
overflow: hidden;
}
.facilityList {
column-count: 2;
column-gap: var(--Spacing-x2);
}
.bedOptions {
display: flex;
flex-direction: column;
@@ -43,6 +38,11 @@
margin-bottom: var(--Spacing-x-half);
align-items: self-start;
justify-content: flex-start;
color: var(--Text-Secondary);
}
.icon {
flex-shrink: 0;
}
.buttonContainer {
@@ -54,8 +54,3 @@
left: 0;
bottom: 0;
}
.iconText {
color: var(--Text-Secondary);
flex-grow: 1;
}