diff --git a/src/app/apple-icon.tsx b/src/app/apple-icon.tsx index f594f156..85294345 100644 --- a/src/app/apple-icon.tsx +++ b/src/app/apple-icon.tsx @@ -86,62 +86,62 @@ export default function Icon({ id }: { id: string }) { xmlns="http://www.w3.org/2000/svg" > diff --git a/src/app/icon.tsx b/src/app/icon.tsx index 61cc18cc..1b981273 100644 --- a/src/app/icon.tsx +++ b/src/app/icon.tsx @@ -66,62 +66,62 @@ export default function Icon({ id }: { id: string }) { xmlns="http://www.w3.org/2000/svg" > diff --git a/src/features/common/assets/arrow-head-icon.component.tsx b/src/features/common/assets/arrow-head-icon.component.tsx index a0a25859..0910b453 100644 --- a/src/features/common/assets/arrow-head-icon.component.tsx +++ b/src/features/common/assets/arrow-head-icon.component.tsx @@ -12,9 +12,9 @@ export const ArrowHeadIconComponent: React.FC = () => { ); diff --git a/src/features/common/assets/download-icon.component.tsx b/src/features/common/assets/download-icon.component.tsx index c7eb7ec2..f401a279 100644 --- a/src/features/common/assets/download-icon.component.tsx +++ b/src/features/common/assets/download-icon.component.tsx @@ -12,16 +12,16 @@ export const DownloadIconComponent: React.FC = () => { ); diff --git a/src/features/common/components/bars/ribbon/assets/dark-icon.component.tsx b/src/features/common/components/bars/ribbon/assets/dark-icon.component.tsx index 7607d241..3961a806 100644 --- a/src/features/common/components/bars/ribbon/assets/dark-icon.component.tsx +++ b/src/features/common/components/bars/ribbon/assets/dark-icon.component.tsx @@ -12,8 +12,8 @@ export const DarkIconComponent: React.FC = () => { ); diff --git a/src/features/common/components/bars/ribbon/assets/globe-icon.component.tsx b/src/features/common/components/bars/ribbon/assets/globe-icon.component.tsx index 7552b6c2..71ee0194 100644 --- a/src/features/common/components/bars/ribbon/assets/globe-icon.component.tsx +++ b/src/features/common/components/bars/ribbon/assets/globe-icon.component.tsx @@ -3,10 +3,11 @@ import React from "react"; export const GlobeIconComponent: React.FC = () => { return ( { strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" + stroke="currentColor" > { height="6" rx="3" stroke="currentColor" - stroke-width="1.5" + strokeWidth="1.5" > diff --git a/src/features/common/components/bars/ribbon/assets/system-icon.component.tsx b/src/features/common/components/bars/ribbon/assets/system-icon.component.tsx index dbc0ed77..17d5c982 100644 --- a/src/features/common/components/bars/ribbon/assets/system-icon.component.tsx +++ b/src/features/common/components/bars/ribbon/assets/system-icon.component.tsx @@ -12,7 +12,7 @@ export const SystemIconComponent: React.FC = () => { { width="8" height="5" stroke="currentColor" - stroke-width="1.5" - stroke-linejoin="round" + strokeWidth="1.5" + strokeLinejoin="round" > { ); diff --git a/src/features/common/components/bars/ribbon/ribbon.component.tsx b/src/features/common/components/bars/ribbon/ribbon.component.tsx index 3403d15e..4827cb2f 100644 --- a/src/features/common/components/bars/ribbon/ribbon.component.tsx +++ b/src/features/common/components/bars/ribbon/ribbon.component.tsx @@ -14,7 +14,6 @@ import { import { RibbonPickerComponent } from "@/features/common/components/bars/ribbon/ribbon-picker/ribbon-picker.component"; import { LightIconComponent } from "@/features/common/components/bars/ribbon/assets/light-icon.component"; import { DarkIconComponent } from "@/features/common/components/bars/ribbon/assets/dark-icon.component"; -import { GlobeIconComponent } from "@/features/common/components/bars/ribbon/assets/globe-icon.component"; import { ThemeModel } from "@/features/common/models/theme.model"; import { useAppStore } from "@/features/common/services/app.store"; import { SystemIconComponent } from "@/features/common/components/bars/ribbon/assets/system-icon.component"; @@ -37,10 +36,6 @@ export const RibbonComponent: React.FC = ({ }) => { const theme$ = useAppStore((state) => state.theme$); - const currentLanguage = dictionary.languagePicker.options.filter( - (element) => element.code === languageCode, - )[0]; - const sanitizedThemePickerCodeValue = useMemo(() => { return getSanitizedThemePickerCodeValue(themeCode); }, [themeCode]); @@ -85,25 +80,6 @@ export const RibbonComponent: React.FC = ({ [dictionary.themePicker.options], ); - const languageOptions = useMemo( - () => - dictionary.languagePicker.options.map((option) => { - return { - code: option.code, - full: { - ...option, - icon: null, - }, - compact: { - ...option, - label: option.code.toUpperCase(), - icon: null, - }, - }; - }), - [dictionary.languagePicker.options], - ); - const handleThemeSelection = useCallback( async (value: ThemePickerCodeValues) => { const themePreference = await savePreferredThemeInCookie( @@ -202,21 +178,6 @@ export const RibbonComponent: React.FC = ({ listLabel: dictionary.themePicker.list.ariaLabel, }} /> - {dictionary.languagePicker.options.length > 1 && ( - } - label={currentLanguage.label} - compactLabel={currentLanguage.code.toUpperCase()} - languageCode={languageCode} - selectedOptionCode={languageCode} - handleSelection={handleLanguageSelection} - options={languageOptions} - aria={{ - buttonLabel: dictionary.languagePicker.button.ariaLabel, - listLabel: dictionary.languagePicker.list.ariaLabel, - }} - /> - )} > diff --git a/src/features/common/components/footer/footer.component.tsx b/src/features/common/components/footer/footer.component.tsx index ad06e00d..1c91d81c 100644 --- a/src/features/common/components/footer/footer.component.tsx +++ b/src/features/common/components/footer/footer.component.tsx @@ -1,6 +1,12 @@ "use client"; import React, { MouseEvent, useState } from "react"; +import Select, { + SingleValue, + OptionsOrGroups, + GroupBase, + NonceProvider, +} from "react-select"; import { FooterIconsComponent } from "./footer-Icons.component"; import { MonoFont, SecondaryFont } from "@/libs/theme/fonts"; import Image from "next/image"; @@ -18,6 +24,9 @@ import { SiteBrandComponent } from "@/features/common/components/site-brand/site import { Button } from "react-aria-components"; import { Auth0LogoComponent } from "../../assets/auth0-logo.component"; import { getBrandDictionary } from "@/features/localization/services/brand-dictionary.service"; +import { savePreferredLanguage } from "@/features/localization/services/ui-language.utils"; +import { UiLanguageModel } from "../../models/ui-language.model"; +import { GlobeIconComponent } from "../bars/ribbon/assets/globe-icon.component"; interface FooterComponentProps { languageCode: string; @@ -28,8 +37,19 @@ export const FooterComponent: React.FC = ({ languageCode, dictionary, }) => { + const currentLanguage = dictionary.languagePicker.options.filter( + (element) => element.value === languageCode + )[0]; + + const handleChange = (selection: SingleValue) => { + if (!selection) { + return; + } + savePreferredLanguage(selection.value); + }; + const [modalState, setModalState] = useState( - ModalStateValues.CLOSED, + ModalStateValues.CLOSED ); const images = getBrandDictionary(languageCode); @@ -64,7 +84,10 @@ export const FooterComponent: React.FC = ({ contentClassName={styles.content} > - + @@ -110,7 +133,7 @@ export const FooterComponent: React.FC = ({ }} className={clsx( styles.resource__button, - SecondaryFont.className, + SecondaryFont.className )} > {trigger.text} @@ -158,11 +181,80 @@ export const FooterComponent: React.FC = ({ target="_blank" href="https://auth0.com/" > - + {dictionary.copyright} + {dictionary.languagePicker.options.length > 1 && ( + + + + > + } + menuPortalTarget={document.body} + classNamePrefix={"language-select"} + isSearchable={false} + placeholder={currentLanguage.label} + value={currentLanguage} + styles={{ + control: (base, state) => ({ + ...base, + fontSize: "0.875rem", + background: "transparent", + border: "none", + borderRadius: "0px", + display: "flex", + alignItems: "center", + cursor: "pointer", + minHeight: "2.5rem", + boxSizing: "border-box", + boxShadow: "none", + }), + input: (base) => ({ + ...base, + margin: "0px", + }), + indicatorSeparator: () => ({ + display: "none", + }), + indicatorsContainer: (base) => ({ + ...base, + height: "20px", + alignSelf: "center", + }), + dropdownIndicator: (base) => ({ + ...base, + padding: "0px", + height: "100%", + alignSelf: "center", + color: "inherit", + }), + valueContainer: (base) => ({ + ...base, + padding: "0px", + }), + singleValue: (base) => ({ + ...base, + color: "unset", + }), + menu: (base) => ({ + ...base, + top: "unset", + bottom: "1.75rem", + right: "0", + }), + }} + /> + + )} diff --git a/src/features/common/components/footer/footer.module.scss b/src/features/common/components/footer/footer.module.scss index 83710432..0607c215 100644 --- a/src/features/common/components/footer/footer.module.scss +++ b/src/features/common/components/footer/footer.module.scss @@ -267,3 +267,14 @@ color: var(--color_fg_default); } + +.subFooter__languagePicker { + display: flex; + align-items: center; + gap: 0.25rem; + color: var(--color_fg_default); + + &:hover { + color: (var(--color_fg_bold)); + } +} diff --git a/src/features/common/models/ui-language.model.ts b/src/features/common/models/ui-language.model.ts index 2c33f718..0d12bf65 100644 --- a/src/features/common/models/ui-language.model.ts +++ b/src/features/common/models/ui-language.model.ts @@ -1,4 +1,4 @@ export interface UiLanguageModel { - code: string; + value: string; label: string; } diff --git a/src/features/localization/dictionaries/layout/en.tsx b/src/features/localization/dictionaries/layout/en.tsx index 1bbe8fa3..14da086d 100644 --- a/src/features/localization/dictionaries/layout/en.tsx +++ b/src/features/localization/dictionaries/layout/en.tsx @@ -39,20 +39,6 @@ export const enLayoutDictionary: LayoutDictionaryModel = { }, ], }, - languagePicker: { - button: { - ariaLabel: "Select page language", - }, - list: { - ariaLabel: "list of page languages", - }, - options: [ - { - code: "en", - label: "English", - }, - ], - }, }, header: { links: [ @@ -162,6 +148,20 @@ export const enLayoutDictionary: LayoutDictionaryModel = { }, ], }, + languagePicker: { + button: { + ariaLabel: "Select page language", + }, + list: { + ariaLabel: "list of page languages", + }, + options: [ + { + value: "en", + label: "English", + }, + ], + }, }, errors: { notFound: { @@ -187,8 +187,8 @@ export const enLayoutDictionary: LayoutDictionaryModel = { }; if (withJapanese) { - enLayoutDictionary.ribbon.languagePicker.options.push({ + enLayoutDictionary.footer.languagePicker.options.push({ label: "日本語", - code: "ja", + value: "ja", }); } diff --git a/src/features/localization/dictionaries/layout/ja.tsx b/src/features/localization/dictionaries/layout/ja.tsx index c24af750..de2f0697 100644 --- a/src/features/localization/dictionaries/layout/ja.tsx +++ b/src/features/localization/dictionaries/layout/ja.tsx @@ -39,20 +39,6 @@ export const jaLayoutDictionary: LayoutDictionaryModel = { }, ], }, - languagePicker: { - button: { - ariaLabel: "ページの言語を選択してください", - }, - list: { - ariaLabel: "ページ言語の一覧", - }, - options: [ - { - code: "en", - label: "English", - }, - ], - }, }, header: { links: [ @@ -162,6 +148,20 @@ export const jaLayoutDictionary: LayoutDictionaryModel = { }, ], }, + languagePicker: { + button: { + ariaLabel: "ページの言語を選択してください", + }, + list: { + ariaLabel: "ページ言語の一覧", + }, + options: [ + { + value: "en", + label: "English", + }, + ], + }, }, errors: { notFound: { @@ -187,8 +187,8 @@ export const jaLayoutDictionary: LayoutDictionaryModel = { }; if (withJapanese) { - jaLayoutDictionary.ribbon.languagePicker.options.push({ + jaLayoutDictionary.footer.languagePicker.options.push({ label: "日本語", - code: "ja", + value: "ja", }); } diff --git a/src/features/localization/models/layout-dictionary.model.ts b/src/features/localization/models/layout-dictionary.model.ts index df259944..3b57231a 100644 --- a/src/features/localization/models/layout-dictionary.model.ts +++ b/src/features/localization/models/layout-dictionary.model.ts @@ -26,7 +26,6 @@ export interface LayoutDictionaryModel { }; }; themePicker: RibbonPickerModel; - languagePicker: RibbonPickerModel; }; header: { links: LinkMetadataModel[]; @@ -69,6 +68,7 @@ export interface LayoutDictionaryModel { id: string; }[]; }; + languagePicker: RibbonPickerModel; }; errors: { notFound: { diff --git a/src/libs/theme/styles/globals.scss b/src/libs/theme/styles/globals.scss index b3c577ce..5b23baa0 100644 --- a/src/libs/theme/styles/globals.scss +++ b/src/libs/theme/styles/globals.scss @@ -553,7 +553,8 @@ $picker-list-offset-lg: calc(($picker-list-width-lg - $picker-width-lg) / 2); height: 100% !important; } -.react-select__menu { +.react-select__menu, +.language-select__menu { background-color: var(--color_bg_layer) !important; border-radius: 0.75rem !important; box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 4px 11px !important; @@ -570,7 +571,8 @@ $picker-list-offset-lg: calc(($picker-list-width-lg - $picker-width-lg) / 2); .react-select__menu-portal { } -.react-select__menu-list { +.react-select__menu-list, +.language-select__menu-list { font-family: var(--font-primary), monospace !important; padding: 0 !important; @@ -596,7 +598,8 @@ $picker-list-offset-lg: calc(($picker-list-width-lg - $picker-width-lg) / 2); } } -.react-select__option { +.react-select__option, +.language-select__option { cursor: pointer !important; display: flex !important; font-size: 0.875rem !important; @@ -622,7 +625,8 @@ $picker-list-offset-lg: calc(($picker-list-width-lg - $picker-width-lg) / 2); } } -.react-select__option--is-selected { +.react-select__option--is-selected, +.language-select__option--is-selected { color: var(--color_fg_bold) !important; font-weight: 500;