Merged in feat/SW-2782-create-sas-branded-header (pull request #2878)
feat(SW-2782): Updated header as per design, added language switcher and user menu * feat(SW-2782): Updated header as per design, added language switcher and user menu * feat(SW-2782): Updated UI as per design * feat(SW-2782): Optimised code with use of Popover and modal from RAC Approved-by: Anton Gunnarsson
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
"use client"
|
||||
|
||||
import { usePathname } from "next/navigation"
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
Popover,
|
||||
} from "react-aria-components"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import {
|
||||
type Lang,
|
||||
languages,
|
||||
type LanguageSwitcherData,
|
||||
} from "@scandic-hotels/common/constants/language"
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import Link from "@scandic-hotels/design-system/Link"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import styles from "./languageSwitcher.module.css"
|
||||
|
||||
type LanguageSwitcherProps = {
|
||||
currentLanguage: Lang
|
||||
isMobile?: boolean
|
||||
}
|
||||
|
||||
export function replaceUrlPart(currentPath: string, newPart: string): string {
|
||||
const pathSegments = currentPath.split("/").filter((segment) => segment)
|
||||
|
||||
const newPathSegments = newPart
|
||||
.replace(/\/$/, "")
|
||||
.split("/")
|
||||
.filter((segment) => segment)
|
||||
|
||||
const isFullPathReplacement = newPathSegments.length > 1
|
||||
|
||||
if (isFullPathReplacement) {
|
||||
return `/${newPathSegments.join("/")}`
|
||||
}
|
||||
|
||||
const updatedPathSegments = pathSegments.slice(1)
|
||||
const updatedPath = `/${newPathSegments.concat(updatedPathSegments).join("/")}`
|
||||
|
||||
return updatedPath
|
||||
}
|
||||
|
||||
export function LanguageSwitcher({
|
||||
currentLanguage,
|
||||
isMobile = false,
|
||||
}: LanguageSwitcherProps) {
|
||||
return (
|
||||
<div className={styles.languageSwitcher}>
|
||||
<DialogTrigger>
|
||||
<Button className={styles.triggerButton} variant={"Text"} wrapping>
|
||||
{isMobile ? null : (
|
||||
<MaterialIcon
|
||||
icon="globe"
|
||||
size={16}
|
||||
color={"Icon/Inverted"}
|
||||
className={styles.globeIcon}
|
||||
/>
|
||||
)}
|
||||
<Typography
|
||||
variant={
|
||||
isMobile
|
||||
? "Body/Paragraph/mdRegular"
|
||||
: "Body/Supporting text (caption)/smRegular"
|
||||
}
|
||||
>
|
||||
<span className={styles.triggerText}>
|
||||
{languages[currentLanguage]}
|
||||
<MaterialIcon
|
||||
className={styles.chevron}
|
||||
icon="keyboard_arrow_down"
|
||||
size={isMobile ? 24 : 20}
|
||||
color={isMobile ? "Icon/Default" : "Icon/Inverted"}
|
||||
/>
|
||||
</span>
|
||||
</Typography>
|
||||
</Button>
|
||||
{isMobile ? (
|
||||
<ModalOverlay isDismissable className={styles.languageModalOverlay}>
|
||||
<Modal className={styles.languageModal}>
|
||||
<Dialog>
|
||||
{({ close }) => (
|
||||
<LanguageSwitcherContent
|
||||
closeModal={close}
|
||||
isMobile={true}
|
||||
currentLanguage={currentLanguage}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalOverlay>
|
||||
) : (
|
||||
<Popover offset={28} className={styles.languageSwitcherPopover}>
|
||||
<Dialog>
|
||||
{({ close }) => (
|
||||
<LanguageSwitcherContent
|
||||
closeModal={close}
|
||||
isMobile={false}
|
||||
currentLanguage={currentLanguage}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</Popover>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function LanguageSwitcherContent({
|
||||
closeModal,
|
||||
currentLanguage,
|
||||
isMobile,
|
||||
}: {
|
||||
closeModal: () => void
|
||||
currentLanguage: Lang
|
||||
isMobile?: boolean
|
||||
}) {
|
||||
const intl = useIntl()
|
||||
const pathname = usePathname()
|
||||
|
||||
const urls: LanguageSwitcherData = {
|
||||
da: { url: "/da/" },
|
||||
de: { url: "/de/" },
|
||||
en: { url: "/en/" },
|
||||
fi: { url: "/fi/" },
|
||||
no: { url: "/no/" },
|
||||
sv: { url: "/sv/" },
|
||||
}
|
||||
|
||||
const urlKeys = (Object.keys(urls) as (keyof typeof urls)[]).sort((a, b) => {
|
||||
return languages[a].localeCompare(languages[b])
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={styles.languageSwitcherContent}>
|
||||
{isMobile ? (
|
||||
<>
|
||||
<Button
|
||||
variant={"Text"}
|
||||
size={"Medium"}
|
||||
onPress={closeModal}
|
||||
className={styles.arrowBack}
|
||||
>
|
||||
<MaterialIcon
|
||||
icon="chevron_left"
|
||||
size={28}
|
||||
className={styles.arrowBackIcon}
|
||||
color={"CurrentColor"}
|
||||
/>
|
||||
</Button>
|
||||
<Typography variant={"Title/Subtitle/md"}>
|
||||
<h3 className={styles.title}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Select your language",
|
||||
})}
|
||||
</h3>
|
||||
</Typography>
|
||||
</>
|
||||
) : null}
|
||||
<ul className={styles.languageSwitcherListContainer}>
|
||||
{urlKeys.map((key) => {
|
||||
const url = urls[key]?.url
|
||||
const isActive = currentLanguage === key
|
||||
|
||||
if (url) {
|
||||
return (
|
||||
<li key={key} className={styles.languageSwitcherListItem}>
|
||||
<Typography
|
||||
variant={
|
||||
isActive
|
||||
? "Body/Paragraph/mdBold"
|
||||
: "Body/Paragraph/mdRegular"
|
||||
}
|
||||
>
|
||||
<Link
|
||||
className={styles.link}
|
||||
href={replaceUrlPart(pathname, url)}
|
||||
variant="languageSwitcher"
|
||||
keepSearchParams
|
||||
>
|
||||
{languages[key]}
|
||||
{isActive ? (
|
||||
<MaterialIcon
|
||||
icon="check"
|
||||
color="Icon/Interactive/Default"
|
||||
/>
|
||||
) : null}
|
||||
</Link>
|
||||
</Typography>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
.languageSwitcher {
|
||||
.triggerButton {
|
||||
gap: var(--Space-x1);
|
||||
padding: var(--Space-x1);
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
border: 0 none;
|
||||
}
|
||||
|
||||
.triggerText {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
color: var(--Text-sas-20);
|
||||
}
|
||||
}
|
||||
|
||||
.languageSwitcherContent {
|
||||
background: white;
|
||||
gap: var(--Space-x3);
|
||||
padding: 0 var(--Space-x2);
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
.arrowBack {
|
||||
color: var(--TEMP-sas-40);
|
||||
padding: var(--Space-x2) 0;
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.languageSwitcherListItem .link {
|
||||
padding: var(--Space-x1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: var(--Space-x1);
|
||||
}
|
||||
|
||||
.languageModalOverlay {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.languageModal {
|
||||
position: fixed;
|
||||
top: calc(var(--main-menu-mobile-height) + var(--sitewide-alert-height));
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: white;
|
||||
z-index: 1001;
|
||||
|
||||
.closeModal {
|
||||
position: fixed;
|
||||
top: var(--Space-x2);
|
||||
right: var(--Space-x2);
|
||||
background-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.languageSwitcher {
|
||||
.triggerText {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.triggerButton {
|
||||
color: white;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.triggerButton:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.triggerButton[aria-expanded="true"] .chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.languageSwitcherContent {
|
||||
min-width: 200px;
|
||||
border-radius: var(--Space-x15);
|
||||
padding: var(--Space-x2) var(--Space-x3);
|
||||
box-shadow: 0 0 14px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chevron {
|
||||
transition: 0.3s;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user