diff --git a/components/ContentType/HotelPage/Facilities/CardGrid/cardGrid.module.css b/components/ContentType/HotelPage/Facilities/CardGrid/cardGrid.module.css new file mode 100644 index 000000000..c205f68fe --- /dev/null +++ b/components/ContentType/HotelPage/Facilities/CardGrid/cardGrid.module.css @@ -0,0 +1,31 @@ +.one { + grid-column: span 1; +} + +.two { + grid-column: span 2; +} + +.three { + grid-column: 1/-1; +} + +.desktopGrid { + display: none; +} + +.mobileGrid { + display: grid; + gap: var(--Spacing-x-quarter); +} + +@media screen and (min-width: 768px) { + .desktopGrid { + display: grid; + gap: var(--Spacing-x1); + } + + .mobileGrid { + display: none; + } +} diff --git a/components/ContentType/HotelPage/Facilities/CardGrid/index.tsx b/components/ContentType/HotelPage/Facilities/CardGrid/index.tsx new file mode 100644 index 000000000..8d98c689d --- /dev/null +++ b/components/ContentType/HotelPage/Facilities/CardGrid/index.tsx @@ -0,0 +1,34 @@ +import Card from "@/components/TempDesignSystem/Card" +import CardImage from "@/components/TempDesignSystem/Card/CardImage" +import Grids from "@/components/TempDesignSystem/Grids" +import { sortCards } from "@/utils/imageCard" + +import styles from "./cardGrid.module.css" + +import type { CardGridProps } from "@/types/components/hotelPage/facilities" + +export default async function CardGrid({ facility }: CardGridProps) { + const imageCard = sortCards(facility) + return ( +
+ + {facility.map((card: any, idx: number) => ( + + ))} + + + + +
+ ) +} diff --git a/components/ContentType/HotelPage/Facilities/facilities.module.css b/components/ContentType/HotelPage/Facilities/facilities.module.css new file mode 100644 index 000000000..aea674734 --- /dev/null +++ b/components/ContentType/HotelPage/Facilities/facilities.module.css @@ -0,0 +1,9 @@ +.grid { + gap: var(--Spacing-x2); +} + +@media screen and (min-width: 768px) { + .grid { + gap: var(--Spacing-x7); + } +} diff --git a/components/ContentType/HotelPage/Facilities/index.tsx b/components/ContentType/HotelPage/Facilities/index.tsx new file mode 100644 index 000000000..8775d0de2 --- /dev/null +++ b/components/ContentType/HotelPage/Facilities/index.tsx @@ -0,0 +1,17 @@ +import SectionContainer from "@/components/Section/Container" + +import CardGrid from "./CardGrid" + +import styles from "./facilities.module.css" + +import type { FacilityProps } from "@/types/components/hotelPage/facilities" + +export default async function Facilities({ facilities }: FacilityProps) { + return ( + + {facilities.map((facility: any, idx: number) => ( + + ))} + + ) +} diff --git a/components/ContentType/HotelPage/Facilities/mockData.ts b/components/ContentType/HotelPage/Facilities/mockData.ts new file mode 100644 index 000000000..a54a39309 --- /dev/null +++ b/components/ContentType/HotelPage/Facilities/mockData.ts @@ -0,0 +1,159 @@ +import { + activities, + meetingsAndConferences, + restaurantAndBar, + wellnessAndExercise, +} from "@/constants/routes/hotelPageParams" + +import { getLang } from "@/i18n/serverContext" + +import type { Facilities } from "@/types/components/hotelPage/facilities" + +const lang = getLang() +/* +Most of this will be available from the api. Some will need to come from Contentstack. "Activities" will most likely come from Contentstack, which is prepped for. + */ +export const MOCK_FACILITIES: Facilities = [ + [ + { + id: "restaurant-and-bar", + theme: "primaryDark", + scriptedTopTitle: "Restaurant & Bar", + heading: "Enjoy relaxed restaurant experience", + secondaryButton: { + href: `?s=${restaurantAndBar[lang]}`, + title: "Read more & book a table", + isExternal: false, + }, + columnSpan: "one", + }, + { + backgroundImage: { + url: "https://imagevault.scandichotels.com/publishedmedia/79xttlmnum0kjbwhyh18/scandic-helsinki-hub-restaurant-food-tuna.jpg", + title: "scandic-helsinki-hub-restaurant-food-tuna.jpg", + meta: { + alt: "food in restaurant at scandic helsinki hub", + caption: "food in restaurant at scandic helsinki hub", + }, + id: 81751, + dimensions: { + width: 5935, + height: 3957, + aspectRatio: 1.499873641647713, + }, + }, + columnSpan: "one", + }, + { + backgroundImage: { + url: "https://imagevault.scandichotels.com/publishedmedia/48sb3eyhhzj727l2j1af/Scandic-helsinki-hub-II-centro-41.jpg", + meta: { + alt: "restaurant il centro at scandic helsinki hu", + caption: "restaurant il centro at scandic helsinki hub", + }, + id: 82457, + title: "Scandic-helsinki-hub-II-centro-41.jpg", + dimensions: { + width: 4200, + height: 2800, + aspectRatio: 1.5, + }, + }, + columnSpan: "one", + }, + ], + [ + { + backgroundImage: { + url: "https://imagevault.scandichotels.com/publishedmedia/csef06n329hjfiet1avj/Scandic-spectrum-8.jpg", + meta: { + alt: "man with a laptop", + caption: "man with a laptop", + }, + id: 82713, + title: "Scandic-spectrum-8.jpg", + dimensions: { + width: 7499, + height: 4999, + aspectRatio: 1.500100020004001, + }, + }, + columnSpan: "two", + }, + { + id: "meetings-and-conferences", + theme: "primaryDim", + scriptedTopTitle: "Meetings & Conferences", + heading: "Events that make an impression", + secondaryButton: { + href: `?s=${meetingsAndConferences[lang]}`, + title: "About meetings & conferences", + isExternal: false, + }, + columnSpan: "one", + }, + ], + [ + { + id: "wellness-and-exercise", + theme: "one", + scriptedTopTitle: "Wellness & Exercise", + heading: "Sauna and gym", + secondaryButton: { + href: `?s=${wellnessAndExercise[lang]}`, + title: "Read more about wellness & exercise", + isExternal: false, + }, + columnSpan: "one", + }, + { + backgroundImage: { + url: "https://imagevault.scandichotels.com/publishedmedia/69acct5i3pk5be7d6ub0/scandic-helsinki-hub-sauna.jpg", + meta: { + alt: "sauna at scandic helsinki hub", + caption: "sauna at scandic helsinki hub", + }, + id: 81814, + title: "scandic-helsinki-hub-sauna.jpg", + dimensions: { + width: 4000, + height: 2667, + aspectRatio: 1.4998125234345707, + }, + }, + columnSpan: "one", + }, + { + backgroundImage: { + url: "https://imagevault.scandichotels.com/publishedmedia/eu70o6z85idy24r92ysf/Scandic-Helsinki-Hub-gym-22.jpg", + meta: { + alt: "Gym at hotel Scandic Helsinki Hub", + caption: "Gym at hotel Scandic Helsinki Hub", + }, + id: 81867, + title: "Scandic-Helsinki-Hub-gym-22.jpg", + dimensions: { + width: 4000, + height: 2667, + aspectRatio: 1.4998125234345707, + }, + }, + columnSpan: "one", + }, + ], + [ + { + id: "activities", + theme: "primaryDark", + scriptedTopTitle: "Activities", + heading: "Upcoming activities at DownTown Camper", + bodyText: "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + secondaryButton: { + href: `?s=${activities[lang]}`, + title: "Discover activities", + isExternal: false, + }, + columnSpan: "three", + }, + ], +] diff --git a/components/ContentType/HotelPage/SidePeeks.tsx b/components/ContentType/HotelPage/SidePeeks.tsx index 272ea4287..2fcf9b46f 100644 --- a/components/ContentType/HotelPage/SidePeeks.tsx +++ b/components/ContentType/HotelPage/SidePeeks.tsx @@ -4,7 +4,14 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation" import { useEffect, useState } from "react" import { useIntl } from "react-intl" -import { about, amenities } from "@/constants/routes/hotelPageParams" +import { + about, + activities, + amenities, + meetingsAndConferences, + restaurantAndBar, + wellnessAndExercise, +} from "@/constants/routes/hotelPageParams" import SidePeek from "@/components/TempDesignSystem/SidePeek" import SidePeekItem from "@/components/TempDesignSystem/SidePeek/Item" @@ -57,6 +64,34 @@ function SidePeekContainer() { > Some additional information about the hotel + + {/* TODO */} + Restaurant & Bar + + + {/* TODO */} + Wellness & Exercise + + + {/* TODO */} + Activities + + + {/* TODO */} + Meetings & Conferences + ) } diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index 4c686d174..f29e8e28c 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -2,7 +2,9 @@ import { serverClient } from "@/lib/trpc/server" import { getLang } from "@/i18n/serverContext" +import { MOCK_FACILITIES } from "./Facilities/mockData" import AmenitiesList from "./AmenitiesList" +import Facilities from "./Facilities" import IntroSection from "./IntroSection" import { Rooms } from "./Rooms" import SidePeeks from "./SidePeeks" @@ -45,6 +47,7 @@ export default async function HotelPage() { + ) diff --git a/components/TempDesignSystem/Card/CardImage/cardImage.module.css b/components/TempDesignSystem/Card/CardImage/cardImage.module.css new file mode 100644 index 000000000..5b647da30 --- /dev/null +++ b/components/TempDesignSystem/Card/CardImage/cardImage.module.css @@ -0,0 +1,22 @@ +.image { + object-fit: cover; + overflow: hidden; + width: 100%; + min-height: 180px; /* Fixed height from Figma */ + border-radius: var(--Corner-radius-Medium); +} + +.imageContainer { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: var(--Spacing-x-quarter); +} + +.card { + height: 254px; /* Fixed height from Figma */ +} + +.container { + display: grid; + gap: var(--Spacing-x-quarter); +} diff --git a/components/TempDesignSystem/Card/CardImage/index.tsx b/components/TempDesignSystem/Card/CardImage/index.tsx new file mode 100644 index 000000000..dc9c4f7f9 --- /dev/null +++ b/components/TempDesignSystem/Card/CardImage/index.tsx @@ -0,0 +1,34 @@ +import Image from "@/components/Image" + +import Card from ".." + +import styles from "./cardImage.module.css" + +import type { CardImageProps } from "@/types/components/cardImage" + +export default function CardImage({ + card, + imageCards, + className, +}: CardImageProps) { + return ( +
+
+ {imageCards.map( + ({ backgroundImage }) => + backgroundImage && ( + {backgroundImage.title} + ) + )} +
+ +
+ ) +} diff --git a/components/TempDesignSystem/Card/card.module.css b/components/TempDesignSystem/Card/card.module.css index e7a2124d8..099701852 100644 --- a/components/TempDesignSystem/Card/card.module.css +++ b/components/TempDesignSystem/Card/card.module.css @@ -1,15 +1,29 @@ .container { align-items: center; display: flex; - border-radius: var(--Corner-radius-xLarge); + border-radius: var(--Corner-radius-Medium); flex-direction: column; - gap: var(--Spacing-x2); - height: 480px; + height: 320px; /* Fixed height from Figma */ justify-content: center; margin-right: var(--Spacing-x2); - padding: var(--Spacing-x0) var(--Spacing-x4); text-align: center; width: 100%; + text-wrap: balance; + overflow: hidden; +} + +.image { + object-fit: cover; + overflow: hidden; + width: 100%; + height: auto; + min-height: 320px; /* Fixed height from Figma */ +} + +.content { + margin: var(--Spacing-x0) var(--Spacing-x4); + display: grid; + gap: var(--Spacing-x2); } .themeOne { @@ -33,6 +47,42 @@ background: var(--Tertiary-Light-Surface-Normal); } +.themePrimaryDark { + --font-color: var(--Primary-Dark-On-Surface-Text); + --script-color: var(--Primary-Dark-On-Surface-Accent); + + background: var(--Primary-Dark-Surface-Normal); +} + +.themePrimaryDim { + --font-color: var(--Primary-Light-On-Surface-Text); + --script-color: var(--Primary-Dim-On-Surface-Accent); + + background: var(--Primary-Dim-Surface-Normal); +} + +.themePrimaryInverted { + --font-color: var(--Primary-Light-On-Surface-Text); + --script-color: var(--Primary-Light-On-Surface-Accent); + + background: var(--Base-Surface-Primary-light-Normal); +} + +.themePrimaryStrong { + --font-color: var(--Primary-Strong-On-Surface-Text); + --script-color: var(--Primary-Strong-On-Surface-Accent); + + background: var(--Primary-Strong-Surface-Normal); +} + +.themeImage { + --font-color: var(--Base-Text-Inverted); + --script-color: var(--Base-Text-Inverted); + + border: 1px; /* px from Figma */ + border-color: var(--Base-Border-Subtle); +} + .scriptContainer { display: grid; gap: var(--Spacing-x1); @@ -42,7 +92,6 @@ span.scriptedTitle { color: var(--script-color); padding: var(--Spacing-x1); margin: 0; - transform: rotate(-3deg); } .heading { diff --git a/components/TempDesignSystem/Card/card.ts b/components/TempDesignSystem/Card/card.ts index bf6cb0b7c..a27e0f962 100644 --- a/components/TempDesignSystem/Card/card.ts +++ b/components/TempDesignSystem/Card/card.ts @@ -2,6 +2,8 @@ import { cardVariants } from "./variants" import type { VariantProps } from "class-variance-authority" +import type { ImageVaultAsset } from "@/types/components/imageVault" + export interface CardProps extends React.HTMLAttributes, VariantProps { @@ -20,5 +22,5 @@ export interface CardProps scriptedTopTitle?: string | null heading?: string | null bodyText?: string | null - backgroundImage?: { url: string } + backgroundImage?: ImageVaultAsset } diff --git a/components/TempDesignSystem/Card/index.tsx b/components/TempDesignSystem/Card/index.tsx index 8c51da7aa..d611c784a 100644 --- a/components/TempDesignSystem/Card/index.tsx +++ b/components/TempDesignSystem/Card/index.tsx @@ -1,14 +1,15 @@ +import Image from "@/components/Image" import Button from "@/components/TempDesignSystem/Button" import Link from "@/components/TempDesignSystem/Link" import BiroScript from "@/components/TempDesignSystem/Text/BiroScript" import Body from "@/components/TempDesignSystem/Text/Body" import Title from "@/components/TempDesignSystem/Text/Title" +import { getTheme } from "@/utils/cardTheme" import { cardVariants } from "./variants" import styles from "./card.module.css" -import type { ButtonProps } from "@/components/TempDesignSystem/Button/button" import type { CardProps } from "./card" export default function Card({ @@ -19,20 +20,9 @@ export default function Card({ bodyText, className, theme, + backgroundImage, }: CardProps) { - let buttonTheme: ButtonProps["theme"] = "primaryLight" - - switch (theme) { - case "one": - buttonTheme = "primaryLight" - break - case "two": - buttonTheme = "secondaryLight" - break - case "three": - buttonTheme = "tertiaryLight" - break - } + const { buttonTheme, primaryLinkColor, secondaryLinkColor } = getTheme(theme) return (
- {scriptedTopTitle ? ( -
- - {scriptedTopTitle} - -
- ) : null} - - {heading} - - {bodyText ? ( - - {bodyText} - - ) : null} -
- {primaryButton ? ( - + {scriptedTopTitle} + + ) : null} - {secondaryButton ? ( - + ) : null} + {secondaryButton ? ( + - ) : null} + + {secondaryButton.title} + + + ) : null} +
) diff --git a/components/TempDesignSystem/Card/variants.ts b/components/TempDesignSystem/Card/variants.ts index 1d660ac17..8c4ab4e73 100644 --- a/components/TempDesignSystem/Card/variants.ts +++ b/components/TempDesignSystem/Card/variants.ts @@ -8,6 +8,13 @@ export const cardVariants = cva(styles.container, { one: styles.themeOne, two: styles.themeTwo, three: styles.themeThree, + + primaryDark: styles.themePrimaryDark, + primaryDim: styles.themePrimaryDim, + primaryInverted: styles.themePrimaryInverted, + primaryStrong: styles.themePrimaryStrong, + + image: styles.themeImage, }, }, defaultVariants: { diff --git a/components/TempDesignSystem/Link/link.module.css b/components/TempDesignSystem/Link/link.module.css index 0261002de..6e43601b9 100644 --- a/components/TempDesignSystem/Link/link.module.css +++ b/components/TempDesignSystem/Link/link.module.css @@ -130,6 +130,10 @@ color: var(--Primary-Light-On-Surface-Accent); } +.red { + color: var(--Primary-Strong-Button-Primary-On-Fill-Normal); +} + .peach80:hover, .peach80:active { color: var(--Primary-Light-On-Surface-Hover); diff --git a/components/TempDesignSystem/Link/variants.ts b/components/TempDesignSystem/Link/variants.ts index fa0738c50..ceb0960b5 100644 --- a/components/TempDesignSystem/Link/variants.ts +++ b/components/TempDesignSystem/Link/variants.ts @@ -14,6 +14,7 @@ export const linkVariants = cva(styles.link, { pale: styles.pale, peach80: styles.peach80, white: styles.white, + red: styles.red, }, size: { small: styles.small, diff --git a/components/TempDesignSystem/Text/BiroScript/biroScript.module.css b/components/TempDesignSystem/Text/BiroScript/biroScript.module.css index a357a0c88..87db768a7 100644 --- a/components/TempDesignSystem/Text/BiroScript/biroScript.module.css +++ b/components/TempDesignSystem/Text/BiroScript/biroScript.module.css @@ -26,10 +26,14 @@ line-height: var(--typography-Script-2-lineHeight); } -.tiltedSmall { +.tiltedExtraSmall { transform: rotate(-2deg); } +.tiltedSmall { + transform: rotate(-3deg); +} + .tiltedMedium { transform: rotate(-4deg) translate(0px, -15px); } @@ -59,7 +63,7 @@ } .peach80 { - color: var(--Scandic-Peach-80); + color: var(--Base-Text-Medium-contrast); } .plosa { @@ -69,3 +73,7 @@ .red { color: var(--Scandic-Brand-Scandic-Red); } + +.pink { + color: var(--Primary-Dark-On-Surface-Accent); +} diff --git a/components/TempDesignSystem/Text/BiroScript/variants.ts b/components/TempDesignSystem/Text/BiroScript/variants.ts index 3e0aaa4fa..f7e330b48 100644 --- a/components/TempDesignSystem/Text/BiroScript/variants.ts +++ b/components/TempDesignSystem/Text/BiroScript/variants.ts @@ -11,6 +11,7 @@ const config = { peach80: styles.peach80, primaryLightOnSurfaceAccent: styles.plosa, red: styles.red, + pink: styles.pink, }, textAlign: { center: styles.center, @@ -21,6 +22,7 @@ const config = { two: styles.two, }, tilted: { + extraSmall: styles.tiltedExtraSmall, small: styles.tiltedSmall, medium: styles.tiltedMedium, large: styles.tiltedLarge, diff --git a/constants/routes/hotelPageParams.js b/constants/routes/hotelPageParams.js index a6fcb1a5a..9eadf2996 100644 --- a/constants/routes/hotelPageParams.js +++ b/constants/routes/hotelPageParams.js @@ -16,6 +16,49 @@ export const amenities = { de: "annehmlichkeiten", } -const params = { about, amenities } +export const wellnessAndExercise = { + en: "wellness-and-exercise", + sv: "halsa-och-träning", + no: "velvære-og-trening", + da: "wellness-og-motion", + fi: "hyvinvointia-ja-liikuntaa", + de: "Wellness-und-Bewegung", +} + +export const activities = { + en: "activities", + sv: "aktiviteter", + no: "aktiviteter", + da: "aktiviteter", + fi: "toimintaa", + de: "Aktivitäten", +} + +export const meetingsAndConferences = { + en: "meetings-and-conferences", + sv: "moten-och-konferenser", + no: "møter-og-konferansers", + da: "møder-og-konferencer", + fi: "kokoukset-ja-konferenssit", + de: "Tagungen-und-Konferenzen", +} + +export const restaurantAndBar = { + en: "restaurant-and-bar", + sv: "restaurant-och-bar", + no: "restaurant-og-bar", + da: "restaurant-og-bar", + fi: "ravintola-ja-baari", + de: "Restaurant-und-Bar", +} + +const params = { + about, + amenities, + wellnessAndExercise, + activities, + meetingsAndConferences, + restaurantAndBar, +} export default params diff --git a/types/components/cardImage.ts b/types/components/cardImage.ts new file mode 100644 index 000000000..9976a2db3 --- /dev/null +++ b/types/components/cardImage.ts @@ -0,0 +1,7 @@ +import type { CardProps } from "@/components/TempDesignSystem/Card/card" +import type { FacilityCard } from "./hotelPage/facilities" + +export interface CardImageProps extends React.HTMLAttributes { + card: FacilityCard | undefined + imageCards: Pick[] +} diff --git a/types/components/hotelPage/facilities.ts b/types/components/hotelPage/facilities.ts new file mode 100644 index 000000000..228093fe0 --- /dev/null +++ b/types/components/hotelPage/facilities.ts @@ -0,0 +1,19 @@ +import type { CardProps } from "@/components/TempDesignSystem/Card/card" + +interface ColumnSpanOptions { + columnSpan: "one" | "two" | "three" +} + +export type FacilityCard = CardProps & ColumnSpanOptions + +export type Facility = Array + +export type Facilities = Array + +export type FacilityProps = { + facilities: Facilities +} + +export type CardGridProps = { + facility: Facility +} diff --git a/utils/cardTheme.ts b/utils/cardTheme.ts new file mode 100644 index 000000000..39f42a4c1 --- /dev/null +++ b/utils/cardTheme.ts @@ -0,0 +1,52 @@ +import type { ButtonProps } from "@/components/TempDesignSystem/Button/button" +import type { CardProps } from "@/components/TempDesignSystem/Card/card" +import type { LinkProps } from "@/components/TempDesignSystem/Link/link" + +export function getTheme(theme: CardProps["theme"]) { + let buttonTheme: ButtonProps["theme"] = "primaryLight" + let primaryLinkColor: LinkProps["color"] = "pale" + let secondaryLinkColor: LinkProps["color"] = "burgundy" + + switch (theme) { + case "one": + buttonTheme = "primaryLight" + primaryLinkColor = "pale" + secondaryLinkColor = "burgundy" + break + case "two": + buttonTheme = "secondaryLight" + primaryLinkColor = "pale" + secondaryLinkColor = "burgundy" + break + case "three": + buttonTheme = "tertiaryLight" + primaryLinkColor = "pale" + secondaryLinkColor = "burgundy" + break + case "primaryDark": + buttonTheme = "primaryDark" + primaryLinkColor = "burgundy" + secondaryLinkColor = "pale" + break + case "primaryDim": + buttonTheme = "primaryLight" + primaryLinkColor = "pale" + secondaryLinkColor = "burgundy" + break + case "primaryInverted": + buttonTheme = "primaryLight" + primaryLinkColor = "pale" + secondaryLinkColor = "burgundy" + break + case "primaryStrong" || "image": + buttonTheme = "primaryStrong" + primaryLinkColor = "red" + secondaryLinkColor = "white" + } + + return { + buttonTheme: buttonTheme, + primaryLinkColor: primaryLinkColor, + secondaryLinkColor: secondaryLinkColor, + } +} diff --git a/utils/imageCard.ts b/utils/imageCard.ts new file mode 100644 index 000000000..b66c65954 --- /dev/null +++ b/utils/imageCard.ts @@ -0,0 +1,18 @@ +import type { + Facility, + FacilityCard, +} from "@/types/components/hotelPage/facilities" + +export function sortCards(grid: Facility) { + const sortedCards = grid.slice(0).sort((a: FacilityCard, b: FacilityCard) => { + if (!a.backgroundImage && b.backgroundImage) { + return 1 + } + if (a.backgroundImage && !b.backgroundImage) { + return -1 + } + return 0 + }) + + return { card: sortedCards.pop(), images: sortedCards } +}