Merge branch 'develop' into feat/sw-386-header-fixes

This commit is contained in:
Linus Flood
2024-09-17 12:42:58 +02:00
24 changed files with 513 additions and 82 deletions

View File

@@ -1,6 +1,7 @@
import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header"
import Card from "@/components/TempDesignSystem/Card"
import ContentCard from "@/components/TempDesignSystem/ContentCard"
import Grids from "@/components/TempDesignSystem/Grids"
import LoyaltyCard from "@/components/TempDesignSystem/LoyaltyCard"
@@ -21,8 +22,19 @@ export default function CardsGrid({
<Grids.Stackable>
{cards_grid.cards.map((card) => {
switch (card.__typename) {
case CardsGridEnum.Card: {
return (
case CardsGridEnum.Card:
return card.isContentCard ? (
<ContentCard
key={card.system.uid}
title={card.heading || ""}
description={card.body_text || ""}
primaryButton={card.primaryButton}
secondaryButton={card.secondaryButton}
sidePeekButton={card.sidePeekButton}
backgroundImage={card.background_image}
style="default"
/>
) : (
<Card
theme={cards_grid.theme || "one"}
key={card.system.uid}
@@ -33,7 +45,6 @@ export default function CardsGrid({
primaryButton={card.primaryButton}
/>
)
}
case CardsGridEnum.LoyaltyCard:
return (
<LoyaltyCard

View File

@@ -74,7 +74,7 @@ export function RoomCard({
onClick={handleRoomCtaClick}
>
{intl.formatMessage({ id: "See room details" })}
<ChevronRightIcon className={styles.chevron} />
<ChevronRightIcon />
</Button>
</div>
</article>

View File

@@ -0,0 +1,63 @@
import { useIntl } from "react-intl"
import useDropdownStore from "@/stores/main-menu"
import { ChevronLeftIcon } from "@/components/Icons"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./languageSwitcherContainer.module.css"
import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
import {
type LanguageSwitcherContainerProps,
LanguageSwitcherTypesEnum,
} from "@/types/components/languageSwitcher/languageSwitcher"
export default function LanguageSwitcherContainer({
children,
type,
}: LanguageSwitcherContainerProps) {
const { toggleDropdown } = useDropdownStore()
const intl = useIntl()
const isFooter = type === LanguageSwitcherTypesEnum.Footer
const isMobileHeader = type === LanguageSwitcherTypesEnum.MobileHeader
const position = isFooter
? DropdownTypeEnum.FooterLanguageSwitcher
: DropdownTypeEnum.HamburgerMenu
return (
<div>
{isMobileHeader ? (
<div className={styles.backWrapper}>
<button
type="button"
className={styles.backButton}
onClick={() => toggleDropdown(position)}
>
<ChevronLeftIcon color="red" />
<Subtitle type="one">
{intl.formatMessage({
id: "Main menu",
})}
</Subtitle>
</button>
</div>
) : null}
{isFooter ? (
<div className={styles.closeWrapper}>
<button
type="button"
className={styles.closeButton}
aria-label={intl.formatMessage({
id: "Close menu",
})}
onClick={() => toggleDropdown(position)}
>
<span className={styles.bar}></span>
</button>
</div>
) : null}
{children}
</div>
)
}

View File

@@ -0,0 +1,70 @@
.backWrapper {
background-color: var(--Base-Surface-Secondary-light-Normal);
padding: var(--Spacing-x2);
}
.backButton {
background-color: transparent;
border: none;
padding: 0;
cursor: pointer;
display: flex;
align-items: center;
gap: var(--Spacing-x1);
}
.closeWrapper {
display: flex;
justify-content: flex-end;
padding: var(--Spacing-x2);
border-bottom: 1px solid var(--Base-Border-Subtle);
}
.closeButton {
background-color: transparent;
border: none;
cursor: pointer;
justify-self: flex-start;
padding: 11px var(--Spacing-x1) var(--Spacing-x2);
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.3s;
width: var(--Spacing-x4);
}
.bar::after,
.bar::before {
content: "";
left: 0;
position: absolute;
top: 0;
transform-origin: 50% 50%;
width: var(--Spacing-x4);
}
.bar {
background: transparent;
}
.bar::after {
transform: rotate(-45deg);
}
.bar::before {
transform: rotate(45deg);
}
@media screen and (min-width: 768px) {
.closeWrapper {
display: none;
}
}

View File

@@ -3,9 +3,8 @@
import { useIntl } from "react-intl"
import { Lang, languages } from "@/constants/languages"
import useDropdownStore from "@/stores/main-menu"
import { CheckIcon, ChevronLeftIcon } from "@/components/Icons"
import { CheckIcon } from "@/components/Icons"
import Link from "@/components/TempDesignSystem/Link"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useLang from "@/hooks/useLang"
@@ -13,38 +12,19 @@ import { useTrapFocus } from "@/hooks/useTrapFocus"
import styles from "./languageSwitcherContent.module.css"
import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
import type { LanguageSwitcherProps } from "@/types/components/languageSwitcher/languageSwitcher"
import type { LanguageSwitcherContentProps } from "@/types/components/languageSwitcher/languageSwitcher"
export default function LanguageSwitcherContent({
urls,
type,
}: LanguageSwitcherProps) {
}: LanguageSwitcherContentProps) {
const intl = useIntl()
const currentLanguage = useLang()
const { toggleDropdown } = useDropdownStore()
const languageSwitcherRef = useTrapFocus()
const urlKeys = Object.keys(urls) as Lang[]
const position =
type === "footer"
? DropdownTypeEnum.FooterLanguageSwitcher
: DropdownTypeEnum.HamburgerMenu
return (
<div className={styles.languageSwitcherContent} ref={languageSwitcherRef}>
{type === "mobileHeader" ? (
<div className={styles.backWrapper}>
<button
type="button"
className={styles.backButton}
onClick={() => toggleDropdown(position)}
>
<ChevronLeftIcon color="red" />
<Subtitle type="one">Main Menu</Subtitle>
</button>
</div>
) : null}
<div className={styles.languageWrapper}>
<Subtitle className={styles.subtitle} type="two">
{intl.formatMessage({ id: "Select your language" })}

View File

@@ -1,22 +1,3 @@
.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);

View File

@@ -1,5 +1,6 @@
"use client"
import { useEffect } from "react"
import { useIntl } from "react-intl"
import { languages } from "@/constants/languages"
@@ -9,13 +10,17 @@ import { ChevronDownIcon, GlobeIcon } from "@/components/Icons"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import useLang from "@/hooks/useLang"
import LanguageSwitcherContainer from "./LanguageSwitcherContainer"
import LanguageSwitcherContent from "./LanguageSwitcherContent"
import { languageSwitcherVariants } from "./variants"
import styles from "./languageSwitcher.module.css"
import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
import type { LanguageSwitcherProps } from "@/types/components/languageSwitcher/languageSwitcher"
import {
type LanguageSwitcherProps,
LanguageSwitcherTypesEnum,
} from "@/types/components/languageSwitcher/languageSwitcher"
export default function LanguageSwitcher({
urls,
@@ -30,8 +35,11 @@ export default function LanguageSwitcher({
isHeaderLanguageSwitcherMobileOpen,
} = useDropdownStore()
const position = type === "footer" ? "footer" : "header"
const color = type === "footer" ? "pale" : "burgundy"
const isFooter = type === LanguageSwitcherTypesEnum.Footer
const isHeader = !isFooter
const position = isFooter ? "footer" : "header"
const color = isFooter ? "pale" : "burgundy"
const dropdownType = {
footer: DropdownTypeEnum.FooterLanguageSwitcher,
@@ -40,8 +48,8 @@ export default function LanguageSwitcher({
}[type]
const isLanguageSwitcherOpen =
(type === "footer" && isFooterLanguageSwitcherOpen) ||
(type !== "footer" &&
(isFooter && isFooterLanguageSwitcherOpen) ||
(isHeader &&
(isHeaderLanguageSwitcherOpen || isHeaderLanguageSwitcherMobileOpen))
useHandleKeyUp((event: KeyboardEvent) => {
@@ -50,6 +58,18 @@ export default function LanguageSwitcher({
}
})
useEffect(() => {
if (isFooter && isFooterLanguageSwitcherOpen) {
document.body.style.overflow = "hidden"
} else {
document.body.style.overflow = ""
}
return () => {
document.body.style.overflow = ""
}
}, [isFooter, isFooterLanguageSwitcherOpen])
const classNames = languageSwitcherVariants({ color, position })
return (
@@ -78,7 +98,9 @@ export default function LanguageSwitcher({
className={`${styles.dropdown} ${isLanguageSwitcherOpen ? styles.isExpanded : ""}`}
>
{isLanguageSwitcherOpen ? (
<LanguageSwitcherContent urls={urls} type={type} />
<LanguageSwitcherContainer type={type}>
<LanguageSwitcherContent urls={urls} />
</LanguageSwitcherContainer>
) : null}
</div>
</div>

View File

@@ -31,20 +31,36 @@
.dropdown {
position: fixed;
top: var(--main-menu-mobile-height);
right: -100vw;
bottom: 0;
width: 100%;
background-color: var(--Base-Surface-Primary-light-Normal);
transition: right 0.3s;
z-index: var(--menu-overlay-z-index);
}
.dropdown.isExpanded {
.top .dropdown {
right: -100vw;
top: var(--main-menu-mobile-height);
bottom: 0;
transition: right 0.3s;
}
.top .dropdown.isExpanded {
display: block;
right: 0;
}
.bottom .dropdown {
transition: transform 0.3s;
width: 100%;
height: 100vh;
left: 0;
bottom: 0;
transform: translateY(100%);
}
.bottom .dropdown.isExpanded {
transform: translateY(0);
}
@media screen and (min-width: 768px) {
.languageSwitcher {
position: relative;
@@ -81,10 +97,16 @@
}
.bottom .dropdown {
top: auto;
transition: none;
height: auto;
left: -100%;
bottom: 2.25rem;
}
.bottom .dropdown.isExpanded {
display: block;
}
.bottom .dropdown::before {
top: 100%;
}

View File

@@ -0,0 +1,71 @@
.card {
border-radius: var(--Corner-radius-Medium);
display: flex;
flex-direction: column;
max-width: 399px;
overflow: hidden;
}
.default {
background-color: var(--Base-Surface-Subtle-Normal);
}
.featured {
background-color: var(--Main-Grey-White);
}
.default,
.featured {
border: 1px solid var(--Base-Border-Subtle);
}
.imageContainer {
width: 100%;
height: 12.58625rem; /* 201.38px / 16 = 12.58625rem */
overflow: hidden;
}
.backgroundImage {
width: 100%;
height: 100%;
object-fit: cover;
}
.content {
display: flex;
flex-direction: column;
gap: var(--Spacing-x-one-and-half);
align-items: flex-start;
padding: var(--Spacing-x2) var(--Spacing-x3);
}
.description {
color: var(--Base-Text-Medium-contrast);
}
.ctaContainer {
display: grid;
grid-template-columns: 1fr;
gap: var(--Spacing-x1);
width: 100%;
}
.ctaButton {
width: 100%;
}
@media (min-width: 1367px) {
.card:not(.alwaysStack) .ctaContainer {
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}
.card:not(.alwaysStack) .ctaContainer:has(:only-child) {
grid-template-columns: 1fr;
}
}
.sidePeekCTA {
/* TODO: Create ticket to remove padding on "link" buttons,
align w. design on this. */
padding: 0 !important;
}

View File

@@ -0,0 +1,98 @@
import React from "react"
import { ChevronRightIcon } from "@/components/Icons"
import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "../Text/Subtitle"
import { contentCardVariants } from "./variants"
import styles from "./contentCard.module.css"
import type { ContentCardProps } from "@/types/components/contentCard"
export default function ContentCard({
title,
description,
primaryButton,
secondaryButton,
sidePeekButton,
backgroundImage,
style = "default",
alwaysStack = false,
className,
}: ContentCardProps) {
const cardClasses = contentCardVariants({ style, alwaysStack, className })
return (
<div className={cardClasses}>
{backgroundImage && (
<div className={styles.imageContainer}>
<Image
src={backgroundImage.url}
alt={backgroundImage.meta?.alt || ""}
className={styles.backgroundImage}
width={399}
height={201}
/>
</div>
)}
<div className={styles.content}>
<Subtitle textAlign="left" type="two" color="black">
{title}
</Subtitle>
<Body color="black">{description}</Body>
{!!sidePeekButton ? (
<Button
// onClick={() => {
// // TODO: Implement sidePeek functionality once SW-341 is merged.
// }}
theme="base"
variant="icon"
intent="text"
size="small"
className={styles.sidePeekCTA}
>
{sidePeekButton.title}
<ChevronRightIcon />
</Button>
) : (
<div className={styles.ctaContainer}>
{primaryButton && (
<Button
asChild
intent="primary"
size="small"
className={styles.ctaButton}
>
<Link
href={primaryButton.href}
target={primaryButton.openInNewTab ? "_blank" : undefined}
>
{primaryButton.title}
</Link>
</Button>
)}
{secondaryButton && (
<Button
asChild
intent="secondary"
size="small"
className={styles.ctaButton}
>
<Link
href={secondaryButton.href}
target={secondaryButton.openInNewTab ? "_blank" : undefined}
>
{secondaryButton.title}
</Link>
</Button>
)}
</div>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,20 @@
import { cva } from "class-variance-authority"
import styles from "./contentCard.module.css"
export const contentCardVariants = cva(styles.card, {
variants: {
style: {
default: styles.default,
featured: styles.featured,
},
alwaysStack: {
true: styles.alwaysStack,
false: "",
},
},
defaultVariants: {
style: "default",
alwaysStack: false,
},
})