From 3a38e99a71266b98b9fb6b8378e1e68995eff3bb Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Wed, 5 Nov 2025 08:30:55 +0000 Subject: [PATCH] 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 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Approved-by: Christel Westerberg Approved-by: Matilda Landström --- .../(contentTypes)/hotel_page/[uid]/page.tsx | 9 +- apps/scandic-web/app/[lang]/(live)/layout.tsx | 3 +- .../joinScandicFriends.module.css | 10 +- .../HotelListItem/hotelListItem.module.css | 2 +- .../HotelMapPage/Sidebar/sidebar.module.css | 9 +- .../HotelMapPage/hotelMapPage.module.css | 4 +- .../Facility/facility.module.css | 23 +++-- .../ContentType/HotelPage/index.tsx | 10 +- .../accessibilitySubpage.module.css | 23 +++-- .../AccessibilitySubpage/index.tsx | 17 ++-- .../HeroHeader/heroHeader.module.css | 6 +- .../HotelSubpage/HeroHeader/index.tsx | 14 ++- .../HtmlContent/htmlContent.module.css | 44 ++++++--- .../HotelSubpage/HtmlContent/index.tsx | 85 ++++++++++------- .../MeetingRooms}/MeetingRoomCard/index.tsx | 91 +++++++------------ .../meetingRoomCard.module.css | 63 ++++++------- .../MeetingRooms}/MeetingRoomCard/utils.ts | 0 .../MeetingsSubpage/MeetingRooms/index.tsx | 34 +++++-- .../MeetingRooms/meetingRooms.module.css | 42 ++++++--- .../HotelSubpage/MeetingsSubpage/index.tsx | 11 ++- .../meetingsSubpage.module.css | 17 ++-- .../HotelSubpage/RestaurantSubpage/index.tsx | 1 + .../restaurantSubpage.module.css | 5 +- .../HotelSubpage/Sidebar/MeetingsSidebar.tsx | 10 +- .../HotelSubpage/Sidebar/ParkingSidebar.tsx | 13 +-- .../Sidebar/RestaurantSidebar.tsx | 48 +++++----- .../HotelSubpage/Sidebar/sidebar.module.css | 1 + .../Multiroom/multiroom.module.css | 2 +- .../public/_static/icons/check_circle.svg | 9 +- .../public/_static/icons/heart.svg | 9 +- packages/common/package.json | 3 +- .../common/utils/{theme.ts => theme/index.ts} | 0 packages/common/utils/theme/serverContext.ts | 31 +++++++ .../ChipButton/chip-button.module.css | 4 +- .../Form/RadioCard/radioCard.module.css | 2 +- .../lib/components/Image/index.tsx | 17 +++- .../JsonToHtml/jsontohtml.module.css | 13 ++- .../InteractiveMap/interactiveMap.module.css | 86 ++++++++---------- .../Map/Markers/PoiMarker/index.tsx | 19 ++-- .../{poi.module.css => poiMarker.module.css} | 56 ++++++------ .../Map/Markers/PoiMarker/variants.ts | 8 +- .../lib/components/Map/Markers/utils.ts | 9 +- .../ParkingInformation/ParkingList/index.tsx | 12 +-- .../ParkingList/parkingList.module.css | 27 ++++-- .../components/ParkingInformation/index.tsx | 3 +- .../parkingInformation.module.css | 10 +- .../components/TextLink/textLink.module.css | 2 - 47 files changed, 524 insertions(+), 393 deletions(-) rename apps/scandic-web/components/{TempDesignSystem => ContentType/HotelSubpage/MeetingsSubpage/MeetingRooms}/MeetingRoomCard/index.tsx (67%) rename apps/scandic-web/components/{TempDesignSystem => ContentType/HotelSubpage/MeetingsSubpage/MeetingRooms}/MeetingRoomCard/meetingRoomCard.module.css (75%) rename apps/scandic-web/components/{TempDesignSystem => ContentType/HotelSubpage/MeetingsSubpage/MeetingRooms}/MeetingRoomCard/utils.ts (100%) rename packages/common/utils/{theme.ts => theme/index.ts} (100%) create mode 100644 packages/common/utils/theme/serverContext.ts rename packages/design-system/lib/components/Map/Markers/PoiMarker/{poi.module.css => poiMarker.module.css} (77%) diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/hotel_page/[uid]/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/hotel_page/[uid]/page.tsx index f4e13b6a7..0d5c46319 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/hotel_page/[uid]/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/(contentTypes)/hotel_page/[uid]/page.tsx @@ -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 (
@@ -59,11 +62,7 @@ export default async function HotelPagePage( } else { return (
- +
) } diff --git a/apps/scandic-web/app/[lang]/(live)/layout.tsx b/apps/scandic-web/app/[lang]/(live)/layout.tsx index fb2a814c7..1c168e019 100644 --- a/apps/scandic-web/app/[lang]/(live)/layout.tsx +++ b/apps/scandic-web/app/[lang]/(live)/layout.tsx @@ -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 || [] `} - +
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; + } } diff --git a/apps/scandic-web/components/ContentType/HotelPage/index.tsx b/apps/scandic-web/components/ContentType/HotelPage/index.tsx index 7dca96823..63628dd3a 100644 --- a/apps/scandic-web/components/ContentType/HotelPage/index.tsx +++ b/apps/scandic-web/components/ContentType/HotelPage/index.tsx @@ -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 (
@@ -226,7 +226,7 @@ export default async function HotelPage({ healthAndWellness={healthAndWellness ?? null} pageSections={pageSections} activities={activities} - hotelTheme={theme} + hotelTheme={hotelTheme} /> {campaignsBlock ? (

{accessibilityGroup.name}

-
    - {accessibilityGroup.specialNeeds.map((groupItem) => ( - -
  • + +
      + {accessibilityGroup.specialNeeds.map((groupItem) => ( +
    • {groupItem.details ? // eslint-disable-next-line formatjs/no-literal-string-in-jsx `${groupItem.name}: ${groupItem.details}` : groupItem.name}
    • - - ))} -
    + ))} +
+
))}
diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/HeroHeader/heroHeader.module.css b/apps/scandic-web/components/ContentType/HotelSubpage/HeroHeader/heroHeader.module.css index 809182ac9..a808af27e 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/HeroHeader/heroHeader.module.css +++ b/apps/scandic-web/components/ContentType/HotelSubpage/HeroHeader/heroHeader.module.css @@ -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 { diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/HeroHeader/index.tsx b/apps/scandic-web/components/ContentType/HotelSubpage/HeroHeader/index.tsx index 03b14f100..d91e02b05 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/HeroHeader/index.tsx +++ b/apps/scandic-web/components/ContentType/HotelSubpage/HeroHeader/index.tsx @@ -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 ( -
+
}> - + {heroImage ? ( diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/HtmlContent/htmlContent.module.css b/apps/scandic-web/components/ContentType/HotelSubpage/HtmlContent/htmlContent.module.css index df51f40e2..cdb495c52 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/HtmlContent/htmlContent.module.css +++ b/apps/scandic-web/components/ContentType/HotelSubpage/HtmlContent/htmlContent.module.css @@ -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; } diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/HtmlContent/index.tsx b/apps/scandic-web/components/ContentType/HotelSubpage/HtmlContent/index.tsx index ad83e97f7..604b06123 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/HtmlContent/index.tsx +++ b/apps/scandic-web/components/ContentType/HotelSubpage/HtmlContent/index.tsx @@ -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 {renderChildren(domNode)} } return ( - {renderChildren(domNode)} - + ) case NodeNames.ul: @@ -117,19 +115,23 @@ function renderNode(domNode: Node, idx: number) { numberOfRows = Math.ceil(half) } return ( -
    - {renderChildren(domNode)} -
+
    + {renderChildren(domNode)} +
+ ) case NodeNames.ol: @@ -139,23 +141,31 @@ function renderNode(domNode: Node, idx: number) { numberOfOlRows = Math.ceil(half) } return ( -
    - {renderChildren(domNode)} -
+
    + {renderChildren(domNode)} +
+ ) case NodeNames.li: - return
  • {renderChildren(domNode)}
  • + return ( +
  • + {renderChildren(domNode)} +
  • + ) case NodeNames.td: return ( @@ -208,13 +218,24 @@ function renderNode(domNode: Node, idx: number) { ) case NodeNames.em: - return {renderChildren(domNode)} + return {renderChildren(domNode)} case NodeNames.strong: - return {renderChildren(domNode)} + return ( + + {renderChildren(domNode)} + + ) case NodeNames.span: - return {renderChildren(domNode)} + return ( + + {renderChildren(domNode)} + + ) } } else if (domNode.type === ElementType.Text) { return domNode.data diff --git a/apps/scandic-web/components/TempDesignSystem/MeetingRoomCard/index.tsx b/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/MeetingRooms/MeetingRoomCard/index.tsx similarity index 67% rename from apps/scandic-web/components/TempDesignSystem/MeetingRoomCard/index.tsx rename to apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/MeetingRooms/MeetingRoomCard/index.tsx index 8ff786136..3ceb350dd 100644 --- a/apps/scandic-web/components/TempDesignSystem/MeetingRoomCard/index.tsx +++ b/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/MeetingRooms/MeetingRoomCard/index.tsx @@ -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 (
    - {image?.src ? ( - {image.altText - ) : ( - - )} + {image?.altText

    {room.name}

    -
    -
    - - + +
    + + {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} - {room.size} m² - + {room.size} m² + + {maxSeatings ? ( + + + {intl.formatMessage( + { + id: "meetingRoomCard.maxSeatings", + defaultMessage: "max {seatings} pers", + }, + { seatings: maxSeatings } + )} + + ) : null}
    - {maxSeatings ? ( -
    - - - - {intl.formatMessage( - { - id: "meetingRoomCard.maxSeatings", - defaultMessage: "max {seatings} pers", - }, - { seatings: maxSeatings } - )} - - -
    - ) : null} -
    + {room.content.texts.descriptions.medium ? ( - +

    {room.content.texts.descriptions.medium}

    ) : null} {opened && ( - - +
    + {room.seatings.map((seating, idx) => ( ))} - - + { 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 ( -
    - +
      {rooms.map((room) => ( - +
    • + +
    • ))} - +
    {showToggleButton ? ( ) : null} -
    + ) } diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/MeetingRooms/meetingRooms.module.css b/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/MeetingRooms/meetingRooms.module.css index 7ae5122b9..8a4e04a23 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/MeetingRooms/meetingRooms.module.css +++ b/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/MeetingRooms/meetingRooms.module.css @@ -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); + } } diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/index.tsx b/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/index.tsx index e235fe2dc..05bf99630 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/index.tsx +++ b/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/index.tsx @@ -75,12 +75,13 @@ export default async function MeetingsSubpage({ {mainBody ? : null} - {meetingRooms ? ( -
    - -
    - ) : null} + {meetingRooms ? ( + + ) : null} ) diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/meetingsSubpage.module.css b/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/meetingsSubpage.module.css index 934960c2f..176318e3c 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/meetingsSubpage.module.css +++ b/apps/scandic-web/components/ContentType/HotelSubpage/MeetingsSubpage/meetingsSubpage.module.css @@ -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; - } } diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/RestaurantSubpage/index.tsx b/apps/scandic-web/components/ContentType/HotelSubpage/RestaurantSubpage/index.tsx index a5cb8c1f9..cdd5e1ab5 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/RestaurantSubpage/index.tsx +++ b/apps/scandic-web/components/ContentType/HotelSubpage/RestaurantSubpage/index.tsx @@ -59,6 +59,7 @@ export default async function RestaurantSubpage({ {intl.formatMessage({ diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/RestaurantSubpage/restaurantSubpage.module.css b/apps/scandic-web/components/ContentType/HotelSubpage/RestaurantSubpage/restaurantSubpage.module.css index c89b51cef..9bbe01ab6 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/RestaurantSubpage/restaurantSubpage.module.css +++ b/apps/scandic-web/components/ContentType/HotelSubpage/RestaurantSubpage/restaurantSubpage.module.css @@ -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; } diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/MeetingsSidebar.tsx b/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/MeetingsSidebar.tsx index 4fe263c45..fd3b3c8e4 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/MeetingsSidebar.tsx +++ b/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/MeetingsSidebar.tsx @@ -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({
    - {phoneNumber} + {phoneNumber} - {email && ( - - {email} - - )} + {email ? {email} : null}
    diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx b/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx index c9f970a0e..2cc2e635c 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx +++ b/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/ParkingSidebar.tsx @@ -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({
    - + {contactInformation.phoneNumber} - + - + {contactInformation.email} - +
    diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/RestaurantSidebar.tsx b/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/RestaurantSidebar.tsx index 76f8a9f63..813533f67 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/RestaurantSidebar.tsx +++ b/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/RestaurantSidebar.tsx @@ -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 && (
    - + + {intl.formatMessage({ + id: "restaurantBar.bookATable", + defaultMessage: "Book a table", + })} +
    )} {restaurant.menus.length ? ( @@ -70,19 +73,14 @@ export default async function RestaurantSidebar({
      {restaurant.menus.map(({ name, url }) => (
    • - + {name} - +
    • ))}
    @@ -116,17 +114,15 @@ export default async function RestaurantSidebar({
    - {phoneNumber && ( + {phoneNumber ? ( <> - {phoneNumber} + {phoneNumber} - )} - {email && ( - - {email} - - )} + ) : null} + {email ? ( + {email} + ) : null}
    )} diff --git a/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/sidebar.module.css b/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/sidebar.module.css index 5d972a6b1..5f0ffba3e 100644 --- a/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/sidebar.module.css +++ b/apps/scandic-web/components/ContentType/HotelSubpage/Sidebar/sidebar.module.css @@ -6,6 +6,7 @@ .content { display: grid; gap: var(--Space-x15); + color: var(--Text-Default); } .menuList { diff --git a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/multiroom.module.css b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/multiroom.module.css index 8ed5b8e2c..afa20071b 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/multiroom.module.css +++ b/apps/scandic-web/components/HotelReservation/MyStay/ReferenceCard/Actions/NotCancelled/ManageStay/Actions/CancelStay/Steps/Confirmation/Multiroom/multiroom.module.css @@ -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_"] { diff --git a/apps/scandic-web/public/_static/icons/check_circle.svg b/apps/scandic-web/public/_static/icons/check_circle.svg index d83769858..6d4fb4edf 100644 --- a/apps/scandic-web/public/_static/icons/check_circle.svg +++ b/apps/scandic-web/public/_static/icons/check_circle.svg @@ -1,8 +1,3 @@ - - - - - - - + + diff --git a/apps/scandic-web/public/_static/icons/heart.svg b/apps/scandic-web/public/_static/icons/heart.svg index e46e94905..0d3b535d9 100644 --- a/apps/scandic-web/public/_static/icons/heart.svg +++ b/apps/scandic-web/public/_static/icons/heart.svg @@ -1,8 +1,3 @@ - - - - - - - + + diff --git a/packages/common/package.json b/packages/common/package.json index 044ac9804..80c69deca 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -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" diff --git a/packages/common/utils/theme.ts b/packages/common/utils/theme/index.ts similarity index 100% rename from packages/common/utils/theme.ts rename to packages/common/utils/theme/index.ts diff --git a/packages/common/utils/theme/serverContext.ts b/packages/common/utils/theme/serverContext.ts new file mode 100644 index 000000000..82169cab2 --- /dev/null +++ b/packages/common/utils/theme/serverContext.ts @@ -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 +} diff --git a/packages/design-system/lib/components/ChipButton/chip-button.module.css b/packages/design-system/lib/components/ChipButton/chip-button.module.css index 2e57c8dcb..125e4ca98 100644 --- a/packages/design-system/lib/components/ChipButton/chip-button.module.css +++ b/packages/design-system/lib/components/ChipButton/chip-button.module.css @@ -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); diff --git a/packages/design-system/lib/components/Form/RadioCard/radioCard.module.css b/packages/design-system/lib/components/Form/RadioCard/radioCard.module.css index 82d7e60f2..197ce79bd 100644 --- a/packages/design-system/lib/components/Form/RadioCard/radioCard.module.css +++ b/packages/design-system/lib/components/Form/RadioCard/radioCard.module.css @@ -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 { diff --git a/packages/design-system/lib/components/Image/index.tsx b/packages/design-system/lib/components/Image/index.tsx index 853ddf378..ee62b9809 100644 --- a/packages/design-system/lib/components/Image/index.tsx +++ b/packages/design-system/lib/components/Image/index.tsx @@ -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) { + if (onError) { + onError(error) + } else { + setImageError(true) + } + } + + if (!src || imageError) { return } return ( ) diff --git a/packages/design-system/lib/components/JsonToHtml/jsontohtml.module.css b/packages/design-system/lib/components/JsonToHtml/jsontohtml.module.css index ffa9ec883..ebfcb3ac6 100644 --- a/packages/design-system/lib/components/JsonToHtml/jsontohtml.module.css +++ b/packages/design-system/lib/components/JsonToHtml/jsontohtml.module.css @@ -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 > * { diff --git a/packages/design-system/lib/components/Map/InteractiveMap/interactiveMap.module.css b/packages/design-system/lib/components/Map/InteractiveMap/interactiveMap.module.css index 7698a1e9e..b6043b768 100644 --- a/packages/design-system/lib/components/Map/InteractiveMap/interactiveMap.module.css +++ b/packages/design-system/lib/components/Map/InteractiveMap/interactiveMap.module.css @@ -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; - } } diff --git a/packages/design-system/lib/components/Map/Markers/PoiMarker/index.tsx b/packages/design-system/lib/components/Map/Markers/PoiMarker/index.tsx index 7ec361224..f4eb29a9f 100644 --- a/packages/design-system/lib/components/Map/Markers/PoiMarker/index.tsx +++ b/packages/design-system/lib/components/Map/Markers/PoiMarker/index.tsx @@ -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 { +interface PoiMarkerProps extends VariantProps { group: PointOfInterestGroup categoryName?: string className?: string @@ -33,7 +28,7 @@ export function PoiMarker({ diff --git a/packages/design-system/lib/components/Map/Markers/PoiMarker/poi.module.css b/packages/design-system/lib/components/Map/Markers/PoiMarker/poiMarker.module.css similarity index 77% rename from packages/design-system/lib/components/Map/Markers/PoiMarker/poi.module.css rename to packages/design-system/lib/components/Map/Markers/PoiMarker/poiMarker.module.css index 139f6a437..d3c53844f 100644 --- a/packages/design-system/lib/components/Map/Markers/PoiMarker/poi.module.css +++ b/packages/design-system/lib/components/Map/Markers/PoiMarker/poiMarker.module.css @@ -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; -} diff --git a/packages/design-system/lib/components/Map/Markers/PoiMarker/variants.ts b/packages/design-system/lib/components/Map/Markers/PoiMarker/variants.ts index afe0b854d..30af4ca06 100644 --- a/packages/design-system/lib/components/Map/Markers/PoiMarker/variants.ts +++ b/packages/design-system/lib/components/Map/Markers/PoiMarker/variants.ts @@ -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, skipBackground: { - true: styles.transparent, + true: styles.skipBackground, false: '', }, size: { diff --git a/packages/design-system/lib/components/Map/Markers/utils.ts b/packages/design-system/lib/components/Map/Markers/utils.ts index 5a17879c0..0312d7180 100644 --- a/packages/design-system/lib/components/Map/Markers/utils.ts +++ b/packages/design-system/lib/components/Map/Markers/utils.ts @@ -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, diff --git a/packages/design-system/lib/components/ParkingInformation/ParkingList/index.tsx b/packages/design-system/lib/components/ParkingInformation/ParkingList/index.tsx index 43f18fe52..02235b628 100644 --- a/packages/design-system/lib/components/ParkingInformation/ParkingList/index.tsx +++ b/packages/design-system/lib/components/ParkingInformation/ParkingList/index.tsx @@ -37,9 +37,9 @@ export default function ParkingList({ return ( -
      +
        {numberOfChargingSpaces ? ( -
      • +
      • {intl.formatMessage( { id: 'parkingInformation.numberOfChargingPoints', @@ -50,13 +50,13 @@ export default function ParkingList({ )}
      • ) : null} -
      • +
      • {canMakeReservation ? canMakeReservationYesMsg : canMakeReservationNoMsg}
      • {numberOfParkingSpots ? ( -
      • +
      • {intl.formatMessage( { id: 'parkingInformation.numberOfParkingSpots', @@ -67,7 +67,7 @@ export default function ParkingList({
      • ) : null} {distanceToHotel ? ( -
      • +
      • {intl.formatMessage( { id: 'parkingInformation.distanceToHotel', @@ -78,7 +78,7 @@ export default function ParkingList({
      • ) : null} {address ? ( -
      • +
      • {intl.formatMessage( { id: 'parkingInformation.address', diff --git a/packages/design-system/lib/components/ParkingInformation/ParkingList/parkingList.module.css b/packages/design-system/lib/components/ParkingInformation/ParkingList/parkingList.module.css index 4a1494579..e0065a15f 100644 --- a/packages/design-system/lib/components/ParkingInformation/ParkingList/parkingList.module.css +++ b/packages/design-system/lib/components/ParkingInformation/ParkingList/parkingList.module.css @@ -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; + } } diff --git a/packages/design-system/lib/components/ParkingInformation/index.tsx b/packages/design-system/lib/components/ParkingInformation/index.tsx index bd417de28..8679b9b30 100644 --- a/packages/design-system/lib/components/ParkingInformation/index.tsx +++ b/packages/design-system/lib/components/ParkingInformation/index.tsx @@ -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 && ( diff --git a/packages/design-system/lib/components/ParkingInformation/parkingInformation.module.css b/packages/design-system/lib/components/ParkingInformation/parkingInformation.module.css index a5714acb4..ff8c34978 100644 --- a/packages/design-system/lib/components/ParkingInformation/parkingInformation.module.css +++ b/packages/design-system/lib/components/ParkingInformation/parkingInformation.module.css @@ -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 { diff --git a/packages/design-system/lib/components/TextLink/textLink.module.css b/packages/design-system/lib/components/TextLink/textLink.module.css index 7c95b49ae..2fe3fe46e 100644 --- a/packages/design-system/lib/components/TextLink/textLink.module.css +++ b/packages/design-system/lib/components/TextLink/textLink.module.css @@ -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;