From bc3233ff64b206e91b65c27f8547cc7c658d0951 Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Tue, 3 Sep 2024 08:43:13 +0000 Subject: [PATCH 01/27] Merged in feat/SW-196-design-fixes (pull request #547) Feat/SW-196 design fixes * feat(SW-196): Updated copy My credit cards -> My payment cards * fix: update design system version * feat(SW-196): Update Header component to use Preamble instead of Subtitle * feat(SW-196): Minor design fixes Approved-by: Christel Westerberg --- .../(protected)/my-pages/profile/@creditCards/page.tsx | 2 +- components/ContentType/HotelPage/Rooms/index.tsx | 2 +- components/Loyalty/Blocks/CardsGrid/index.tsx | 2 +- components/Loyalty/Blocks/DynamicContent/index.tsx | 2 +- components/MyPages/Blocks/Benefits/CurrentLevel/index.tsx | 2 +- components/MyPages/Blocks/Benefits/NextLevel/index.tsx | 2 +- components/MyPages/Blocks/Overview/index.tsx | 2 +- components/MyPages/Blocks/Points/EarnAndBurn/index.tsx | 2 +- components/MyPages/Blocks/Points/Overview/index.tsx | 2 +- components/MyPages/Blocks/Shortcuts/index.tsx | 2 +- components/MyPages/Blocks/Stays/Previous/index.tsx | 2 +- components/MyPages/Blocks/Stays/Soonest/index.tsx | 2 +- components/MyPages/Blocks/Stays/Upcoming/index.tsx | 2 +- components/Section/Header/header.module.css | 4 ++-- components/Section/Header/index.tsx | 6 +++--- components/Section/Link/index.tsx | 2 +- components/TempDesignSystem/Link/link.module.css | 6 +++--- components/TempDesignSystem/Text/Subtitle/variants.ts | 2 +- i18n/dictionaries/da.json | 2 +- i18n/dictionaries/de.json | 2 +- i18n/dictionaries/en.json | 2 +- i18n/dictionaries/fi.json | 2 +- i18n/dictionaries/no.json | 2 +- i18n/dictionaries/sv.json | 4 ++-- package-lock.json | 2 +- package.json | 2 +- types/components/myPages/header.ts | 2 +- 27 files changed, 33 insertions(+), 33 deletions(-) diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx index 400f10f2c..a37b0047b 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx @@ -23,7 +23,7 @@ export default async function CreditCardSlot({ params }: PageArgs) {
- {formatMessage({ id: "My credit cards" })} + {formatMessage({ id: "My payment cards" })} {formatMessage({ diff --git a/components/ContentType/HotelPage/Rooms/index.tsx b/components/ContentType/HotelPage/Rooms/index.tsx index 163f83aa9..08e8f07ce 100644 --- a/components/ContentType/HotelPage/Rooms/index.tsx +++ b/components/ContentType/HotelPage/Rooms/index.tsx @@ -54,7 +54,7 @@ export function Rooms({ rooms }: RoomsProps) { {mappedRooms.map( diff --git a/components/Loyalty/Blocks/CardsGrid/index.tsx b/components/Loyalty/Blocks/CardsGrid/index.tsx index 1cdccc0b3..72da61722 100644 --- a/components/Loyalty/Blocks/CardsGrid/index.tsx +++ b/components/Loyalty/Blocks/CardsGrid/index.tsx @@ -15,7 +15,7 @@ export default function CardsGrid({ diff --git a/components/Loyalty/Blocks/DynamicContent/index.tsx b/components/Loyalty/Blocks/DynamicContent/index.tsx index e033b1028..79908505c 100644 --- a/components/Loyalty/Blocks/DynamicContent/index.tsx +++ b/components/Loyalty/Blocks/DynamicContent/index.tsx @@ -53,7 +53,7 @@ export default function DynamicContent({ ) : displayHeader ? ( diff --git a/components/MyPages/Blocks/Benefits/CurrentLevel/index.tsx b/components/MyPages/Blocks/Benefits/CurrentLevel/index.tsx index b6b2c7179..71f94fdea 100644 --- a/components/MyPages/Blocks/Benefits/CurrentLevel/index.tsx +++ b/components/MyPages/Blocks/Benefits/CurrentLevel/index.tsx @@ -43,7 +43,7 @@ export default async function CurrentBenefitsBlock({ return ( - + {currentLevel.benefits.map((benefit, idx) => (
diff --git a/components/MyPages/Blocks/Benefits/NextLevel/index.tsx b/components/MyPages/Blocks/Benefits/NextLevel/index.tsx index 5716d1495..7b82a3dd0 100644 --- a/components/MyPages/Blocks/Benefits/NextLevel/index.tsx +++ b/components/MyPages/Blocks/Benefits/NextLevel/index.tsx @@ -39,7 +39,7 @@ export default async function NextLevelBenefitsBlock({ // TODO: how to handle different count of unlockable benefits? return ( - + {nextLevel.benefits.map((benefit) => (
diff --git a/components/MyPages/Blocks/Overview/index.tsx b/components/MyPages/Blocks/Overview/index.tsx index db6874997..f9b4deab7 100644 --- a/components/MyPages/Blocks/Overview/index.tsx +++ b/components/MyPages/Blocks/Overview/index.tsx @@ -26,7 +26,7 @@ export default async function Overview({ return ( - + diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx index 83a325a04..9172bf7fd 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx @@ -13,7 +13,7 @@ export default async function EarnAndBurn({ }: AccountPageComponentProps) { return ( - + diff --git a/components/MyPages/Blocks/Points/Overview/index.tsx b/components/MyPages/Blocks/Points/Overview/index.tsx index 39f8b6031..047bb6b93 100644 --- a/components/MyPages/Blocks/Points/Overview/index.tsx +++ b/components/MyPages/Blocks/Points/Overview/index.tsx @@ -25,7 +25,7 @@ export default async function PointsOverview({ return ( - + diff --git a/components/MyPages/Blocks/Shortcuts/index.tsx b/components/MyPages/Blocks/Shortcuts/index.tsx index 3f2012708..1188c06fe 100644 --- a/components/MyPages/Blocks/Shortcuts/index.tsx +++ b/components/MyPages/Blocks/Shortcuts/index.tsx @@ -16,7 +16,7 @@ export default function Shortcuts({ }: ShortcutsProps) { return ( - +
{shortcuts.map((shortcut) => ( - + diff --git a/components/MyPages/Blocks/Stays/Soonest/index.tsx b/components/MyPages/Blocks/Stays/Soonest/index.tsx index 990c7b850..5069d5276 100644 --- a/components/MyPages/Blocks/Stays/Soonest/index.tsx +++ b/components/MyPages/Blocks/Stays/Soonest/index.tsx @@ -22,7 +22,7 @@ export default async function SoonestStays({ return ( - + {response.data.length ? ( {response.data.map((stay) => ( diff --git a/components/MyPages/Blocks/Stays/Upcoming/index.tsx b/components/MyPages/Blocks/Stays/Upcoming/index.tsx index c56bf8f41..6c3e91b71 100644 --- a/components/MyPages/Blocks/Stays/Upcoming/index.tsx +++ b/components/MyPages/Blocks/Stays/Upcoming/index.tsx @@ -22,7 +22,7 @@ export default async function UpcomingStays({ return ( - + {initialUpcomingStays?.data.length ? ( ) : ( diff --git a/components/Section/Header/header.module.css b/components/Section/Header/header.module.css index 0a0b6513e..48c539418 100644 --- a/components/Section/Header/header.module.css +++ b/components/Section/Header/header.module.css @@ -6,7 +6,7 @@ } .title, -.subtitle { +.preamble { grid-column: 1 / -1; } @@ -19,7 +19,7 @@ grid-column: 1 / 2; } - .subtitle { + .preamble { grid-column: 1 / 2; } } diff --git a/components/Section/Header/index.tsx b/components/Section/Header/index.tsx index e005a8c07..b52a741dc 100644 --- a/components/Section/Header/index.tsx +++ b/components/Section/Header/index.tsx @@ -1,4 +1,4 @@ -import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import Preamble from "@/components/TempDesignSystem/Text/Preamble" import Title from "@/components/TempDesignSystem/Text/Title" import SectionLink from "../Link" @@ -9,7 +9,7 @@ import type { HeaderProps } from "@/types/components/myPages/header" export default function SectionHeader({ link, - subtitle, + preamble, title, topTitle = false, textTransform, @@ -24,7 +24,7 @@ export default function SectionHeader({ > {title} - {subtitle && {subtitle}} + {preamble && {preamble}} ) diff --git a/components/Section/Link/index.tsx b/components/Section/Link/index.tsx index c8b27aedd..a4acab664 100644 --- a/components/Section/Link/index.tsx +++ b/components/Section/Link/index.tsx @@ -20,8 +20,8 @@ export default function SectionLink({ link, variant }: SectionLinkProps) { href={link.href} variant="underscored" > - {link.text} + ) } diff --git a/components/TempDesignSystem/Link/link.module.css b/components/TempDesignSystem/Link/link.module.css index ca926d6c6..d7f157a30 100644 --- a/components/TempDesignSystem/Link/link.module.css +++ b/components/TempDesignSystem/Link/link.module.css @@ -131,7 +131,7 @@ } .peach80 { - color: var(--Primary-Light-On-Surface-Accent); + color: var(--Base-Text-Medium-contrast); } .red { @@ -140,12 +140,12 @@ .peach80:hover, .peach80:active { - color: var(--Primary-Light-On-Surface-Hover); + color: var(--Base-Text-High-contrast); } .peach80:hover *, .peach80:active * { - fill: var(--Primary-Light-On-Surface-Hover); + fill: var(--Base-Text-High-contrast); } .white { diff --git a/components/TempDesignSystem/Text/Subtitle/variants.ts b/components/TempDesignSystem/Text/Subtitle/variants.ts index a4cfb6095..afb33bde1 100644 --- a/components/TempDesignSystem/Text/Subtitle/variants.ts +++ b/components/TempDesignSystem/Text/Subtitle/variants.ts @@ -24,7 +24,7 @@ const config = { }, }, defaultVariants: { - color: "burgundy", + color: "black", textAlign: "left", textTransform: "regular", type: "one", diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index a50f0448a..fa4433bbd 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -94,9 +94,9 @@ "Modify": "Ændre", "Month": "Måned", "My communication preferences": "Mine kommunikationspræferencer", - "My credit cards": "Mine kreditkort", "My membership cards": "Mine medlemskort", "My pages": "Mine sider", + "My payment cards": "Mine betalingskort", "My wishes": "Mine ønsker", "New password": "Nyt kodeord", "Next": "Næste", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 10a8e7fcf..b52bba60e 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -92,9 +92,9 @@ "Modify": "Ändern", "Month": "Monat", "My communication preferences": "Meine Kommunikationseinstellungen", - "My credit cards": "Meine Kreditkarten", "My membership cards": "Meine Mitgliedskarten", "My pages": "Meine Seiten", + "My payment cards": "Meine Zahlungskarten", "My wishes": "Meine Wünsche", "New password": "Neues Kennwort", "Next": "Nächste", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 8d6ce6f44..89a87b728 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -99,9 +99,9 @@ "Modify": "Modify", "Month": "Month", "My communication preferences": "My communication preferences", - "My credit cards": "My credit cards", "My membership cards": "My membership cards", "My pages": "My pages", + "My payment cards": "My payment cards", "My wishes": "My wishes", "New password": "New password", "Next": "Next", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 3c33b7848..92dc61768 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -94,9 +94,9 @@ "Modify": "Muokkaa", "Month": "Kuukausi", "My communication preferences": "Viestintämieltymykseni", - "My credit cards": "Luottokorttini", "My membership cards": "Jäsenkorttini", "My pages": "Omat sivut", + "My payment cards": "Minun maksukortit", "My wishes": "Toiveeni", "New password": "Uusi salasana", "Next": "Seuraava", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 7cebd9aac..ef8593a95 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -94,9 +94,9 @@ "Modify": "Endre", "Month": "Måned", "My communication preferences": "Mine kommunikasjonspreferanser", - "My credit cards": "Kredittkortene mine", "My membership cards": "Mine medlemskort", "My pages": "Mine sider", + "My payment cards": "Mine betalingskort", "My wishes": "Mine ønsker", "New password": "Nytt passord", "Next": "Neste", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index e08e9afe3..1103fa4c3 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -96,9 +96,9 @@ "Modify": "Ändra", "Month": "Månad", "My communication preferences": "Mina kommunikationspreferenser", - "My credit cards": "Mina kreditkort", "My membership cards": "Mina medlemskort", "My pages": "Mina sidor", + "My payment cards": "Mina betalningskort", "My wishes": "Mina önskningar", "New password": "Nytt lösenord", "Next": "Nästa", @@ -183,7 +183,7 @@ "Type of room": "Rumstyp", "uppercase letter": "stor bokstav", "Use bonus cheque": "Use bonus cheque", - "User information": "Användar information", + "User information": "Användarinformation", "View your booking": "Visa din bokning", "Visiting address": "Besöksadress", "We could not add a card right now, please try again later.": "Vi kunde inte lägga till ett kort just nu, vänligen försök igen senare.", diff --git a/package-lock.json b/package-lock.json index dd2ab62da..14db5a10c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-slot": "^1.0.2", "@react-aria/ssr": "^3.9.5", - "@scandic-hotels/design-system": "git+https://x-token-auth:$DESIGN_SYSTEM_ACCESS_TOKEN@bitbucket.org/scandic-swap/design-system.git#v0.1.0-rc.8", + "@scandic-hotels/design-system": "git+https://x-token-auth:$DESIGN_SYSTEM_ACCESS_TOKEN@bitbucket.org/scandic-swap/design-system.git#v0.1.0-rc.9", "@t3-oss/env-nextjs": "^0.9.2", "@tanstack/react-query": "^5.28.6", "@trpc/client": "^11.0.0-rc.467", diff --git a/package.json b/package.json index 948db7f1a..bc4b33000 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-slot": "^1.0.2", "@react-aria/ssr": "^3.9.5", - "@scandic-hotels/design-system": "git+https://x-token-auth:$DESIGN_SYSTEM_ACCESS_TOKEN@bitbucket.org/scandic-swap/design-system.git#v0.1.0-rc.8", + "@scandic-hotels/design-system": "git+https://x-token-auth:$DESIGN_SYSTEM_ACCESS_TOKEN@bitbucket.org/scandic-swap/design-system.git#v0.1.0-rc.9", "@t3-oss/env-nextjs": "^0.9.2", "@tanstack/react-query": "^5.28.6", "@trpc/client": "^11.0.0-rc.467", diff --git a/types/components/myPages/header.ts b/types/components/myPages/header.ts index af1ed6a7e..619dc805b 100644 --- a/types/components/myPages/header.ts +++ b/types/components/myPages/header.ts @@ -5,7 +5,7 @@ export type HeaderProps = { href: string text: string } - subtitle: string | null + preamble: string | null textTransform?: HeadingProps["textTransform"] title: string | null topTitle?: boolean From 70297bec91f5b345b523aea0b28c57efe4a6bcd7 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Mon, 2 Sep 2024 15:21:54 +0200 Subject: [PATCH 02/27] feat(SW-330): layout and some styling fixes for hotel-pages --- app/globals.css | 4 +- .../IntroSection/introSection.module.css | 1 + .../HotelPage/PreviewImages/index.tsx | 1 - .../PreviewImages/previewImages.module.css | 32 ++++++++------- .../HotelPage/Rooms/RoomCard/index.tsx | 14 +++---- .../ContentType/HotelPage/Rooms/index.tsx | 2 +- .../TabNavigation/tabNavigation.module.css | 13 ++++-- .../HotelPage/hotelPage.module.css | 41 ++++++++++++++----- components/ContentType/HotelPage/index.tsx | 10 +++-- components/MaxWidth/maxWidth.module.css | 2 +- 10 files changed, 75 insertions(+), 45 deletions(-) diff --git a/app/globals.css b/app/globals.css index 6180a72f6..e3ad605ff 100644 --- a/app/globals.css +++ b/app/globals.css @@ -97,7 +97,9 @@ } :root { - --max-width: 113.5rem; + --current-max-width: 113.5rem; + + --max-width: 94.5rem; --max-width-content: 74.75rem; --max-width-text-block: 49.5rem; --mobile-site-header-height: 70.047px; diff --git a/components/ContentType/HotelPage/IntroSection/introSection.module.css b/components/ContentType/HotelPage/IntroSection/introSection.module.css index 4332cadbc..1697b7ee8 100644 --- a/components/ContentType/HotelPage/IntroSection/introSection.module.css +++ b/components/ContentType/HotelPage/IntroSection/introSection.module.css @@ -2,6 +2,7 @@ display: grid; gap: var(--Spacing-x2); position: relative; + max-width: var(--max-width-text-block); } .mainContent { diff --git a/components/ContentType/HotelPage/PreviewImages/index.tsx b/components/ContentType/HotelPage/PreviewImages/index.tsx index 3b17f0dc4..3ee992bce 100644 --- a/components/ContentType/HotelPage/PreviewImages/index.tsx +++ b/components/ContentType/HotelPage/PreviewImages/index.tsx @@ -20,7 +20,6 @@ export default async function PreviewImages({ images }: PreviewImagesProps) { title={image.title} width={index === 0 ? 752 : 292} height={index === 0 ? 540 : 266} - objectFit="cover" className={styles.image} /> ))} diff --git a/components/ContentType/HotelPage/PreviewImages/previewImages.module.css b/components/ContentType/HotelPage/PreviewImages/previewImages.module.css index 70323fe20..7a1818557 100644 --- a/components/ContentType/HotelPage/PreviewImages/previewImages.module.css +++ b/components/ContentType/HotelPage/PreviewImages/previewImages.module.css @@ -1,26 +1,17 @@ .imageWrapper { display: grid; - grid-template-areas: - "main" - "main" - "main"; gap: 8px; position: relative; width: 100%; - padding-top: var(--Spacing-x2); - background-color: var(--Base-Surface-Subtle-Normal); + padding: var(--Spacing-x2) var(--Spacing-x2) 0; } .image { object-fit: cover; border-radius: var(--Corner-radius-Medium); - display: block; width: 100%; height: 100%; -} - -.imageWrapper > :first-child { - grid-area: main; + max-height: 30vh; } .imageWrapper > :nth-child(2), @@ -33,20 +24,29 @@ bottom: var(--Spacing-x2); right: var(--Spacing-x4); z-index: 1; - display: block; } @media screen and (min-width: 1367px) { .imageWrapper { - grid-template-columns: 72fr 28fr; + grid-template-columns: 70% 30%; + grid-template-rows: repeat(2, 16.625rem); grid-template-areas: "main side1" "main side2"; + padding: var(--Spacing-x2) var(--Spacing-x5) 0; + } + + .image { + max-height: none; + } + + .imageWrapper > :first-child { + grid-area: main; } .imageWrapper > :nth-child(2), .imageWrapper > :nth-child(3) { - display: block; + display: initial; } .imageWrapper > :nth-child(2) { @@ -56,4 +56,8 @@ .desktopGrid > :nth-child(3) { grid-area: side2; } + + .seeAllButton { + right: var(--Spacing-x7); + } } diff --git a/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx b/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx index c9d299b6b..e0678cd5e 100644 --- a/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx +++ b/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx @@ -2,9 +2,9 @@ import { useIntl } from "react-intl" -import { ImageIcon } from "@/components/Icons" +import { ChevronRightIcon, ImageIcon } from "@/components/Icons" import Image from "@/components/Image" -import Link from "@/components/TempDesignSystem/Link" +import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" import Title from "@/components/TempDesignSystem/Text/Title" @@ -62,14 +62,10 @@ export function RoomCard({ {subtitle} - +
) diff --git a/components/ContentType/HotelPage/Rooms/index.tsx b/components/ContentType/HotelPage/Rooms/index.tsx index 08e8f07ce..b6fa4f7dc 100644 --- a/components/ContentType/HotelPage/Rooms/index.tsx +++ b/components/ContentType/HotelPage/Rooms/index.tsx @@ -80,7 +80,7 @@ export function Rooms({ rooms }: RoomsProps) {
+ ) : ( + + {title} + + ) +} diff --git a/components/Header/MainMenu/MenuItem/menuItem.module.css b/components/Header/MainMenu/MenuItem/menuItem.module.css new file mode 100644 index 000000000..4b8afc4de --- /dev/null +++ b/components/Header/MainMenu/MenuItem/menuItem.module.css @@ -0,0 +1,21 @@ +.navigationButton { + display: flex; + gap: var(--Spacing-x1); + align-items: center; + background-color: transparent; + color: var(--Base-Text-High-contrast); + border-width: 0; + padding: 0; + cursor: pointer; + font-family: var(--typography-Body-Bold-fontFamily); + font-weight: var(--typography-Body-Bold-fontWeight); + font-size: var(--typography-Body-Bold-fontSize); +} + +.chevron { + transition: transform 0.2s; +} + +.chevron.isExpanded { + transform: rotate(180deg); +} diff --git a/components/Header/MainMenu/MenuItem/menuItem.ts b/components/Header/MainMenu/MenuItem/menuItem.ts new file mode 100644 index 000000000..b4c761110 --- /dev/null +++ b/components/Header/MainMenu/MenuItem/menuItem.ts @@ -0,0 +1,5 @@ +import { MenuItem } from ".." + +export interface MenuItemProps { + item: MenuItem +} diff --git a/components/Header/MainMenu/User/Avatar/avatar.module.css b/components/Header/MainMenu/User/Avatar/avatar.module.css new file mode 100644 index 000000000..0c6055350 --- /dev/null +++ b/components/Header/MainMenu/User/Avatar/avatar.module.css @@ -0,0 +1,16 @@ +.avatar { + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + border-radius: 50%; + width: 2rem; + height: 2rem; + background-color: var(--Main-Grey-40); +} + +.initials { + font-size: 0.75rem; + color: var(--Base-Text-Inverted); + background-color: var(--Scandic-Peach-70); +} diff --git a/components/Header/MainMenu/User/Avatar/avatar.ts b/components/Header/MainMenu/User/Avatar/avatar.ts new file mode 100644 index 000000000..6156e0bbe --- /dev/null +++ b/components/Header/MainMenu/User/Avatar/avatar.ts @@ -0,0 +1,6 @@ +import { ImageProps } from "next/image" + +export interface AvatarProps { + image?: ImageProps + initials?: string +} diff --git a/components/Header/MainMenu/User/Avatar/index.tsx b/components/Header/MainMenu/User/Avatar/index.tsx new file mode 100644 index 000000000..ceff75675 --- /dev/null +++ b/components/Header/MainMenu/User/Avatar/index.tsx @@ -0,0 +1,19 @@ +import { PersonIcon } from "@/components/Icons" +import Image from "@/components/Image" + +import { AvatarProps } from "./avatar" + +import styles from "./avatar.module.css" + +export default function Avatar({ image, initials }: AvatarProps) { + let classNames = [styles.avatar] + let element = + if (image) { + classNames.push(styles.image) + element = {image.alt} + } else if (initials) { + classNames.push(styles.initials) + element = {initials} + } + return {element} +} diff --git a/components/Header/MainMenu/User/index.tsx b/components/Header/MainMenu/User/index.tsx new file mode 100644 index 000000000..d26bc1173 --- /dev/null +++ b/components/Header/MainMenu/User/index.tsx @@ -0,0 +1,16 @@ +import Link from "next/link" + +import Avatar from "./Avatar" + +import styles from "./user.module.css" + +export default function User() { + return ( +
+ + + Log in/Join + +
+ ) +} diff --git a/components/Header/MainMenu/User/user.module.css b/components/Header/MainMenu/User/user.module.css new file mode 100644 index 000000000..c43fa17fe --- /dev/null +++ b/components/Header/MainMenu/User/user.module.css @@ -0,0 +1,12 @@ +.user { +} + +.link { + display: flex; + gap: var(--Spacing-x1); + align-items: center; + font-family: var(--typography-Body-Bold-fontFamily); + font-weight: 600; + color: var(--Base-Text-High-contrast); + text-decoration: none; +} diff --git a/components/Header/MainMenu/index.tsx b/components/Header/MainMenu/index.tsx new file mode 100644 index 000000000..d0b85ceb4 --- /dev/null +++ b/components/Header/MainMenu/index.tsx @@ -0,0 +1,125 @@ +import Link from "next/link" + +import Image from "@/components/Image" + +import Menu from "./Menu" +import User from "./User" + +import styles from "./mainMenu.module.css" + +export interface MenuItem { + id: string + title: string + href: string + children?: { + groupTitle: string + children: { + id: string + title: string + href: string + }[] + }[] + seeAllLinkText?: string + infoCard?: { + scriptedTitle: string + title: string + description: string + ctaLink: string + } +} + +export default async function MainMenu() { + const menuItems: MenuItem[] = [ + { + id: "hotels", + title: "Hotels", + href: "/hotels", + children: [], + }, + { + id: "business", + title: "Business", + href: "/business", + children: [ + { + groupTitle: "Top conference venues", + children: [ + { + id: "stockholm", + title: "Stockholm", + href: "/stockholm", + }, + { + id: "bergen", + title: "Bergen", + href: "/bergen", + }, + { + id: "copenhagen", + title: "Copenhagen", + href: "/copenhagen", + }, + ], + }, + { + groupTitle: "Scandic for business", + children: [ + { + id: "book-a-venue", + title: "Book a venue", + href: "/book-a-venue", + }, + { + id: "conference-packages", + title: "Conference packages", + href: "/conference-packages", + }, + { + id: "co-working", + title: "Co-working", + href: "/co-working", + }, + ], + }, + ], + seeAllLinkText: "See all conference & meeting venues", + infoCard: { + scriptedTitle: "Stockholm", + title: "Meeting venues in Stockholm", + description: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et felis metus. Sed et felis metus.", + ctaLink: "/stockholm", + }, + }, + { + id: "offers", + title: "Offers", + href: "/offers", + }, + { + id: "restaurants", + title: "Restaurants", + href: "/restaurants", + }, + ] + return ( +
+ +
+ ) +} diff --git a/components/Header/MainMenu/mainMenu.module.css b/components/Header/MainMenu/mainMenu.module.css new file mode 100644 index 000000000..e58ac423c --- /dev/null +++ b/components/Header/MainMenu/mainMenu.module.css @@ -0,0 +1,15 @@ +.mainMenu { + background-color: var(--Base-Surface-Primary-light-Normal); + padding: var(--Spacing-x2); + border-top: 1px solid var(--Base-Border-Subtle); + border-bottom: 1px solid var(--Base-Border-Subtle); +} + +.content { + max-width: 89.5rem; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--Spacing-x3); +} diff --git a/components/Header/TopMenu/Button/button.module.css b/components/Header/TopMenu/Button/button.module.css new file mode 100644 index 000000000..004e01d19 --- /dev/null +++ b/components/Header/TopMenu/Button/button.module.css @@ -0,0 +1,9 @@ +.button { + background-color: transparent; + color: var(--Base-Text-High-contrast); + border-width: 0; + cursor: pointer; + display: flex; + gap: var(--Spacing-x1); + align-items: center; +} diff --git a/components/Header/TopMenu/Button/button.ts b/components/Header/TopMenu/Button/button.ts new file mode 100644 index 000000000..9aa84a33f --- /dev/null +++ b/components/Header/TopMenu/Button/button.ts @@ -0,0 +1,2 @@ +export interface ButtonProps + extends React.ButtonHTMLAttributes {} diff --git a/components/Header/TopMenu/Button/index.tsx b/components/Header/TopMenu/Button/index.tsx new file mode 100644 index 000000000..437fe2d73 --- /dev/null +++ b/components/Header/TopMenu/Button/index.tsx @@ -0,0 +1,11 @@ +import { ButtonProps } from "./button" + +import styles from "./button.module.css" + +export default function Button({ children, ...props }: ButtonProps) { + return ( + + ) +} diff --git a/components/Header/TopMenu/LanguageSwitcher/index.tsx b/components/Header/TopMenu/LanguageSwitcher/index.tsx new file mode 100644 index 000000000..f13a0153a --- /dev/null +++ b/components/Header/TopMenu/LanguageSwitcher/index.tsx @@ -0,0 +1,30 @@ +"use client" + +import { useState } from "react" + +import { ChevronDownIcon, GlobeIcon } from "@/components/Icons" + +import Button from "../Button" + +import styles from "./languageSwitcher.module.css" + +export default function LanguageSwitcher() { + const [isExpanded, setIsExpanded] = useState(false) + + function handleButtonClick() { + setIsExpanded((prev) => !prev) + } + + return ( + + ) +} diff --git a/components/Header/TopMenu/LanguageSwitcher/languageSwitcher.module.css b/components/Header/TopMenu/LanguageSwitcher/languageSwitcher.module.css new file mode 100644 index 000000000..739d7d34e --- /dev/null +++ b/components/Header/TopMenu/LanguageSwitcher/languageSwitcher.module.css @@ -0,0 +1,17 @@ +.button { + background-color: transparent; + color: var(--Base-Text-High-contrast); + border-width: 0; + cursor: pointer; + display: flex; + gap: var(--Spacing-x1); + align-items: center; +} + +.chevron { + transition: transform 0.2s; +} + +.chevron.isExpanded { + transform: rotate(180deg); +} diff --git a/components/Header/TopMenu/Search/index.tsx b/components/Header/TopMenu/Search/index.tsx new file mode 100644 index 000000000..5668d49aa --- /dev/null +++ b/components/Header/TopMenu/Search/index.tsx @@ -0,0 +1,12 @@ +import { SearchIcon } from "@/components/Icons" + +import Button from "../Button" + +export default function Search() { + return ( + + ) +} diff --git a/components/Header/TopMenu/Search/search.module.css b/components/Header/TopMenu/Search/search.module.css new file mode 100644 index 000000000..004e01d19 --- /dev/null +++ b/components/Header/TopMenu/Search/search.module.css @@ -0,0 +1,9 @@ +.button { + background-color: transparent; + color: var(--Base-Text-High-contrast); + border-width: 0; + cursor: pointer; + display: flex; + gap: var(--Spacing-x1); + align-items: center; +} diff --git a/components/Header/TopMenu/index.tsx b/components/Header/TopMenu/index.tsx new file mode 100644 index 000000000..73261bc5c --- /dev/null +++ b/components/Header/TopMenu/index.tsx @@ -0,0 +1,24 @@ +import { GiftIcon } from "@/components/Icons" +import Link from "@/components/TempDesignSystem/Link" + +import LanguageSwitcher from "./LanguageSwitcher" +import Search from "./Search" + +import styles from "./topMenu.module.css" + +export default async function TopMenu() { + return ( +
+
+ + + Join Scandic Friends + +
+ + +
+
+
+ ) +} diff --git a/components/Header/TopMenu/topMenu.module.css b/components/Header/TopMenu/topMenu.module.css new file mode 100644 index 000000000..4d86f55d2 --- /dev/null +++ b/components/Header/TopMenu/topMenu.module.css @@ -0,0 +1,18 @@ +.topMenu { + background-color: var(--Base-Surface-Subtle-Normal); + padding: var(--Spacing-x2) var(--Spacing-x-one-and-half); +} + +.content { + max-width: 89.5rem; + margin: 0 auto; + display: flex; + justify-content: space-between; + gap: var(--Spacing-x3); +} + +.right { + display: flex; + gap: var(--Spacing-x2); + align-items: center; +} diff --git a/components/Header/header.module.css b/components/Header/header.module.css new file mode 100644 index 000000000..a3d728549 --- /dev/null +++ b/components/Header/header.module.css @@ -0,0 +1,4 @@ +.header { + font-family: var(--typography-Body-Regular-fontFamily); + color: var(--Base-Text-High-contrast); +} diff --git a/components/Header/index.tsx b/components/Header/index.tsx new file mode 100644 index 000000000..d6693a52b --- /dev/null +++ b/components/Header/index.tsx @@ -0,0 +1,13 @@ +import MainMenu from "./MainMenu" +import TopMenu from "./TopMenu" + +import styles from "./header.module.css" + +export default async function Header() { + return ( +
+ + +
+ ) +} diff --git a/components/Icons/Gift.tsx b/components/Icons/Gift.tsx new file mode 100644 index 000000000..b07015db5 --- /dev/null +++ b/components/Icons/Gift.tsx @@ -0,0 +1,36 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function GiftIcon({ className, color, ...props }: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + + + + + + ) +} diff --git a/components/Icons/Person.tsx b/components/Icons/Person.tsx index 20c66c236..bc2452ac4 100644 --- a/components/Icons/Person.tsx +++ b/components/Icons/Person.tsx @@ -7,28 +7,28 @@ export default function PersonIcon({ className, color, ...props }: IconProps) { return ( - + - + diff --git a/components/Icons/Search.tsx b/components/Icons/Search.tsx new file mode 100644 index 000000000..3e91f1f5c --- /dev/null +++ b/components/Icons/Search.tsx @@ -0,0 +1,36 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function GiftIcon({ className, color, ...props }: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + + + + + + ) +} diff --git a/components/Icons/get-icon-by-icon-name.ts b/components/Icons/get-icon-by-icon-name.ts index e10495d52..3c7a37d7e 100644 --- a/components/Icons/get-icon-by-icon-name.ts +++ b/components/Icons/get-icon-by-icon-name.ts @@ -25,6 +25,7 @@ import { ElectricBikeIcon, EmailIcon, FitnessIcon, + GiftIcon, GlobeIcon, HouseIcon, ImageIcon, @@ -39,6 +40,7 @@ import { PlusCircleIcon, RestaurantIcon, SaunaIcon, + SearchIcon, TshirtWashIcon, WarningTriangle, WifiIcon, @@ -92,6 +94,8 @@ export function getIconByIconName(icon?: IconName): FC | null { return FacebookIcon case IconName.Fitness: return FitnessIcon + case IconName.Gift: + return GiftIcon case IconName.Globe: return GlobeIcon case IconName.House: @@ -122,6 +126,8 @@ export function getIconByIconName(icon?: IconName): FC | null { return RestaurantIcon case IconName.Sauna: return SaunaIcon + case IconName.Search: + return SearchIcon case IconName.Tripadvisor: return TripAdvisorIcon case IconName.TshirtWash: diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index 7f0259352..0dbb3b628 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -21,6 +21,7 @@ export { default as DoorOpenIcon } from "./DoorOpen" export { default as ElectricBikeIcon } from "./ElectricBike" export { default as EmailIcon } from "./Email" export { default as FitnessIcon } from "./Fitness" +export { default as GiftIcon } from "./Gift" export { default as GlobeIcon } from "./Globe" export { default as HouseIcon } from "./House" export { default as ImageIcon } from "./Image" @@ -37,6 +38,7 @@ export { default as PriceTagIcon } from "./PriceTag" export { default as RestaurantIcon } from "./Restaurant" export { default as SaunaIcon } from "./Sauna" export { default as ScandicLogoIcon } from "./ScandicLogo" +export { default as SearchIcon } from "./Search" export { default as TshirtWashIcon } from "./TshirtWash" export { default as WarningTriangle } from "./WarningTriangle" export { default as WifiIcon } from "./Wifi" diff --git a/types/components/icon.ts b/types/components/icon.ts index d965a4e78..ed0d3da3b 100644 --- a/types/components/icon.ts +++ b/types/components/icon.ts @@ -29,6 +29,7 @@ export enum IconName { Email = "Email", Facebook = "Facebook", Fitness = "Fitness", + Gift = "Gift", Globe = "Globe", House = "House", Image = "Image", @@ -44,6 +45,7 @@ export enum IconName { PlusCircle = "PlusCircle", Restaurant = "Restaurant", Sauna = "Sauna", + Search = "Search", Tripadvisor = "Tripadvisor", TshirtWash = "TshirtWash", Wifi = "Wifi", From 45095a27d4c4512134beb17966c5d5d0175445f3 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Tue, 20 Aug 2024 13:31:14 +0200 Subject: [PATCH 04/27] feat(SW-184): added menu buttons and my pages menu --- .../Header/MainMenu/MenuButton/index.tsx | 11 +++ .../MainMenu/MenuButton/menuButton.module.css | 13 +++ components/Header/MainMenu/MenuItem/index.tsx | 9 +- .../MainMenu/MenuItem/menuItem.module.css | 14 ---- .../Avatar/avatar.module.css | 0 .../{User => MyPages}/Avatar/avatar.ts | 2 +- .../{User => MyPages}/Avatar/index.tsx | 0 components/Header/MainMenu/MyPages/index.tsx | 53 ++++++++++++ .../MainMenu/MyPages/myPages.module.css | 11 +++ .../Header/MainMenu/MyPagesMenu/index.tsx | 83 +++++++++++++++++++ .../MyPagesMenu/myPagesMenu.module.css | 58 +++++++++++++ components/Header/MainMenu/User/index.tsx | 16 ---- .../Header/MainMenu/User/user.module.css | 12 --- components/Header/MainMenu/index.tsx | 11 ++- .../Header/MainMenu/mainMenu.module.css | 2 +- components/Header/TopMenu/topMenu.module.css | 1 + components/Header/header.module.css | 2 + .../TempDesignSystem/Link/link.module.css | 20 ++--- i18n/dictionaries/en.json | 2 + stores/main-menu.ts | 1 + 20 files changed, 255 insertions(+), 66 deletions(-) create mode 100644 components/Header/MainMenu/MenuButton/index.tsx create mode 100644 components/Header/MainMenu/MenuButton/menuButton.module.css rename components/Header/MainMenu/{User => MyPages}/Avatar/avatar.module.css (100%) rename components/Header/MainMenu/{User => MyPages}/Avatar/avatar.ts (77%) rename components/Header/MainMenu/{User => MyPages}/Avatar/index.tsx (100%) create mode 100644 components/Header/MainMenu/MyPages/index.tsx create mode 100644 components/Header/MainMenu/MyPages/myPages.module.css create mode 100644 components/Header/MainMenu/MyPagesMenu/index.tsx create mode 100644 components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css delete mode 100644 components/Header/MainMenu/User/index.tsx delete mode 100644 components/Header/MainMenu/User/user.module.css diff --git a/components/Header/MainMenu/MenuButton/index.tsx b/components/Header/MainMenu/MenuButton/index.tsx new file mode 100644 index 000000000..f0ee1f2f1 --- /dev/null +++ b/components/Header/MainMenu/MenuButton/index.tsx @@ -0,0 +1,11 @@ +import styles from "./menuButton.module.css" + +export default function MenuButton({ + className, + ...props +}: React.ButtonHTMLAttributes) { + const classNames = className + ? `${styles.menuButton} ${className}` + : styles.menuButton + return + ) : ( {title} diff --git a/components/Header/MainMenu/MenuItem/menuItem.module.css b/components/Header/MainMenu/MenuItem/menuItem.module.css index 4b8afc4de..c743f0649 100644 --- a/components/Header/MainMenu/MenuItem/menuItem.module.css +++ b/components/Header/MainMenu/MenuItem/menuItem.module.css @@ -1,17 +1,3 @@ -.navigationButton { - display: flex; - gap: var(--Spacing-x1); - align-items: center; - background-color: transparent; - color: var(--Base-Text-High-contrast); - border-width: 0; - padding: 0; - cursor: pointer; - font-family: var(--typography-Body-Bold-fontFamily); - font-weight: var(--typography-Body-Bold-fontWeight); - font-size: var(--typography-Body-Bold-fontSize); -} - .chevron { transition: transform 0.2s; } diff --git a/components/Header/MainMenu/User/Avatar/avatar.module.css b/components/Header/MainMenu/MyPages/Avatar/avatar.module.css similarity index 100% rename from components/Header/MainMenu/User/Avatar/avatar.module.css rename to components/Header/MainMenu/MyPages/Avatar/avatar.module.css diff --git a/components/Header/MainMenu/User/Avatar/avatar.ts b/components/Header/MainMenu/MyPages/Avatar/avatar.ts similarity index 77% rename from components/Header/MainMenu/User/Avatar/avatar.ts rename to components/Header/MainMenu/MyPages/Avatar/avatar.ts index 6156e0bbe..1cb269879 100644 --- a/components/Header/MainMenu/User/Avatar/avatar.ts +++ b/components/Header/MainMenu/MyPages/Avatar/avatar.ts @@ -2,5 +2,5 @@ import { ImageProps } from "next/image" export interface AvatarProps { image?: ImageProps - initials?: string + initials?: string | null } diff --git a/components/Header/MainMenu/User/Avatar/index.tsx b/components/Header/MainMenu/MyPages/Avatar/index.tsx similarity index 100% rename from components/Header/MainMenu/User/Avatar/index.tsx rename to components/Header/MainMenu/MyPages/Avatar/index.tsx diff --git a/components/Header/MainMenu/MyPages/index.tsx b/components/Header/MainMenu/MyPages/index.tsx new file mode 100644 index 000000000..580d9b1a3 --- /dev/null +++ b/components/Header/MainMenu/MyPages/index.tsx @@ -0,0 +1,53 @@ +"use client" + +import Link from "next/link" +import { useIntl } from "react-intl" + +import { myPages } from "@/constants/routes/myPages" +import { navigationQueryRouter } from "@/server/routers/contentstack/myPages/navigation/query" +import useDropdownStore from "@/stores/main-menu" + +import { ChevronDownIcon } from "@/components/Icons" +import useLang from "@/hooks/useLang" +import { getInitials } from "@/utils/user" + +import MenuButton from "../MenuButton" +import MyPagesMenu from "../MyPagesMenu" +import Avatar from "./Avatar" + +import styles from "./myPages.module.css" + +import { User } from "@/types/user" + +export default function MyPages({ + myPagesNavigation, + user, +}: { + myPagesNavigation: Awaited> + user: Pick | null +}) { + const intl = useIntl() + const lang = useLang() + + const { toggleMyPagesMobileMenu, isMyPagesMobileMenuOpen } = + useDropdownStore() + + return user ? ( + <> + + + {intl.formatMessage({ id: "Hi" })} {user.firstName}! + + + + + ) : ( + + + {intl.formatMessage({ id: "Log in/Join" })} + + ) +} diff --git a/components/Header/MainMenu/MyPages/myPages.module.css b/components/Header/MainMenu/MyPages/myPages.module.css new file mode 100644 index 000000000..2a5bc7a33 --- /dev/null +++ b/components/Header/MainMenu/MyPages/myPages.module.css @@ -0,0 +1,11 @@ +.button { + font-weight: 600; +} + +.chevron { + transition: transform 0.2s; +} + +.chevron.isExpanded { + transform: rotate(180deg); +} diff --git a/components/Header/MainMenu/MyPagesMenu/index.tsx b/components/Header/MainMenu/MyPagesMenu/index.tsx new file mode 100644 index 000000000..3d4e1ebf4 --- /dev/null +++ b/components/Header/MainMenu/MyPagesMenu/index.tsx @@ -0,0 +1,83 @@ +"use client" +import { useIntl } from "react-intl" + +import { logout } from "@/constants/routes/handleAuth" +import { navigationQueryRouter } from "@/server/routers/contentstack/myPages/navigation/query" +import useDropdownStore from "@/stores/main-menu" + +import { ArrowRightIcon } from "@/components/Icons" +import Link from "@/components/TempDesignSystem/Link" +import useLang from "@/hooks/useLang" + +import styles from "./myPagesMenu.module.css" + +type Navigation = Awaited> + +export default function MyPagesMenu({ + navigation, +}: { + navigation: Navigation +}) { + const { formatMessage } = useIntl() + const lang = useLang() + const { toggleMyPagesMobileMenu, isMyPagesMobileMenuOpen } = + useDropdownStore() + + if (!navigation) { + return null + } + + console.log(navigation) + + return ( + + ) +} diff --git a/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css b/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css new file mode 100644 index 000000000..94f096a6f --- /dev/null +++ b/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css @@ -0,0 +1,58 @@ +.myPagesMenu { + position: absolute; + top: 46px; + right: 0; + background-color: #fff; + border-top: 1px solid #e3e0db; + display: none; + list-style: none; + overflow-y: visible; + margin: 0; + padding: var(--Spacing-x2) var(--Spacing-x4); + border-radius: var(--Corner-radius-Large); + box-shadow: 0px 0px 14px 6px #0000001a; +} + +.myPagesMenu.isExpanded { + display: block; +} + +.friendTypeWrapper { + padding: 0 var(--Spacing-x1) var(--Spacing-x2); + font-weight: 400; + color: var(--UI-Text-Medium-contrast); +} +.friendType { + font-family: var(--typography-Title-5-fontFamily); + letter-spacing: var(--typography-Title-5-letterSpacing); + font-size: var(--typography-Caption-Bold-fontSize); + text-transform: uppercase; +} + +.friendType::after { + content: " · "; + display: inline; + padding: 0 var(--Spacing-x-half); +} + +.groups, +.menuItems { + list-style: none; +} + +.group { + padding: var(--Spacing-x2) 0; + border-top: 1px solid var(--Base-Border-Subtle); +} + +.group:last-child { + padding-bottom: 0; +} + +.arrow { + opacity: 0; +} + +.link:hover .arrow { + opacity: 1; +} diff --git a/components/Header/MainMenu/User/index.tsx b/components/Header/MainMenu/User/index.tsx deleted file mode 100644 index d26bc1173..000000000 --- a/components/Header/MainMenu/User/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import Link from "next/link" - -import Avatar from "./Avatar" - -import styles from "./user.module.css" - -export default function User() { - return ( -
- - - Log in/Join - -
- ) -} diff --git a/components/Header/MainMenu/User/user.module.css b/components/Header/MainMenu/User/user.module.css deleted file mode 100644 index c43fa17fe..000000000 --- a/components/Header/MainMenu/User/user.module.css +++ /dev/null @@ -1,12 +0,0 @@ -.user { -} - -.link { - display: flex; - gap: var(--Spacing-x1); - align-items: center; - font-family: var(--typography-Body-Bold-fontFamily); - font-weight: 600; - color: var(--Base-Text-High-contrast); - text-decoration: none; -} diff --git a/components/Header/MainMenu/index.tsx b/components/Header/MainMenu/index.tsx index d0b85ceb4..d16f829f1 100644 --- a/components/Header/MainMenu/index.tsx +++ b/components/Header/MainMenu/index.tsx @@ -1,9 +1,11 @@ import Link from "next/link" +import { serverClient } from "@/lib/trpc/server" + import Image from "@/components/Image" import Menu from "./Menu" -import User from "./User" +import MyPages from "./MyPages" import styles from "./mainMenu.module.css" @@ -29,6 +31,11 @@ export interface MenuItem { } export default async function MainMenu() { + const myPagesNavigation = + await serverClient().contentstack.myPages.navigation.get() + + const user = await serverClient().user.name() + const menuItems: MenuItem[] = [ { id: "hotels", @@ -118,7 +125,7 @@ export default async function MainMenu() { /> - +
) diff --git a/components/Header/MainMenu/mainMenu.module.css b/components/Header/MainMenu/mainMenu.module.css index e58ac423c..99a9a67e7 100644 --- a/components/Header/MainMenu/mainMenu.module.css +++ b/components/Header/MainMenu/mainMenu.module.css @@ -1,11 +1,11 @@ .mainMenu { background-color: var(--Base-Surface-Primary-light-Normal); padding: var(--Spacing-x2); - border-top: 1px solid var(--Base-Border-Subtle); border-bottom: 1px solid var(--Base-Border-Subtle); } .content { + position: relative; max-width: 89.5rem; margin: 0 auto; display: flex; diff --git a/components/Header/TopMenu/topMenu.module.css b/components/Header/TopMenu/topMenu.module.css index 4d86f55d2..f23170779 100644 --- a/components/Header/TopMenu/topMenu.module.css +++ b/components/Header/TopMenu/topMenu.module.css @@ -1,6 +1,7 @@ .topMenu { background-color: var(--Base-Surface-Subtle-Normal); padding: var(--Spacing-x2) var(--Spacing-x-one-and-half); + border-bottom: 1px solid var(--Base-Border-Subtle); } .content { diff --git a/components/Header/header.module.css b/components/Header/header.module.css index a3d728549..54032e59b 100644 --- a/components/Header/header.module.css +++ b/components/Header/header.module.css @@ -1,4 +1,6 @@ .header { + position: relative; font-family: var(--typography-Body-Regular-fontFamily); color: var(--Base-Text-High-contrast); + z-index: 1; } diff --git a/components/TempDesignSystem/Link/link.module.css b/components/TempDesignSystem/Link/link.module.css index d7f157a30..3fc3b2582 100644 --- a/components/TempDesignSystem/Link/link.module.css +++ b/components/TempDesignSystem/Link/link.module.css @@ -41,26 +41,18 @@ font-family: var(--typography-Body-Regular-fontFamily); font-size: var(--typography-Body-Regular-fontSize); line-height: var(--typography-Body-Regular-lineHeight); - letter-spacing: 0.096px; - padding: var(--Spacing-x1) var(--Spacing-x1) var(--Spacing-x1) - var(--Spacing-x-one-and-half); - width: 100%; + letter-spacing: var(--typography-Body-Regular-letterSpacing); + padding: var(--Spacing-x1); border-radius: var(--Corner-radius-Medium); - gap: 4px; display: flex; + gap: var(--Spacing-x1); + justify-content: space-between; align-items: center; - gap: 4px; - align-self: stretch; } -.myPageMobileDropdown.active { - background-color: var(--Scandic-Brand-Pale-Peach); +.myPageMobileDropdown:hover { + background-color: var(--Base-Surface-Primary-light-Hover-alt); border-radius: var(--Corner-radius-Medium); - font-family: var(--typography-Body-Underline-fontFamily); - font-size: var(--typography-Body-Underline-fontSize); - font-weight: var(--typography-Body-Underline-fontWeight); - letter-spacing: var(--typography-Body-Underline-letterSpacing); - line-height: var(--typography-Body-Underline-lineHeight); } .shortcut { diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 89a87b728..6a8459041 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -84,8 +84,10 @@ "Level 5": "Level 5", "Level 6": "Level 6", "Level 7": "Level 7", + "Hi": "Hi", "Level up to unlock": "Level up to unlock", "Log in": "Log in", + "Log in/Join": "Log in/Join", "Log in here": "Log in here", "Log out": "Log out", "Manage preferences": "Manage preferences", diff --git a/stores/main-menu.ts b/stores/main-menu.ts index cbd6f1004..68d4d2ba1 100644 --- a/stores/main-menu.ts +++ b/stores/main-menu.ts @@ -21,6 +21,7 @@ const useDropdownStore = create((set) => ({ toggleMyPagesMobileMenu: () => set((state) => { // Close the other dropdown if it's open + console.log({ state }) if (!state.isMyPagesMobileMenuOpen && state.isHamburgerMenuOpen) { set({ isHamburgerMenuOpen: false }) } From b37cc7a34caedd639fa1726ab8f04eb328b94eb2 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Tue, 20 Aug 2024 14:51:47 +0200 Subject: [PATCH 05/27] feat(SW-184): added language switcher --- .../(live)/@header/[...paths]/layout.tsx | 1 + app/[lang]/(live)/@header/page.tsx | 15 +---- .../Header/MainMenu/MyPagesMenu/index.tsx | 2 - .../MyPagesMenu/myPagesMenu.module.css | 9 +-- .../Header/TopMenu/LanguageSwitcher/index.tsx | 61 ++++++++++++++---- .../languageSwitcher.module.css | 63 +++++++++++++++++++ .../LanguageSwitcher/languageSwitcher.ts | 5 ++ components/Header/TopMenu/index.tsx | 10 ++- components/Header/TopMenu/topMenu.module.css | 1 + components/Header/header.module.css | 2 +- stores/main-menu.ts | 1 - 11 files changed, 133 insertions(+), 37 deletions(-) create mode 100644 components/Header/TopMenu/LanguageSwitcher/languageSwitcher.ts diff --git a/app/[lang]/(live)/@header/[...paths]/layout.tsx b/app/[lang]/(live)/@header/[...paths]/layout.tsx index 17edf6a50..bbd5697c2 100644 --- a/app/[lang]/(live)/@header/[...paths]/layout.tsx +++ b/app/[lang]/(live)/@header/[...paths]/layout.tsx @@ -5,5 +5,6 @@ import type { LangParams, LayoutArgs } from "@/types/params" export default function HeaderLayout({ params }: LayoutArgs) { setLang(params.lang) + return
} diff --git a/app/[lang]/(live)/@header/page.tsx b/app/[lang]/(live)/@header/page.tsx index 09220fbb6..adccd9484 100644 --- a/app/[lang]/(live)/@header/page.tsx +++ b/app/[lang]/(live)/@header/page.tsx @@ -1,9 +1,4 @@ -import { baseUrls } from "@/constants/routes/baseUrls" -import { serverClient } from "@/lib/trpc/server" - -import Header from "@/components/Current/Header" -import LanguageSwitcher from "@/components/Current/Header/LanguageSwitcher" -import MyPagesMobileDropdown from "@/components/Current/Header/MyPagesMobileDropdown" +import Header from "@/components/Header" import { setLang } from "@/i18n/serverContext" import { LangParams, PageArgs } from "@/types/params" @@ -11,11 +6,5 @@ import { LangParams, PageArgs } from "@/types/params" export default async function HeaderPage({ params }: PageArgs) { setLang(params.lang) - const navigation = await serverClient().contentstack.myPages.navigation.get() - return ( -
} - languageSwitcher={} - /> - ) + return
} diff --git a/components/Header/MainMenu/MyPagesMenu/index.tsx b/components/Header/MainMenu/MyPagesMenu/index.tsx index 3d4e1ebf4..3f4eb4213 100644 --- a/components/Header/MainMenu/MyPagesMenu/index.tsx +++ b/components/Header/MainMenu/MyPagesMenu/index.tsx @@ -27,8 +27,6 @@ export default function MyPagesMenu({ return null } - console.log(navigation) - return ( - + ) : ( diff --git a/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css b/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css index d6cbc237d..459fb6d9f 100644 --- a/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css +++ b/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css @@ -1,3 +1,7 @@ +.myPagesMenu { + position: relative; +} + .button { font-weight: 600; } @@ -10,7 +14,7 @@ transform: rotate(180deg); } -.myPagesMenu { +.dropdown { position: absolute; top: 46px; right: 0; @@ -18,11 +22,25 @@ padding: var(--Spacing-x2) var(--Spacing-x4); border-radius: var(--Corner-radius-Large); box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1); + min-width: 20rem; z-index: 1; display: none; } -.myPagesMenu.isExpanded { +/* Triangle above dropdown */ +.dropdown::before { + content: ""; + position: absolute; + top: -1.25rem; + right: 2.4rem; + transform: rotate(180deg); + border-width: 0.75rem; + border-style: solid; + border-color: var(--Base-Surface-Primary-light-Normal) transparent transparent + transparent; +} + +.dropdown.isExpanded { display: block; } diff --git a/components/Header/MainMenu/mainMenu.module.css b/components/Header/MainMenu/mainMenu.module.css index 83a5defcb..ddcb33627 100644 --- a/components/Header/MainMenu/mainMenu.module.css +++ b/components/Header/MainMenu/mainMenu.module.css @@ -5,8 +5,7 @@ } .nav { - position: relative; - max-width: 89.5rem; + max-width: var(--max-width-navigation); margin: 0 auto; display: flex; justify-content: space-between; diff --git a/components/Header/TopMenu/LanguageSwitcher/index.tsx b/components/Header/TopMenu/LanguageSwitcher/index.tsx index 8416c25b6..638a58dba 100644 --- a/components/Header/TopMenu/LanguageSwitcher/index.tsx +++ b/components/Header/TopMenu/LanguageSwitcher/index.tsx @@ -1,9 +1,9 @@ "use client" import Link from "next/link" -import { useState } from "react" import { Lang, languages } from "@/constants/languages" +import useDropdownStore from "@/stores/main-menu" import { CheckIcon, ChevronDownIcon, GlobeIcon } from "@/components/Icons" import useLang from "@/hooks/useLang" @@ -16,28 +16,24 @@ import { LanguageSwitcherProps } from "@/types/components/current/languageSwitch export default function LanguageSwitcher({ urls }: LanguageSwitcherProps) { const currentLanguage = useLang() - const [isExpanded, setIsExpanded] = useState(false) - - function toggleExpand() { - setIsExpanded((prev) => !prev) - } + const { toggleLanguageSwitcher, isLanguageSwitcherOpen } = useDropdownStore() const urlKeys = Object.keys(urls) as Lang[] return (
- + {languages[currentLanguage]}
    {urlKeys.map((key) => { diff --git a/components/Header/TopMenu/topMenu.module.css b/components/Header/TopMenu/topMenu.module.css index fbdec4a8b..e78f57d92 100644 --- a/components/Header/TopMenu/topMenu.module.css +++ b/components/Header/TopMenu/topMenu.module.css @@ -5,8 +5,7 @@ } .content { - position: relative; - max-width: 89.5rem; + max-width: var(--max-width-navigation); margin: 0 auto; display: flex; justify-content: space-between; diff --git a/components/Header/header.module.css b/components/Header/header.module.css index 7b40be816..1c3360388 100644 --- a/components/Header/header.module.css +++ b/components/Header/header.module.css @@ -1,5 +1,4 @@ .header { - position: relative; font-family: var(--typography-Body-Regular-fontFamily); color: var(--Base-Text-High-contrast); z-index: 10; diff --git a/stores/main-menu.ts b/stores/main-menu.ts index cbd6f1004..fe9fc50f3 100644 --- a/stores/main-menu.ts +++ b/stores/main-menu.ts @@ -1,15 +1,23 @@ import { create } from "zustand" +// TODO: When MyPagesMobileMenu is removed, also remove the +// isMyPagesMobileMenuOpen state and toggleMyPagesMobileMenu function interface DropdownState { isHamburgerMenuOpen: boolean isMyPagesMobileMenuOpen: boolean + isMyPagesMenuOpen: boolean + isLanguageSwitcherOpen: boolean toggleHamburgerMenu: () => void toggleMyPagesMobileMenu: () => void + toggleMyPagesMenu: () => void + toggleLanguageSwitcher: () => void } const useDropdownStore = create((set) => ({ isHamburgerMenuOpen: false, isMyPagesMobileMenuOpen: false, + isMyPagesMenuOpen: false, + isLanguageSwitcherOpen: false, toggleHamburgerMenu: () => set((state) => { // Close the other dropdown if it's open @@ -26,6 +34,22 @@ const useDropdownStore = create((set) => ({ } return { isMyPagesMobileMenuOpen: !state.isMyPagesMobileMenuOpen } }), + toggleMyPagesMenu: () => + set(({ isLanguageSwitcherOpen, isMyPagesMenuOpen }) => { + // Close the other dropdown if it's open + if (!isMyPagesMenuOpen && isLanguageSwitcherOpen) { + set({ isLanguageSwitcherOpen: false }) + } + return { isMyPagesMenuOpen: !isMyPagesMenuOpen } + }), + toggleLanguageSwitcher: () => + set(({ isLanguageSwitcherOpen, isMyPagesMenuOpen }) => { + // Close the other dropdown if it's open + if (!isLanguageSwitcherOpen && isMyPagesMenuOpen) { + set({ isMyPagesMenuOpen: false }) + } + return { isLanguageSwitcherOpen: !isLanguageSwitcherOpen } + }), })) export default useDropdownStore From a2e2cf575e960196ae2b716d15c33d463fd2e066 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Wed, 21 Aug 2024 14:38:29 +0200 Subject: [PATCH 09/27] feat(SW-184): implementing mobile design --- app/globals.css | 1 + .../Header/HeaderLink/headerLink.module.css | 6 + components/Header/HeaderLink/index.tsx | 13 +++ .../MainMenuButton/menuButton.module.css | 1 + .../Header/MainMenu/MobileMenu/index.tsx | 65 +++++++++++ .../MainMenu/MobileMenu/mobileMenu.module.css | 109 ++++++++++++++++++ .../Header/MainMenu/MyPagesMenu/index.tsx | 14 ++- .../MyPagesMenu/myPagesMenu.module.css | 24 +++- .../NavigationMenuItem/index.tsx | 29 +++-- .../navigationMenuItem.module.css | 7 ++ .../NavigationMenuItem/variants.ts | 15 +++ .../Header/MainMenu/NavigationMenu/index.tsx | 12 +- .../NavigationMenu/navigationMenu.module.css | 20 +++- .../MainMenu/NavigationMenu/variants.ts | 15 +++ components/Header/MainMenu/index.tsx | 19 ++- .../Header/MainMenu/mainMenu.module.css | 33 +++++- components/Header/TopMenu/Search/index.tsx | 6 +- .../Header/TopMenu/Search/search.module.css | 11 ++ .../Header/TopMenu/TopMenuButton/index.tsx | 14 --- .../TopMenuButton/topMenuButton.module.css | 9 -- components/Header/TopMenu/index.tsx | 29 +++-- components/Header/TopMenu/topMenu.module.css | 7 ++ components/Header/index.tsx | 12 +- components/Icons/ChevronRight.tsx | 41 +++---- components/Icons/Search.tsx | 2 +- components/Icons/Service.tsx | 36 ++++++ components/Icons/get-icon-by-icon-name.ts | 3 + components/Icons/index.tsx | 1 + .../TopMenu => }/LanguageSwitcher/index.tsx | 11 +- .../languageSwitcher.module.css | 16 ++- i18n/dictionaries/da.json | 2 + i18n/dictionaries/de.json | 2 + i18n/dictionaries/en.json | 2 + i18n/dictionaries/fi.json | 2 + i18n/dictionaries/no.json | 2 + i18n/dictionaries/sv.json | 2 + stores/main-menu.ts | 8 +- types/components/header/headerLink.ts | 3 + types/components/header/mainMenu.ts | 5 + types/components/header/mobileMenu.ts | 8 ++ types/components/header/navigationMenu.ts | 7 +- types/components/header/navigationMenuItem.ts | 7 +- types/components/header/topMenu.ts | 5 + types/components/icon.ts | 1 + 44 files changed, 526 insertions(+), 111 deletions(-) create mode 100644 components/Header/HeaderLink/headerLink.module.css create mode 100644 components/Header/HeaderLink/index.tsx create mode 100644 components/Header/MainMenu/MobileMenu/index.tsx create mode 100644 components/Header/MainMenu/MobileMenu/mobileMenu.module.css create mode 100644 components/Header/MainMenu/NavigationMenu/NavigationMenuItem/variants.ts create mode 100644 components/Header/MainMenu/NavigationMenu/variants.ts delete mode 100644 components/Header/TopMenu/TopMenuButton/index.tsx delete mode 100644 components/Header/TopMenu/TopMenuButton/topMenuButton.module.css create mode 100644 components/Icons/Service.tsx rename components/{Header/TopMenu => }/LanguageSwitcher/index.tsx (93%) rename components/{Header/TopMenu => }/LanguageSwitcher/languageSwitcher.module.css (78%) create mode 100644 types/components/header/headerLink.ts create mode 100644 types/components/header/mainMenu.ts create mode 100644 types/components/header/mobileMenu.ts create mode 100644 types/components/header/topMenu.ts diff --git a/app/globals.css b/app/globals.css index 58a807453..0c84c08ec 100644 --- a/app/globals.css +++ b/app/globals.css @@ -104,6 +104,7 @@ --max-width-text-block: 49.5rem; --mobile-site-header-height: 70.047px; --max-width-navigation: 89.5rem; + --main-menu-mobile-height: 75px; } * { diff --git a/components/Header/HeaderLink/headerLink.module.css b/components/Header/HeaderLink/headerLink.module.css new file mode 100644 index 000000000..7049d4857 --- /dev/null +++ b/components/Header/HeaderLink/headerLink.module.css @@ -0,0 +1,6 @@ +.topLink { + display: flex; + align-items: center; + gap: var(--Spacing-x1); + font-size: var(--typography-Caption-Regular-fontSize); +} diff --git a/components/Header/HeaderLink/index.tsx b/components/Header/HeaderLink/index.tsx new file mode 100644 index 000000000..9b2195ebb --- /dev/null +++ b/components/Header/HeaderLink/index.tsx @@ -0,0 +1,13 @@ +import Link from "@/components/TempDesignSystem/Link" + +import styles from "./headerLink.module.css" + +import { HeaderLinkProps } from "@/types/components/header/headerLink" + +export default function HeaderLink({ children, ...props }: HeaderLinkProps) { + return ( + + {children} + + ) +} diff --git a/components/Header/MainMenu/MainMenuButton/menuButton.module.css b/components/Header/MainMenu/MainMenuButton/menuButton.module.css index 9746f1808..1d999b3ad 100644 --- a/components/Header/MainMenu/MainMenuButton/menuButton.module.css +++ b/components/Header/MainMenu/MainMenuButton/menuButton.module.css @@ -2,6 +2,7 @@ display: flex; gap: var(--Spacing-x1); align-items: center; + width: 100%; background-color: transparent; color: var(--Base-Text-High-contrast); border-width: 0; diff --git a/components/Header/MainMenu/MobileMenu/index.tsx b/components/Header/MainMenu/MobileMenu/index.tsx new file mode 100644 index 000000000..616cd10bb --- /dev/null +++ b/components/Header/MainMenu/MobileMenu/index.tsx @@ -0,0 +1,65 @@ +"use client" + +import { Dialog, Modal } from "react-aria-components" +import { useIntl } from "react-intl" + +import useDropdownStore from "@/stores/main-menu" + +import { GiftIcon, SearchIcon, ServiceIcon } from "@/components/Icons" +import LanguageSwitcher from "@/components/LanguageSwitcher" + +import HeaderLink from "../../HeaderLink" +import NavigationMenu from "../NavigationMenu" + +import styles from "./mobileMenu.module.css" + +import { MobileMenuProps } from "@/types/components/header/mobileMenu" + +export default function MobileMenu({ + mainNavigation, + languageUrls, +}: MobileMenuProps) { + const intl = useIntl() + const { isHamburgerMenuOpen, toggleHamburgerMenu } = useDropdownStore() + + return ( + <> + + + + +
    + + + {intl.formatMessage({ id: "Find booking" })} + + + + {intl.formatMessage({ id: "Join Scandic Friends" })} + + + + {intl.formatMessage({ id: "Customer service" })} + + +
    +
    +
    + + ) +} diff --git a/components/Header/MainMenu/MobileMenu/mobileMenu.module.css b/components/Header/MainMenu/MobileMenu/mobileMenu.module.css new file mode 100644 index 000000000..a36886e21 --- /dev/null +++ b/components/Header/MainMenu/MobileMenu/mobileMenu.module.css @@ -0,0 +1,109 @@ +@keyframes slide-in { + from { + right: -100vw; + } + + to { + right: 0; + } +} + +.hamburger { + background-color: transparent; + border: none; + cursor: pointer; + justify-self: flex-start; + padding: 11px 8px 16px; + user-select: none; +} + +.bar, +.bar::after, +.bar::before { + background: var(--Base-Text-High-contrast); + border-radius: 2.3px; + display: inline-block; + height: 3px; + position: relative; + transition: all 0.2s; + width: 32px; +} + +.bar::after, +.bar::before { + content: ""; + left: 0; + position: absolute; + transform-origin: 2.286px center; +} + +.bar::after { + top: -8px; +} + +.bar::before { + top: 8px; +} + +.isExpanded .bar { + background: transparent; +} + +.isExpanded .bar::after, +.isExpanded .bar::before { + top: 0; + transform-origin: 50% 50%; + width: 32px; +} + +.isExpanded .bar::after { + transform: rotate(-45deg); +} + +.isExpanded .bar::before { + transform: rotate(45deg); +} + +@media screen and (min-width: 768px) { + .hamburger { + display: none; + } +} + +.overlay { + position: absolute; + top: var(--main-menu-mobile-height); + bottom: 0; + left: 0; + right: 0; +} + +.modal { + position: fixed; + right: auto; + top: var(--main-menu-mobile-height); + bottom: 0; + width: 100%; + background-color: var(--Base-Surface-Primary-light-Normal); +} + +.modal[data-entering] { + animation: slide-in 0.3s; +} +.modal[data-exiting] { + animation: slide-in 0.3s reverse; +} + +.dialog { + height: 100%; + overflow-y: auto; + display: grid; + align-content: space-between; +} + +.footer { + background-color: var(--Base-Surface-Subtle-Normal); + padding: var(--Spacing-x4) var(--Spacing-x2); + display: grid; + gap: var(--Spacing-x2); +} diff --git a/components/Header/MainMenu/MyPagesMenu/index.tsx b/components/Header/MainMenu/MyPagesMenu/index.tsx index 6407cf382..42cd370ab 100644 --- a/components/Header/MainMenu/MyPagesMenu/index.tsx +++ b/components/Header/MainMenu/MyPagesMenu/index.tsx @@ -35,7 +35,9 @@ export default function MyPagesMenu({ navigation, user }: MyPagesMenuProps) {
    - {intl.formatMessage({ id: "Hi" })} {user.firstName}! + + {intl.formatMessage({ id: "Hi" })} {user.firstName}! +
    ) : ( - + - {intl.formatMessage({ id: "Log in/Join" })} + + {intl.formatMessage({ id: "Log in/Join" })} + ) } diff --git a/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css b/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css index 459fb6d9f..8d6b2fe1c 100644 --- a/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css +++ b/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css @@ -2,11 +2,8 @@ position: relative; } -.button { - font-weight: 600; -} - .chevron { + display: none; transition: transform 0.2s; } @@ -14,6 +11,12 @@ transform: rotate(180deg); } +.userName { + display: none; + font-weight: 600; + color: var(--Base-Text-High-contrast); +} + .dropdown { position: absolute; top: 46px; @@ -80,6 +83,15 @@ opacity: 0; } -.link:hover .arrow { - opacity: 1; +.loginLink { + display: flex; + align-items: center; + gap: var(--Spacing-x1); +} + +@media screen and (min-width: 768px) { + .userName, + .chevron { + display: initial; + } } diff --git a/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/index.tsx b/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/index.tsx index 6d00244ff..4efc44086 100644 --- a/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/index.tsx +++ b/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/index.tsx @@ -2,33 +2,46 @@ import { useState } from "react" -import { ChevronDownIcon } from "@/components/Icons" +import { ChevronDownIcon, ChevronRightIcon } from "@/components/Icons" import Link from "@/components/TempDesignSystem/Link" import MainMenuButton from "../../MainMenuButton" +import { navigationMenuItemVariants } from "./variants" import styles from "./navigationMenuItem.module.css" import { NavigationMenuItemProps } from "@/types/components/header/navigationMenuItem" -export default function MenuItem({ item }: NavigationMenuItemProps) { +export default function MenuItem({ item, variant }: NavigationMenuItemProps) { const { children, title, href, seeAllLinkText, infoCard } = item const [isExpanded, setIsExpanded] = useState(false) + const isMobile = variant === "mobile" function handleButtonClick() { setIsExpanded((prev) => !prev) } return children?.length ? ( - + {title} - + {isMobile ? ( + + ) : ( + + )} ) : ( - + {title} ) diff --git a/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/navigationMenuItem.module.css b/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/navigationMenuItem.module.css index c743f0649..b509f4e73 100644 --- a/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/navigationMenuItem.module.css +++ b/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/navigationMenuItem.module.css @@ -1,3 +1,10 @@ +.navigationMenuItem.mobile { + display: flex; + justify-content: space-between; + padding: var(--Spacing-x2) 0; + font-size: var(--typography-Subtitle-1-Mobile-fontSize); +} + .chevron { transition: transform 0.2s; } diff --git a/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/variants.ts b/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/variants.ts new file mode 100644 index 000000000..06eead455 --- /dev/null +++ b/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/variants.ts @@ -0,0 +1,15 @@ +import { cva } from "class-variance-authority" + +import styles from "./navigationMenuItem.module.css" + +export const navigationMenuItemVariants = cva(styles.navigationMenuItem, { + variants: { + variant: { + default: styles.default, + mobile: styles.mobile, + }, + }, + defaultVariants: { + variant: "default", + }, +}) diff --git a/components/Header/MainMenu/NavigationMenu/index.tsx b/components/Header/MainMenu/NavigationMenu/index.tsx index f5168ab21..08534f56e 100644 --- a/components/Header/MainMenu/NavigationMenu/index.tsx +++ b/components/Header/MainMenu/NavigationMenu/index.tsx @@ -1,15 +1,19 @@ import NavigationMenuItem from "./NavigationMenuItem" +import { navigationMenuVariants } from "./variants" import styles from "./navigationMenu.module.css" import { NavigationMenuProps } from "@/types/components/header/navigationMenu" -export default function NavigationMenu({ items }: NavigationMenuProps) { +export default function NavigationMenu({ + items, + variant, +}: NavigationMenuProps) { return ( -
      +
        {items.map((item) => ( -
      • - +
      • +
      • ))}
      diff --git a/components/Header/MainMenu/NavigationMenu/navigationMenu.module.css b/components/Header/MainMenu/NavigationMenu/navigationMenu.module.css index 066aca610..6c554acde 100644 --- a/components/Header/MainMenu/NavigationMenu/navigationMenu.module.css +++ b/components/Header/MainMenu/NavigationMenu/navigationMenu.module.css @@ -1,8 +1,26 @@ .navigationMenu { list-style: none; margin: 0; - display: flex; justify-content: space-between; align-items: center; gap: var(--Spacing-x4); + display: none; +} + +.navigationMenu.mobile { + display: grid; + width: 100%; + gap: 0; + justify-content: stretch; + padding: var(--Spacing-x-one-and-half) var(--Spacing-x2) var(--Spacing-x2); +} + +.navigationMenu.mobile .item { + border-bottom: 1px solid var(--Base-Border-Subtle); +} + +@media screen and (min-width: 768px) { + .navigationMenu.default { + display: flex; + } } diff --git a/components/Header/MainMenu/NavigationMenu/variants.ts b/components/Header/MainMenu/NavigationMenu/variants.ts new file mode 100644 index 000000000..b1c37d49b --- /dev/null +++ b/components/Header/MainMenu/NavigationMenu/variants.ts @@ -0,0 +1,15 @@ +import { cva } from "class-variance-authority" + +import styles from "./navigationMenu.module.css" + +export const navigationMenuVariants = cva(styles.navigationMenu, { + variants: { + variant: { + default: styles.default, + mobile: styles.mobile, + }, + }, + defaultVariants: { + variant: "default", + }, +}) diff --git a/components/Header/MainMenu/index.tsx b/components/Header/MainMenu/index.tsx index 288400a4b..db7d448d2 100644 --- a/components/Header/MainMenu/index.tsx +++ b/components/Header/MainMenu/index.tsx @@ -6,12 +6,15 @@ import Image from "@/components/Image" import { getIntl } from "@/i18n" import { navigationMenuItems } from "../tempHeaderData" +import MobileMenu from "./MobileMenu" import MyPagesMenu from "./MyPagesMenu" import NavigationMenu from "./NavigationMenu" import styles from "./mainMenu.module.css" -export default async function MainMenu() { +import { MainMenuProps } from "@/types/components/header/mainMenu" + +export default async function MainMenu({ languageUrls }: MainMenuProps) { const intl = await getIntl() const myPagesNavigation = await serverClient().contentstack.myPages.navigation.get() @@ -28,13 +31,19 @@ export default async function MainMenu() { data-js="scandiclogoimg" data-nosvgsrc="/_static/img/scandic-logotype.png" itemProp="logo" - height={24} + height={22} src="/_static/img/scandic-logotype.svg" - width={113} + width={103} /> - - +
      + + + +
) diff --git a/components/Header/MainMenu/mainMenu.module.css b/components/Header/MainMenu/mainMenu.module.css index ddcb33627..26c13c8e3 100644 --- a/components/Header/MainMenu/mainMenu.module.css +++ b/components/Header/MainMenu/mainMenu.module.css @@ -7,8 +7,35 @@ .nav { max-width: var(--max-width-navigation); margin: 0 auto; - display: flex; - justify-content: space-between; + display: grid; + grid-template-columns: max-content 1fr; align-items: center; - gap: var(--Spacing-x3); + gap: var(--Spacing-x2); +} + +.menus { + display: flex; + justify-self: end; + align-items: center; + gap: var(--Spacing-x2); +} + +.logoLink { + display: inline-flex; + width: auto; +} + +.logo { + width: 6.4375rem; +} + +@media screen and (min-width: 768px) { + .nav { + display: flex; + justify-content: space-between; + gap: var(--Spacing-x3); + } + .menus { + display: contents; + } } diff --git a/components/Header/TopMenu/Search/index.tsx b/components/Header/TopMenu/Search/index.tsx index f0b3316a7..abd840bc0 100644 --- a/components/Header/TopMenu/Search/index.tsx +++ b/components/Header/TopMenu/Search/index.tsx @@ -4,15 +4,15 @@ import { useIntl } from "react-intl" import { SearchIcon } from "@/components/Icons" -import TopMenuButton from "../TopMenuButton" +import styles from "./search.module.css" export default function Search() { const intl = useIntl() return ( - + ) } diff --git a/components/Header/TopMenu/Search/search.module.css b/components/Header/TopMenu/Search/search.module.css index 004e01d19..45d5bb1b1 100644 --- a/components/Header/TopMenu/Search/search.module.css +++ b/components/Header/TopMenu/Search/search.module.css @@ -1,9 +1,20 @@ .button { background-color: transparent; color: var(--Base-Text-High-contrast); + font-family: var(--typography-Caption-Regular-fontFamily); + font-size: var(--typography-Caption-Regular-fontSize); border-width: 0; + padding: 0; cursor: pointer; display: flex; gap: var(--Spacing-x1); align-items: center; + width: 100%; +} + +@media screen and (min-width: 768px) { + .button { + font-size: var(--typography-Body-Bold-fontSize); + font-family: var(--typography-Body-Bold-fontFamily); + } } diff --git a/components/Header/TopMenu/TopMenuButton/index.tsx b/components/Header/TopMenu/TopMenuButton/index.tsx deleted file mode 100644 index 4e42b05e0..000000000 --- a/components/Header/TopMenu/TopMenuButton/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import styles from "./topMenuButton.module.css" - -import { TopMenuButtonProps } from "@/types/components/header/topMenuButton" - -export default function TopMenuButton({ - children, - ...props -}: TopMenuButtonProps) { - return ( - - ) -} diff --git a/components/Header/TopMenu/TopMenuButton/topMenuButton.module.css b/components/Header/TopMenu/TopMenuButton/topMenuButton.module.css deleted file mode 100644 index 004e01d19..000000000 --- a/components/Header/TopMenu/TopMenuButton/topMenuButton.module.css +++ /dev/null @@ -1,9 +0,0 @@ -.button { - background-color: transparent; - color: var(--Base-Text-High-contrast); - border-width: 0; - cursor: pointer; - display: flex; - gap: var(--Spacing-x1); - align-items: center; -} diff --git a/components/Header/TopMenu/index.tsx b/components/Header/TopMenu/index.tsx index f1555a5bf..b47a6d36d 100644 --- a/components/Header/TopMenu/index.tsx +++ b/components/Header/TopMenu/index.tsx @@ -1,32 +1,29 @@ -import { serverClient } from "@/lib/trpc/server" - -import { GiftIcon } from "@/components/Icons" -import Link from "@/components/TempDesignSystem/Link" +import { GiftIcon, SearchIcon } from "@/components/Icons" +import LanguageSwitcher from "@/components/LanguageSwitcher" import { getIntl } from "@/i18n" -import LanguageSwitcher from "./LanguageSwitcher" -import Search from "./Search" +import HeaderLink from "../HeaderLink" import styles from "./topMenu.module.css" -export default async function TopMenu() { - const intl = await getIntl() - const languages = await serverClient().contentstack.languageSwitcher.get() +import { TopMenuProps } from "@/types/components/header/topMenu" - if (!languages) { - return null - } +export default async function TopMenu({ languageUrls }: TopMenuProps) { + const intl = await getIntl() return (
- + {intl.formatMessage({ id: "Join Scandic Friends" })} - +
- - + + + + {intl.formatMessage({ id: "Find booking" })} +
diff --git a/components/Header/TopMenu/topMenu.module.css b/components/Header/TopMenu/topMenu.module.css index e78f57d92..17a739989 100644 --- a/components/Header/TopMenu/topMenu.module.css +++ b/components/Header/TopMenu/topMenu.module.css @@ -1,4 +1,5 @@ .topMenu { + display: none; background-color: var(--Base-Surface-Subtle-Normal); padding: var(--Spacing-x2) var(--Spacing-x-one-and-half); border-bottom: 1px solid var(--Base-Border-Subtle); @@ -17,3 +18,9 @@ gap: var(--Spacing-x2); align-items: center; } + +@media screen and (min-width: 768px) { + .topMenu { + display: block; + } +} diff --git a/components/Header/index.tsx b/components/Header/index.tsx index d6693a52b..d4e01de0f 100644 --- a/components/Header/index.tsx +++ b/components/Header/index.tsx @@ -1,13 +1,21 @@ +import { serverClient } from "@/lib/trpc/server" + import MainMenu from "./MainMenu" import TopMenu from "./TopMenu" import styles from "./header.module.css" export default async function Header() { + const languages = await serverClient().contentstack.languageSwitcher.get() + + if (!languages) { + return null + } + return (
- - + +
) } diff --git a/components/Icons/ChevronRight.tsx b/components/Icons/ChevronRight.tsx index 1b66b90a3..9930ac095 100644 --- a/components/Icons/ChevronRight.tsx +++ b/components/Icons/ChevronRight.tsx @@ -11,32 +11,29 @@ export default function ChevronRightIcon({ return ( - - - - - - - + + + + + ) diff --git a/components/Icons/Search.tsx b/components/Icons/Search.tsx index 3e91f1f5c..aa9f15e52 100644 --- a/components/Icons/Search.tsx +++ b/components/Icons/Search.tsx @@ -2,7 +2,7 @@ import { iconVariants } from "./variants" import type { IconProps } from "@/types/components/icon" -export default function GiftIcon({ className, color, ...props }: IconProps) { +export default function SearchIcon({ className, color, ...props }: IconProps) { const classNames = iconVariants({ className, color }) return ( + + + + + + + + ) +} diff --git a/components/Icons/get-icon-by-icon-name.ts b/components/Icons/get-icon-by-icon-name.ts index 3c7a37d7e..79feb4b0c 100644 --- a/components/Icons/get-icon-by-icon-name.ts +++ b/components/Icons/get-icon-by-icon-name.ts @@ -41,6 +41,7 @@ import { RestaurantIcon, SaunaIcon, SearchIcon, + ServiceIcon, TshirtWashIcon, WarningTriangle, WifiIcon, @@ -128,6 +129,8 @@ export function getIconByIconName(icon?: IconName): FC | null { return SaunaIcon case IconName.Search: return SearchIcon + case IconName.Service: + return ServiceIcon case IconName.Tripadvisor: return TripAdvisorIcon case IconName.TshirtWash: diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index 0dbb3b628..31abcd5ca 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -39,6 +39,7 @@ export { default as RestaurantIcon } from "./Restaurant" export { default as SaunaIcon } from "./Sauna" export { default as ScandicLogoIcon } from "./ScandicLogo" export { default as SearchIcon } from "./Search" +export { default as ServiceIcon } from "./Service" export { default as TshirtWashIcon } from "./TshirtWash" export { default as WarningTriangle } from "./WarningTriangle" export { default as WifiIcon } from "./Wifi" diff --git a/components/Header/TopMenu/LanguageSwitcher/index.tsx b/components/LanguageSwitcher/index.tsx similarity index 93% rename from components/Header/TopMenu/LanguageSwitcher/index.tsx rename to components/LanguageSwitcher/index.tsx index 638a58dba..e5ab88c5e 100644 --- a/components/Header/TopMenu/LanguageSwitcher/index.tsx +++ b/components/LanguageSwitcher/index.tsx @@ -8,8 +8,6 @@ import useDropdownStore from "@/stores/main-menu" import { CheckIcon, ChevronDownIcon, GlobeIcon } from "@/components/Icons" import useLang from "@/hooks/useLang" -import TopMenuButton from "../TopMenuButton" - import styles from "./languageSwitcher.module.css" import { LanguageSwitcherProps } from "@/types/components/current/languageSwitcher" @@ -22,7 +20,11 @@ export default function LanguageSwitcher({ urls }: LanguageSwitcherProps) { return (
- + +
diff --git a/components/Header/TopMenu/LanguageSwitcher/languageSwitcher.module.css b/components/LanguageSwitcher/languageSwitcher.module.css similarity index 78% rename from components/Header/TopMenu/LanguageSwitcher/languageSwitcher.module.css rename to components/LanguageSwitcher/languageSwitcher.module.css index ff2301963..1eeeab77b 100644 --- a/components/Header/TopMenu/LanguageSwitcher/languageSwitcher.module.css +++ b/components/LanguageSwitcher/languageSwitcher.module.css @@ -5,14 +5,20 @@ .button { background-color: transparent; color: var(--Base-Text-High-contrast); + font-family: var(--typography-Caption-Regular-fontFamily); + font-size: var(--typography-Caption-Regular-fontSize); border-width: 0; + padding: 0; cursor: pointer; - display: flex; + display: grid; + grid-template-columns: repeat(2, max-content) 1fr; gap: var(--Spacing-x1); align-items: center; + width: 100%; } .chevron { + justify-self: end; transition: transform 0.2s; } @@ -78,3 +84,11 @@ .link:hover { font-weight: 600; } + +@media screen and (min-width: 768px) { + .button { + grid-template-columns: repeat(3, max-content); + font-size: var(--typography-Body-Bold-fontSize); + font-family: var(--typography-Body-Bold-fontFamily); + } +} diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index f4553b9ce..43b927104 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -44,6 +44,7 @@ "Country code": "Landekode", "Credit card deleted successfully": "Kreditkort blev slettet", "Current password": "Nuværende kodeord", + "Customer service": "Kundeservice", "Date of Birth": "Fødselsdato", "Day": "Dag", "Description": "Beskrivelse", @@ -94,6 +95,7 @@ "Members": "Medlemmer", "Membership cards": "Medlemskort", "Membership ID": "Medlems-id", + "Menu": "Menu", "Modify": "Ændre", "Month": "Måned", "My communication preferences": "Mine kommunikationspræferencer", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index b80e56960..8814ff721 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -43,6 +43,7 @@ "Country code": "Landesvorwahl", "Credit card deleted successfully": "Kreditkarte erfolgreich gelöscht", "Current password": "Aktuelles Passwort", + "Customer service": "Kundendienst", "Date of Birth": "Geburtsdatum", "Day": "Tag", "Description": "Beschreibung", @@ -92,6 +93,7 @@ "Members": "Mitglieder", "Membership cards": "Mitgliedskarten", "Membership ID": "Mitglieds-ID", + "Menu": "Menu", "Modify": "Ändern", "Month": "Monat", "My communication preferences": "Meine Kommunikationseinstellungen", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index f86bab933..ea205dd05 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -44,6 +44,7 @@ "Country code": "Country code", "Credit card deleted successfully": "Credit card deleted successfully", "Current password": "Current password", + "Customer service": "Customer service", "Date of Birth": "Date of Birth", "Day": "Day", "Description": "Description", @@ -99,6 +100,7 @@ "Members": "Members", "Membership cards": "Membership cards", "Membership ID": "Membership ID", + "Menu": "Menu", "Modify": "Modify", "Month": "Month", "My communication preferences": "My communication preferences", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index a3dd9cff8..5781dea73 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -44,6 +44,7 @@ "Country code": "Maatunnus", "Credit card deleted successfully": "Luottokortti poistettu onnistuneesti", "Current password": "Nykyinen salasana", + "Customer service": "Asiakaspalvelu", "Date of Birth": "Syntymäaika", "Day": "Päivä", "Description": "Kuvaus", @@ -93,6 +94,7 @@ "Members": "Jäsenet", "Membership cards": "Jäsenkortit", "Membership ID": "Jäsentunnus", + "Menu": "Menu", "Modify": "Muokkaa", "Month": "Kuukausi", "My communication preferences": "Viestintämieltymykseni", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 8dbf2cb2c..0f38a89b0 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -42,6 +42,7 @@ "Could not find requested resource": "Kunne ikke finne den forespurte ressursen", "Country": "Land", "Country code": "Landskode", + "Customer service": "Kundeservice", "Credit card deleted successfully": "Kredittkort slettet", "Current password": "Nåværende passord", "Date of Birth": "Fødselsdato", @@ -94,6 +95,7 @@ "Members": "Medlemmer", "Membership cards": "Medlemskort", "Membership ID": "Medlems-ID", + "Menu": "Menu", "Modify": "Endre", "Month": "Måned", "My communication preferences": "Mine kommunikasjonspreferanser", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 25eb5acf5..53f38241e 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -44,6 +44,7 @@ "Country code": "Landskod", "Credit card deleted successfully": "Kreditkort har tagits bort", "Current password": "Nuvarande lösenord", + "Customer service": "Kundservice", "Date of Birth": "Födelsedatum", "Day": "Dag", "Description": "Beskrivning", @@ -96,6 +97,7 @@ "Members": "Medlemmar", "Membership cards": "Medlemskort", "Membership ID": "Medlems-ID", + "Menu": "Meny", "Modify": "Ändra", "Month": "Månad", "My communication preferences": "Mina kommunikationspreferenser", diff --git a/stores/main-menu.ts b/stores/main-menu.ts index fe9fc50f3..008cce9b7 100644 --- a/stores/main-menu.ts +++ b/stores/main-menu.ts @@ -19,13 +19,7 @@ const useDropdownStore = create((set) => ({ isMyPagesMenuOpen: false, isLanguageSwitcherOpen: false, toggleHamburgerMenu: () => - set((state) => { - // Close the other dropdown if it's open - if (!state.isHamburgerMenuOpen && state.isMyPagesMobileMenuOpen) { - set({ isMyPagesMobileMenuOpen: false }) - } - return { isHamburgerMenuOpen: !state.isHamburgerMenuOpen } - }), + set((state) => ({ isHamburgerMenuOpen: !state.isHamburgerMenuOpen })), toggleMyPagesMobileMenu: () => set((state) => { // Close the other dropdown if it's open diff --git a/types/components/header/headerLink.ts b/types/components/header/headerLink.ts new file mode 100644 index 000000000..671ab6d0e --- /dev/null +++ b/types/components/header/headerLink.ts @@ -0,0 +1,3 @@ +import { LinkProps } from "@/components/TempDesignSystem/Link/link" + +export interface HeaderLinkProps extends React.PropsWithChildren {} diff --git a/types/components/header/mainMenu.ts b/types/components/header/mainMenu.ts new file mode 100644 index 000000000..a867be31b --- /dev/null +++ b/types/components/header/mainMenu.ts @@ -0,0 +1,5 @@ +import { LanguageSwitcherData } from "@/types/requests/languageSwitcher" + +export interface MainMenuProps { + languageUrls: LanguageSwitcherData +} diff --git a/types/components/header/mobileMenu.ts b/types/components/header/mobileMenu.ts new file mode 100644 index 000000000..79f11a563 --- /dev/null +++ b/types/components/header/mobileMenu.ts @@ -0,0 +1,8 @@ +import { MainNavigationItem } from "./mainNavigationItem" + +import { LanguageSwitcherData } from "@/types/requests/languageSwitcher" + +export interface MobileMenuProps { + languageUrls: LanguageSwitcherData + mainNavigation: MainNavigationItem[] +} diff --git a/types/components/header/navigationMenu.ts b/types/components/header/navigationMenu.ts index f429d557d..11642b11f 100644 --- a/types/components/header/navigationMenu.ts +++ b/types/components/header/navigationMenu.ts @@ -1,5 +1,10 @@ +import { VariantProps } from "class-variance-authority" + +import { navigationMenuVariants } from "@/components/Header/MainMenu/NavigationMenu/variants" + import { MainNavigationItem } from "@/types/components/header/mainNavigationItem" -export interface NavigationMenuProps { +export interface NavigationMenuProps + extends VariantProps { items: MainNavigationItem[] } diff --git a/types/components/header/navigationMenuItem.ts b/types/components/header/navigationMenuItem.ts index d53c2313a..942b706c8 100644 --- a/types/components/header/navigationMenuItem.ts +++ b/types/components/header/navigationMenuItem.ts @@ -1,5 +1,10 @@ +import { VariantProps } from "class-variance-authority" + +import { navigationMenuItemVariants } from "@/components/Header/MainMenu/NavigationMenu/NavigationMenuItem/variants" + import { MainNavigationItem } from "@/types/components/header/mainNavigationItem" -export interface NavigationMenuItemProps { +export interface NavigationMenuItemProps + extends VariantProps { item: MainNavigationItem } diff --git a/types/components/header/topMenu.ts b/types/components/header/topMenu.ts new file mode 100644 index 000000000..4e20a6e7f --- /dev/null +++ b/types/components/header/topMenu.ts @@ -0,0 +1,5 @@ +import { LanguageSwitcherData } from "@/types/requests/languageSwitcher" + +export interface TopMenuProps { + languageUrls: LanguageSwitcherData +} diff --git a/types/components/icon.ts b/types/components/icon.ts index ed0d3da3b..0e705b7e6 100644 --- a/types/components/icon.ts +++ b/types/components/icon.ts @@ -46,6 +46,7 @@ export enum IconName { Restaurant = "Restaurant", Sauna = "Sauna", Search = "Search", + Service = "Service", Tripadvisor = "Tripadvisor", TshirtWash = "TshirtWash", Wifi = "Wifi", From 7ef7b4a54431181bdc28a1b2ad6e1974c169d2aa Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Thu, 22 Aug 2024 14:21:24 +0200 Subject: [PATCH 10/27] feat(SW-184): language switcher mobile/desktop functionality --- .../Header/MainMenu/MobileMenu/index.tsx | 13 +- .../MainMenu/MobileMenu/mobileMenu.module.css | 3 +- components/Icons/Check.tsx | 22 ++-- components/Icons/ChevronLeft.tsx | 40 ++++++ components/Icons/get-icon-by-icon-name.ts | 3 + components/Icons/index.tsx | 1 + components/LanguageSwitcher/index.tsx | 88 +++++++++---- .../languageSwitcher.module.css | 121 ++++++++++++++---- hooks/useHandleKeyPress.ts | 12 +- hooks/useHandleKeyUp.ts | 12 ++ hooks/useTrapFocus.ts | 82 ++++++++++++ stores/main-menu.ts | 27 +++- types/components/current/languageSwitcher.ts | 3 +- types/components/icon.ts | 1 + utils/tabbable.ts | 62 +++++++++ 15 files changed, 407 insertions(+), 83 deletions(-) create mode 100644 components/Icons/ChevronLeft.tsx create mode 100644 hooks/useHandleKeyUp.ts create mode 100644 hooks/useTrapFocus.ts create mode 100644 utils/tabbable.ts diff --git a/components/Header/MainMenu/MobileMenu/index.tsx b/components/Header/MainMenu/MobileMenu/index.tsx index 616cd10bb..d9e63fc5b 100644 --- a/components/Header/MainMenu/MobileMenu/index.tsx +++ b/components/Header/MainMenu/MobileMenu/index.tsx @@ -7,6 +7,7 @@ import useDropdownStore from "@/stores/main-menu" import { GiftIcon, SearchIcon, ServiceIcon } from "@/components/Icons" import LanguageSwitcher from "@/components/LanguageSwitcher" +import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import HeaderLink from "../../HeaderLink" import NavigationMenu from "../NavigationMenu" @@ -22,6 +23,12 @@ export default function MobileMenu({ const intl = useIntl() const { isHamburgerMenuOpen, toggleHamburgerMenu } = useDropdownStore() + useHandleKeyUp((event: KeyboardEvent) => { + if (event.key === "Escape" && isHamburgerMenuOpen) { + toggleHamburgerMenu() + } + }) + return ( <> - + - + - + diff --git a/components/Icons/ChevronLeft.tsx b/components/Icons/ChevronLeft.tsx new file mode 100644 index 000000000..eb14d07dd --- /dev/null +++ b/components/Icons/ChevronLeft.tsx @@ -0,0 +1,40 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function ChevronLeftIcon({ + className, + color, + ...props +}: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + + + + + + ) +} diff --git a/components/Icons/get-icon-by-icon-name.ts b/components/Icons/get-icon-by-icon-name.ts index 79feb4b0c..d7508b83a 100644 --- a/components/Icons/get-icon-by-icon-name.ts +++ b/components/Icons/get-icon-by-icon-name.ts @@ -15,6 +15,7 @@ import { CheckCircleIcon, CheckIcon, ChevronDownIcon, + ChevronLeftIcon, ChevronRightIcon, CloseIcon, CloseLarge, @@ -75,6 +76,8 @@ export function getIconByIconName(icon?: IconName): FC | null { return CheckCircleIcon case IconName.ChevronDown: return ChevronDownIcon + case IconName.ChevronLeft: + return ChevronLeftIcon case IconName.ChevronRight: return ChevronRightIcon case IconName.Close: diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index 31abcd5ca..9ed34a098 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -9,6 +9,7 @@ export { default as CellphoneIcon } from "./Cellphone" export { default as CheckIcon } from "./Check" export { default as CheckCircleIcon } from "./CheckCircle" export { default as ChevronDownIcon } from "./ChevronDown" +export { default as ChevronLeftIcon } from "./ChevronLeft" export { default as ChevronRightIcon } from "./ChevronRight" export { default as CloseIcon } from "./Close" export { default as CloseLarge } from "./CloseLarge" diff --git a/components/LanguageSwitcher/index.tsx b/components/LanguageSwitcher/index.tsx index e5ab88c5e..229920f01 100644 --- a/components/LanguageSwitcher/index.tsx +++ b/components/LanguageSwitcher/index.tsx @@ -1,28 +1,54 @@ "use client" import Link from "next/link" +import { useIntl } from "react-intl" import { Lang, languages } from "@/constants/languages" import useDropdownStore from "@/stores/main-menu" -import { CheckIcon, ChevronDownIcon, GlobeIcon } from "@/components/Icons" +import { + CheckIcon, + ChevronDownIcon, + ChevronLeftIcon, + GlobeIcon, +} from "@/components/Icons" +import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import useLang from "@/hooks/useLang" +import { useTrapFocus } from "@/hooks/useTrapFocus" + +import Subtitle from "../TempDesignSystem/Text/Subtitle" import styles from "./languageSwitcher.module.css" import { LanguageSwitcherProps } from "@/types/components/current/languageSwitcher" -export default function LanguageSwitcher({ urls }: LanguageSwitcherProps) { +export default function LanguageSwitcher({ + urls, + location = "header", +}: LanguageSwitcherProps) { + const intl = useIntl() + const languageSwitcherRef = useTrapFocus() const currentLanguage = useLang() const { toggleLanguageSwitcher, isLanguageSwitcherOpen } = useDropdownStore() const urlKeys = Object.keys(urls) as Lang[] + useHandleKeyUp((event: KeyboardEvent) => { + if (event.key === "Escape" && isLanguageSwitcherOpen) { + toggleLanguageSwitcher() + } + }) + return ( -
+
+
+ +
+ + {intl.formatMessage({ id: "Select your language" })} + +
    + {urlKeys.map((key) => { + const url = urls[key]?.url + const isActive = currentLanguage === key + if (url) { + return ( +
  • + + {languages[key]} + {isActive ? : null} + +
  • + ) + } + })} +
+
) diff --git a/components/LanguageSwitcher/languageSwitcher.module.css b/components/LanguageSwitcher/languageSwitcher.module.css index 1eeeab77b..9bdf4f188 100644 --- a/components/LanguageSwitcher/languageSwitcher.module.css +++ b/components/LanguageSwitcher/languageSwitcher.module.css @@ -1,5 +1,11 @@ -.languageSwitcher { - position: relative; +@keyframes slide-in { + from { + right: -100vw; + } + + to { + right: 0; + } } .button { @@ -27,33 +33,50 @@ } .dropdown { - position: absolute; - top: 2.25rem; - right: 0; + position: fixed; + top: var(--main-menu-mobile-height); + right: -100vw; + bottom: 0; + width: 100%; background-color: var(--Base-Surface-Primary-light-Normal); - padding: var(--Spacing-x2) var(--Spacing-x3); - border-radius: var(--Corner-radius-Large); - box-shadow: 0px 0px 14px 6px #0000001a; - display: none; - min-width: 12.5rem; - z-index: 1; -} - -/* Triangle above dropdown */ -.dropdown::before { - content: ""; - position: absolute; - top: -1.25rem; - right: 2.4rem; - transform: rotate(180deg); - border-width: 0.75rem; - border-style: solid; - border-color: var(--Base-Surface-Primary-light-Normal) transparent transparent - transparent; + transition: right 0.3s; } .dropdown.isExpanded { display: block; + right: 0; +} + +.backWrapper { + background-color: var(--Base-Surface-Secondary-light-Normal); + padding: var(--Spacing-x2); +} + +.backButton { + background-color: transparent; + border: none; + color: var(--Base-Text-High-contrast); + font-family: var(--typography-Subtitle-1-fontFamily); + font-weight: var(--typography-Subtitle-1-fontWeight); + font-size: var(--typography-Subtitle-1-Mobile-fontSize); + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + gap: var(--Spacing-x1); +} + +.languageWrapper { + display: grid; + gap: var(--Spacing-x3); + padding: var(--Spacing-x3) var(--Spacing-x2); +} + +.subtitle { + font-family: var(--typography-Subtitle-2-fontFamily); + font-size: var(--typography-Subtitle-2-Mobile-fontSize); + font-weight: var(--typography-Subtitle-2-fontWeight); + color: var(--Base-Text-High-contrast, #4d001b); } .list { @@ -73,22 +96,64 @@ justify-content: space-between; align-items: center; text-decoration: none; -} - -.link:hover { - background-color: var(--Base-Surface-Primary-light-Hover-alt); border-radius: var(--Corner-radius-Medium); } .link.active, .link:hover { + background-color: var(--Base-Surface-Primary-light-Hover-alt); font-weight: 600; } @media screen and (min-width: 768px) { + .languageSwitcher { + position: relative; + } + + .backWrapper { + display: none; + } + + .languageWrapper { + padding: var(--Spacing-x2) var(--Spacing-x3); + } + + .subtitle { + display: none; + } + + .dropdown { + position: absolute; + top: 2.25rem; + background-color: var(--Base-Surface-Primary-light-Normal); + border-radius: var(--Corner-radius-Large); + box-shadow: 0px 0px 14px 6px #0000001a; + display: none; + min-width: 12.5rem; + z-index: 1; + bottom: auto; + } + + /* Triangle above dropdown */ + .dropdown::before { + content: ""; + position: absolute; + top: -1.25rem; + right: 2.4rem; + transform: rotate(180deg); + border-width: 0.75rem; + border-style: solid; + border-color: var(--Base-Surface-Primary-light-Normal) transparent + transparent transparent; + } + .button { grid-template-columns: repeat(3, max-content); font-size: var(--typography-Body-Bold-fontSize); font-family: var(--typography-Body-Bold-fontFamily); } + + .link.active:not(:hover) { + background-color: transparent; + } } diff --git a/hooks/useHandleKeyPress.ts b/hooks/useHandleKeyPress.ts index d9fdc0ea2..b240650d1 100644 --- a/hooks/useHandleKeyPress.ts +++ b/hooks/useHandleKeyPress.ts @@ -1,11 +1,11 @@ "use client" -import { useEffect } from 'react'; +import { useEffect } from "react" export function useHandleKeyPress(callback: (event: KeyboardEvent) => void) { useEffect(() => { - window.addEventListener('keydown', callback); + window.addEventListener("keydown", callback) return () => { - window.removeEventListener('keydown', callback); - }; - }, [callback]); -} \ No newline at end of file + window.removeEventListener("keydown", callback) + } + }, [callback]) +} diff --git a/hooks/useHandleKeyUp.ts b/hooks/useHandleKeyUp.ts new file mode 100644 index 000000000..b44f82d5a --- /dev/null +++ b/hooks/useHandleKeyUp.ts @@ -0,0 +1,12 @@ +"use client" + +import { useEffect } from "react" + +export function useHandleKeyUp(callback: (event: KeyboardEvent) => void) { + useEffect(() => { + window.addEventListener("keyup", callback) + return () => { + window.removeEventListener("keyup", callback) + } + }, [callback]) +} diff --git a/hooks/useTrapFocus.ts b/hooks/useTrapFocus.ts new file mode 100644 index 000000000..7d2e214c7 --- /dev/null +++ b/hooks/useTrapFocus.ts @@ -0,0 +1,82 @@ +"use client" + +import { useCallback, useEffect, useRef, useState } from "react" + +import { useHandleKeyPress } from "@/hooks/useHandleKeyPress" +import findTabbableDescendants from "@/utils/tabbable" + +const TAB_KEY = "Tab" +const optionsDefault = { focusOnRender: true, returnFocus: true } +type OptionsType = { + focusOnRender?: boolean + returnFocus?: boolean +} +export function useTrapFocus(opts?: OptionsType) { + const options = opts ? { ...optionsDefault, ...opts } : optionsDefault + const ref = useRef(null) + const previouseFocusedElement = useRef( + document.activeElement as HTMLElement + ) + const [tabbableElements, setTabbableElements] = useState([]) + // Handle initial focus of the referenced element, and return focus to previously focused element on cleanup + // and find all the tabbable elements in the referenced element + + useEffect(() => { + const { current } = ref + if (current) { + const focusableChildNodes = findTabbableDescendants(current) + if (options.focusOnRender) { + current.focus() + } + + setTabbableElements(focusableChildNodes) + } + return () => { + const { current } = previouseFocusedElement + if (current instanceof HTMLElement && options.returnFocus) { + current.focus() + } + } + }, [options.focusOnRender, options.returnFocus, ref, setTabbableElements]) + + const handleUserKeyPress = useCallback( + (event: KeyboardEvent) => { + const { code, shiftKey } = event + const first = tabbableElements[0] + const last = tabbableElements[tabbableElements.length - 1] + const currentActiveElement = document.activeElement + // Scope current tabs to current root element + if (isWithinCurrentElementScope([...tabbableElements, ref.current])) { + if (code === TAB_KEY) { + if ( + currentActiveElement === first || + currentActiveElement === ref.current + ) { + // move focus to last element if shift+tab while currently focusing the first tabbable element + if (shiftKey) { + event.preventDefault() + last.focus() + } + } + if (currentActiveElement === last) { + // move focus back to first if tabbing while currently focusing the last tabbable element + if (!shiftKey) { + event.preventDefault() + first.focus() + } + } + } + } + }, + [ref, tabbableElements] + ) + useHandleKeyPress(handleUserKeyPress) + + return ref +} +function isWithinCurrentElementScope( + elementList: (HTMLInputElement | Element | null)[] +) { + const currentActiveElement = document.activeElement + return elementList.includes(currentActiveElement) +} diff --git a/stores/main-menu.ts b/stores/main-menu.ts index 008cce9b7..1cfcd3039 100644 --- a/stores/main-menu.ts +++ b/stores/main-menu.ts @@ -19,7 +19,15 @@ const useDropdownStore = create((set) => ({ isMyPagesMenuOpen: false, isLanguageSwitcherOpen: false, toggleHamburgerMenu: () => - set((state) => ({ isHamburgerMenuOpen: !state.isHamburgerMenuOpen })), + set(({ isHamburgerMenuOpen, isMyPagesMenuOpen }) => { + // Close the other dropdown if it's open + if (!isHamburgerMenuOpen && isMyPagesMenuOpen) { + set({ isMyPagesMenuOpen: false }) + } + return { isHamburgerMenuOpen: !isHamburgerMenuOpen } + }), + // toggleHamburgerMenu: () => + // set((state) => ({ isHamburgerMenuOpen: !state.isHamburgerMenuOpen })), toggleMyPagesMobileMenu: () => set((state) => { // Close the other dropdown if it's open @@ -29,13 +37,18 @@ const useDropdownStore = create((set) => ({ return { isMyPagesMobileMenuOpen: !state.isMyPagesMobileMenuOpen } }), toggleMyPagesMenu: () => - set(({ isLanguageSwitcherOpen, isMyPagesMenuOpen }) => { - // Close the other dropdown if it's open - if (!isMyPagesMenuOpen && isLanguageSwitcherOpen) { - set({ isLanguageSwitcherOpen: false }) + set( + ({ isHamburgerMenuOpen, isLanguageSwitcherOpen, isMyPagesMenuOpen }) => { + // Close the other dropdown if it's open + if (!isMyPagesMenuOpen && isLanguageSwitcherOpen) { + set({ isLanguageSwitcherOpen: false }) + } + if (!isMyPagesMenuOpen && isHamburgerMenuOpen) { + set({ isHamburgerMenuOpen: false }) + } + return { isMyPagesMenuOpen: !isMyPagesMenuOpen } } - return { isMyPagesMenuOpen: !isMyPagesMenuOpen } - }), + ), toggleLanguageSwitcher: () => set(({ isLanguageSwitcherOpen, isMyPagesMenuOpen }) => { // Close the other dropdown if it's open diff --git a/types/components/current/languageSwitcher.ts b/types/components/current/languageSwitcher.ts index 0f409d36e..dd39494a9 100644 --- a/types/components/current/languageSwitcher.ts +++ b/types/components/current/languageSwitcher.ts @@ -1,5 +1,3 @@ -import { Lang } from "@/constants/languages" - import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher" export type LanguageSwitcherLink = { @@ -9,4 +7,5 @@ export type LanguageSwitcherLink = { export type LanguageSwitcherProps = { urls: LanguageSwitcherData + location?: "header" | "footer" } diff --git a/types/components/icon.ts b/types/components/icon.ts index 0e705b7e6..b0f6359d8 100644 --- a/types/components/icon.ts +++ b/types/components/icon.ts @@ -19,6 +19,7 @@ export enum IconName { CrossCircle = "CrossCircle", CheckCircle = "CheckCircle", ChevronDown = "ChevronDown", + ChevronLeft = "ChevronLeft", ChevronRight = "ChevronRight", Close = "Close", CloseLarge = "CloseLarge", diff --git a/utils/tabbable.ts b/utils/tabbable.ts new file mode 100644 index 000000000..b38352c99 --- /dev/null +++ b/utils/tabbable.ts @@ -0,0 +1,62 @@ +/*! + * Adapted from jQuery UI core + * + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/category/ui-core/ + */ + +const tabbableNode = /input|select|textarea|button|object/ + +function hidesContents(element: HTMLElement) { + const zeroSize = element.offsetWidth <= 0 && element.offsetHeight <= 0 + + // If the node is empty, this is good enough + if (zeroSize && !element.innerHTML) return true + + // Otherwise we need to check some styles + const style = window.getComputedStyle(element) + return zeroSize + ? style.getPropertyValue("overflow") !== "visible" + : style.getPropertyValue("display") === "none" +} + +function visible(element: any) { + let parentElement = element + while (parentElement) { + if (parentElement === document.body) break + if (hidesContents(parentElement)) return false + parentElement = parentElement.parentNode + } + return true +} + +export function focusable(element: HTMLElement, isTabIndexNotNaN: boolean) { + const nodeName = element.nodeName.toLowerCase() + const res = + //@ts-ignore + (tabbableNode.test(nodeName) && !element.disabled) || + //@ts-ignore + (nodeName === "a" ? element.href || isTabIndexNotNaN : isTabIndexNotNaN) + return res && visible(element) +} + +export function tabbable(element: HTMLElement) { + let tabIndex = element.getAttribute("tabindex") + //@ts-ignore + if (tabIndex === null) tabIndex = undefined + //@ts-ignore + const isTabIndexNaN = isNaN(tabIndex) + //@ts-ignore + return (isTabIndexNaN || tabIndex >= 0) && focusable(element, !isTabIndexNaN) +} + +export default function findTabbableDescendants( + element: HTMLElement +): HTMLElement[] { + return [].slice.call(element.querySelectorAll("*"), 0).filter(tabbable) +} From bdec054ecd90a809242f58fd00aae284d3116417 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Fri, 23 Aug 2024 13:25:41 +0200 Subject: [PATCH 11/27] feat(SW-184): my pages menu mobile/desktop functionality --- .../Avatar/avatar.module.css | 0 .../{MyPagesMenu => }/Avatar/index.tsx | 0 .../MainMenu/MobileMenu/mobileMenu.module.css | 20 +-- .../Header/MainMenu/MyPagesMenu/index.tsx | 99 +++----------- .../MyPagesMenu/myPagesMenu.module.css | 128 ++++++------------ .../MainMenu/MyPagesMenuContent/index.tsx | 81 +++++++++++ .../myPagesMenuContent.module.css | 74 ++++++++++ .../MainMenu/MyPagesMobileMenu/index.tsx | 55 ++++++++ .../myPagesMobileMenu.module.css | 38 ++++++ components/Header/MainMenu/index.tsx | 30 +++- .../Header/MainMenu/mainMenu.module.css | 6 + components/LanguageSwitcher/index.tsx | 25 ++-- .../languageSwitcher.module.css | 3 +- hooks/useTrapFocus.ts | 1 + i18n/dictionaries/da.json | 4 + i18n/dictionaries/de.json | 4 + i18n/dictionaries/en.json | 4 + i18n/dictionaries/fi.json | 4 + i18n/dictionaries/no.json | 4 + i18n/dictionaries/sv.json | 4 + stores/main-menu.ts | 95 +++++++++---- types/components/header/myPagesMenu.ts | 6 +- 22 files changed, 459 insertions(+), 226 deletions(-) rename components/Header/MainMenu/{MyPagesMenu => }/Avatar/avatar.module.css (100%) rename components/Header/MainMenu/{MyPagesMenu => }/Avatar/index.tsx (100%) create mode 100644 components/Header/MainMenu/MyPagesMenuContent/index.tsx create mode 100644 components/Header/MainMenu/MyPagesMenuContent/myPagesMenuContent.module.css create mode 100644 components/Header/MainMenu/MyPagesMobileMenu/index.tsx create mode 100644 components/Header/MainMenu/MyPagesMobileMenu/myPagesMobileMenu.module.css diff --git a/components/Header/MainMenu/MyPagesMenu/Avatar/avatar.module.css b/components/Header/MainMenu/Avatar/avatar.module.css similarity index 100% rename from components/Header/MainMenu/MyPagesMenu/Avatar/avatar.module.css rename to components/Header/MainMenu/Avatar/avatar.module.css diff --git a/components/Header/MainMenu/MyPagesMenu/Avatar/index.tsx b/components/Header/MainMenu/Avatar/index.tsx similarity index 100% rename from components/Header/MainMenu/MyPagesMenu/Avatar/index.tsx rename to components/Header/MainMenu/Avatar/index.tsx diff --git a/components/Header/MainMenu/MobileMenu/mobileMenu.module.css b/components/Header/MainMenu/MobileMenu/mobileMenu.module.css index 1e8fad253..da62ccfff 100644 --- a/components/Header/MainMenu/MobileMenu/mobileMenu.module.css +++ b/components/Header/MainMenu/MobileMenu/mobileMenu.module.css @@ -64,20 +64,6 @@ transform: rotate(45deg); } -@media screen and (min-width: 768px) { - .hamburger { - display: none; - } -} - -.overlay { - position: absolute; - top: var(--main-menu-mobile-height); - bottom: 0; - left: 0; - right: 0; -} - .modal { position: fixed; top: var(--main-menu-mobile-height); @@ -108,3 +94,9 @@ display: grid; gap: var(--Spacing-x2); } + +@media screen and (min-width: 768px) { + .hamburger { + display: none; + } +} diff --git a/components/Header/MainMenu/MyPagesMenu/index.tsx b/components/Header/MainMenu/MyPagesMenu/index.tsx index 42cd370ab..02d30cad7 100644 --- a/components/Header/MainMenu/MyPagesMenu/index.tsx +++ b/components/Header/MainMenu/MyPagesMenu/index.tsx @@ -2,113 +2,54 @@ import { useIntl } from "react-intl" -import { logout } from "@/constants/routes/handleAuth" -import { myPages } from "@/constants/routes/myPages" import useDropdownStore from "@/stores/main-menu" -import { ArrowRightIcon, ChevronDownIcon } from "@/components/Icons" -import Link from "@/components/TempDesignSystem/Link" +import { ChevronDownIcon } from "@/components/Icons" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import useLang from "@/hooks/useLang" import { getInitials } from "@/utils/user" +import Avatar from "../Avatar" import MainMenuButton from "../MainMenuButton" -import Avatar from "./Avatar" +import MyPagesMenuContent from "../MyPagesMenuContent" import styles from "./myPagesMenu.module.css" import { MyPagesMenuProps } from "@/types/components/header/myPagesMenu" -// This component is mostly the same as MyPagesMobileDropdown, but with a -// different name and some different styles. Should probably be refactored in -// a later stage to fit the design from Figma better. - export default function MyPagesMenu({ navigation, user }: MyPagesMenuProps) { const intl = useIntl() const lang = useLang() const { toggleMyPagesMenu, isMyPagesMenuOpen } = useDropdownStore() - if (!navigation) { - return null - } + useHandleKeyUp((event: KeyboardEvent) => { + if (event.key === "Escape" && isMyPagesMenuOpen) { + toggleMyPagesMenu() + } + }) - return user ? ( + return (
- + {intl.formatMessage({ id: "Hi" })} {user.firstName}! - + - + +
- ) : ( - - - - {intl.formatMessage({ id: "Log in/Join" })} - - ) } diff --git a/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css b/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css index 8d6b2fe1c..d322955e3 100644 --- a/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css +++ b/components/Header/MainMenu/MyPagesMenu/myPagesMenu.module.css @@ -1,97 +1,47 @@ .myPagesMenu { - position: relative; -} - -.chevron { display: none; - transition: transform 0.2s; -} - -.chevron.isExpanded { - transform: rotate(180deg); -} - -.userName { - display: none; - font-weight: 600; - color: var(--Base-Text-High-contrast); -} - -.dropdown { - position: absolute; - top: 46px; - right: 0; - background-color: var(--Base-Surface-Primary-light-Normal); - padding: var(--Spacing-x2) var(--Spacing-x4); - border-radius: var(--Corner-radius-Large); - box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1); - min-width: 20rem; - z-index: 1; - display: none; -} - -/* Triangle above dropdown */ -.dropdown::before { - content: ""; - position: absolute; - top: -1.25rem; - right: 2.4rem; - transform: rotate(180deg); - border-width: 0.75rem; - border-style: solid; - border-color: var(--Base-Surface-Primary-light-Normal) transparent transparent - transparent; -} - -.dropdown.isExpanded { - display: block; -} - -.friendTypeWrapper { - padding: 0 var(--Spacing-x1) var(--Spacing-x2); - font-weight: 400; - color: var(--UI-Text-Medium-contrast); -} -.friendType { - font-family: var(--typography-Title-5-fontFamily); - letter-spacing: var(--typography-Title-5-letterSpacing); - font-size: var(--typography-Caption-Bold-fontSize); - text-transform: uppercase; -} - -.friendType::after { - content: " · "; - display: inline; - padding: 0 var(--Spacing-x-half); -} - -.groups, -.menuItems { - list-style: none; -} - -.group { - padding: var(--Spacing-x2) 0; - border-top: 1px solid var(--Base-Border-Subtle); -} - -.group:last-child { - padding-bottom: 0; -} - -.arrow { - opacity: 0; -} - -.loginLink { - display: flex; - align-items: center; - gap: var(--Spacing-x1); } @media screen and (min-width: 768px) { - .userName, + .myPagesMenu { + display: block; + position: relative; + } + .chevron { - display: initial; + transition: transform 0.2s; + } + + .chevron.isExpanded { + transform: rotate(180deg); + } + + .dropdown { + position: absolute; + top: 46px; + right: 0; + background-color: var(--Base-Surface-Primary-light-Normal); + border-radius: var(--Corner-radius-Large); + box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1); + min-width: 20rem; + z-index: 1; + display: none; + } + + /* Triangle above dropdown */ + .dropdown::before { + content: ""; + position: absolute; + top: -1.25rem; + right: 2.4rem; + transform: rotate(180deg); + border-width: 0.75rem; + border-style: solid; + border-color: var(--Base-Surface-Primary-light-Normal) transparent + transparent transparent; + } + + .dropdown.isExpanded { + display: block; } } diff --git a/components/Header/MainMenu/MyPagesMenuContent/index.tsx b/components/Header/MainMenu/MyPagesMenuContent/index.tsx new file mode 100644 index 000000000..e87c25a94 --- /dev/null +++ b/components/Header/MainMenu/MyPagesMenuContent/index.tsx @@ -0,0 +1,81 @@ +"use client" + +import Link from "next/link" +import { useIntl } from "react-intl" + +import { logout } from "@/constants/routes/handleAuth" + +import { ArrowRightIcon } from "@/components/Icons" +import Divider from "@/components/TempDesignSystem/Divider" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import useLang from "@/hooks/useLang" +import { useTrapFocus } from "@/hooks/useTrapFocus" + +import styles from "./myPagesMenuContent.module.css" + +import { MyPagesMenuContentProps } from "@/types/components/header/myPagesMenu" + +export default function MyPagesMenuContent({ + navigation, + toggleOpenStateFn, + user, +}: MyPagesMenuContentProps) { + const intl = useIntl() + const lang = useLang() + const myPagesMenuContentRef = useTrapFocus() + + if (!navigation) { + return null + } + + return ( + + ) +} diff --git a/components/Header/MainMenu/MyPagesMenuContent/myPagesMenuContent.module.css b/components/Header/MainMenu/MyPagesMenuContent/myPagesMenuContent.module.css new file mode 100644 index 000000000..800f646b5 --- /dev/null +++ b/components/Header/MainMenu/MyPagesMenuContent/myPagesMenuContent.module.css @@ -0,0 +1,74 @@ +.myPagesMenuContent { + padding: var(--Spacing-x3) var(--Spacing-x2); +} + +.intro { + padding: 0 var(--Spacing-x1); +} + +.myPagesMenuContent .friendTypeWrapper { + color: var(--UI-Text-Medium-contrast); +} + +.divider { + margin: var(--Spacing-x2) 0; +} + +.friendType { + font-family: var(--typography-Title-5-fontFamily); + letter-spacing: var(--typography-Title-5-letterSpacing); + font-size: var(--typography-Caption-Bold-fontSize); + text-transform: uppercase; +} + +.friendType::after { + content: " · "; + display: inline; + padding: 0 var(--Spacing-x-half); +} + +.groups, +.menuItems { + list-style: none; +} + +.link { + display: flex; + align-items: center; + justify-content: space-between; + text-decoration: none; + padding: var(--Spacing-x1); + gap: var(--Spacing-x-one-and-half); + color: var(--Scandic-Brand-Burgundy); + font-family: var(--typography-Body-Bold-fontFamily); + font-size: var(--typography-Body-Bold-fontSize); + font-weight: 500; + line-height: var(--typography-Body-Bold-lineHeight); + letter-spacing: var(--typography-Body-Bold-letterSpacing); + border-radius: var(--Corner-radius-Medium); +} + +.link:hover { + background-color: var(--Base-Surface-Primary-light-Hover-alt); +} + +.link.smallLink { + font-family: var(--typography-Body-Regular-fontFamily); + font-size: var(--typography-Body-Regular-fontSize); + font-weight: var(--typography-Body-Regular-fontWeight); + line-height: var(--typography-Body-Regular-lineHeight); + letter-spacing: var(--typography-Body-Regular-letterSpacing); +} + +.link:not(:hover) .arrow { + opacity: 0; +} + +@media screen and (min-width: 768px) { + .myPagesMenuContent { + padding: var(--Spacing-x2) var(--Spacing-x4); + } + .userName { + display: none; + } +} diff --git a/components/Header/MainMenu/MyPagesMobileMenu/index.tsx b/components/Header/MainMenu/MyPagesMobileMenu/index.tsx new file mode 100644 index 000000000..2a9f5dac7 --- /dev/null +++ b/components/Header/MainMenu/MyPagesMobileMenu/index.tsx @@ -0,0 +1,55 @@ +"use client" + +import { Dialog, Modal } from "react-aria-components" +import { useIntl } from "react-intl" + +import useDropdownStore from "@/stores/main-menu" + +import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" +import { getInitials } from "@/utils/user" + +import Avatar from "../Avatar" +import MainMenuButton from "../MainMenuButton" +import MyPagesMenuContent from "../MyPagesMenuContent" + +import styles from "./myPagesMobileMenu.module.css" + +import { MyPagesMenuProps } from "@/types/components/header/myPagesMenu" + +export default function MyPagesMobileMenu({ + navigation, + user, +}: MyPagesMenuProps) { + const intl = useIntl() + const { toggleMyPagesMobileMenu, isMyPagesMobileMenuOpen } = + useDropdownStore() + + useHandleKeyUp((event: KeyboardEvent) => { + if (event.key === "Escape" && isMyPagesMobileMenuOpen) { + toggleMyPagesMobileMenu() + } + }) + + return ( +
+ + + + + + + + +
+ ) +} diff --git a/components/Header/MainMenu/MyPagesMobileMenu/myPagesMobileMenu.module.css b/components/Header/MainMenu/MyPagesMobileMenu/myPagesMobileMenu.module.css new file mode 100644 index 000000000..ba3a687c4 --- /dev/null +++ b/components/Header/MainMenu/MyPagesMobileMenu/myPagesMobileMenu.module.css @@ -0,0 +1,38 @@ +@keyframes slide-in { + from { + right: -100vw; + } + + to { + right: 0; + } +} + +.modal { + position: fixed; + top: var(--main-menu-mobile-height); + right: auto; + bottom: 0; + width: 100%; + background-color: var(--Base-Surface-Primary-light-Normal); + transition: right 0.3s; +} + +.modal[data-entering] { + animation: slide-in 0.3s; +} +.modal[data-exiting] { + animation: slide-in 0.3s reverse; +} + +.dialog { + height: 100%; + overflow-y: auto; +} + +@media screen and (min-width: 767px) { + .myPagesMobileMenu, + .modal { + display: none; + } +} diff --git a/components/Header/MainMenu/index.tsx b/components/Header/MainMenu/index.tsx index db7d448d2..3ce22c53a 100644 --- a/components/Header/MainMenu/index.tsx +++ b/components/Header/MainMenu/index.tsx @@ -1,13 +1,18 @@ -import Link from "next/link" +import NextLink from "next/link" +import { myPages } from "@/constants/routes/myPages" import { serverClient } from "@/lib/trpc/server" import Image from "@/components/Image" +import Link from "@/components/TempDesignSystem/Link" import { getIntl } from "@/i18n" +import { getLang } from "@/i18n/serverContext" import { navigationMenuItems } from "../tempHeaderData" +import Avatar from "./Avatar" import MobileMenu from "./MobileMenu" import MyPagesMenu from "./MyPagesMenu" +import MyPagesMobileMenu from "./MyPagesMobileMenu" import NavigationMenu from "./NavigationMenu" import styles from "./mainMenu.module.css" @@ -16,6 +21,7 @@ import { MainMenuProps } from "@/types/components/header/mainMenu" export default async function MainMenu({ languageUrls }: MainMenuProps) { const intl = await getIntl() + const lang = getLang() const myPagesNavigation = await serverClient().contentstack.myPages.navigation.get() @@ -24,7 +30,7 @@ export default async function MainMenu({ languageUrls }: MainMenuProps) { return (
) } diff --git a/components/BookingWidget/bookingWidget.module.css b/components/Forms/BookingWidget/bookingWidget.module.css similarity index 100% rename from components/BookingWidget/bookingWidget.module.css rename to components/Forms/BookingWidget/bookingWidget.module.css From 8c34a17954b7ba970be49e2f5f9b52794915f408 Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Wed, 21 Aug 2024 15:49:32 +0200 Subject: [PATCH 20/27] feat(SW-237): Fixed rebased conflicts --- .../bookingWidget.module.css | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/{Forms/BookingWidget => BookingWidgetContainer}/bookingWidget.module.css (100%) diff --git a/components/Forms/BookingWidget/bookingWidget.module.css b/components/BookingWidgetContainer/bookingWidget.module.css similarity index 100% rename from components/Forms/BookingWidget/bookingWidget.module.css rename to components/BookingWidgetContainer/bookingWidget.module.css From 097ef701e52e757c172cfbf6d19d019212c5f454 Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Wed, 21 Aug 2024 15:57:43 +0200 Subject: [PATCH 21/27] feat(SW-237): Renamed Bookingwidgetcontainer to BookingWidget --- .../(protected)/my-pages/@bookingwidget/[...path]/page.tsx | 4 ++-- app/[lang]/(live)/(public)/[contentType]/[uid]/layout.tsx | 4 ++-- app/[lang]/(live)/(public)/hotelreservation/layout.tsx | 4 ++-- app/[lang]/(live-current)/layout.tsx | 4 ++-- .../bookingWidget.module.css | 0 .../{BookingWidgetContainer => BookingWidget}/index.tsx | 5 +++-- 6 files changed, 11 insertions(+), 10 deletions(-) rename components/{BookingWidgetContainer => BookingWidget}/bookingWidget.module.css (100%) rename components/{BookingWidgetContainer => BookingWidget}/index.tsx (91%) diff --git a/app/[lang]/(live)/(protected)/my-pages/@bookingwidget/[...path]/page.tsx b/app/[lang]/(live)/(protected)/my-pages/@bookingwidget/[...path]/page.tsx index ef55f4c90..487bfcb5d 100644 --- a/app/[lang]/(live)/(protected)/my-pages/@bookingwidget/[...path]/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/@bookingwidget/[...path]/page.tsx @@ -1,5 +1,5 @@ -import BookingWidgetContainer from "@/components/BookingWidgetContainer" +import BookingWidget from "@/components/BookingWidget" export default function BookingWidgetPage() { - return + return } diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/layout.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/layout.tsx index 0d3bf209c..28d7ed59c 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/layout.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/layout.tsx @@ -1,4 +1,4 @@ -import BookingWidgetContainer from "@/components/BookingWidgetContainer" +import BookingWidget from "@/components/BookingWidget" import styles from "./layout.module.css" @@ -19,7 +19,7 @@ export default function ContentTypeLayout({ >) { return ( <> - +
{breadcrumbs} {children} diff --git a/app/[lang]/(live)/(public)/hotelreservation/layout.tsx b/app/[lang]/(live)/(public)/hotelreservation/layout.tsx index e7e4fb678..a5fce013f 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/layout.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/layout.tsx @@ -1,4 +1,4 @@ -import BookingWidgetContainer from "@/components/BookingWidgetContainer" +import BookingWidget from "@/components/BookingWidget" import styles from "./layout.module.css" @@ -9,7 +9,7 @@ export default function HotelReservationLayout({ }: React.PropsWithChildren>) { return ( <> - +
{children}
) diff --git a/app/[lang]/(live-current)/layout.tsx b/app/[lang]/(live-current)/layout.tsx index 8401dc96f..12578764e 100644 --- a/app/[lang]/(live-current)/layout.tsx +++ b/app/[lang]/(live-current)/layout.tsx @@ -4,7 +4,7 @@ import "@scandic-hotels/design-system/style.css" import Script from "next/script" import TokenRefresher from "@/components/Auth/TokenRefresher" -import BookingWidgetContainer from "@/components/BookingWidgetContainer" +import BookingWidget from "@/components/BookingWidget" import AdobeScript from "@/components/Current/AdobeScript" import Footer from "@/components/Current/Footer" import Header from "@/components/Current/Header" @@ -72,7 +72,7 @@ export default async function RootLayout({ myPagesMobileDropdown={myPagesMobileDropdown} languageSwitcher={languageSwitcher} /> - + {children}