feat(SW-184): added main components for new header

This commit is contained in:
Erik Tiekstra
2024-08-19 12:52:35 +02:00
parent 70297bec91
commit 08cde7ae2f
31 changed files with 548 additions and 26 deletions

View File

@@ -1,21 +1,9 @@
import Header from "@/components/Current/Header"
import Header from "@/components/Header"
import { setLang } from "@/i18n/serverContext"
import type { LangParams, LayoutArgs } from "@/types/params"
export default function HeaderLayout({
languageSwitcher,
myPagesMobileDropdown,
params,
}: LayoutArgs<LangParams> & {
languageSwitcher: React.ReactNode
myPagesMobileDropdown: React.ReactNode
}) {
export default function HeaderLayout({ params }: LayoutArgs<LangParams>) {
setLang(params.lang)
return (
<Header
myPagesMobileDropdown={myPagesMobileDropdown}
languageSwitcher={languageSwitcher}
/>
)
return <Header />
}

View File

@@ -0,0 +1,18 @@
import MenuItem from "../MenuItem"
import { MenuProps } from "./menu"
import styles from "./menu.module.css"
export default function Menu({ items }: MenuProps) {
function handleButtonClick() {}
return (
<ul className={styles.menu}>
{items.map((item) => (
<li key={item.id}>
<MenuItem item={item} />
</li>
))}
</ul>
)
}

View File

@@ -0,0 +1,8 @@
.menu {
list-style: none;
margin: 0;
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--Spacing-x4);
}

View File

@@ -0,0 +1,5 @@
import { MenuItem } from ".."
export interface MenuProps {
items: MenuItem[]
}

View File

@@ -0,0 +1,37 @@
"use client"
import { useState } from "react"
import { ChevronDownIcon } from "@/components/Icons"
import Link from "@/components/TempDesignSystem/Link"
import { MenuItemProps } from "./menuItem"
import styles from "./menuItem.module.css"
export default function MenuItem({ item }: MenuItemProps) {
const { children, title, href, seeAllLinkText, infoCard } = item
const [isExpanded, setIsExpanded] = useState(false)
function handleButtonClick() {
setIsExpanded((prev) => !prev)
}
return children?.length ? (
<button
type="button"
className={styles.navigationButton}
onClick={handleButtonClick}
>
{title}
<ChevronDownIcon
className={`${styles.chevron} ${isExpanded ? styles.isExpanded : ""}`}
color="red"
/>
</button>
) : (
<Link href={href} color="burgundy">
{title}
</Link>
)
}

View File

@@ -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);
}

View File

@@ -0,0 +1,5 @@
import { MenuItem } from ".."
export interface MenuItemProps {
item: MenuItem
}

View File

@@ -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);
}

View File

@@ -0,0 +1,6 @@
import { ImageProps } from "next/image"
export interface AvatarProps {
image?: ImageProps
initials?: string
}

View File

@@ -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 = <PersonIcon color="white" />
if (image) {
classNames.push(styles.image)
element = <Image src={image.src} alt={image.alt} width={28} height={28} />
} else if (initials) {
classNames.push(styles.initials)
element = <span>{initials}</span>
}
return <span className={classNames.join(" ")}>{element}</span>
}

View File

@@ -0,0 +1,16 @@
import Link from "next/link"
import Avatar from "./Avatar"
import styles from "./user.module.css"
export default function User() {
return (
<div className={styles.user}>
<Link href="#" className={styles.link}>
<Avatar />
Log in/Join
</Link>
</div>
)
}

View File

@@ -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;
}

View File

@@ -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 (
<div className={styles.mainMenu}>
<nav className={styles.content}>
<Link className={styles.logoLink} href="/">
<Image
alt="Back to scandichotels.com"
className={styles.logo}
data-js="scandiclogoimg"
data-nosvgsrc="/_static/img/scandic-logotype.png"
itemProp="logo"
height={24}
src="/_static/img/scandic-logotype.svg"
width={113}
/>
</Link>
<Menu items={menuItems} />
<User />
</nav>
</div>
)
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -0,0 +1,2 @@
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {}

