Merged in feat/SW-1740-final-design-subpages (pull request #1435)

feat/SW-1740 final design subpages

* feat(SW-1740): update design for subpages

* feat(SW-1740): add padding

* feat(SW-1740): use not pseudo class


Approved-by: Erik Tiekstra
Approved-by: Matilda Landström
This commit is contained in:
Fredrik Thorsson
2025-02-27 14:40:21 +00:00
parent 8061ab63a8
commit bda22f39ad
10 changed files with 190 additions and 149 deletions

View File

@@ -22,23 +22,25 @@ export default async function MeetingsSidebar({
return ( return (
<aside className={styles.sidebar}> <aside className={styles.sidebar}>
<Title level="h3" as="h4"> <div className={styles.content}>
{intl.formatMessage({ id: "Contact us" })} <Title level="h3" as="h4">
</Title> {intl.formatMessage({ id: "Contact us" })}
<div> </Title>
<Link href={`tel:${phoneNumber}`}>{phoneNumber}</Link> <div>
{country === Country.Finland ? ( <Link href={`tel:${phoneNumber}`}>{phoneNumber}</Link>
<Body> {country === Country.Finland ? (
{intl.formatMessage({ <Body>
id: "Price 0,16 €/min + local call charges", {intl.formatMessage({
})} id: "Price 0,16 €/min + local call charges",
</Body> })}
) : null} </Body>
{email && ( ) : null}
<Link textDecoration="underline" href={`mailto:${email}`}> {email && (
{email} <Link textDecoration="underline" href={`mailto:${email}`}>
</Link> {email}
)} </Link>
)}
</div>
</div> </div>
</aside> </aside>
) )

View File

@@ -1,7 +1,3 @@
import NextLink from "next/link"
import { OpenInNewIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
@@ -18,43 +14,29 @@ interface HotelSidebarProps {
export default async function ParkingSidebar({ hotel }: HotelSidebarProps) { export default async function ParkingSidebar({ hotel }: HotelSidebarProps) {
const intl = await getIntl() const intl = await getIntl()
const parking = hotel.parking
.map((parking) => ({ url: parking.externalParkingUrl, type: parking.type }))
.filter(
(parking): parking is { type: string; url: string } => !!parking.url
)
return ( return (
<aside className={styles.sidebar}> <aside className={styles.sidebar}>
<Title level="h3" as="h4"> <div className={styles.content}>
{intl.formatMessage({ id: "Address" })} <Title level="h3" as="h4">
</Title> {intl.formatMessage({ id: "Address" })}
<div> </Title>
<Body color="uiTextHighContrast">{hotel.address.streetAddress}</Body> <div>
<Body color="uiTextHighContrast"> <Body color="uiTextHighContrast">{hotel.address.streetAddress}</Body>
{hotel.address.zipCode} {hotel.address.city} <Body color="uiTextHighContrast">
</Body> {hotel.address.zipCode} {hotel.address.city}
<Body color="uiTextHighContrast">{hotel.address.country}</Body> </Body>
<Body color="uiTextHighContrast">{hotel.address.country}</Body>
</div>
</div> </div>
<Title level="h3" as="h4"> <div className={styles.content}>
{intl.formatMessage({ id: "Contact us" })} <Title level="h3" as="h4">
</Title> {intl.formatMessage({ id: "Contact us" })}
<Link href={`tel:${hotel.contactInformation.phoneNumber}`}> </Title>
{hotel.contactInformation.phoneNumber} <Link href={`tel:${hotel.contactInformation.phoneNumber}`}>
</Link> {hotel.contactInformation.phoneNumber}
</Link>
{parking.map(({ url, type }) => ( </div>
<Button key={type} theme="base" intent="primary" variant="icon" asChild>
<NextLink href={url} target="_blank">
{intl.formatMessage(
{ id: "Book {type} parking" },
{ type: type.toLowerCase() }
)}
<OpenInNewIcon />
</NextLink>
</Button>
))}
</aside> </aside>
) )
} }

View File

@@ -6,7 +6,7 @@ import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import styles from "./restaurantSiderbar.module.css" import styles from "./sidebar.module.css"
import type { Hotel, Restaurant } from "@/types/hotel" import type { Hotel, Restaurant } from "@/types/hotel"
@@ -42,11 +42,13 @@ export default async function RestaurantSidebar({
</div> </div>
) : null} ) : null}
{bookTableUrl && ( {bookTableUrl && (
<Button intent="primary" theme="base" asChild> <div className={styles.buttonContainer}>
<a href={bookTableUrl}> <Button intent="primary" theme="base" asChild>
{intl.formatMessage({ id: "Book a table online" })} <a href={bookTableUrl}>
</a> {intl.formatMessage({ id: "Book a table online" })}
</Button> </a>
</Button>
</div>
)} )}
{restaurant.menus.length ? ( {restaurant.menus.length ? (
<div className={styles.content}> <div className={styles.content}>

View File

@@ -1,15 +0,0 @@
.sidebar {
display: grid;
gap: var(--Spacing-x3);
}
.content {
display: grid;
gap: var(--Spacing-x-one-and-half);
}
.menuList {
display: grid;
gap: var(--Spacing-x-half);
list-style-type: none;
}

View File

@@ -19,59 +19,69 @@ export default async function WellnessSidebar({ hotel }: WellnessSidebarProps) {
return ( return (
<aside className={styles.sidebar}> <aside className={styles.sidebar}>
<Title level="h3" as="h4"> <div className={styles.content}>
{intl.formatMessage({ id: "Opening hours" })} <Title level="h3" as="h4">
</Title> {intl.formatMessage({ id: "Opening hours" })}
{hotel.healthFacilities.map((facility) => ( </Title>
<div key={facility.type}> {hotel.healthFacilities.map((facility) => (
<Subtitle type="two" color="uiTextHighContrast" asChild> <div key={facility.type}>
<h4>{translateWellnessType(facility.type, intl)}</h4> <Subtitle type="two" color="uiTextHighContrast" asChild>
</Subtitle> <h4>{translateWellnessType(facility.type, intl)}</h4>
</Subtitle>
<Body color="uiTextHighContrast">
{facility.openingDetails.openingHours.ordinary.alwaysOpen
? intl.formatMessage({ id: "Mon-Fri Always open" })
: intl.formatMessage(
{ id: "Mon-Fri {openingTime}-{closingTime}" },
{
openingTime:
facility.openingDetails.openingHours.ordinary
.openingTime,
closingTime:
facility.openingDetails.openingHours.ordinary
.closingTime,
}
)}
</Body>
<Body color="uiTextHighContrast">
{facility.openingDetails.openingHours.weekends.alwaysOpen
? intl.formatMessage({ id: "Sat-Sun Always open" })
: intl.formatMessage(
{ id: "Sat-Sun {openingTime}-{closingTime}" },
{
openingTime:
facility.openingDetails.openingHours.weekends
.openingTime,
closingTime:
facility.openingDetails.openingHours.weekends
.closingTime,
}
)}
</Body>
</div>
))}
</div>
<div className={styles.content}>
<Title level="h3" as="h4">
{intl.formatMessage({ id: "Address" })}
</Title>
<div>
<Body color="uiTextHighContrast">{hotel.address.streetAddress}</Body>
<Body color="uiTextHighContrast"> <Body color="uiTextHighContrast">
{facility.openingDetails.openingHours.ordinary.alwaysOpen {hotel.address.zipCode} {hotel.address.city}
? intl.formatMessage({ id: "Mon-Fri Always open" })
: intl.formatMessage(
{ id: "Mon-Fri {openingTime}-{closingTime}" },
{
openingTime:
facility.openingDetails.openingHours.ordinary.openingTime,
closingTime:
facility.openingDetails.openingHours.ordinary.closingTime,
}
)}
</Body>
<Body color="uiTextHighContrast">
{facility.openingDetails.openingHours.weekends.alwaysOpen
? intl.formatMessage({ id: "Sat-Sun Always open" })
: intl.formatMessage(
{ id: "Sat-Sun {openingTime}-{closingTime}" },
{
openingTime:
facility.openingDetails.openingHours.weekends.openingTime,
closingTime:
facility.openingDetails.openingHours.weekends.closingTime,
}
)}
</Body> </Body>
<Body color="uiTextHighContrast">{hotel.address.country}</Body>
</div> </div>
))}
<Title level="h3" as="h4">
{intl.formatMessage({ id: "Address" })}
</Title>
<div>
<Body color="uiTextHighContrast">{hotel.address.streetAddress}</Body>
<Body color="uiTextHighContrast">
{hotel.address.zipCode} {hotel.address.city}
</Body>
<Body color="uiTextHighContrast">{hotel.address.country}</Body>
</div> </div>
<Title level="h3" as="h4"> <div className={styles.content}>
{intl.formatMessage({ id: "Contact us" })} <Title level="h3" as="h4">
</Title> {intl.formatMessage({ id: "Contact us" })}
<Link href={`tel:${hotel.contactInformation.phoneNumber}`}> </Title>
{hotel.contactInformation.phoneNumber} <Link href={`tel:${hotel.contactInformation.phoneNumber}`}>
</Link> {hotel.contactInformation.phoneNumber}
</Link>
</div>
</aside> </aside>
) )
} }

View File

@@ -1,6 +1,6 @@
import RestaurantSidebar from "./RestaurantSidebar/RestaurantSidebar"
import MeetingsSidebar from "./MeetingsSidebar" import MeetingsSidebar from "./MeetingsSidebar"
import ParkingSidebar from "./ParkingSidebar" import ParkingSidebar from "./ParkingSidebar"
import RestaurantSidebar from "./RestaurantSidebar"
import WellnessSidebar from "./WellnessSidebar" import WellnessSidebar from "./WellnessSidebar"
import type { MeetingRooms } from "@/types/components/hotelPage/meetingRooms" import type { MeetingRooms } from "@/types/components/hotelPage/meetingRooms"

View File

@@ -1,13 +1,32 @@
.sidebar { .sidebar {
display: grid; display: grid;
gap: var(--Spacing-x2); gap: var(--Spacing-x3);
grid-column: 1; grid-column: 1;
} }
.content {
display: grid;
gap: var(--Spacing-x-one-and-half);
}
.menuList {
display: grid;
gap: var(--Spacing-x-half);
list-style-type: none;
}
.buttonContainer {
display: none;
}
@media (min-width: 1367px) { @media (min-width: 1367px) {
.sidebar { .sidebar {
grid-column: 2; grid-column: 2;
grid-row: 1; grid-row: 1 / span 2;
align-items: start; align-items: start;
} }
.buttonContainer {
display: block;
}
} }

View File

@@ -1,4 +1,4 @@
.hotelSubpage { .hotelSubpage:not(.hasStickyButton) {
padding-bottom: var(--Spacing-x9); padding-bottom: var(--Spacing-x9);
} }
@@ -19,7 +19,7 @@
.contentContainer { .contentContainer {
display: grid; display: grid;
gap: var(--Spacing-x4); gap: var(--Spacing-x3);
align-items: start; align-items: start;
width: 100%; width: 100%;
max-width: var(--max-width-content); max-width: var(--max-width-content);
@@ -49,19 +49,43 @@
text-align: center; text-align: center;
} }
.buttonContainer {
position: sticky;
padding: var(--Spacing-x3) var(--Spacing-x2);
background-color: var(--Base-Surface-Secondary-light-Normal);
border-top: 1px solid var(--Base-Border-Subtle);
bottom: 0;
}
@media (min-width: 1367px) { @media (min-width: 1367px) {
.hotelSubpage {
padding-bottom: var(--Spacing-x9);
}
.contentContainer { .contentContainer {
grid-template-columns: var(--max-width-text-block) 1fr; grid-template-columns: var(--max-width-text-block) 1fr;
grid-template-rows: auto 1fr;
row-gap: var(--Spacing-x2);
column-gap: var(--Spacing-x9);
padding: var(--Spacing-x4) 0 0; padding: var(--Spacing-x4) 0 0;
} }
.divider {
display: none;
}
.mainContent { .mainContent {
padding: 0; padding: 0;
max-width: none;
margin: 0; margin: 0;
gap: var(--Spacing-x3);
max-width: none;
} }
.meetingsContent { .meetingsContent {
grid-column: 1 / span 2; grid-column: 1 / span 2;
} }
.buttonContainer {
display: none;
}
} }

View File

@@ -10,6 +10,8 @@ import {
import Breadcrumbs from "@/components/Breadcrumbs" import Breadcrumbs from "@/components/Breadcrumbs"
import Hero from "@/components/Hero" import Hero from "@/components/Hero"
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Preamble from "@/components/TempDesignSystem/Text/Preamble" import Preamble from "@/components/TempDesignSystem/Text/Preamble"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
@@ -56,6 +58,10 @@ export default async function HotelSubpage({
meetingRooms = await getMeetingRooms({ hotelId: hotelId, language: lang }) meetingRooms = await getMeetingRooms({ hotelId: hotelId, language: lang })
} }
const restaurantButton = restaurants.find(
(restaurant) => restaurant.nameInUrl === subpage
)
const meetingBookingWidget = meetingRooms ? ( const meetingBookingWidget = meetingRooms ? (
<div className={styles.meetingBookingWidget}> <div className={styles.meetingBookingWidget}>
Booking Widget Placeholder Booking Widget Placeholder
@@ -64,7 +70,9 @@ export default async function HotelSubpage({
return ( return (
<> <>
<section className={styles.hotelSubpage}> <section
className={`${styles.hotelSubpage} ${restaurantButton?.bookTableUrl ? styles.hasStickyButton : ""} `}
>
<div className={styles.header}> <div className={styles.header}>
<Suspense fallback={<BreadcrumbsSkeleton />}> <Suspense fallback={<BreadcrumbsSkeleton />}>
<Breadcrumbs <Breadcrumbs
@@ -87,11 +95,20 @@ export default async function HotelSubpage({
</div> </div>
<div className={styles.contentContainer}> <div className={styles.contentContainer}>
<Title as="h2" level="h1">
{pageData.heading}
</Title>
<HotelSubpageSidebar
subpage={subpage}
hotel={hotel}
additionalData={additionalData}
restaurants={restaurants}
meetingRooms={meetingRooms}
/>
<Divider color="baseSurfaceSubtleHover" className={styles.divider} />
<main className={styles.mainContent}> <main className={styles.mainContent}>
<div className={styles.intro}> <div className={styles.intro}>
<Title as="h2" level="h1">
{pageData.heading}
</Title>
<Preamble>{pageData.elevatorPitch}</Preamble> <Preamble>{pageData.elevatorPitch}</Preamble>
</div> </div>
@@ -103,21 +120,21 @@ export default async function HotelSubpage({
additionalData={additionalData} additionalData={additionalData}
/> />
</main> </main>
{meetingRooms && ( {meetingRooms && (
<div className={styles.meetingsContent}> <div className={styles.meetingsContent}>
<MeetingsAdditionalContent rooms={meetingRooms} /> <MeetingsAdditionalContent rooms={meetingRooms} />
</div> </div>
)} )}
<HotelSubpageSidebar
subpage={subpage}
hotel={hotel}
additionalData={additionalData}
restaurants={restaurants}
meetingRooms={meetingRooms}
/>
</div> </div>
{restaurantButton?.bookTableUrl && (
<div className={styles.buttonContainer}>
<Button intent="primary" theme="base" asChild>
<a href={restaurantButton.bookTableUrl}>
{intl.formatMessage({ id: "Book a table online" })}
</a>
</Button>
</div>
)}
</section> </section>
{/* Tracking */} {/* Tracking */}
</> </>

View File

@@ -21,31 +21,31 @@ export default async function OpeningHours({
const weekdayDefinitions = [ const weekdayDefinitions = [
{ {
key: "monday", key: "monday",
label: intl.formatMessage({ id: "monday" }), label: intl.formatMessage({ id: "Monday" }),
}, },
{ {
key: "tuesday", key: "tuesday",
label: intl.formatMessage({ id: "tuesday" }), label: intl.formatMessage({ id: "Tuesday" }),
}, },
{ {
key: "wednesday", key: "wednesday",
label: intl.formatMessage({ id: "wednesday" }), label: intl.formatMessage({ id: "Wednesday" }),
}, },
{ {
key: "thursday", key: "thursday",
label: intl.formatMessage({ id: "thursday" }), label: intl.formatMessage({ id: "Thursday" }),
}, },
{ {
key: "friday", key: "friday",
label: intl.formatMessage({ id: "friday" }), label: intl.formatMessage({ id: "Friday" }),
}, },
{ {
key: "saturday", key: "saturday",
label: intl.formatMessage({ id: "saturday" }), label: intl.formatMessage({ id: "Saturday" }),
}, },
{ {
key: "sunday", key: "sunday",
label: intl.formatMessage({ id: "sunday" }), label: intl.formatMessage({ id: "Sunday" }),
}, },
] as const ] as const