Feat/BOOK-63 hotel subpages branding

* feat(BOOK-63): Replaced css variables and components to apply hotel branding on subpages
* feat(BOOK-63): Replaced css variables and components to apply hotel branding on hotel page map view

Approved-by: Christel Westerberg
Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-11-05 08:30:55 +00:00
parent 7fc49428c7
commit 3a38e99a71
47 changed files with 524 additions and 393 deletions

View File

@@ -5,6 +5,7 @@ import {
DEFAULT_THEME, DEFAULT_THEME,
getThemeByHotel, getThemeByHotel,
} from "@scandic-hotels/common/utils/theme" } from "@scandic-hotels/common/utils/theme"
import { setTheme } from "@scandic-hotels/common/utils/theme/serverContext"
import { env } from "@/env/server" import { env } from "@/env/server"
import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests" import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
@@ -44,6 +45,8 @@ export default async function HotelPagePage(
? getThemeByHotel(hotelPageData.hotel_page_id, hotelData.hotel.hotelType) ? getThemeByHotel(hotelPageData.hotel_page_id, hotelData.hotel.hotelType)
: DEFAULT_THEME : DEFAULT_THEME
setTheme(hotelTheme)
if (searchParams.subpage) { if (searchParams.subpage) {
return ( return (
<div className={hotelTheme}> <div className={hotelTheme}>
@@ -59,11 +62,7 @@ export default async function HotelPagePage(
} else { } else {
return ( return (
<div className={cx(styles.hotelPage, hotelTheme)}> <div className={cx(styles.hotelPage, hotelTheme)}>
<HotelPage <HotelPage hotelData={hotelData} hotelPageData={hotelPageData} />
theme={hotelTheme}
hotelData={hotelData}
hotelPageData={hotelPageData}
/>
</div> </div>
) )
} }

View File

@@ -12,6 +12,7 @@ import { SessionProvider } from "next-auth/react"
import StorageCleaner from "@scandic-hotels/booking-flow/components/EnterDetails/StorageCleaner" import StorageCleaner from "@scandic-hotels/booking-flow/components/EnterDetails/StorageCleaner"
import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs" import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs"
import { Lang } from "@scandic-hotels/common/constants/language" import { Lang } from "@scandic-hotels/common/constants/language"
import { DEFAULT_THEME } from "@scandic-hotels/common/utils/theme"
import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler" import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler"
import TrpcProvider from "@/lib/trpc/Provider" import TrpcProvider from "@/lib/trpc/Provider"
@@ -63,7 +64,7 @@ export default async function RootLayout(
window.dataLayer = window.dataLayer || [] window.dataLayer = window.dataLayer || []
`}</Script> `}</Script>
</head> </head>
<body className="scandic"> <body className={DEFAULT_THEME}>
<div className="root"> <div className="root">
<SessionProvider basePath="/api/web/auth"> <SessionProvider basePath="/api/web/auth">
<ClientIntlProvider <ClientIntlProvider

View File

@@ -94,9 +94,17 @@
} }
.usp li:before { .usp li:before {
content: url("/_static/icons/heart.svg"); content: "";
position: relative; position: relative;
top: 3px; top: 3px;
display: inline-flex;
flex-shrink: 0;
width: 16px;
height: 16px;
background-color: var(--Icon-Accent);
mask-image: url("/_static/icons/heart.svg");
mask-size: contain;
mask-repeat: no-repeat;
} }
.primaryButton { .primaryButton {

View File

@@ -8,7 +8,7 @@
} }
.activeCard { .activeCard {
border: 1px solid var(--Border-Interactive-Selected); border: 1px solid var(--Border-Interactive-Active);
} }
.content { .content {

View File

@@ -1,5 +1,5 @@
.sidebar { .sidebar {
background-color: var(--Base-Surface-Primary-light-Normal); background-color: var(--Background-Primary);
z-index: 2; z-index: 2;
} }
@@ -36,7 +36,7 @@
grid-template-columns: 1fr max-content; grid-template-columns: 1fr max-content;
align-items: center; align-items: center;
gap: var(--Space-x2); gap: var(--Space-x2);
background-color: var(--Base-Surface-Primary-light-Normal); background-color: var(--Background-Primary);
border-width: 0; border-width: 0;
color: var(--Text-Default); color: var(--Text-Default);
width: 100%; width: 100%;
@@ -86,11 +86,12 @@
.sidebarToggle { .sidebarToggle {
position: relative; position: relative;
color: var(--Text-Secondary); color: var(--Text-Secondary);
background-color: var(--Base-Surface-Primary-light-Normal); background-color: var(--Background-Primary);
border-width: 0; border-width: 0;
margin: var(--Space-x2) 0; margin: var(--Space-x2) 0;
padding: var(--Space-x2); padding: var(--Space-x2);
width: 100%; width: 100%;
cursor: pointer;
} }
.sidebarToggle::before { .sidebarToggle::before {
@@ -124,7 +125,7 @@
width: 40vw; width: 40vw;
min-width: 10rem; min-width: 10rem;
max-width: 26.25rem; max-width: 26.25rem;
background-color: var(--Base-Surface-Primary-light-Normal); background-color: var(--Background-Primary);
} }
.sidebarToggle { .sidebarToggle {

View File

@@ -5,13 +5,11 @@
display: flex; display: flex;
height: var(--hotel-map-height); height: var(--hotel-map-height);
width: 100dvw; width: 100dvw;
background-color: var(--Base-Surface-Primary-light-Normal); background-color: var(--Background-Default);
z-index: 1; z-index: 1;
} }
.closeButton { .closeButton {
box-shadow: var(--button-box-shadow); box-shadow: var(--button-box-shadow);
color: var(--Component-Button-Inverted-On-fill-Default);
pointer-events: initial; pointer-events: initial;
gap: var(--Space-x05);
} }

View File

@@ -14,10 +14,21 @@
list-style-type: none; list-style-type: none;
} }
.heartList > li::before { .heartList > li {
content: url("/_static/icons/heart.svg"); display: flex;
position: relative; gap: var(--Space-x1);
height: 8px;
top: 3px; &::before {
margin-right: var(--Space-x1); content: "";
position: relative;
top: 3px;
display: inline-flex;
flex-shrink: 0;
width: 16px;
height: 16px;
background-color: var(--Icon-Accent);
mask-image: url("/_static/icons/heart.svg");
mask-size: contain;
mask-repeat: no-repeat;
}
} }

View File

@@ -5,7 +5,8 @@ import { Suspense } from "react"
import { dt } from "@scandic-hotels/common/dt" import { dt } from "@scandic-hotels/common/dt"
import { safeTry } from "@scandic-hotels/common/utils/safeTry" import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { DEFAULT_THEME, type Theme } from "@scandic-hotels/common/utils/theme" import { DEFAULT_THEME } from "@scandic-hotels/common/utils/theme"
import { getTheme } from "@scandic-hotels/common/utils/theme/serverContext"
import { Alert } from "@scandic-hotels/design-system/Alert" import { Alert } from "@scandic-hotels/design-system/Alert"
import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK" import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
import { type HotelPageData } from "@scandic-hotels/trpc/types/hotelPage" import { type HotelPageData } from "@scandic-hotels/trpc/types/hotelPage"
@@ -58,16 +59,15 @@ import { HotelHashValues } from "@/types/enums/hotelPage"
interface HotelPageProps { interface HotelPageProps {
hotelData: HotelData hotelData: HotelData
hotelPageData: HotelPageData hotelPageData: HotelPageData
theme: Theme
} }
export default async function HotelPage({ export default async function HotelPage({
hotelData, hotelData,
hotelPageData, hotelPageData,
theme,
}: HotelPageProps) { }: HotelPageProps) {
const lang = await getLang() const lang = await getLang()
const intl = await getIntl() const intl = await getIntl()
const hotelTheme = getTheme()
const [meetingRoomsData] = await safeTry( const [meetingRoomsData] = await safeTry(
getMeetingRooms({ hotelId: hotelData.hotel.operaId, language: lang }) getMeetingRooms({ hotelId: hotelData.hotel.operaId, language: lang })
@@ -146,7 +146,7 @@ export default async function HotelPage({
) )
const trackingHotelData = getTrackingHotelData(hotelData.hotel) const trackingHotelData = getTrackingHotelData(hotelData.hotel)
const isThemed = theme !== DEFAULT_THEME const isThemed = hotelTheme !== DEFAULT_THEME
return ( return (
<div className={styles.pageContainer}> <div className={styles.pageContainer}>
@@ -226,7 +226,7 @@ export default async function HotelPage({
healthAndWellness={healthAndWellness ?? null} healthAndWellness={healthAndWellness ?? null}
pageSections={pageSections} pageSections={pageSections}
activities={activities} activities={activities}
hotelTheme={theme} hotelTheme={hotelTheme}
/> />
{campaignsBlock ? ( {campaignsBlock ? (
<HotelCampaigns <HotelCampaigns

View File

@@ -46,12 +46,23 @@
list-style-type: none; list-style-type: none;
} }
.listItem::before { .listItem {
content: url("/_static/icons/heart.svg"); display: flex;
position: relative; gap: var(--Space-x1);
height: 8px;
top: 3px; &::before {
margin-right: var(--Space-x1); content: "";
position: relative;
top: 3px;
display: inline-flex;
flex-shrink: 0;
width: 16px;
height: 16px;
background-color: var(--Icon-Accent);
mask-image: url("/_static/icons/heart.svg");
mask-size: contain;
mask-repeat: no-repeat;
}
} }
@media screen and (min-width: 1367px) { @media screen and (min-width: 1367px) {

View File

@@ -72,21 +72,18 @@ export default async function AccessibilitySubpage({
<Typography variant="Title/Subtitle/md"> <Typography variant="Title/Subtitle/md">
<h2>{accessibilityGroup.name}</h2> <h2>{accessibilityGroup.name}</h2>
</Typography> </Typography>
<ul className={styles.list}> <Typography variant="Body/Paragraph/mdRegular">
{accessibilityGroup.specialNeeds.map((groupItem) => ( <ul className={styles.list}>
<Typography {accessibilityGroup.specialNeeds.map((groupItem) => (
key={groupItem.name} <li key={groupItem.name} className={styles.listItem}>
variant="Body/Paragraph/mdRegular"
>
<li className={styles.listItem}>
{groupItem.details {groupItem.details
? // eslint-disable-next-line formatjs/no-literal-string-in-jsx ? // eslint-disable-next-line formatjs/no-literal-string-in-jsx
`${groupItem.name}: ${groupItem.details}` `${groupItem.name}: ${groupItem.details}`
: groupItem.name} : groupItem.name}
</li> </li>
</Typography> ))}
))} </ul>
</ul> </Typography>
</div> </div>
))} ))}
</div> </div>

View File

@@ -1,7 +1,11 @@
.header { .header {
display: grid; display: grid;
background-color: var(--Base-Surface-Subtle-Normal); background-color: var(--Background-Primary);
padding-bottom: var(--Space-x4); padding-bottom: var(--Space-x4);
&.isThemed {
gap: var(--Space-x3);
}
} }
.heroWrapper { .heroWrapper {

View File

@@ -1,5 +1,9 @@
import { cx } from "class-variance-authority"
import { Suspense } from "react" import { Suspense } from "react"
import { DEFAULT_THEME } from "@scandic-hotels/common/utils/theme"
import { getTheme } from "@scandic-hotels/common/utils/theme/serverContext"
import Breadcrumbs from "@/components/Breadcrumbs" import Breadcrumbs from "@/components/Breadcrumbs"
import Hero from "@/components/Hero" import Hero from "@/components/Hero"
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
@@ -17,10 +21,16 @@ export default async function HeroHeader({
breadcrumbsTitle, breadcrumbsTitle,
heroImage, heroImage,
}: HeroHeaderProps) { }: HeroHeaderProps) {
const hotelTheme = getTheme()
const isThemed = hotelTheme !== DEFAULT_THEME
return ( return (
<div className={styles.header}> <div className={cx(styles.header, { [styles.isThemed]: isThemed })}>
<Suspense fallback={<BreadcrumbsSkeleton size="contentWidth" />}> <Suspense fallback={<BreadcrumbsSkeleton size="contentWidth" />}>
<Breadcrumbs subpageTitle={breadcrumbsTitle} size="contentWidth" /> <Breadcrumbs
subpageTitle={breadcrumbsTitle}
size="contentWidth"
isThemed={isThemed}
/>
</Suspense> </Suspense>
{heroImage ? ( {heroImage ? (

View File

@@ -1,21 +1,39 @@
.ul, .ul,
.ol { .ol {
padding: var(--Space-x2) var(--Space-x0); padding: 0;
display: grid; margin: var(--Space-x2) 0;
gap: var(--Space-x1);
margin-left: var(--Space-x2);
}
.ol > li::marker {
color: var(--Primary-Light-On-Surface-Accent);
} }
.li { .li {
margin-left: var(--Space-x3); display: flex;
gap: var(--Space-x1);
* {
display: inline;
margin: 0;
}
} }
.li > p { .ul {
display: inline; list-style-type: none;
.li::before {
content: "";
position: relative;
top: 3px;
display: inline-flex;
flex-shrink: 0;
width: 16px;
height: 16px;
background-color: var(--Icon-Accent);
mask-image: url("/_static/icons/heart.svg");
mask-size: contain;
mask-repeat: no-repeat;
}
}
.ol > .li::marker {
color: var(--Icon-Accent);
} }
.heading { .heading {
@@ -27,8 +45,8 @@
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.ol:has(li:nth-last-child(n + 5)), .ol:has(.li:nth-last-child(n + 5)),
.ul:has(li:nth-last-child(n + 5)) { .ul:has(.li:nth-last-child(n + 5)) {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-auto-flow: column; grid-auto-flow: column;
} }

View File

@@ -1,8 +1,8 @@
import { ElementType } from "domelementtype" import { ElementType } from "domelementtype"
import parse, { type DOMNode, Element, type Text } from "html-react-parser" import parse, { type DOMNode, Element, type Text } from "html-react-parser"
import Link from "@scandic-hotels/design-system/OldDSLink"
import Table from "@scandic-hotels/design-system/Table" import Table from "@scandic-hotels/design-system/Table"
import { TextLink } from "@scandic-hotels/design-system/TextLink"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { NodeNames } from "./utils" import { NodeNames } from "./utils"
@@ -98,16 +98,14 @@ function renderNode(domNode: Node, idx: number) {
return <span>{renderChildren(domNode)}</span> return <span>{renderChildren(domNode)}</span>
} }
return ( return (
<Link <TextLink
key={domNode.name + idx} key={domNode.name + idx}
color="Text/Interactive/Secondary"
textDecoration="underline"
weight="bold"
target="_blank" //Always open in new tab target="_blank" //Always open in new tab
href={domNode.attribs.href} href={domNode.attribs.href}
isInline
> >
{renderChildren(domNode)} {renderChildren(domNode)}
</Link> </TextLink>
) )
case NodeNames.ul: case NodeNames.ul:
@@ -117,19 +115,23 @@ function renderNode(domNode: Node, idx: number) {
numberOfRows = Math.ceil(half) numberOfRows = Math.ceil(half)
} }
return ( return (
<ul <Typography
key={domNode.name + idx} key={domNode.name + idx}
className={styles.ul} variant="Body/Paragraph/mdRegular"
style={
numberOfRows
? {
gridTemplateRows: `repeat(${numberOfRows}, auto)`,
}
: {}
}
> >
{renderChildren(domNode)} <ul
</ul> className={styles.ul}
style={
numberOfRows
? {
gridTemplateRows: `repeat(${numberOfRows}, auto)`,
}
: {}
}
>
{renderChildren(domNode)}
</ul>
</Typography>
) )
case NodeNames.ol: case NodeNames.ol:
@@ -139,23 +141,31 @@ function renderNode(domNode: Node, idx: number) {
numberOfOlRows = Math.ceil(half) numberOfOlRows = Math.ceil(half)
} }
return ( return (
<ol <Typography
key={domNode.name + idx} key={domNode.name + idx}
className={styles.ol} variant="Body/Paragraph/mdRegular"
style={
numberOfOlRows
? {
gridTemplateRows: `repeat(${numberOfOlRows}, auto)`,
}
: {}
}
> >
{renderChildren(domNode)} <ol
</ol> className={styles.ol}
style={
numberOfOlRows
? {
gridTemplateRows: `repeat(${numberOfOlRows}, auto)`,
}
: {}
}
>
{renderChildren(domNode)}
</ol>
</Typography>
) )
case NodeNames.li: case NodeNames.li:
return <li key={domNode.name + idx}>{renderChildren(domNode)}</li> return (
<li className={styles.li} key={domNode.name + idx}>
{renderChildren(domNode)}
</li>
)
case NodeNames.td: case NodeNames.td:
return ( return (
@@ -208,13 +218,24 @@ function renderNode(domNode: Node, idx: number) {
) )
case NodeNames.em: case NodeNames.em:
return <em>{renderChildren(domNode)}</em> return <em key={domNode.name + idx}>{renderChildren(domNode)}</em>
case NodeNames.strong: case NodeNames.strong:
return <strong>{renderChildren(domNode)}</strong> return (
<Typography variant="Body/Paragraph/mdBold" key={domNode.name + idx}>
<strong>{renderChildren(domNode)}</strong>
</Typography>
)
case NodeNames.span: case NodeNames.span:
return <span>{renderChildren(domNode)}</span> return (
<Typography
variant="Body/Paragraph/mdRegular"
key={domNode.name + idx}
>
<span>{renderChildren(domNode)}</span>
</Typography>
)
} }
} else if (domNode.type === ElementType.Text) { } else if (domNode.type === ElementType.Text) {
return domNode.data return domNode.data

View File

@@ -2,13 +2,12 @@
import { useState } from "react" import { useState } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Image from "@scandic-hotels/design-system/Image" import Image from "@scandic-hotels/design-system/Image"
import ImageFallback from "@scandic-hotels/design-system/ImageFallback"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import ShowMoreButton from "../ShowMoreButton" import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
import { translateRoomLighting, translateSeatingType } from "./utils" import { translateRoomLighting, translateSeatingType } from "./utils"
import styles from "./meetingRoomCard.module.css" import styles from "./meetingRoomCard.module.css"
@@ -34,72 +33,51 @@ export default function MeetingRoomCard({ room }: MeetingRoomCardProps) {
return ( return (
<article className={styles.card}> <article className={styles.card}>
{image?.src ? ( <Image
<Image src={image?.src || ""}
src={image.src} alt={image?.altText || image?.altText_En || ""}
alt={image.altText || image.altText_En || ""} className={styles.image}
className={styles.image} width={386}
width={386} height={200}
height={200} sizes="(min-width: 768px) 386px, 100vw"
sizes="(min-width: 768px) 386px, 100vw" />
/>
) : (
<ImageFallback />
)}
<div className={styles.content}> <div className={styles.content}>
<Typography variant="Title/Subtitle/lg"> <Typography variant="Title/Subtitle/lg">
<h3>{room.name}</h3> <h3>{room.name}</h3>
</Typography> </Typography>
<div className={styles.capacity}> <Typography variant="Body/Supporting text (caption)/smRegular">
<div className={styles.iconText}> <div className={styles.capacity}>
<MaterialIcon <span className={styles.roomDetails}>
icon="straighten" <MaterialIcon icon="straighten" color="CurrentColor" size={16} />
color="Icon/Interactive/Placeholder"
/>
<Typography
variant="Body/Supporting text (caption)/smRegular"
className={styles.roomDetails}
>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>{room.size} m²</span> {room.size} m²
</Typography> </span>
{maxSeatings ? (
<span className={styles.roomDetails}>
<MaterialIcon icon="person" color="CurrentColor" size={16} />
{intl.formatMessage(
{
id: "meetingRoomCard.maxSeatings",
defaultMessage: "max {seatings} pers",
},
{ seatings: maxSeatings }
)}
</span>
) : null}
</div> </div>
{maxSeatings ? ( </Typography>
<div className={styles.iconText}>
<MaterialIcon
icon="person"
color="Icon/Interactive/Placeholder"
size={16}
/>
<Typography
variant="Body/Supporting text (caption)/smRegular"
className={styles.roomDetails}
>
<span>
{intl.formatMessage(
{
id: "meetingRoomCard.maxSeatings",
defaultMessage: "max {seatings} pers",
},
{ seatings: maxSeatings }
)}
</span>
</Typography>
</div>
) : null}
</div>
{room.content.texts.descriptions.medium ? ( {room.content.texts.descriptions.medium ? (
<Typography> <Typography variant="Body/Paragraph/mdRegular">
<p>{room.content.texts.descriptions.medium}</p> <p>{room.content.texts.descriptions.medium}</p>
</Typography> </Typography>
) : null} ) : null}
{opened && ( {opened && (
<table className={styles.openedInfo}> <table className={styles.infoTable}>
<tbody className={styles.rowItem}> <tbody className={styles.tableBody}>
{room.seatings.map((seating, idx) => ( {room.seatings.map((seating, idx) => (
<TableRow <TableRow
key={seating.type} key={seating.type}
id={String(seating.capacity) + seating.type + idx} id={`${seating.capacity} ${seating.type} ${idx}`}
name={translateSeatingType(seating.type, intl)} name={translateSeatingType(seating.type, intl)}
value={intl.formatMessage( value={intl.formatMessage(
{ {
@@ -111,8 +89,7 @@ export default function MeetingRoomCard({ room }: MeetingRoomCardProps) {
/> />
))} ))}
</tbody> </tbody>
<Divider color="Border/Divider/Subtle" /> <tbody className={styles.tableBody}>
<tbody className={styles.rowItem}>
<TableRow <TableRow
name={intl.formatMessage({ name={intl.formatMessage({
id: "meetingRoomCard.locationInHotel", id: "meetingRoomCard.locationInHotel",

View File

@@ -1,38 +1,11 @@
.card { .card {
background-color: var(--Base-Surface-Primary-light-Normal); background-color: var(--Surface-Primary-Default);
border-radius: var(--Corner-radius-md); border-radius: var(--Corner-radius-md);
border: 1px solid var(--Base-Border-Subtle); border: 1px solid var(--Border-Default);
display: flex; display: grid;
flex-direction: column;
overflow: hidden; overflow: hidden;
} }
.capacity {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--Space-x1);
text-align: left;
}
.iconText {
display: flex;
gap: var(--Space-x05);
align-items: center;
}
.rowItem {
display: grid;
gap: var(--Space-x05);
}
.openedInfo {
background-color: var(--Base-Surface-Secondary-light-Normal);
border-radius: var(--Corner-radius-md);
padding: var(--Space-x2);
display: grid;
gap: var(--Space-x2);
}
.image { .image {
height: 200px; height: 200px;
width: 100%; width: 100%;
@@ -47,10 +20,38 @@
flex-grow: 1; flex-grow: 1;
} }
.roomDetails { .capacity {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--Space-x1);
text-align: left;
color: var(--Text-Tertiary); color: var(--Text-Tertiary);
} }
.roomDetails {
display: flex;
gap: var(--Space-x05);
align-items: center;
}
.infoTable {
background-color: var(--Surface-Primary-OnSurface-Default);
border-radius: var(--Corner-radius-md);
padding: var(--Space-x2);
display: grid;
gap: var(--Space-x2);
}
.tableBody {
display: grid;
gap: var(--Space-x05);
&:not(:first-of-type) {
border-top: 1px solid var(--Border-Divider-Default);
padding-top: var(--Space-x2);
}
}
.leftColumn { .leftColumn {
color: var(--Text-Secondary); color: var(--Text-Secondary);
font-weight: inherit; font-weight: inherit;

View File

@@ -1,19 +1,24 @@
"use client" "use client"
import { cx } from "class-variance-authority"
import { useRef, useState } from "react" import { useRef, useState } from "react"
import Grids from "@/components/TempDesignSystem/Grids"
import MeetingRoomCard from "@/components/TempDesignSystem/MeetingRoomCard"
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton" import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
import MeetingRoomCard from "./MeetingRoomCard"
import styles from "./meetingRooms.module.css" import styles from "./meetingRooms.module.css"
import type { MeetingRooms } from "@/types/components/hotelPage/meetingRooms" import type { MeetingRooms } from "@/types/components/hotelPage/meetingRooms"
interface MeetingRoomsProps { interface MeetingRoomsProps extends React.HTMLAttributes<HTMLDivElement> {
rooms: MeetingRooms rooms: MeetingRooms
} }
export default function MeetingRooms({ rooms }: MeetingRoomsProps) { export default function MeetingRooms({
rooms,
className,
...props
}: MeetingRoomsProps) {
const showToggleButton = rooms.length > 3 const showToggleButton = rooms.length > 3
const [allRoomsVisible, setAllRoomsVisible] = useState(!showToggleButton) const [allRoomsVisible, setAllRoomsVisible] = useState(!showToggleButton)
@@ -27,20 +32,29 @@ export default function MeetingRooms({ rooms }: MeetingRoomsProps) {
} }
return ( return (
<div className={styles.section} ref={scrollRef}> <section
<Grids.Stackable className={cx(styles.roomsContainer, className)}
className={`${styles.grid} ${allRoomsVisible ? styles.allVisible : ""}`} ref={scrollRef}
{...props}
>
<ul
className={cx(styles.roomsList, {
[styles.allVisible]: allRoomsVisible,
})}
> >
{rooms.map((room) => ( {rooms.map((room) => (
<MeetingRoomCard key={room.id} room={room.attributes} /> <li key={room.id} className={styles.roomsItem}>
<MeetingRoomCard room={room.attributes} />
</li>
))} ))}
</Grids.Stackable> </ul>
{showToggleButton ? ( {showToggleButton ? (
<ShowMoreButton <ShowMoreButton
className={styles.showMoreButton}
loadMoreData={handleShowMore} loadMoreData={handleShowMore}
showLess={allRoomsVisible} showLess={allRoomsVisible}
/> />
) : null} ) : null}
</div> </section>
) )
} }

View File

@@ -1,13 +1,33 @@
.grid { .roomsContainer {
align-items: flex-start; position: relative;
} color: var(--Text-Default);
.grid:not(.allVisible) > :nth-child(n + 4) {
display: none;
}
.section {
display: grid; display: grid;
gap: var(--Space-x4); gap: var(--Space-x3);
z-index: 0; }
.roomsList {
display: grid;
gap: var(--Space-x2);
grid-template-columns: 1fr;
list-style: none;
&:not(.allVisible) > :nth-child(n + 4) {
display: none;
}
}
.showMoreButton {
justify-self: center;
}
@media screen and (min-width: 768px) {
.roomsList {
grid-template-columns: repeat(2, 1fr);
}
}
@media screen and (min-width: 1367px) {
.roomsList {
grid-template-columns: repeat(3, 1fr);
}
} }

View File

@@ -75,12 +75,13 @@ export default async function MeetingsSubpage({
{mainBody ? <HtmlContent html={mainBody} /> : null} {mainBody ? <HtmlContent html={mainBody} /> : null}
</main> </main>
{meetingRooms ? (
<div className={styles.meetingsInformation}>
<MeetingsAdditionalContent rooms={meetingRooms} />
</div>
) : null}
</div> </div>
{meetingRooms ? (
<MeetingsAdditionalContent
className={styles.additionalContent}
rooms={meetingRooms}
/>
) : null}
</section> </section>
</> </>
) )

View File

@@ -1,8 +1,7 @@
.meetingsSubpage { .meetingsSubpage {
display: grid;
padding-bottom: var(--Space-x9); padding-bottom: var(--Space-x9);
color: var(--Text-Default); color: var(--Text-Default);
display: grid;
gap: var(--Space-x4);
} }
.contentContainer { .contentContainer {
@@ -10,7 +9,11 @@
gap: var(--Space-x3); gap: var(--Space-x3);
width: var(--max-width-content); width: var(--max-width-content);
margin: 0 auto; margin: 0 auto;
color: var(--Text-Default); }
.additionalContent {
width: var(--max-width-content);
margin: var(--Space-x4) auto 0;
} }
.mainContent { .mainContent {
@@ -48,6 +51,10 @@
column-gap: var(--Space-x9); column-gap: var(--Space-x9);
} }
.additionalContent {
margin-top: var(--Space-x7);
}
.divider { .divider {
display: none; display: none;
} }
@@ -57,8 +64,4 @@
grid-row: 1 / span 2; grid-row: 1 / span 2;
align-items: start; align-items: start;
} }
.meetingsInformation {
grid-column: 1 / span 2;
}
} }

View File

@@ -59,6 +59,7 @@ export default async function RestaurantSubpage({
<ButtonLink <ButtonLink
href={bookTableUrl} href={bookTableUrl}
variant="Primary" variant="Primary"
size="Medium"
typography="Body/Paragraph/mdBold" typography="Body/Paragraph/mdBold"
> >
{intl.formatMessage({ {intl.formatMessage({

View File

@@ -10,7 +10,6 @@
gap: var(--Space-x3); gap: var(--Space-x3);
width: var(--max-width-content); width: var(--max-width-content);
margin: 0 auto; margin: 0 auto;
color: var(--Text-Default);
} }
.mainContent { .mainContent {
@@ -38,8 +37,8 @@
.buttonContainer { .buttonContainer {
position: sticky; position: sticky;
padding: var(--Space-x3) var(--Space-x2); padding: var(--Space-x3) var(--Space-x2);
background-color: var(--Base-Surface-Secondary-light-Normal); background-color: var(--Background-Primary);
border-top: 1px solid var(--Base-Border-Subtle); border-top: 1px solid var(--Border-Default);
bottom: 0; bottom: 0;
} }

View File

@@ -1,4 +1,4 @@
import Link from "@scandic-hotels/design-system/OldDSLink" import { TextLink } from "@scandic-hotels/design-system/TextLink"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import LocalCallCharges from "@/components/LocalCallCharges" import LocalCallCharges from "@/components/LocalCallCharges"
@@ -31,13 +31,9 @@ export default async function MeetingsSidebar({
</h3> </h3>
</Typography> </Typography>
<div className={styles.contactDetails}> <div className={styles.contactDetails}>
<Link href={`tel:${phoneNumber}`}>{phoneNumber}</Link> <TextLink href={`tel:${phoneNumber}`}>{phoneNumber}</TextLink>
<LocalCallCharges country={country} /> <LocalCallCharges country={country} />
{email && ( {email ? <TextLink href={`mailto:${email}`}>{email}</TextLink> : null}
<Link textDecoration="underline" href={`mailto:${email}`}>
{email}
</Link>
)}
</div> </div>
</div> </div>
</> </>

View File

@@ -1,4 +1,4 @@
import Link from "@scandic-hotels/design-system/OldDSLink" import { TextLink } from "@scandic-hotels/design-system/TextLink"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import LocalCallCharges from "@/components/LocalCallCharges" import LocalCallCharges from "@/components/LocalCallCharges"
@@ -52,16 +52,13 @@ export default async function ParkingSidebar({
</h3> </h3>
</Typography> </Typography>
<div className={styles.contactDetails}> <div className={styles.contactDetails}>
<Link href={`tel:${contactInformation.phoneNumber}`}> <TextLink href={`tel:${contactInformation.phoneNumber}`}>
{contactInformation.phoneNumber} {contactInformation.phoneNumber}
</Link> </TextLink>
<LocalCallCharges country={address.country} /> <LocalCallCharges country={address.country} />
<Link <TextLink href={`mailto:${contactInformation.email}`}>
textDecoration="underline"
href={`mailto:${contactInformation.email}`}
>
{contactInformation.email} {contactInformation.email}
</Link> </TextLink>
</div> </div>
</div> </div>
</> </>

View File

@@ -1,7 +1,7 @@
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import Link from "@scandic-hotels/design-system/OldDSLink"
import OpeningHours from "@scandic-hotels/design-system/OpeningHours" import OpeningHours from "@scandic-hotels/design-system/OpeningHours"
import { TextLink } from "@scandic-hotels/design-system/TextLink"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import LocalCallCharges from "@/components/LocalCallCharges" import LocalCallCharges from "@/components/LocalCallCharges"
@@ -47,14 +47,17 @@ export default async function RestaurantSidebar({
) : null} ) : null}
{bookTableUrl && ( {bookTableUrl && (
<div className={styles.buttonContainer}> <div className={styles.buttonContainer}>
<Button intent="primary" theme="base" asChild> <ButtonLink
<a href={bookTableUrl}> href={bookTableUrl}
{intl.formatMessage({ variant="Primary"
id: "restaurantBar.bookATable", size="Medium"
defaultMessage: "Book a table", typography="Body/Paragraph/mdBold"
})} >
</a> {intl.formatMessage({
</Button> id: "restaurantBar.bookATable",
defaultMessage: "Book a table",
})}
</ButtonLink>
</div> </div>
)} )}
{restaurant.menus.length ? ( {restaurant.menus.length ? (
@@ -70,19 +73,14 @@ export default async function RestaurantSidebar({
<ul className={styles.menuList}> <ul className={styles.menuList}>
{restaurant.menus.map(({ name, url }) => ( {restaurant.menus.map(({ name, url }) => (
<li key={name}> <li key={name}>
<Link <TextLink href={url} target="_blank">
href={url}
color="Text/Interactive/Secondary"
textDecoration="underline"
variant="icon"
>
{name} {name}
<MaterialIcon <MaterialIcon
icon="open_in_new" icon="open_in_new"
size={20} size={24}
color="CurrentColor" color="CurrentColor"
/> />
</Link> </TextLink>
</li> </li>
))} ))}
</ul> </ul>
@@ -116,17 +114,15 @@ export default async function RestaurantSidebar({
</h3> </h3>
</Typography> </Typography>
<div className={styles.contactDetails}> <div className={styles.contactDetails}>
{phoneNumber && ( {phoneNumber ? (
<> <>
<Link href={`tel:${phoneNumber}`}>{phoneNumber}</Link> <TextLink href={`tel:${phoneNumber}`}>{phoneNumber}</TextLink>
<LocalCallCharges country={hotelAddress.country} /> <LocalCallCharges country={hotelAddress.country} />
</> </>
)} ) : null}
{email && ( {email ? (
<Link textDecoration="underline" href={`mailto:${email}`}> <TextLink href={`mailto:${email}`}>{email}</TextLink>
{email} ) : null}
</Link>
)}
</div> </div>
</div> </div>
)} )}

View File

@@ -6,6 +6,7 @@
.content { .content {
display: grid; display: grid;
gap: var(--Space-x15); gap: var(--Space-x15);
color: var(--Text-Default);
} }
.menuList { .menuList {

View File

@@ -33,7 +33,7 @@
} }
.checkbox:has(input:checked) { .checkbox:has(input:checked) {
border-color: var(--Border-Interactive-Selected); border-color: var(--Border-Interactive-Active);
} }
.checkbox:has(input:checked) span[class*="checkbox_checkbox_"] { .checkbox:has(input:checked) span[class*="checkbox_checkbox_"] {

View File

@@ -1,8 +1,3 @@
<svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<mask id="mask0_2991_3013" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="33" height="32"> <path d="M7.08333 9.21667L5.65738 7.79072C5.53579 7.66913 5.38889 7.60833 5.21667 7.60833C5.04444 7.60833 4.89722 7.66944 4.775 7.79167C4.65278 7.91389 4.59167 8.06111 4.59167 8.23333C4.59167 8.40556 4.6518 8.5518 4.77207 8.67207L6.64167 10.5417C6.76742 10.6694 6.91414 10.7333 7.08182 10.7333C7.2495 10.7333 7.39722 10.6694 7.525 10.5417L11.225 6.84167C11.3472 6.71944 11.4083 6.57222 11.4083 6.4C11.4083 6.22778 11.3472 6.08056 11.225 5.95833C11.1028 5.83611 10.9556 5.775 10.7833 5.775C10.6111 5.775 10.4646 5.83538 10.3439 5.95613L7.08333 9.21667ZM8 14.5C7.10103 14.5 6.25623 14.3291 5.46558 13.9873C4.67493 13.6455 3.98717 13.1816 3.4023 12.5956C2.81743 12.0097 2.35417 11.3217 2.0125 10.5319C1.67083 9.74202 1.5 8.89806 1.5 8C1.5 7.10103 1.67091 6.25623 2.01272 5.46558C2.35453 4.67493 2.81842 3.98717 3.40438 3.4023C3.99035 2.81743 4.67826 2.35417 5.46812 2.0125C6.25798 1.67083 7.10194 1.5 8 1.5C8.89897 1.5 9.74377 1.67091 10.5344 2.01272C11.3251 2.35453 12.0128 2.81842 12.5977 3.40438C13.1826 3.99035 13.6458 4.67826 13.9875 5.46812C14.3292 6.25798 14.5 7.10194 14.5 8C14.5 8.89897 14.3291 9.74377 13.9873 10.5344C13.6455 11.3251 13.1816 12.0128 12.5956 12.5977C12.0097 13.1826 11.3217 13.6458 10.5319 13.9875C9.74202 14.3292 8.89806 14.5 8 14.5ZM8 13.25C9.46111 13.25 10.7014 12.7403 11.7208 11.7208C12.7403 10.7014 13.25 9.46111 13.25 8C13.25 6.53889 12.7403 5.29861 11.7208 4.27917C10.7014 3.25972 9.46111 2.75 8 2.75C6.53889 2.75 5.29861 3.25972 4.27917 4.27917C3.25972 5.29861 2.75 6.53889 2.75 8C2.75 9.46111 3.25972 10.7014 4.27917 11.7208C5.29861 12.7403 6.53889 13.25 8 13.25Z" fill="CurrentColor"/>
<rect x="0.5" width="32" height="32" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_2991_3013)">
<path d="M14.6667 18.4333L11.8148 15.5814C11.5716 15.3383 11.2778 15.2167 10.9333 15.2167C10.5889 15.2167 10.2944 15.3389 10.05 15.5833C9.80556 15.8278 9.68333 16.1222 9.68333 16.4667C9.68333 16.8111 9.8036 17.1036 10.0441 17.3441L13.7833 21.0833C14.0348 21.3389 14.3283 21.4667 14.6636 21.4667C14.999 21.4667 15.2944 21.3389 15.55 21.0833L22.95 13.6833C23.1944 13.4389 23.3167 13.1444 23.3167 12.8C23.3167 12.4556 23.1944 12.1611 22.95 11.9167C22.7056 11.6722 22.4111 11.55 22.0667 11.55C21.7222 11.55 21.4292 11.6708 21.1877 11.9123L14.6667 18.4333ZM16.5 29C14.7021 29 13.0125 28.6582 11.4312 27.9746C9.84986 27.2909 8.47433 26.3632 7.3046 25.1912C6.13487 24.0193 5.20833 22.6435 4.525 21.0638C3.84167 19.484 3.5 17.7961 3.5 16C3.5 14.2021 3.84181 12.5125 4.52543 10.9312C5.20906 9.34986 6.13683 7.97433 7.30877 6.8046C8.4807 5.63487 9.85652 4.70833 11.4362 4.025C13.016 3.34167 14.7039 3 16.5 3C18.2979 3 19.9875 3.34181 21.5688 4.02543C23.1501 4.70906 24.5257 5.63683 25.6954 6.80877C26.8651 7.9807 27.7917 9.35652 28.475 10.9362C29.1583 12.516 29.5 14.2039 29.5 16C29.5 17.7979 29.1582 19.4875 28.4746 21.0688C27.7909 22.6501 26.8632 24.0257 25.6912 25.1954C24.5193 26.3651 23.1435 27.2917 21.5638 27.975C19.984 28.6583 18.2961 29 16.5 29Z" fill="#33800A"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,8 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<mask id="mask0_5625_32301" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16"> <path d="M7.99994 13.4167L7.23327 12.725C6.14142 11.752 5.24068 10.9184 4.53105 10.2244C3.82142 9.53035 3.26225 8.91443 2.85354 8.37663C2.44481 7.83884 2.16148 7.34728 2.00353 6.90195C1.84558 6.45663 1.7666 6.00042 1.7666 5.53333C1.7666 4.57768 2.09435 3.77209 2.74985 3.11658C3.40536 2.46108 4.21095 2.13333 5.1666 2.13333C5.70314 2.13333 6.22338 2.25 6.72734 2.48333C7.23129 2.71666 7.65549 3.05 7.99994 3.48333C8.36105 3.05 8.78882 2.71666 9.28327 2.48333C9.77771 2.25 10.2944 2.13333 10.8333 2.13333C11.7889 2.13333 12.5945 2.46108 13.25 3.11658C13.9055 3.77209 14.2333 4.57768 14.2333 5.53333C14.2333 6.00042 14.1571 6.45107 14.0047 6.88528C13.8523 7.3195 13.5717 7.80273 13.163 8.33496C12.7543 8.86721 12.1923 9.48591 11.4772 10.1911C10.762 10.8962 9.84734 11.752 8.73327 12.7583L7.99994 13.4167Z" fill="CurrentColor"/>
<rect width="16" height="16" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_5625_32301)">
<path d="M7.99994 13.4171L7.23327 12.7255C6.14142 11.7524 5.24068 10.9189 4.53105 10.2249C3.82142 9.53081 3.26225 8.91489 2.85354 8.37709C2.44481 7.8393 2.16148 7.34774 2.00353 6.90241C1.84558 6.45708 1.7666 6.00088 1.7666 5.53379C1.7666 4.57813 2.09435 3.77255 2.74985 3.11704C3.40536 2.46154 4.21095 2.13379 5.1666 2.13379C5.70314 2.13379 6.22338 2.25046 6.72734 2.48379C7.23129 2.71712 7.65549 3.05046 7.99994 3.48379C8.36105 3.05046 8.78882 2.71712 9.28327 2.48379C9.77771 2.25046 10.2944 2.13379 10.8333 2.13379C11.7889 2.13379 12.5945 2.46154 13.25 3.11704C13.9055 3.77255 14.2333 4.57813 14.2333 5.53379C14.2333 6.00088 14.1571 6.45153 14.0047 6.88574C13.8523 7.31996 13.5717 7.80319 13.163 8.33542C12.7543 8.86767 12.1923 9.48637 11.4772 10.1915C10.762 10.8967 9.84734 11.7524 8.73327 12.7588L7.99994 13.4171Z" fill="#B05B65"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 931 B

View File

@@ -64,7 +64,8 @@
"./utils/promiseWithTimeout": "./utils/promiseWithTimeout.ts", "./utils/promiseWithTimeout": "./utils/promiseWithTimeout.ts",
"./utils/rangeArray": "./utils/rangeArray.ts", "./utils/rangeArray": "./utils/rangeArray.ts",
"./utils/safeTry": "./utils/safeTry.ts", "./utils/safeTry": "./utils/safeTry.ts",
"./utils/theme": "./utils/theme.ts", "./utils/theme": "./utils/theme/index.ts",
"./utils/theme/serverContext": "./utils/theme/serverContext.ts",
"./utils/toCapitalCase": "./utils/toCapitalCase.ts", "./utils/toCapitalCase": "./utils/toCapitalCase.ts",
"./utils/url": "./utils/url.ts", "./utils/url": "./utils/url.ts",
"./utils/zod/*": "./utils/zod/*.ts" "./utils/zod/*": "./utils/zod/*.ts"

View File

@@ -0,0 +1,31 @@
import "server-only"
import { cache } from "react"
import { DEFAULT_THEME, type Theme } from "."
const getRef = cache(() => ({ current: DEFAULT_THEME as Theme }))
/**
* Set the global theme
*
* It works kind of like React's context,
* but on the server side, per request.
*
* @param newTheme
*/
export function setTheme(newTheme: Theme) {
getRef().current = newTheme
return newTheme
}
/**
* Get the global theme
*
* Note: This must be called after setTheme() has been called in the page/layout.
* If called before setTheme(), it will return DEFAULT_THEME.
*/
export function getTheme(): Theme {
return getRef().current ?? DEFAULT_THEME
}

View File

@@ -27,12 +27,12 @@
} }
.Outlined:active { .Outlined:active {
border-color: var(--Border-Interactive-Selected); border-color: var(--Border-Interactive-Active);
} }
.FilterRounded { .FilterRounded {
background-color: transparent; background-color: transparent;
border: 1px solid var(--Border-Interactive-Selected); border: 1px solid var(--Border-Interactive-Active);
border-radius: var(--Corner-radius-rounded); border-radius: var(--Corner-radius-rounded);
padding: var(--Space-x025) var(--Space-x2); padding: var(--Space-x025) var(--Space-x2);
color: var(--Text-Default); color: var(--Text-Default);

View File

@@ -29,7 +29,7 @@
} }
.label:has(:checked) { .label:has(:checked) {
border: 2px solid var(--Border-Interactive-Selected); border: 2px solid var(--Border-Interactive-Active);
} }
.label:not(:has(:checked)) .selectedIcon { .label:not(:has(:checked)) .selectedIcon {

View File

@@ -4,7 +4,7 @@ import NextImage, { ImageProps as NextImageProps } from 'next/image'
import ImageFallback from '../ImageFallback' import ImageFallback from '../ImageFallback'
import type { CSSProperties } from 'react' import { useState, type CSSProperties, type SyntheticEvent } from 'react'
import { imageLoader } from './imageLoader' import { imageLoader } from './imageLoader'
type FocalPoint = { type FocalPoint = {
@@ -22,8 +22,11 @@ export default function Image({
focalPoint, focalPoint,
dimensions, dimensions,
style, style,
src,
onError,
...props ...props
}: ImageProps) { }: ImageProps) {
const [imageError, setImageError] = useState(false)
const styles: CSSProperties = focalPoint const styles: CSSProperties = focalPoint
? { ? {
objectFit: 'cover', objectFit: 'cover',
@@ -32,14 +35,24 @@ export default function Image({
} }
: { ...style } : { ...style }
if (!props.src) { function handleError(error: SyntheticEvent<HTMLImageElement, Event>) {
if (onError) {
onError(error)
} else {
setImageError(true)
}
}
if (!src || imageError) {
return <ImageFallback /> return <ImageFallback />
} }
return ( return (
<NextImage <NextImage
{...props} {...props}
src={src}
style={styles} style={styles}
onError={handleError}
loader={imageLoader({ dimensions, focalPoint })} loader={imageLoader({ dimensions, focalPoint })}
/> />
) )

View File

@@ -87,19 +87,26 @@
.li:has(.heart)::before, .li:has(.heart)::before,
.check > .li::before, .check > .li::before,
.li:has(.check)::before { .li:has(.check)::before {
content: '';
position: relative; position: relative;
height: 8px;
top: 3px; top: 3px;
width: 16px;
height: 16px;
display: inline-flex;
flex-shrink: 0;
background-color: var(--Icon-Accent);
mask-size: contain;
mask-repeat: no-repeat;
} }
.check > .li::before, .check > .li::before,
.li:has(.check)::before { .li:has(.check)::before {
content: url('/_static/icons/check-ring.svg'); mask-image: url('/_static/icons/check_circle.svg');
} }
.heart > .li::before, .heart > .li::before,
.li:has(.heart)::before { .li:has(.heart)::before {
content: url('/_static/icons/heart.svg'); mask-image: url('/_static/icons/heart.svg');
} }
.li > * { .li > * {

View File

@@ -4,77 +4,63 @@
height: 100%; height: 100%;
position: relative; position: relative;
z-index: 0; z-index: 0;
}
.mapContainer :global(.gm-style .gm-style-iw-d) { &::after {
padding: 0 !important; content: '';
overflow: hidden !important; position: absolute;
max-height: none !important; top: 0;
max-width: none !important; right: 0;
} background: linear-gradient(
43deg,
rgba(172, 172, 172, 0) 57.66%,
rgba(0, 0, 0, 0.25) 92.45%
);
width: 100%;
height: 100%;
pointer-events: none;
}
.mapContainer :global(.gm-style .gm-style-iw-c) { :global(.gm-style .gm-style-iw-d),
padding: 0 !important; :global(.gm-style .gm-style-iw-c) {
overflow: hidden !important; padding: 0 !important;
max-height: none !important; overflow: hidden !important;
max-width: none !important; max-height: none !important;
} max-width: none !important;
}
.mapContainer::after {
content: '';
position: absolute;
top: 0;
right: 0;
background: linear-gradient(
43deg,
rgba(172, 172, 172, 0) 57.66%,
rgba(0, 0, 0, 0.25) 92.45%
);
width: 100%;
height: 100%;
pointer-events: none;
} }
.ctaButtons { .ctaButtons {
position: absolute; position: absolute;
top: var(--Spacing-x2); top: var(--Space-x2);
right: var(--Spacing-x2); right: var(--Space-x2);
z-index: 1;
display: flex; display: flex;
gap: var(--Space-x7);
flex-direction: column; flex-direction: column;
gap: var(--Spacing-x7);
align-items: flex-end; align-items: flex-end;
pointer-events: none; pointer-events: none;
z-index: 1;
} }
.zoomButtons { .zoomButtons {
display: grid; display: flex;
gap: var(--Spacing-x2); gap: var(--Space-x2);
}
.closeButton {
pointer-events: initial;
box-shadow: var(--button-box-shadow);
gap: var(--Spacing-x-half);
} }
.zoomButton { .zoomButton {
width: var(--Space-x5);
height: var(--Space-x5);
padding: 0;
pointer-events: initial; pointer-events: initial;
box-shadow: var(--button-box-shadow); }
@media screen and (max-width: 767px) {
.zoomButtons {
flex-direction: column;
}
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.ctaButtons { .ctaButtons {
top: var(--Spacing-x4); top: var(--Space-x4);
right: var(--Spacing-x5); right: var(--Space-x5);
bottom: var(--Spacing-x7); bottom: var(--Space-x7);
justify-content: space-between; justify-content: space-between;
} }
.zoomButtons {
display: flex;
}
} }

View File

@@ -1,19 +1,14 @@
import { IconByIconName } from '../../../Icons/IconByIconName' import { IconByIconName } from '../../../Icons/IconByIconName'
import { getIconByPoiGroupAndCategory } from '../utils' import {
getIconByPoiGroupAndCategory,
type PointOfInterestGroup,
} from '../utils'
import { poiVariants } from './variants' import { poiVariants } from './variants'
import { VariantProps } from 'class-variance-authority' import type { VariantProps } from 'class-variance-authority'
export type PointOfInterestGroup = interface PoiMarkerProps extends VariantProps<typeof poiVariants> {
| 'Public transport'
| 'Attractions'
| 'Business'
| 'Location'
| 'Parking'
| 'Shopping & Dining'
export interface PoiMarkerProps extends VariantProps<typeof poiVariants> {
group: PointOfInterestGroup group: PointOfInterestGroup
categoryName?: string categoryName?: string
className?: string className?: string
@@ -33,7 +28,7 @@ export function PoiMarker({
<span className={classNames}> <span className={classNames}>
<IconByIconName <IconByIconName
iconName={iconName} iconName={iconName}
color={skipBackground ? 'Icon/Feedback/Neutral' : 'Icon/Inverted'} color={skipBackground ? 'CurrentColor' : 'Icon/Inverted'}
size={size === 'small' ? 16 : size === 'large' ? 24 : 20} size={size === 'small' ? 16 : size === 'large' ? 24 : 20}
/> />
</span> </span>

View File

@@ -1,9 +1,37 @@
.icon { .poiMarker {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: var(--Corner-radius-rounded); border-radius: var(--Corner-radius-rounded);
background-color: var(--Surface-UI-Fill-Default); background-color: var(--Surface-Feedback-Neutral);
> span {
display: inline-flex;
}
&.skipBackground {
background-color: transparent;
padding: 0;
}
}
.shoppingDining {
background-color: var(--Surface-Accent-1);
}
.publicTransport {
background-color: var(--Surface-Accent-2);
}
.attractions {
background-color: var(--Surface-Accent-3);
}
.business {
background-color: var(--Surface-Accent-4);
}
.parking {
background-color: var(--Surface-Accent-5);
}
.location {
background-color: var(--Surface-Feedback-Neutral);
} }
.small { .small {
@@ -14,27 +42,3 @@
width: var(--Space-x4); width: var(--Space-x4);
height: var(--Space-x4); height: var(--Space-x4);
} }
.attractions {
background-color: var(--Surface-Accent-3);
}
.business {
background-color: var(--Surface-Accent-4);
}
.location {
background-color: var(--Surface-Feedback-Neutral);
}
.parking {
background-color: var(--Surface-Accent-5);
}
.publicTransport {
background-color: var(--Surface-Accent-2);
}
.shoppingDining {
background-color: var(--Surface-Accent-1);
}
.icon.transparent {
background-color: transparent;
padding: 0;
}

View File

@@ -1,9 +1,9 @@
import { cva } from 'class-variance-authority' import { cva } from 'class-variance-authority'
import styles from './poi.module.css' import type { PointOfInterestGroup } from '../utils'
import { PointOfInterestGroup } from '.' import styles from './poiMarker.module.css'
export const poiVariants = cva(styles.icon, { export const poiVariants = cva(styles.poiMarker, {
variants: { variants: {
group: { group: {
['Attractions']: styles.attractions, ['Attractions']: styles.attractions,
@@ -14,7 +14,7 @@ export const poiVariants = cva(styles.icon, {
['Shopping & Dining']: styles.shoppingDining, ['Shopping & Dining']: styles.shoppingDining,
} satisfies Record<PointOfInterestGroup, string>, } satisfies Record<PointOfInterestGroup, string>,
skipBackground: { skipBackground: {
true: styles.transparent, true: styles.skipBackground,
false: '', false: '',
}, },
size: { size: {

View File

@@ -1,5 +1,12 @@
import { IconName } from '../../Icons/iconName' import { IconName } from '../../Icons/iconName'
import { PointOfInterestGroup } from './PoiMarker'
export type PointOfInterestGroup =
| 'Public transport'
| 'Attractions'
| 'Business'
| 'Location'
| 'Parking'
| 'Shopping & Dining'
export function getIconByPoiGroupAndCategory( export function getIconByPoiGroupAndCategory(
group: PointOfInterestGroup, group: PointOfInterestGroup,

View File

@@ -37,9 +37,9 @@ export default function ParkingList({
return ( return (
<Typography variant="Body/Paragraph/mdRegular"> <Typography variant="Body/Paragraph/mdRegular">
<ul className={styles.listStyling}> <ul className={styles.list}>
{numberOfChargingSpaces ? ( {numberOfChargingSpaces ? (
<li> <li className={styles.listItem}>
{intl.formatMessage( {intl.formatMessage(
{ {
id: 'parkingInformation.numberOfChargingPoints', id: 'parkingInformation.numberOfChargingPoints',
@@ -50,13 +50,13 @@ export default function ParkingList({
)} )}
</li> </li>
) : null} ) : null}
<li> <li className={styles.listItem}>
{canMakeReservation {canMakeReservation
? canMakeReservationYesMsg ? canMakeReservationYesMsg
: canMakeReservationNoMsg} : canMakeReservationNoMsg}
</li> </li>
{numberOfParkingSpots ? ( {numberOfParkingSpots ? (
<li> <li className={styles.listItem}>
{intl.formatMessage( {intl.formatMessage(
{ {
id: 'parkingInformation.numberOfParkingSpots', id: 'parkingInformation.numberOfParkingSpots',
@@ -67,7 +67,7 @@ export default function ParkingList({
</li> </li>
) : null} ) : null}
{distanceToHotel ? ( {distanceToHotel ? (
<li> <li className={styles.listItem}>
{intl.formatMessage( {intl.formatMessage(
{ {
id: 'parkingInformation.distanceToHotel', id: 'parkingInformation.distanceToHotel',
@@ -78,7 +78,7 @@ export default function ParkingList({
</li> </li>
) : null} ) : null}
{address ? ( {address ? (
<li> <li className={styles.listItem}>
{intl.formatMessage( {intl.formatMessage(
{ {
id: 'parkingInformation.address', id: 'parkingInformation.address',

View File

@@ -1,11 +1,24 @@
.listStyling { .list {
display: grid;
gap: var(--Space-x1);
list-style-type: none; list-style-type: none;
} }
.listStyling > li::before { .listItem {
content: url('/_static/icons/heart.svg'); display: flex;
position: relative; gap: var(--Space-x1);
height: 8px;
top: 3px; &::before {
margin-right: var(--Spacing-x1); content: '';
position: relative;
top: 3px;
display: inline-flex;
flex-shrink: 0;
width: 16px;
height: 16px;
background-color: var(--Icon-Accent);
mask-image: url('/_static/icons/heart.svg');
mask-size: contain;
mask-repeat: no-repeat;
}
} }

View File

@@ -2,10 +2,10 @@
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl'
import ButtonLink from '../ButtonLink'
import { Divider } from '../Divider' import { Divider } from '../Divider'
import { MaterialIcon } from '../Icons/MaterialIcon' import { MaterialIcon } from '../Icons/MaterialIcon'
import { Typography } from '../Typography' import { Typography } from '../Typography'
import ButtonLink from '../ButtonLink'
import ParkingList from './ParkingList' import ParkingList from './ParkingList'
import ParkingPrices from './ParkingPrices' import ParkingPrices from './ParkingPrices'
@@ -88,6 +88,7 @@ export default function ParkingInformation({
{parking.externalParkingUrl && showExternalParkingButton && ( {parking.externalParkingUrl && showExternalParkingButton && (
<ButtonLink <ButtonLink
typography="Body/Paragraph/mdBold" typography="Body/Paragraph/mdBold"
size="Medium"
href={parking.externalParkingUrl} href={parking.externalParkingUrl}
target="_blank" target="_blank"
> >

View File

@@ -1,20 +1,20 @@
.parkingInformation { .parkingInformation {
display: grid; display: grid;
gap: var(--Spacing-x3); gap: var(--Space-x3);
} }
.list, .list,
.prices { .prices {
display: grid; display: grid;
gap: var(--Spacing-x-one-and-half); gap: var(--Space-x15);
} }
.priceWrapper { .priceWrapper {
background-color: var(--Base-Surface-Subtle-Normal); background-color: var(--Surface-Secondary-Default);
border-radius: var(--Corner-radius-md); border-radius: var(--Corner-radius-md);
padding: var(--Spacing-x2) var(--Spacing-x3); padding: var(--Space-x2) var(--Space-x3);
display: grid; display: grid;
gap: var(--Spacing-x1); gap: var(--Space-x1);
} }
.heading { .heading {

View File

@@ -19,7 +19,6 @@
.theme-primary:not(.disabled) { .theme-primary:not(.disabled) {
color: var(--Text-Interactive-Secondary); color: var(--Text-Interactive-Secondary);
text-decoration: underline;
&:hover { &:hover {
color: var(--Text-Interactive-Secondary-Hover); color: var(--Text-Interactive-Secondary-Hover);
@@ -28,7 +27,6 @@
.theme-inverted:not(.disabled) { .theme-inverted:not(.disabled) {
color: var(--Text-Inverted); color: var(--Text-Inverted);
text-decoration: underline;
&:hover { &:hover {
opacity: 0.7; opacity: 0.7;