View File

@@ -0,0 +1,11 @@
import { ButtonProps } from "./button"
import styles from "./button.module.css"
export default function Button({ children, ...props }: ButtonProps) {
return (
<button type="button" className={styles.button} {...props}>
{children}
</button>
)
}

View File

@@ -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 (
<Button onClick={handleButtonClick}>
<GlobeIcon width={20} height={20} color="burgundy" />
<span>English</span>
<ChevronDownIcon
className={`${styles.chevron} ${isExpanded ? styles.isExpanded : ""}`}
width={20}
height={20}
color="burgundy"
/>
</Button>
)
}

View File

@@ -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);
}

View File

@@ -0,0 +1,12 @@
import { SearchIcon } from "@/components/Icons"
import Button from "../Button"
export default function Search() {
return (
<Button>
<SearchIcon width={20} height={20} color="burgundy" />
<span>Find Booking</span>
</Button>
)
}

View File

@@ -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;
}

View File

@@ -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 (
<div className={styles.topMenu}>
<div className={styles.content}>
<Link variant="icon" color="burgundy" href="#" size="small">
<GiftIcon width={20} height={20} color="burgundy" />
Join Scandic Friends
</Link>
<div className={styles.right}>
<LanguageSwitcher />
<Search />
</div>
</div>
</div>
)
}

View File

@@ -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;
}

View File

@@ -0,0 +1,4 @@
.header {
font-family: var(--typography-Body-Regular-fontFamily);
color: var(--Base-Text-High-contrast);
}

View File

@@ -0,0 +1,13 @@
import MainMenu from "./MainMenu"
import TopMenu from "./TopMenu"
import styles from "./header.module.css"
export default async function Header() {
return (
<header className={styles.header}>
<TopMenu />
<MainMenu />
</header>
)
}

36
components/Icons/Gift.tsx Normal file
View File

@@ -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 (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<mask
id="mask0_4278_5138"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_4278_5138)">
<path
d="M4 22V11H2V5H7.2C7.11667 4.85 7.0625 4.69167 7.0375 4.525C7.0125 4.35833 7 4.18333 7 4C7 3.16667 7.29167 2.45833 7.875 1.875C8.45833 1.29167 9.16667 1 10 1C10.3833 1 10.7417 1.07083 11.075 1.2125C11.4083 1.35417 11.7167 1.55 12 1.8C12.2833 1.53333 12.5917 1.33333 12.925 1.2C13.2583 1.06667 13.6167 1 14 1C14.8333 1 15.5417 1.29167 16.125 1.875C16.7083 2.45833 17 3.16667 17 4C17 4.18333 16.9833 4.35417 16.95 4.5125C16.9167 4.67083 16.8667 4.83333 16.8 5H22V11H20V22H4ZM14 3C13.7167 3 13.4792 3.09583 13.2875 3.2875C13.0958 3.47917 13 3.71667 13 4C13 4.28333 13.0958 4.52083 13.2875 4.7125C13.4792 4.90417 13.7167 5 14 5C14.2833 5 14.5208 4.90417 14.7125 4.7125C14.9042 4.52083 15 4.28333 15 4C15 3.71667 14.9042 3.47917 14.7125 3.2875C14.5208 3.09583 14.2833 3 14 3ZM9 4C9 4.28333 9.09583 4.52083 9.2875 4.7125C9.47917 4.90417 9.71667 5 10 5C10.2833 5 10.5208 4.90417 10.7125 4.7125C10.9042 4.52083 11 4.28333 11 4C11 3.71667 10.9042 3.47917 10.7125 3.2875C10.5208 3.09583 10.2833 3 10 3C9.71667 3 9.47917 3.09583 9.2875 3.2875C9.09583 3.47917 9 3.71667 9 4ZM4 7V9H11V7H4ZM11 20V11H6V20H11ZM13 20H18V11H13V20ZM20 9V7H13V9H20Z"
fill="#1C1B1F"
/>
</g>
</svg>
)
}

