feat(SW-184): language switcher mobile/desktop functionality
This commit is contained in:
@@ -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 (
|
||||
<>
|
||||
<button
|
||||
@@ -33,11 +40,7 @@ export default function MobileMenu({
|
||||
>
|
||||
<span className={styles.bar}></span>
|
||||
</button>
|
||||
<Modal
|
||||
className={styles.modal}
|
||||
isOpen={isHamburgerMenuOpen}
|
||||
onOpenChange={toggleHamburgerMenu}
|
||||
>
|
||||
<Modal className={styles.modal} isOpen={isHamburgerMenuOpen}>
|
||||
<Dialog
|
||||
className={styles.dialog}
|
||||
aria-label={intl.formatMessage({ id: "Menu" })}
|
||||
|
||||
@@ -80,11 +80,12 @@
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
right: auto;
|
||||
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] {
|
||||
|
||||
+11
-11
@@ -7,27 +7,27 @@ export default function CheckIcon({ className, color, ...props }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
className={classNames}
|
||||
fill="none"
|
||||
height="25"
|
||||
viewBox="0 0 24 25"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<mask
|
||||
height="25"
|
||||
id="mask0_1333_19690"
|
||||
maskUnits="userSpaceOnUse"
|
||||
id="mask0_2570_1776"
|
||||
style={{ maskType: "alpha" }}
|
||||
width="24"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
>
|
||||
<rect y="0.629639" width="24" height="24" fill="#D9D9D9" />
|
||||
<rect width="24" height="24" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_1333_19690)">
|
||||
<g mask="url(#mask0_2570_1776)">
|
||||
<path
|
||||
d="M9.99967 16.6738L6.35547 13.0296L7.39967 11.9854L9.99967 14.5854L16.5997 7.98535L17.6439 9.02955L9.99967 16.6738Z"
|
||||
d="M9.57552 15.1752L17.9505 6.8002C18.1422 6.60853 18.3651 6.5127 18.6193 6.5127C18.8734 6.5127 19.0964 6.60853 19.288 6.8002C19.4797 6.99186 19.5755 7.21478 19.5755 7.46895C19.5755 7.72311 19.4797 7.94603 19.288 8.1377L10.238 17.1877C10.0464 17.3794 9.82552 17.4752 9.57552 17.4752C9.32552 17.4752 9.10469 17.3794 8.91302 17.1877L4.71302 12.9877C4.52136 12.796 4.42761 12.5731 4.43177 12.3189C4.43594 12.0648 4.53386 11.8419 4.72552 11.6502C4.91719 11.4585 5.14011 11.3627 5.39427 11.3627C5.64844 11.3627 5.87136 11.4585 6.06302 11.6502L9.57552 15.1752Z"
|
||||
fill="#4D001B"
|
||||
/>
|
||||
</g>
|
||||
|
||||
@@ -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 (
|
||||
<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_1760"
|
||||
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_1760)">
|
||||
<path
|
||||
d="M7.75 10.0001L13.5729 15.823C13.7535 16.0036 13.8455 16.2258 13.849 16.4897C13.8524 16.7536 13.7639 16.9758 13.5833 17.1563C13.4028 17.3369 13.1806 17.4272 12.9167 17.4272C12.6528 17.4272 12.4306 17.3369 12.25 17.1563L6.08333 10.9793C5.9375 10.8334 5.83507 10.6807 5.77604 10.5209C5.71701 10.3612 5.6875 10.1876 5.6875 10.0001C5.6875 9.8126 5.71701 9.63899 5.77604 9.47927C5.83507 9.31954 5.9375 9.16677 6.08333 9.02093L12.25 2.85426C12.4306 2.67371 12.651 2.5817 12.9115 2.57822C13.1719 2.57475 13.3924 2.66329 13.5729 2.84385C13.7535 3.0244 13.8438 3.24663 13.8438 3.51051C13.8438 3.7744 13.7535 3.99663 13.5729 4.17718L7.75 10.0001Z"
|
||||
fill="#CD0921"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
CheckCircleIcon,
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
CloseIcon,
|
||||
CloseLarge,
|
||||
@@ -75,6 +76,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
|
||||
return CheckCircleIcon
|
||||
case IconName.ChevronDown:
|
||||
return ChevronDownIcon
|
||||
case IconName.ChevronLeft:
|
||||
return ChevronLeftIcon
|
||||
case IconName.ChevronRight:
|
||||
return ChevronRightIcon
|
||||
case IconName.Close:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 (
|
||||
<div className={styles.languageSwitcher}>
|
||||
<div className={styles.languageSwitcher} ref={languageSwitcherRef}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.button}
|
||||
aria-label={intl.formatMessage({
|
||||
id: isLanguageSwitcherOpen
|
||||
? "Close language menu"
|
||||
: "Open language menu",
|
||||
})}
|
||||
onClick={toggleLanguageSwitcher}
|
||||
>
|
||||
<GlobeIcon width={20} height={20} color="burgundy" />
|
||||
@@ -38,26 +64,42 @@ export default function LanguageSwitcher({ urls }: LanguageSwitcherProps) {
|
||||
<div
|
||||
className={`${styles.dropdown} ${isLanguageSwitcherOpen ? styles.isExpanded : ""}`}
|
||||
>
|
||||
<ul className={styles.list}>
|
||||
{urlKeys.map((key) => {
|
||||
const url = urls[key]?.url
|
||||
const isActive = currentLanguage === key
|
||||
if (url) {
|
||||
return (
|
||||
<li key={key}>
|
||||
<Link
|
||||
className={`${styles.link} ${isActive ? styles.active : ""}`}
|
||||
color="burgundy"
|
||||
href={url}
|
||||
>
|
||||
{languages[key]}
|
||||
{isActive ? <CheckIcon color="burgundy" /> : null}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
<div className={styles.backWrapper}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.backButton}
|
||||
onClick={toggleLanguageSwitcher}
|
||||
>
|
||||
<ChevronLeftIcon color="red" />
|
||||
<Subtitle type="one">Main Menu</Subtitle>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.languageWrapper}>
|
||||
<Subtitle className={styles.subtitle} type="two">
|
||||
{intl.formatMessage({ id: "Select your language" })}
|
||||
</Subtitle>
|
||||
<ul className={styles.list}>
|
||||
{urlKeys.map((key) => {
|
||||
const url = urls[key]?.url
|
||||
const isActive = currentLanguage === key
|
||||
if (url) {
|
||||
return (
|
||||
<li key={key}>
|
||||
<Link
|
||||
className={`${styles.link} ${isActive ? styles.active : ""}`}
|
||||
color="burgundy"
|
||||
href={url}
|
||||
>
|
||||
{languages[key]}
|
||||
{isActive ? <CheckIcon color="burgundy" /> : null}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user