feat(SW-184): implementing mobile design
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
6
components/Header/HeaderLink/headerLink.module.css
Normal file
6
components/Header/HeaderLink/headerLink.module.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.topLink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x1);
|
||||
font-size: var(--typography-Caption-Regular-fontSize);
|
||||
}
|
||||
13
components/Header/HeaderLink/index.tsx
Normal file
13
components/Header/HeaderLink/index.tsx
Normal file
@@ -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 (
|
||||
<Link color="burgundy" className={styles.topLink} {...props}>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
65
components/Header/MainMenu/MobileMenu/index.tsx
Normal file
65
components/Header/MainMenu/MobileMenu/index.tsx
Normal file
@@ -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 (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.hamburger} ${isHamburgerMenuOpen ? styles.isExpanded : ""}`}
|
||||
aria-pressed="false"
|
||||
aria-label={intl.formatMessage({ id: "Menu" })}
|
||||
onClick={toggleHamburgerMenu}
|
||||
>
|
||||
<span className={styles.bar}></span>
|
||||
</button>
|
||||
<Modal
|
||||
className={styles.modal}
|
||||
isOpen={isHamburgerMenuOpen}
|
||||
onOpenChange={toggleHamburgerMenu}
|
||||
>
|
||||
<Dialog
|
||||
className={styles.dialog}
|
||||
aria-label={intl.formatMessage({ id: "Menu" })}
|
||||
>
|
||||
<NavigationMenu variant="mobile" items={mainNavigation} />
|
||||
<footer className={styles.footer}>
|
||||
<HeaderLink href="#">
|
||||
<SearchIcon width={20} height={20} color="burgundy" />
|
||||
{intl.formatMessage({ id: "Find booking" })}
|
||||
</HeaderLink>
|
||||
<HeaderLink href="#">
|
||||
<GiftIcon width={20} height={20} color="burgundy" />
|
||||
{intl.formatMessage({ id: "Join Scandic Friends" })}
|
||||
</HeaderLink>
|
||||
<HeaderLink href="#">
|
||||
<ServiceIcon width={20} height={20} color="burgundy" />
|
||||
{intl.formatMessage({ id: "Customer service" })}
|
||||
</HeaderLink>
|
||||
<LanguageSwitcher urls={languageUrls} />
|
||||
</footer>
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
109
components/Header/MainMenu/MobileMenu/mobileMenu.module.css
Normal file
109
components/Header/MainMenu/MobileMenu/mobileMenu.module.css
Normal file
@@ -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);
|
||||
}
|
||||
@@ -35,7 +35,9 @@ export default function MyPagesMenu({ navigation, user }: MyPagesMenuProps) {
|
||||
<div className={styles.myPagesMenu}>
|
||||
<MainMenuButton className={styles.button} onClick={toggleMyPagesMenu}>
|
||||
<Avatar initials={getInitials(user.firstName, user.lastName)} />
|
||||
{intl.formatMessage({ id: "Hi" })} {user.firstName}!
|
||||
<span className={styles.userName}>
|
||||
{intl.formatMessage({ id: "Hi" })} {user.firstName}!
|
||||
</span>
|
||||
<ChevronDownIcon
|
||||
className={`${styles.chevron} ${isMyPagesMenuOpen ? styles.isExpanded : ""}`}
|
||||
color="red"
|
||||
@@ -98,9 +100,15 @@ export default function MyPagesMenu({ navigation, user }: MyPagesMenuProps) {
|
||||
</nav>
|
||||
</div>
|
||||
) : (
|
||||
<Link href={myPages[lang]} className={styles.link}>
|
||||
<Link
|
||||
href={myPages[lang]}
|
||||
className={styles.loginLink}
|
||||
aria-label={intl.formatMessage({ id: "Log in/Join" })}
|
||||
>
|
||||
<Avatar />
|
||||
{intl.formatMessage({ id: "Log in/Join" })}
|
||||
<span className={styles.userName}>
|
||||
{intl.formatMessage({ id: "Log in/Join" })}
|
||||
</span>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ? (
|
||||
<MainMenuButton onClick={handleButtonClick}>
|
||||
<MainMenuButton
|
||||
onClick={handleButtonClick}
|
||||
className={navigationMenuItemVariants({ variant })}
|
||||
>
|
||||
{title}
|
||||
<ChevronDownIcon
|
||||
className={`${styles.chevron} ${isExpanded ? styles.isExpanded : ""}`}
|
||||
color="red"
|
||||
/>
|
||||
{isMobile ? (
|
||||
<ChevronRightIcon className={`${styles.chevron}`} color="red" />
|
||||
) : (
|
||||
<ChevronDownIcon
|
||||
className={`${styles.chevron} ${isExpanded ? styles.isExpanded : ""}`}
|
||||
color="red"
|
||||
/>
|
||||
)}
|
||||
</MainMenuButton>
|
||||
) : (
|
||||
<Link href={href} color="burgundy">
|
||||
<Link
|
||||
href={href}
|
||||
color="burgundy"
|
||||
className={navigationMenuItemVariants({ variant })}
|
||||
>
|
||||
{title}
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
})
|
||||
@@ -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 (
|
||||
<ul className={styles.navigationMenu}>
|
||||
<ul className={navigationMenuVariants({ variant })}>
|
||||
{items.map((item) => (
|
||||
<li key={item.id}>
|
||||
<NavigationMenuItem item={item} />
|
||||
<li key={item.id} className={styles.item}>
|
||||
<NavigationMenuItem variant={variant} item={item} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
15
components/Header/MainMenu/NavigationMenu/variants.ts
Normal file
15
components/Header/MainMenu/NavigationMenu/variants.ts
Normal file
@@ -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",
|
||||
},
|
||||
})
|
||||
@@ -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}
|
||||
/>
|
||||
</Link>
|
||||
<NavigationMenu items={navigationMenuItems} />
|
||||
<MyPagesMenu navigation={myPagesNavigation} user={user} />
|
||||
<div className={styles.menus}>
|
||||
<NavigationMenu items={navigationMenuItems} />
|
||||
<MyPagesMenu navigation={myPagesNavigation} user={user} />
|
||||
<MobileMenu
|
||||
languageUrls={languageUrls}
|
||||
mainNavigation={navigationMenuItems}
|
||||
/>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<TopMenuButton>
|
||||
<button type="button" className={styles.button}>
|
||||
<SearchIcon width={20} height={20} color="burgundy" />
|
||||
{intl.formatMessage({ id: "Find booking" })}
|
||||
</TopMenuButton>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<button type="button" className={styles.button} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 (
|
||||
<div className={styles.topMenu}>
|
||||
<div className={styles.content}>
|
||||
<Link variant="icon" color="burgundy" href="#" size="small">
|
||||
<HeaderLink href="#">
|
||||
<GiftIcon width={20} height={20} color="burgundy" />
|
||||
{intl.formatMessage({ id: "Join Scandic Friends" })}
|
||||
</Link>
|
||||
</HeaderLink>
|
||||
<div className={styles.right}>
|
||||
<LanguageSwitcher urls={languages.urls} />
|
||||
<Search />
|
||||
<LanguageSwitcher urls={languageUrls} />
|
||||
<HeaderLink href="#">
|
||||
<SearchIcon width={20} height={20} color="burgundy" />
|
||||
{intl.formatMessage({ id: "Find booking" })}
|
||||
</HeaderLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<header className={styles.header}>
|
||||
<TopMenu />
|
||||
<MainMenu />
|
||||
<TopMenu languageUrls={languages.urls} />
|
||||
<MainMenu languageUrls={languages.urls} />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,32 +11,29 @@ export default function ChevronRightIcon({
|
||||
return (
|
||||
<svg
|
||||
className={classNames}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<g id="chevron_right_small">
|
||||
<mask
|
||||
id="mask0_4140_3161"
|
||||
style={{ maskType: "alpha" }}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="20"
|
||||
height="20"
|
||||
>
|
||||
<rect id="Bounding box" width="20" height="20" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_4140_3161)">
|
||||
<path
|
||||
id="Vector"
|
||||
d="M10.5417 10.0001L7.3125 6.77095C7.16667 6.62512 7.09375 6.4463 7.09375 6.23449C7.09375 6.02269 7.16667 5.84039 7.3125 5.68762C7.45833 5.53484 7.63889 5.45671 7.85417 5.45324C8.06944 5.44977 8.25347 5.52442 8.40625 5.6772L12.1771 9.44803C12.2535 9.52442 12.3108 9.60949 12.349 9.70324C12.3872 9.79699 12.4063 9.89595 12.4063 10.0001C12.4063 10.1043 12.3872 10.2032 12.349 10.297C12.3108 10.3907 12.2535 10.4758 12.1771 10.5522L8.40625 14.323C8.25347 14.4758 8.06944 14.5505 7.85417 14.547C7.63889 14.5435 7.45833 14.4654 7.3125 14.3126C7.16667 14.1598 7.09375 13.9775 7.09375 13.7657C7.09375 13.5539 7.16667 13.3751 7.3125 13.2293L10.5417 10.0001Z"
|
||||
fill="#8F4350"
|
||||
/>
|
||||
</g>
|
||||
<mask
|
||||
id="mask0_2291_1656"
|
||||
style={{ maskType: "alpha" }}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="20"
|
||||
height="20"
|
||||
>
|
||||
<rect width="20" height="20" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_2291_1656)">
|
||||
<path
|
||||
d="M12.1042 9.99967L6.28125 4.17676C6.10069 3.9962 6.00868 3.77572 6.00521 3.5153C6.00174 3.25488 6.09028 3.0344 6.27083 2.85384C6.45139 2.67329 6.67188 2.58301 6.93229 2.58301C7.19271 2.58301 7.41319 2.67329 7.59375 2.85384L13.7708 9.02051C13.9167 9.16634 14.0191 9.31912 14.0781 9.47884C14.1372 9.63856 14.1667 9.81217 14.1667 9.99967C14.1667 10.1872 14.1372 10.3608 14.0781 10.5205C14.0191 10.6802 13.9167 10.833 13.7708 10.9788L7.60417 17.1455C7.42361 17.3261 7.20139 17.4181 6.9375 17.4215C6.67361 17.425 6.45139 17.3365 6.27083 17.1559C6.09028 16.9754 6 16.7549 6 16.4945C6 16.234 6.09028 16.0136 6.27083 15.833L12.1042 9.99967Z"
|
||||
fill="#CD0921"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -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 (
|
||||
<svg
|
||||
|
||||
36
components/Icons/Service.tsx
Normal file
36
components/Icons/Service.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { iconVariants } from "./variants"
|
||||
|
||||
import type { IconProps } from "@/types/components/icon"
|
||||
|
||||
export default function ServiceIcon({ className, color, ...props }: IconProps) {
|
||||
const classNames = iconVariants({ className, color })
|
||||
return (
|
||||
<svg
|
||||
className={classNames}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<mask
|
||||
id="mask0_2291_1682"
|
||||
style={{ maskType: "alpha" }}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="20"
|
||||
height="20"
|
||||
>
|
||||
<rect width="20" height="20" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_2291_1682)">
|
||||
<path
|
||||
d="M15.792 18.3337C14.0559 18.3337 12.3406 17.9552 10.6462 17.1982C8.95171 16.4413 7.41005 15.3684 6.02116 13.9795C4.63227 12.5906 3.55935 11.0489 2.80241 9.35449C2.04546 7.66005 1.66699 5.94477 1.66699 4.20866C1.66699 3.95866 1.75033 3.75033 1.91699 3.58366C2.08366 3.41699 2.29199 3.33366 2.54199 3.33366H5.91699C6.11144 3.33366 6.28505 3.39963 6.43783 3.53158C6.5906 3.66352 6.68088 3.81977 6.70866 4.00033L7.25033 6.91699C7.2781 7.13921 7.27116 7.32671 7.22949 7.47949C7.18783 7.63227 7.11144 7.76421 7.00033 7.87533L4.97949 9.91699C5.25727 10.4309 5.58713 10.9274 5.96908 11.4066C6.35102 11.8857 6.77116 12.3475 7.22949 12.792C7.66005 13.2225 8.11144 13.6219 8.58366 13.9899C9.05588 14.358 9.55588 14.6948 10.0837 15.0003L12.042 13.042C12.167 12.917 12.3302 12.8232 12.5316 12.7607C12.733 12.6982 12.9309 12.6809 13.1253 12.7087L16.0003 13.292C16.1948 13.3475 16.3545 13.4482 16.4795 13.5941C16.6045 13.7399 16.667 13.9031 16.667 14.0837V17.4587C16.667 17.7087 16.5837 17.917 16.417 18.0837C16.2503 18.2503 16.042 18.3337 15.792 18.3337ZM4.18783 8.33366L5.56283 6.95866L5.20866 5.00033H3.35449C3.42394 5.56977 3.52116 6.13227 3.64616 6.68783C3.77116 7.24338 3.95171 7.79199 4.18783 8.33366ZM11.6462 15.792C12.1878 16.0281 12.7399 16.2156 13.3024 16.3545C13.8649 16.4934 14.4309 16.5837 15.0003 16.6253V14.792L13.042 14.3962L11.6462 15.792ZM14.167 10.0003C13.0142 10.0003 12.0316 9.59408 11.2191 8.78158C10.4066 7.96908 10.0003 6.98644 10.0003 5.83366C10.0003 4.68088 10.4066 3.69824 11.2191 2.88574C12.0316 2.07324 13.0142 1.66699 14.167 1.66699C15.3198 1.66699 16.3024 2.07324 17.1149 2.88574C17.9274 3.69824 18.3337 4.68088 18.3337 5.83366C18.3337 6.98644 17.9274 7.96908 17.1149 8.78158C16.3024 9.59408 15.3198 10.0003 14.167 10.0003ZM13.7503 8.33366H14.5837V5.00033H13.7503V8.33366ZM14.167 4.16699C14.2781 4.16699 14.3753 4.12533 14.4587 4.04199C14.542 3.95866 14.5837 3.86144 14.5837 3.75033C14.5837 3.63921 14.542 3.54199 14.4587 3.45866C14.3753 3.37533 14.2781 3.33366 14.167 3.33366C14.0559 3.33366 13.9587 3.37533 13.8753 3.45866C13.792 3.54199 13.7503 3.63921 13.7503 3.75033C13.7503 3.86144 13.792 3.95866 13.8753 4.04199C13.9587 4.12533 14.0559 4.16699 14.167 4.16699Z"
|
||||
fill="#4D001B"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
RestaurantIcon,
|
||||
SaunaIcon,
|
||||
SearchIcon,
|
||||
ServiceIcon,
|
||||
TshirtWashIcon,
|
||||
WarningTriangle,
|
||||
WifiIcon,
|
||||
@@ -128,6 +129,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
|
||||
return SaunaIcon
|
||||
case IconName.Search:
|
||||
return SearchIcon
|
||||
case IconName.Service:
|
||||
return ServiceIcon
|
||||
case IconName.Tripadvisor:
|
||||
return TripAdvisorIcon
|
||||
case IconName.TshirtWash:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 (
|
||||
<div className={styles.languageSwitcher}>
|
||||
<TopMenuButton onClick={toggleLanguageSwitcher}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.button}
|
||||
onClick={toggleLanguageSwitcher}
|
||||
>
|
||||
<GlobeIcon width={20} height={20} color="burgundy" />
|
||||
<span>{languages[currentLanguage]}</span>
|
||||
<ChevronDownIcon
|
||||
@@ -31,7 +33,8 @@ export default function LanguageSwitcher({ urls }: LanguageSwitcherProps) {
|
||||
height={20}
|
||||
color="burgundy"
|
||||
/>
|
||||
</TopMenuButton>
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={`${styles.dropdown} ${isLanguageSwitcherOpen ? styles.isExpanded : ""}`}
|
||||
>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -19,13 +19,7 @@ const useDropdownStore = create<DropdownState>((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
|
||||
|
||||
3
types/components/header/headerLink.ts
Normal file
3
types/components/header/headerLink.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { LinkProps } from "@/components/TempDesignSystem/Link/link"
|
||||
|
||||
export interface HeaderLinkProps extends React.PropsWithChildren<LinkProps> {}
|
||||
5
types/components/header/mainMenu.ts
Normal file
5
types/components/header/mainMenu.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
|
||||
export interface MainMenuProps {
|
||||
languageUrls: LanguageSwitcherData
|
||||
}
|
||||
8
types/components/header/mobileMenu.ts
Normal file
8
types/components/header/mobileMenu.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { MainNavigationItem } from "./mainNavigationItem"
|
||||
|
||||
import { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
|
||||
export interface MobileMenuProps {
|
||||
languageUrls: LanguageSwitcherData
|
||||
mainNavigation: MainNavigationItem[]
|
||||
}
|
||||
@@ -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<typeof navigationMenuVariants> {
|
||||
items: MainNavigationItem[]
|
||||
}
|
||||
|
||||
@@ -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<typeof navigationMenuItemVariants> {
|
||||
item: MainNavigationItem
|
||||
}
|
||||
|
||||
5
types/components/header/topMenu.ts
Normal file
5
types/components/header/topMenu.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
|
||||
export interface TopMenuProps {
|
||||
languageUrls: LanguageSwitcherData
|
||||
}
|
||||
@@ -46,6 +46,7 @@ export enum IconName {
|
||||
Restaurant = "Restaurant",
|
||||
Sauna = "Sauna",
|
||||
Search = "Search",
|
||||
Service = "Service",
|
||||
Tripadvisor = "Tripadvisor",
|
||||
TshirtWash = "TshirtWash",
|
||||
Wifi = "Wifi",
|
||||
|
||||
Reference in New Issue
Block a user