View File

@@ -7,28 +7,28 @@ export default function PersonIcon({ className, color, ...props }: IconProps) {
return (
<svg
className={classNames}
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<mask
id="mask0_1572_4293"
id="mask0_69_3302"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="20"
height="20"
width="24"
height="24"
>
<rect width="20" height="20" fill="#D9D9D9" />
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_1572_4293)">
<g mask="url(#mask0_69_3302)">
<path
d="M10 10C9.16667 10 8.45833 9.70833 7.875 9.125C7.29167 8.54167 7 7.83333 7 7C7 6.16667 7.29167 5.45833 7.875 4.875C8.45833 4.29167 9.16667 4 10 4C10.8333 4 11.5417 4.29167 12.125 4.875C12.7083 5.45833 13 6.16667 13 7C13 7.83333 12.7083 8.54167 12.125 9.125C11.5417 9.70833 10.8333 10 10 10ZM4 14.5V14C4 13.6806 4.08681 13.3785 4.26042 13.0938C4.43403 12.809 4.67361 12.5694 4.97917 12.375C5.74306 11.9306 6.55064 11.5903 7.40192 11.3542C8.25321 11.1181 9.11779 11 9.99567 11C10.8736 11 11.7396 11.1181 12.5938 11.3542C13.4479 11.5903 14.2569 11.9306 15.0208 12.375C15.3264 12.5556 15.566 12.7917 15.7396 13.0833C15.9132 13.375 16 13.6806 16 14V14.5C16 14.9125 15.853 15.2656 15.5591 15.5594C15.2652 15.8531 14.9119 16 14.4992 16H5.4941C5.08137 16 4.72917 15.8531 4.4375 15.5594C4.14583 15.2656 4 14.9125 4 14.5ZM5.5 14.5H14.5V14C14.5 13.9281 14.479 13.8628 14.437 13.8039C14.395 13.7451 14.3396 13.6993 14.2708 13.6667C13.6319 13.2778 12.9514 12.9861 12.2292 12.7917C11.5069 12.5972 10.7639 12.5 10 12.5C9.23611 12.5 8.49306 12.5972 7.77083 12.7917C7.04861 12.9861 6.36806 13.2778 5.72917 13.6667C5.65972 13.7222 5.60417 13.7759 5.5625 13.8276C5.52083 13.8793 5.5 13.9368 5.5 14V14.5ZM10.0044 8.5C10.4181 8.5 10.7708 8.35269 11.0625 8.05808C11.3542 7.76346 11.5 7.40929 11.5 6.99558C11.5 6.58186 11.3527 6.22917 11.0581 5.9375C10.7635 5.64583 10.4093 5.5 9.99558 5.5C9.58186 5.5 9.22917 5.64731 8.9375 5.94192C8.64583 6.23654 8.5 6.59071 8.5 7.00442C8.5 7.41814 8.64731 7.77083 8.94192 8.0625C9.23654 8.35417 9.59071 8.5 10.0044 8.5Z"
fill="#1C1B1F"
d="M12 12.025C10.925 12.025 10.0104 11.6479 9.25623 10.8938C8.50206 10.1396 8.12498 9.22503 8.12498 8.15002C8.12498 7.07502 8.50206 6.16044 9.25623 5.40627C10.0104 4.65211 10.925 4.27502 12 4.27502C13.075 4.27502 13.9896 4.65211 14.7437 5.40627C15.4979 6.16044 15.875 7.07502 15.875 8.15002C15.875 9.22503 15.4979 10.1396 14.7437 10.8938C13.9896 11.6479 13.075 12.025 12 12.025ZM4.22498 17.8V17.0243C4.22498 16.4748 4.36456 15.9729 4.64373 15.5188C4.92289 15.0646 5.30016 14.7166 5.77553 14.4748C6.79183 13.9749 7.81873 13.5979 8.85623 13.3438C9.89373 13.0896 10.9416 12.9625 12 12.9625C13.0583 12.9625 14.1062 13.0896 15.1437 13.3438C16.1812 13.5979 17.2081 13.9749 18.2244 14.4748C18.6998 14.7166 19.0771 15.0646 19.3562 15.5188C19.6354 15.9729 19.775 16.4748 19.775 17.0243V17.8C19.775 18.3157 19.5914 18.7571 19.2242 19.1243C18.857 19.4914 18.4156 19.675 17.9 19.675H6.09998C5.58434 19.675 5.14293 19.4914 4.77575 19.1243C4.40857 18.7571 4.22498 18.3157 4.22498 17.8ZM6.09998 17.8H17.9V17.025C17.9 16.8385 17.8541 16.669 17.7625 16.5164C17.6708 16.3638 17.5458 16.2459 17.3875 16.1625C16.4958 15.7209 15.6027 15.3896 14.7081 15.1688C13.8135 14.9479 12.9108 14.8375 12 14.8375C11.0833 14.8375 10.1791 14.9479 9.28748 15.1688C8.39581 15.3896 7.50414 15.7209 6.61248 16.1625C6.45414 16.2459 6.32914 16.3638 6.23748 16.5164C6.14581 16.669 6.09998 16.8385 6.09998 17.025V17.8ZM12 10.15C12.55 10.15 13.0208 9.95419 13.4125 9.56252C13.8041 9.17086 14 8.70003 14 8.15002C14 7.60002 13.8041 7.12919 13.4125 6.73752C13.0208 6.34586 12.55 6.15002 12 6.15002C11.45 6.15002 10.9791 6.34586 10.5875 6.73752C10.1958 7.12919 9.99998 7.60002 9.99998 8.15002C9.99998 8.70003 10.1958 9.17086 10.5875 9.56252C10.9791 9.95419 11.45 10.15 12 10.15Z"
fill="#26201E"
/>
</g>
</svg>

View File

@@ -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 (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<mask
id="mask0_69_3280"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3280)">
<path
d="M9.54147 15.85C7.77261 15.85 6.27557 15.2379 5.05035 14.0137C3.82513 12.7894 3.21252 11.2936 3.21252 9.52616C3.21252 7.75873 3.82464 6.26251 5.04887 5.03751C6.27311 3.81251 7.76894 3.20001 9.53637 3.20001C11.3038 3.20001 12.8 3.81262 14.025 5.03784C15.25 6.26305 15.8625 7.7601 15.8625 9.52896C15.8625 10.243 15.75 10.9229 15.525 11.5688C15.3 12.2146 14.9917 12.7833 14.6 13.275L20.1404 18.8035C20.3218 18.9845 20.4125 19.2042 20.4125 19.4625C20.4125 19.7208 20.3209 19.9417 20.1375 20.125C19.9542 20.3083 19.7334 20.4 19.475 20.4C19.2167 20.4 18.9969 20.3094 18.8156 20.1283L13.279 14.5875C12.7847 14.9875 12.2167 15.2979 11.575 15.5188C10.9334 15.7396 10.2555 15.85 9.54147 15.85ZM9.53752 13.975C10.7792 13.975 11.8313 13.5438 12.6938 12.6813C13.5563 11.8188 13.9875 10.7667 13.9875 9.52501C13.9875 8.28335 13.5563 7.23126 12.6938 6.36876C11.8313 5.50626 10.7792 5.07501 9.53752 5.07501C8.29586 5.07501 7.24377 5.50626 6.38127 6.36876C5.51877 7.23126 5.08752 8.28335 5.08752 9.52501C5.08752 10.7667 5.51877 11.8188 6.38127 12.6813C7.24377 13.5438 8.29586 13.975 9.53752 13.975Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -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<IconProps> | 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<IconProps> | null {
return RestaurantIcon
case IconName.Sauna:
return SaunaIcon
case IconName.Search:
return SearchIcon
case IconName.Tripadvisor:
return TripAdvisorIcon
case IconName.TshirtWash:

View File

@@ -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"

View File

@@ -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",