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:
@@ -5,6 +5,7 @@ import {
|
||||
DEFAULT_THEME,
|
||||
getThemeByHotel,
|
||||
} from "@scandic-hotels/common/utils/theme"
|
||||
import { setTheme } from "@scandic-hotels/common/utils/theme/serverContext"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
|
||||
@@ -44,6 +45,8 @@ export default async function HotelPagePage(
|
||||
? getThemeByHotel(hotelPageData.hotel_page_id, hotelData.hotel.hotelType)
|
||||
: DEFAULT_THEME
|
||||
|
||||
setTheme(hotelTheme)
|
||||
|
||||
if (searchParams.subpage) {
|
||||
return (
|
||||
<div className={hotelTheme}>
|
||||
@@ -59,11 +62,7 @@ export default async function HotelPagePage(
|
||||
} else {
|
||||
return (
|
||||
<div className={cx(styles.hotelPage, hotelTheme)}>
|
||||
<HotelPage
|
||||
theme={hotelTheme}
|
||||
hotelData={hotelData}
|
||||
hotelPageData={hotelPageData}
|
||||
/>
|
||||
<HotelPage hotelData={hotelData} hotelPageData={hotelPageData} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { SessionProvider } from "next-auth/react"
|
||||
import StorageCleaner from "@scandic-hotels/booking-flow/components/EnterDetails/StorageCleaner"
|
||||
import { NuqsAdapter } from "@scandic-hotels/booking-flow/utils/nuqs"
|
||||
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 TrpcProvider from "@/lib/trpc/Provider"
|
||||
@@ -63,7 +64,7 @@ export default async function RootLayout(
|
||||
window.dataLayer = window.dataLayer || []
|
||||
`}</Script>
|
||||
</head>
|
||||
<body className="scandic">
|
||||
<body className={DEFAULT_THEME}>
|
||||
<div className="root">
|
||||
<SessionProvider basePath="/api/web/auth">
|
||||
<ClientIntlProvider
|
||||
|
||||
@@ -94,9 +94,17 @@
|
||||
}
|
||||
|
||||
.usp li:before {
|
||||
content: url("/_static/icons/heart.svg");
|
||||
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;
|
||||
}
|
||||
|
||||
.primaryButton {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
.activeCard {
|
||||
border: 1px solid var(--Border-Interactive-Selected);
|
||||
border: 1px solid var(--Border-Interactive-Active);
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.sidebar {
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
background-color: var(--Background-Primary);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
grid-template-columns: 1fr max-content;
|
||||
align-items: center;
|
||||
gap: var(--Space-x2);
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
background-color: var(--Background-Primary);
|
||||
border-width: 0;
|
||||
color: var(--Text-Default);
|
||||
width: 100%;
|
||||
@@ -86,11 +86,12 @@
|
||||
.sidebarToggle {
|
||||
position: relative;
|
||||
color: var(--Text-Secondary);
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
background-color: var(--Background-Primary);
|
||||
border-width: 0;
|
||||
margin: var(--Space-x2) 0;
|
||||
padding: var(--Space-x2);
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebarToggle::before {
|
||||
@@ -124,7 +125,7 @@
|
||||
width: 40vw;
|
||||
min-width: 10rem;
|
||||
max-width: 26.25rem;
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
background-color: var(--Background-Primary);
|
||||
}
|
||||
|
||||
.sidebarToggle {
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
display: flex;
|
||||
height: var(--hotel-map-height);
|
||||
width: 100dvw;
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
background-color: var(--Background-Default);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
box-shadow: var(--button-box-shadow);
|
||||
color: var(--Component-Button-Inverted-On-fill-Default);
|
||||
pointer-events: initial;
|
||||
gap: var(--Space-x05);
|
||||
}
|
||||
|
||||
@@ -14,10 +14,21 @@
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.heartList > li::before {
|
||||
content: url("/_static/icons/heart.svg");
|
||||
position: relative;
|
||||
height: 8px;
|
||||
top: 3px;
|
||||
margin-right: var(--Space-x1);
|
||||
.heartList > li {
|
||||
display: flex;
|
||||
gap: var(--Space-x1);
|
||||
|
||||
&::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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import { Suspense } from "react"
|
||||
|
||||
import { dt } from "@scandic-hotels/common/dt"
|
||||
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 { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
|
||||
import { type HotelPageData } from "@scandic-hotels/trpc/types/hotelPage"
|
||||
@@ -58,16 +59,15 @@ import { HotelHashValues } from "@/types/enums/hotelPage"
|
||||
interface HotelPageProps {
|
||||
hotelData: HotelData
|
||||
hotelPageData: HotelPageData
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
export default async function HotelPage({
|
||||
hotelData,
|
||||
hotelPageData,
|
||||
theme,
|
||||
}: HotelPageProps) {
|
||||
const lang = await getLang()
|
||||
const intl = await getIntl()
|
||||
const hotelTheme = getTheme()
|
||||
|
||||
const [meetingRoomsData] = await safeTry(
|
||||
getMeetingRooms({ hotelId: hotelData.hotel.operaId, language: lang })
|
||||
@@ -146,7 +146,7 @@ export default async function HotelPage({
|
||||
)
|
||||
const trackingHotelData = getTrackingHotelData(hotelData.hotel)
|
||||
|
||||
const isThemed = theme !== DEFAULT_THEME
|
||||
const isThemed = hotelTheme !== DEFAULT_THEME
|
||||
|
||||
return (
|
||||
<div className={styles.pageContainer}>
|
||||
@@ -226,7 +226,7 @@ export default async function HotelPage({
|
||||
healthAndWellness={healthAndWellness ?? null}
|
||||
pageSections={pageSections}
|
||||
activities={activities}
|
||||
hotelTheme={theme}
|
||||
hotelTheme={hotelTheme}
|
||||
/>
|
||||
{campaignsBlock ? (
|
||||
<HotelCampaigns
|
||||
|
||||
@@ -46,12 +46,23 @@
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.listItem::before {
|
||||
content: url("/_static/icons/heart.svg");
|
||||
position: relative;
|
||||
height: 8px;
|
||||
top: 3px;
|
||||
margin-right: var(--Space-x1);
|
||||
.listItem {
|
||||
display: flex;
|
||||
gap: var(--Space-x1);
|
||||
|
||||
&::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;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
|
||||
@@ -72,21 +72,18 @@ export default async function AccessibilitySubpage({
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<h2>{accessibilityGroup.name}</h2>
|
||||
</Typography>
|
||||
<ul className={styles.list}>
|
||||
{accessibilityGroup.specialNeeds.map((groupItem) => (
|
||||
<Typography
|
||||
key={groupItem.name}
|
||||
variant="Body/Paragraph/mdRegular"
|
||||
>
|
||||
<li className={styles.listItem}>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<ul className={styles.list}>
|
||||
{accessibilityGroup.specialNeeds.map((groupItem) => (
|
||||
<li key={groupItem.name} className={styles.listItem}>
|
||||
{groupItem.details
|
||||
? // eslint-disable-next-line formatjs/no-literal-string-in-jsx
|
||||
`${groupItem.name}: ${groupItem.details}`
|
||||
: groupItem.name}
|
||||
</li>
|
||||
</Typography>
|
||||
))}
|
||||
</ul>
|
||||
))}
|
||||
</ul>
|
||||
</Typography>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
.header {
|
||||
display: grid;
|
||||
background-color: var(--Base-Surface-Subtle-Normal);
|
||||
background-color: var(--Background-Primary);
|
||||
padding-bottom: var(--Space-x4);
|
||||
|
||||
&.isThemed {
|
||||
gap: var(--Space-x3);
|
||||
}
|
||||
}
|
||||
|
||||
.heroWrapper {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { cx } from "class-variance-authority"
|
||||
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 Hero from "@/components/Hero"
|
||||
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||
@@ -17,10 +21,16 @@ export default async function HeroHeader({
|
||||
breadcrumbsTitle,
|
||||
heroImage,
|
||||
}: HeroHeaderProps) {
|
||||
const hotelTheme = getTheme()
|
||||
const isThemed = hotelTheme !== DEFAULT_THEME
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<div className={cx(styles.header, { [styles.isThemed]: isThemed })}>
|
||||
<Suspense fallback={<BreadcrumbsSkeleton size="contentWidth" />}>
|
||||
<Breadcrumbs subpageTitle={breadcrumbsTitle} size="contentWidth" />
|
||||
<Breadcrumbs
|
||||
subpageTitle={breadcrumbsTitle}
|
||||
size="contentWidth"
|
||||
isThemed={isThemed}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{heroImage ? (
|
||||
|
||||
@@ -1,21 +1,39 @@
|
||||
.ul,
|
||||
.ol {
|
||||
padding: var(--Space-x2) var(--Space-x0);
|
||||
display: grid;
|
||||
gap: var(--Space-x1);
|
||||
margin-left: var(--Space-x2);
|
||||
}
|
||||
|
||||
.ol > li::marker {
|
||||
color: var(--Primary-Light-On-Surface-Accent);
|
||||
padding: 0;
|
||||
margin: var(--Space-x2) 0;
|
||||
}
|
||||
|
||||
.li {
|
||||
margin-left: var(--Space-x3);
|
||||
display: flex;
|
||||
gap: var(--Space-x1);
|
||||
|
||||
* {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.li > p {
|
||||
display: inline;
|
||||
.ul {
|
||||
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 {
|
||||
@@ -27,8 +45,8 @@
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.ol:has(li:nth-last-child(n + 5)),
|
||||
.ul:has(li:nth-last-child(n + 5)) {
|
||||
.ol:has(.li:nth-last-child(n + 5)),
|
||||
.ul:has(.li:nth-last-child(n + 5)) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ElementType } from "domelementtype"
|
||||
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 { TextLink } from "@scandic-hotels/design-system/TextLink"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { NodeNames } from "./utils"
|
||||
@@ -98,16 +98,14 @@ function renderNode(domNode: Node, idx: number) {
|
||||
return <span>{renderChildren(domNode)}</span>
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
<TextLink
|
||||
key={domNode.name + idx}
|
||||
color="Text/Interactive/Secondary"
|
||||
textDecoration="underline"
|
||||
weight="bold"
|
||||
target="_blank" //Always open in new tab
|
||||
href={domNode.attribs.href}
|
||||
isInline
|
||||
>
|
||||
{renderChildren(domNode)}
|
||||
</Link>
|
||||
</TextLink>
|
||||
)
|
||||
|
||||
case NodeNames.ul:
|
||||
@@ -117,19 +115,23 @@ function renderNode(domNode: Node, idx: number) {
|
||||
numberOfRows = Math.ceil(half)
|
||||
}
|
||||
return (
|
||||
<ul
|
||||
<Typography
|
||||
key={domNode.name + idx}
|
||||
className={styles.ul}
|
||||
style={
|
||||
numberOfRows
|
||||
? {
|
||||
gridTemplateRows: `repeat(${numberOfRows}, auto)`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
variant="Body/Paragraph/mdRegular"
|
||||
>
|
||||
{renderChildren(domNode)}
|
||||
</ul>
|
||||
<ul
|
||||
className={styles.ul}
|
||||
style={
|
||||
numberOfRows
|
||||
? {
|
||||
gridTemplateRows: `repeat(${numberOfRows}, auto)`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{renderChildren(domNode)}
|
||||
</ul>
|
||||
</Typography>
|
||||
)
|
||||
|
||||
case NodeNames.ol:
|
||||
@@ -139,23 +141,31 @@ function renderNode(domNode: Node, idx: number) {
|
||||
numberOfOlRows = Math.ceil(half)
|
||||
}
|
||||
return (
|
||||
<ol
|
||||
<Typography
|
||||
key={domNode.name + idx}
|
||||
className={styles.ol}
|
||||
style={
|
||||
numberOfOlRows
|
||||
? {
|
||||
gridTemplateRows: `repeat(${numberOfOlRows}, auto)`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
variant="Body/Paragraph/mdRegular"
|
||||
>
|
||||
{renderChildren(domNode)}
|
||||
</ol>
|
||||
<ol
|
||||
className={styles.ol}
|
||||
style={
|
||||
numberOfOlRows
|
||||
? {
|
||||
gridTemplateRows: `repeat(${numberOfOlRows}, auto)`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{renderChildren(domNode)}
|
||||
</ol>
|
||||
</Typography>
|
||||
)
|
||||
|
||||
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:
|
||||
return (
|
||||
@@ -208,13 +218,24 @@ function renderNode(domNode: Node, idx: number) {
|
||||
)
|
||||
|
||||
case NodeNames.em:
|
||||
return <em>{renderChildren(domNode)}</em>
|
||||
return <em key={domNode.name + idx}>{renderChildren(domNode)}</em>
|
||||
|
||||
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:
|
||||
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) {
|
||||
return domNode.data
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
import { useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
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 ShowMoreButton from "../ShowMoreButton"
|
||||
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
|
||||
|
||||
import { translateRoomLighting, translateSeatingType } from "./utils"
|
||||
|
||||
import styles from "./meetingRoomCard.module.css"
|
||||
@@ -34,72 +33,51 @@ export default function MeetingRoomCard({ room }: MeetingRoomCardProps) {
|
||||
|
||||
return (
|
||||
<article className={styles.card}>
|
||||
{image?.src ? (
|
||||
<Image
|
||||
src={image.src}
|
||||
alt={image.altText || image.altText_En || ""}
|
||||
className={styles.image}
|
||||
width={386}
|
||||
height={200}
|
||||
sizes="(min-width: 768px) 386px, 100vw"
|
||||
/>
|
||||
) : (
|
||||
<ImageFallback />
|
||||
)}
|
||||
<Image
|
||||
src={image?.src || ""}
|
||||
alt={image?.altText || image?.altText_En || ""}
|
||||
className={styles.image}
|
||||
width={386}
|
||||
height={200}
|
||||
sizes="(min-width: 768px) 386px, 100vw"
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
<Typography variant="Title/Subtitle/lg">
|
||||
<h3>{room.name}</h3>
|
||||
</Typography>
|
||||
<div className={styles.capacity}>
|
||||
<div className={styles.iconText}>
|
||||
<MaterialIcon
|
||||
icon="straighten"
|
||||
color="Icon/Interactive/Placeholder"
|
||||
/>
|
||||
<Typography
|
||||
variant="Body/Supporting text (caption)/smRegular"
|
||||
className={styles.roomDetails}
|
||||
>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<div className={styles.capacity}>
|
||||
<span className={styles.roomDetails}>
|
||||
<MaterialIcon icon="straighten" color="CurrentColor" size={16} />
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
<span>{room.size} m²</span>
|
||||
</Typography>
|
||||
{room.size} m²
|
||||
</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>
|
||||
{maxSeatings ? (
|
||||
<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>
|
||||
</Typography>
|
||||
{room.content.texts.descriptions.medium ? (
|
||||
<Typography>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p>{room.content.texts.descriptions.medium}</p>
|
||||
</Typography>
|
||||
) : null}
|
||||
{opened && (
|
||||
<table className={styles.openedInfo}>
|
||||
<tbody className={styles.rowItem}>
|
||||
<table className={styles.infoTable}>
|
||||
<tbody className={styles.tableBody}>
|
||||
{room.seatings.map((seating, idx) => (
|
||||
<TableRow
|
||||
key={seating.type}
|
||||
id={String(seating.capacity) + seating.type + idx}
|
||||
id={`${seating.capacity} ${seating.type} ${idx}`}
|
||||
name={translateSeatingType(seating.type, intl)}
|
||||
value={intl.formatMessage(
|
||||
{
|
||||
@@ -111,8 +89,7 @@ export default function MeetingRoomCard({ room }: MeetingRoomCardProps) {
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
<Divider color="Border/Divider/Subtle" />
|
||||
<tbody className={styles.rowItem}>
|
||||
<tbody className={styles.tableBody}>
|
||||
<TableRow
|
||||
name={intl.formatMessage({
|
||||
id: "meetingRoomCard.locationInHotel",
|
||||
@@ -1,38 +1,11 @@
|
||||
.card {
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
background-color: var(--Surface-Primary-Default);
|
||||
border-radius: var(--Corner-radius-md);
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--Border-Default);
|
||||
display: grid;
|
||||
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 {
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
@@ -47,10 +20,38 @@
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.roomDetails {
|
||||
.capacity {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--Space-x1);
|
||||
text-align: left;
|
||||
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 {
|
||||
color: var(--Text-Secondary);
|
||||
font-weight: inherit;
|
||||
@@ -1,19 +1,24 @@
|
||||
"use client"
|
||||
import { cx } from "class-variance-authority"
|
||||
import { useRef, useState } from "react"
|
||||
|
||||
import Grids from "@/components/TempDesignSystem/Grids"
|
||||
import MeetingRoomCard from "@/components/TempDesignSystem/MeetingRoomCard"
|
||||
import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
|
||||
|
||||
import MeetingRoomCard from "./MeetingRoomCard"
|
||||
|
||||
import styles from "./meetingRooms.module.css"
|
||||
|
||||
import type { MeetingRooms } from "@/types/components/hotelPage/meetingRooms"
|
||||
|
||||
interface MeetingRoomsProps {
|
||||
interface MeetingRoomsProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
rooms: MeetingRooms
|
||||
}
|
||||
|
||||
export default function MeetingRooms({ rooms }: MeetingRoomsProps) {
|
||||
export default function MeetingRooms({
|
||||
rooms,
|
||||
className,
|
||||
...props
|
||||
}: MeetingRoomsProps) {
|
||||
const showToggleButton = rooms.length > 3
|
||||
const [allRoomsVisible, setAllRoomsVisible] = useState(!showToggleButton)
|
||||
|
||||
@@ -27,20 +32,29 @@ export default function MeetingRooms({ rooms }: MeetingRoomsProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.section} ref={scrollRef}>
|
||||
<Grids.Stackable
|
||||
className={`${styles.grid} ${allRoomsVisible ? styles.allVisible : ""}`}
|
||||
<section
|
||||
className={cx(styles.roomsContainer, className)}
|
||||
ref={scrollRef}
|
||||
{...props}
|
||||
>
|
||||
<ul
|
||||
className={cx(styles.roomsList, {
|
||||
[styles.allVisible]: allRoomsVisible,
|
||||
})}
|
||||
>
|
||||
{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 ? (
|
||||
<ShowMoreButton
|
||||
className={styles.showMoreButton}
|
||||
loadMoreData={handleShowMore}
|
||||
showLess={allRoomsVisible}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,33 @@
|
||||
.grid {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.grid:not(.allVisible) > :nth-child(n + 4) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.section {
|
||||
.roomsContainer {
|
||||
position: relative;
|
||||
color: var(--Text-Default);
|
||||
display: grid;
|
||||
gap: var(--Space-x4);
|
||||
z-index: 0;
|
||||
gap: var(--Space-x3);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,12 +75,13 @@ export default async function MeetingsSubpage({
|
||||
|
||||
{mainBody ? <HtmlContent html={mainBody} /> : null}
|
||||
</main>
|
||||
{meetingRooms ? (
|
||||
<div className={styles.meetingsInformation}>
|
||||
<MeetingsAdditionalContent rooms={meetingRooms} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{meetingRooms ? (
|
||||
<MeetingsAdditionalContent
|
||||
className={styles.additionalContent}
|
||||
rooms={meetingRooms}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
.meetingsSubpage {
|
||||
display: grid;
|
||||
padding-bottom: var(--Space-x9);
|
||||
color: var(--Text-Default);
|
||||
display: grid;
|
||||
gap: var(--Space-x4);
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
@@ -10,7 +9,11 @@
|
||||
gap: var(--Space-x3);
|
||||
width: var(--max-width-content);
|
||||
margin: 0 auto;
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
.additionalContent {
|
||||
width: var(--max-width-content);
|
||||
margin: var(--Space-x4) auto 0;
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
@@ -48,6 +51,10 @@
|
||||
column-gap: var(--Space-x9);
|
||||
}
|
||||
|
||||
.additionalContent {
|
||||
margin-top: var(--Space-x7);
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: none;
|
||||
}
|
||||
@@ -57,8 +64,4 @@
|
||||
grid-row: 1 / span 2;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.meetingsInformation {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ export default async function RestaurantSubpage({
|
||||
<ButtonLink
|
||||
href={bookTableUrl}
|
||||
variant="Primary"
|
||||
size="Medium"
|
||||
typography="Body/Paragraph/mdBold"
|
||||
>
|
||||
{intl.formatMessage({
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
gap: var(--Space-x3);
|
||||
width: var(--max-width-content);
|
||||
margin: 0 auto;
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
@@ -38,8 +37,8 @@
|
||||
.buttonContainer {
|
||||
position: sticky;
|
||||
padding: var(--Space-x3) var(--Space-x2);
|
||||
background-color: var(--Base-Surface-Secondary-light-Normal);
|
||||
border-top: 1px solid var(--Base-Border-Subtle);
|
||||
background-color: var(--Background-Primary);
|
||||
border-top: 1px solid var(--Border-Default);
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 LocalCallCharges from "@/components/LocalCallCharges"
|
||||
@@ -31,13 +31,9 @@ export default async function MeetingsSidebar({
|
||||
</h3>
|
||||
</Typography>
|
||||
<div className={styles.contactDetails}>
|
||||
<Link href={`tel:${phoneNumber}`}>{phoneNumber}</Link>
|
||||
<TextLink href={`tel:${phoneNumber}`}>{phoneNumber}</TextLink>
|
||||
<LocalCallCharges country={country} />
|
||||
{email && (
|
||||
<Link textDecoration="underline" href={`mailto:${email}`}>
|
||||
{email}
|
||||
</Link>
|
||||
)}
|
||||
{email ? <TextLink href={`mailto:${email}`}>{email}</TextLink> : null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -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 LocalCallCharges from "@/components/LocalCallCharges"
|
||||
@@ -52,16 +52,13 @@ export default async function ParkingSidebar({
|
||||
</h3>
|
||||
</Typography>
|
||||
<div className={styles.contactDetails}>
|
||||
<Link href={`tel:${contactInformation.phoneNumber}`}>
|
||||
<TextLink href={`tel:${contactInformation.phoneNumber}`}>
|
||||
{contactInformation.phoneNumber}
|
||||
</Link>
|
||||
</TextLink>
|
||||
<LocalCallCharges country={address.country} />
|
||||
<Link
|
||||
textDecoration="underline"
|
||||
href={`mailto:${contactInformation.email}`}
|
||||
>
|
||||
<TextLink href={`mailto:${contactInformation.email}`}>
|
||||
{contactInformation.email}
|
||||
</Link>
|
||||
</TextLink>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
||||
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 { TextLink } from "@scandic-hotels/design-system/TextLink"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import LocalCallCharges from "@/components/LocalCallCharges"
|
||||
@@ -47,14 +47,17 @@ export default async function RestaurantSidebar({
|
||||
) : null}
|
||||
{bookTableUrl && (
|
||||
<div className={styles.buttonContainer}>
|
||||
<Button intent="primary" theme="base" asChild>
|
||||
<a href={bookTableUrl}>
|
||||
{intl.formatMessage({
|
||||
id: "restaurantBar.bookATable",
|
||||
defaultMessage: "Book a table",
|
||||
})}
|
||||
</a>
|
||||
</Button>
|
||||
<ButtonLink
|
||||
href={bookTableUrl}
|
||||
variant="Primary"
|
||||
size="Medium"
|
||||
typography="Body/Paragraph/mdBold"
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: "restaurantBar.bookATable",
|
||||
defaultMessage: "Book a table",
|
||||
})}
|
||||
</ButtonLink>
|
||||
</div>
|
||||
)}
|
||||
{restaurant.menus.length ? (
|
||||
@@ -70,19 +73,14 @@ export default async function RestaurantSidebar({
|
||||
<ul className={styles.menuList}>
|
||||
{restaurant.menus.map(({ name, url }) => (
|
||||
<li key={name}>
|
||||
<Link
|
||||
href={url}
|
||||
color="Text/Interactive/Secondary"
|
||||
textDecoration="underline"
|
||||
variant="icon"
|
||||
>
|
||||
<TextLink href={url} target="_blank">
|
||||
{name}
|
||||
<MaterialIcon
|
||||
icon="open_in_new"
|
||||
size={20}
|
||||
size={24}
|
||||
color="CurrentColor"
|
||||
/>
|
||||
</Link>
|
||||
</TextLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -116,17 +114,15 @@ export default async function RestaurantSidebar({
|
||||
</h3>
|
||||
</Typography>
|
||||
<div className={styles.contactDetails}>
|
||||
{phoneNumber && (
|
||||
{phoneNumber ? (
|
||||
<>
|
||||
<Link href={`tel:${phoneNumber}`}>{phoneNumber}</Link>
|
||||
<TextLink href={`tel:${phoneNumber}`}>{phoneNumber}</TextLink>
|
||||
<LocalCallCharges country={hotelAddress.country} />
|
||||
</>
|
||||
)}
|
||||
{email && (
|
||||
<Link textDecoration="underline" href={`mailto:${email}`}>
|
||||
{email}
|
||||
</Link>
|
||||
)}
|
||||
) : null}
|
||||
{email ? (
|
||||
<TextLink href={`mailto:${email}`}>{email}</TextLink>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
.content {
|
||||
display: grid;
|
||||
gap: var(--Space-x15);
|
||||
color: var(--Text-Default);
|
||||
}
|
||||
|
||||
.menuList {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
.checkbox:has(input:checked) {
|
||||
border-color: var(--Border-Interactive-Selected);
|
||||
border-color: var(--Border-Interactive-Active);
|
||||
}
|
||||
|
||||
.checkbox:has(input:checked) span[class*="checkbox_checkbox_"] {
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
<svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_2991_3013" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="33" height="32">
|
||||
<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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<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"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1,8 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_5625_32301" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
||||
<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 xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<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"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 931 B |
@@ -64,7 +64,8 @@
|
||||
"./utils/promiseWithTimeout": "./utils/promiseWithTimeout.ts",
|
||||
"./utils/rangeArray": "./utils/rangeArray.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/url": "./utils/url.ts",
|
||||
"./utils/zod/*": "./utils/zod/*.ts"
|
||||
|
||||
31
packages/common/utils/theme/serverContext.ts
Normal file
31
packages/common/utils/theme/serverContext.ts
Normal 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
|
||||
}
|
||||
@@ -27,12 +27,12 @@
|
||||
}
|
||||
|
||||
.Outlined:active {
|
||||
border-color: var(--Border-Interactive-Selected);
|
||||
border-color: var(--Border-Interactive-Active);
|
||||
}
|
||||
|
||||
.FilterRounded {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--Border-Interactive-Selected);
|
||||
border: 1px solid var(--Border-Interactive-Active);
|
||||
border-radius: var(--Corner-radius-rounded);
|
||||
padding: var(--Space-x025) var(--Space-x2);
|
||||
color: var(--Text-Default);
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
.label:has(:checked) {
|
||||
border: 2px solid var(--Border-Interactive-Selected);
|
||||
border: 2px solid var(--Border-Interactive-Active);
|
||||
}
|
||||
|
||||
.label:not(:has(:checked)) .selectedIcon {
|
||||
|
||||
@@ -4,7 +4,7 @@ import NextImage, { ImageProps as NextImageProps } from 'next/image'
|
||||
|
||||
import ImageFallback from '../ImageFallback'
|
||||
|
||||
import type { CSSProperties } from 'react'
|
||||
import { useState, type CSSProperties, type SyntheticEvent } from 'react'
|
||||
import { imageLoader } from './imageLoader'
|
||||
|
||||
type FocalPoint = {
|
||||
@@ -22,8 +22,11 @@ export default function Image({
|
||||
focalPoint,
|
||||
dimensions,
|
||||
style,
|
||||
src,
|
||||
onError,
|
||||
...props
|
||||
}: ImageProps) {
|
||||
const [imageError, setImageError] = useState(false)
|
||||
const styles: CSSProperties = focalPoint
|
||||
? {
|
||||
objectFit: 'cover',
|
||||
@@ -32,14 +35,24 @@ export default function Image({
|
||||
}
|
||||
: { ...style }
|
||||
|
||||
if (!props.src) {
|
||||
function handleError(error: SyntheticEvent<HTMLImageElement, Event>) {
|
||||
if (onError) {
|
||||
onError(error)
|
||||
} else {
|
||||
setImageError(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (!src || imageError) {
|
||||
return <ImageFallback />
|
||||
}
|
||||
|
||||
return (
|
||||
<NextImage
|
||||
{...props}
|
||||
src={src}
|
||||
style={styles}
|
||||
onError={handleError}
|
||||
loader={imageLoader({ dimensions, focalPoint })}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -87,19 +87,26 @@
|
||||
.li:has(.heart)::before,
|
||||
.check > .li::before,
|
||||
.li:has(.check)::before {
|
||||
content: '';
|
||||
position: relative;
|
||||
height: 8px;
|
||||
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,
|
||||
.li:has(.check)::before {
|
||||
content: url('/_static/icons/check-ring.svg');
|
||||
mask-image: url('/_static/icons/check_circle.svg');
|
||||
}
|
||||
|
||||
.heart > .li::before,
|
||||
.li:has(.heart)::before {
|
||||
content: url('/_static/icons/heart.svg');
|
||||
mask-image: url('/_static/icons/heart.svg');
|
||||
}
|
||||
|
||||
.li > * {
|
||||
|
||||
@@ -4,77 +4,63 @@
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.mapContainer :global(.gm-style .gm-style-iw-d) {
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
max-height: none !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
&::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;
|
||||
}
|
||||
|
||||
.mapContainer :global(.gm-style .gm-style-iw-c) {
|
||||
padding: 0 !important;
|
||||
overflow: hidden !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;
|
||||
:global(.gm-style .gm-style-iw-d),
|
||||
:global(.gm-style .gm-style-iw-c) {
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
max-height: none !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ctaButtons {
|
||||
position: absolute;
|
||||
top: var(--Spacing-x2);
|
||||
right: var(--Spacing-x2);
|
||||
z-index: 1;
|
||||
top: var(--Space-x2);
|
||||
right: var(--Space-x2);
|
||||
display: flex;
|
||||
gap: var(--Space-x7);
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x7);
|
||||
align-items: flex-end;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.zoomButtons {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
pointer-events: initial;
|
||||
box-shadow: var(--button-box-shadow);
|
||||
gap: var(--Spacing-x-half);
|
||||
display: flex;
|
||||
gap: var(--Space-x2);
|
||||
}
|
||||
|
||||
.zoomButton {
|
||||
width: var(--Space-x5);
|
||||
height: var(--Space-x5);
|
||||
padding: 0;
|
||||
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) {
|
||||
.ctaButtons {
|
||||
top: var(--Spacing-x4);
|
||||
right: var(--Spacing-x5);
|
||||
bottom: var(--Spacing-x7);
|
||||
top: var(--Space-x4);
|
||||
right: var(--Space-x5);
|
||||
bottom: var(--Space-x7);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.zoomButtons {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { IconByIconName } from '../../../Icons/IconByIconName'
|
||||
|
||||
import { getIconByPoiGroupAndCategory } from '../utils'
|
||||
import {
|
||||
getIconByPoiGroupAndCategory,
|
||||
type PointOfInterestGroup,
|
||||
} from '../utils'
|
||||
import { poiVariants } from './variants'
|
||||
|
||||
import { VariantProps } from 'class-variance-authority'
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
|
||||
export type PointOfInterestGroup =
|
||||
| 'Public transport'
|
||||
| 'Attractions'
|
||||
| 'Business'
|
||||
| 'Location'
|
||||
| 'Parking'
|
||||
| 'Shopping & Dining'
|
||||
|
||||
export interface PoiMarkerProps extends VariantProps<typeof poiVariants> {
|
||||
interface PoiMarkerProps extends VariantProps<typeof poiVariants> {
|
||||
group: PointOfInterestGroup
|
||||
categoryName?: string
|
||||
className?: string
|
||||
@@ -33,7 +28,7 @@ export function PoiMarker({
|
||||
<span className={classNames}>
|
||||
<IconByIconName
|
||||
iconName={iconName}
|
||||
color={skipBackground ? 'Icon/Feedback/Neutral' : 'Icon/Inverted'}
|
||||
color={skipBackground ? 'CurrentColor' : 'Icon/Inverted'}
|
||||
size={size === 'small' ? 16 : size === 'large' ? 24 : 20}
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -1,9 +1,37 @@
|
||||
.icon {
|
||||
.poiMarker {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
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 {
|
||||
@@ -14,27 +42,3 @@
|
||||
width: 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;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
import styles from './poi.module.css'
|
||||
import { PointOfInterestGroup } from '.'
|
||||
import type { PointOfInterestGroup } from '../utils'
|
||||
import styles from './poiMarker.module.css'
|
||||
|
||||
export const poiVariants = cva(styles.icon, {
|
||||
export const poiVariants = cva(styles.poiMarker, {
|
||||
variants: {
|
||||
group: {
|
||||
['Attractions']: styles.attractions,
|
||||
@@ -14,7 +14,7 @@ export const poiVariants = cva(styles.icon, {
|
||||
['Shopping & Dining']: styles.shoppingDining,
|
||||
} satisfies Record<PointOfInterestGroup, string>,
|
||||
skipBackground: {
|
||||
true: styles.transparent,
|
||||
true: styles.skipBackground,
|
||||
false: '',
|
||||
},
|
||||
size: {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { IconName } from '../../Icons/iconName'
|
||||
import { PointOfInterestGroup } from './PoiMarker'
|
||||
|
||||
export type PointOfInterestGroup =
|
||||
| 'Public transport'
|
||||
| 'Attractions'
|
||||
| 'Business'
|
||||
| 'Location'
|
||||
| 'Parking'
|
||||
| 'Shopping & Dining'
|
||||
|
||||
export function getIconByPoiGroupAndCategory(
|
||||
group: PointOfInterestGroup,
|
||||
|
||||
@@ -37,9 +37,9 @@ export default function ParkingList({
|
||||
|
||||
return (
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<ul className={styles.listStyling}>
|
||||
<ul className={styles.list}>
|
||||
{numberOfChargingSpaces ? (
|
||||
<li>
|
||||
<li className={styles.listItem}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: 'parkingInformation.numberOfChargingPoints',
|
||||
@@ -50,13 +50,13 @@ export default function ParkingList({
|
||||
)}
|
||||
</li>
|
||||
) : null}
|
||||
<li>
|
||||
<li className={styles.listItem}>
|
||||
{canMakeReservation
|
||||
? canMakeReservationYesMsg
|
||||
: canMakeReservationNoMsg}
|
||||
</li>
|
||||
{numberOfParkingSpots ? (
|
||||
<li>
|
||||
<li className={styles.listItem}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: 'parkingInformation.numberOfParkingSpots',
|
||||
@@ -67,7 +67,7 @@ export default function ParkingList({
|
||||
</li>
|
||||
) : null}
|
||||
{distanceToHotel ? (
|
||||
<li>
|
||||
<li className={styles.listItem}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: 'parkingInformation.distanceToHotel',
|
||||
@@ -78,7 +78,7 @@ export default function ParkingList({
|
||||
</li>
|
||||
) : null}
|
||||
{address ? (
|
||||
<li>
|
||||
<li className={styles.listItem}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: 'parkingInformation.address',
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
.listStyling {
|
||||
.list {
|
||||
display: grid;
|
||||
gap: var(--Space-x1);
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.listStyling > li::before {
|
||||
content: url('/_static/icons/heart.svg');
|
||||
position: relative;
|
||||
height: 8px;
|
||||
top: 3px;
|
||||
margin-right: var(--Spacing-x1);
|
||||
.listItem {
|
||||
display: flex;
|
||||
gap: var(--Space-x1);
|
||||
|
||||
&::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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import { useIntl } from 'react-intl'
|
||||
|
||||
import ButtonLink from '../ButtonLink'
|
||||
import { Divider } from '../Divider'
|
||||
import { MaterialIcon } from '../Icons/MaterialIcon'
|
||||
import { Typography } from '../Typography'
|
||||
import ButtonLink from '../ButtonLink'
|
||||
import ParkingList from './ParkingList'
|
||||
import ParkingPrices from './ParkingPrices'
|
||||
|
||||
@@ -88,6 +88,7 @@ export default function ParkingInformation({
|
||||
{parking.externalParkingUrl && showExternalParkingButton && (
|
||||
<ButtonLink
|
||||
typography="Body/Paragraph/mdBold"
|
||||
size="Medium"
|
||||
href={parking.externalParkingUrl}
|
||||
target="_blank"
|
||||
>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
.parkingInformation {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x3);
|
||||
gap: var(--Space-x3);
|
||||
}
|
||||
|
||||
.list,
|
||||
.prices {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
gap: var(--Space-x15);
|
||||
}
|
||||
|
||||
.priceWrapper {
|
||||
background-color: var(--Base-Surface-Subtle-Normal);
|
||||
background-color: var(--Surface-Secondary-Default);
|
||||
border-radius: var(--Corner-radius-md);
|
||||
padding: var(--Spacing-x2) var(--Spacing-x3);
|
||||
padding: var(--Space-x2) var(--Space-x3);
|
||||
display: grid;
|
||||
gap: var(--Spacing-x1);
|
||||
gap: var(--Space-x1);
|
||||
}
|
||||
|
||||
.heading {
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
.theme-primary:not(.disabled) {
|
||||
color: var(--Text-Interactive-Secondary);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: var(--Text-Interactive-Secondary-Hover);
|
||||
@@ -28,7 +27,6 @@
|
||||
|
||||
.theme-inverted:not(.disabled) {
|
||||
color: var(--Text-Inverted);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
|
||||
Reference in New Issue
Block a user