Files
web/apps/scandic-web/components/Blocks/CampaignHotelListing/Client.tsx
Erik Tiekstra 0393f7b7b9 feat(SW-2270): Added hotel listing block to campaign overview page
Approved-by: Matilda Landström
2025-06-30 09:29:20 +00:00

132 lines
4.0 KiB
TypeScript

"use client"
import { cx } from "class-variance-authority"
import { useRef, useState } from "react"
import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
import { Button } from "@scandic-hotels/design-system/Button"
import {
MaterialIcon,
type MaterialIconProps,
} from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import HotelListingItem from "./HotelListingItem"
import styles from "./campaignHotelListing.module.css"
import type { HotelDataWithUrl } from "@scandic-hotels/trpc/types/hotel"
interface CampaignHotelListingClientProps {
heading: string
preamble?: string | null
hotels: HotelDataWithUrl[]
visibleCountMobile?: 3 | 6
visibleCountDesktop?: 3 | 6
}
export default function CampaignHotelListingClient({
heading,
preamble,
hotels,
visibleCountMobile = 3,
visibleCountDesktop = 6,
}: CampaignHotelListingClientProps) {
const intl = useIntl()
const isMobile = useMediaQuery("(max-width: 767px)")
const scrollRef = useRef<HTMLElement>(null)
const initialCount = isMobile ? visibleCountMobile : visibleCountDesktop // Initial number of hotels to show
const thresholdCount = initialCount + 3 // This is the threshold at which we start showing the "Show More" button
const showAllThreshold = initialCount * 3 // This is the threshold at which we show the "Show All" button
const incrementCount = initialCount // Number of hotels to increment when the button is clicked
const [visibleCount, setVisibleCount] = useState(() =>
// Set initial visible count based on the number of hotels and the threshold
hotels.length <= thresholdCount ? hotels.length : initialCount
)
// Only show the show more/less button if the length of hotels exceeds the threshold count
const showButton = hotels.length > thresholdCount
// Determine if we are at the stage where the user can click to show all hotels
const canShowAll =
hotels.length > visibleCount &&
(visibleCount + incrementCount > showAllThreshold ||
visibleCount + incrementCount >= hotels.length)
function handleButtonClick() {
if (visibleCount < hotels.length) {
if (canShowAll) {
setVisibleCount(hotels.length)
} else {
setVisibleCount((prev) =>
Math.min(prev + incrementCount, hotels.length)
)
}
} else {
setVisibleCount(initialCount)
if (scrollRef.current) {
scrollRef.current.scrollIntoView({ behavior: "smooth" })
}
}
}
let buttonText = intl.formatMessage({
defaultMessage: "Show more",
})
let iconDirection: MaterialIconProps["icon"] = "keyboard_arrow_down"
if (visibleCount === hotels.length) {
buttonText = intl.formatMessage({
defaultMessage: "Show less",
})
iconDirection = "keyboard_arrow_up"
} else if (canShowAll) {
buttonText = intl.formatMessage({
defaultMessage: "Show all",
})
}
return (
<section className={styles.hotelListingSection} ref={scrollRef}>
<header className={styles.header}>
<Typography variant="Title/Subtitle/lg">
<h3>{heading}</h3>
</Typography>
{preamble ? (
<Typography variant="Body/Paragraph/mdRegular">
<p>{preamble}</p>
</Typography>
) : null}
</header>
<ul className={styles.list}>
{hotels.map(({ hotel, url }, index) => (
<li
key={hotel.id}
className={cx(styles.listItem, {
[styles.hidden]: index >= visibleCount,
})}
>
<HotelListingItem hotel={hotel} url={url} />
</li>
))}
</ul>
{showButton ? (
<Button
variant="Text"
color="Primary"
size="Medium"
typography="Body/Paragraph/mdBold"
onPress={handleButtonClick}
>
<MaterialIcon icon={iconDirection} color="CurrentColor" />
{buttonText}
</Button>
) : null}
</section>
)
}