diff --git a/src/components/login/LoginCard.tsx b/src/components/login/LoginCard.tsx index 934ddeca..410f8096 100644 --- a/src/components/login/LoginCard.tsx +++ b/src/components/login/LoginCard.tsx @@ -1,9 +1,10 @@ import { useState, type ComponentProps } from 'react' -import { RefreshCwIcon, WalletIcon } from 'lucide-react' +import { LanguagesIcon, RefreshCwIcon, WalletIcon } from 'lucide-react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { LanguageSelectorDropdownMenu } from '@/components/ui/jam/LanguageSelector' import { WalletLoadErrorAlert } from '@/components/ui/jam/WalletLoadErrorAlert' import { Skeleton } from '@/components/ui/skeleton' import { Spinner } from '@/components/ui/spinner' @@ -71,22 +72,36 @@ export const LoginCard = ({ <> -
- {listWalletsFetching ? ( - - ) : ( +
+
+ + + + - )} +
{t('login.title')} {listWalletsLoading ? ( @@ -103,7 +118,7 @@ export const LoginCard = ({ ) : null} - + {listWalletsError ? ( <> diff --git a/src/components/settings/SettingsPage.test.tsx b/src/components/settings/SettingsPage.test.tsx index 91c4684a..20e58cb3 100644 --- a/src/components/settings/SettingsPage.test.tsx +++ b/src/components/settings/SettingsPage.test.tsx @@ -59,6 +59,9 @@ vi.mock('next-themes', () => ({ vi.mock('react-i18next', () => ({ useTranslation: () => ({ + i18n: { + resolvedLanguage: 'en', + }, t: (key: string) => key, }), })) @@ -123,7 +126,7 @@ vi.mock('@/components/settings/AccountXpubsDialog', () => ({ ) : null, })) -vi.mock('@/components/settings/LanguageSelector', () => ({ +vi.mock('@/components/ui/jam/LanguageSelector', () => ({ LanguageSelector: () =>
language-selector
, })) diff --git a/src/components/settings/SettingsPage.tsx b/src/components/settings/SettingsPage.tsx index 36205805..3fba6fd4 100644 --- a/src/components/settings/SettingsPage.tsx +++ b/src/components/settings/SettingsPage.tsx @@ -26,8 +26,10 @@ import { useNavigate, type NavigateFunction } from 'react-router-dom' import { useStore } from 'zustand' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { CurrencySymbol } from '@/components/ui/jam/CurrencySymbol' +import { LanguageSelector } from '@/components/ui/jam/LanguageSelector' import PageTitle from '@/components/ui/jam/PageTitle' import { Separator } from '@/components/ui/separator' +import { Spinner } from '@/components/ui/spinner' import { isDebugFeatureEnabled, isDevMode } from '@/constants/debugFeatures' import { JAM_DOCS_URL, JAM_MATRIX_URL, JAM_REPO_URL, JAM_SEED_MODAL_TIMEOUT, JAM_TELEGRAM_URL } from '@/constants/jam' import { routes } from '@/constants/routes' @@ -37,8 +39,6 @@ import { useFeeConfigValidation } from '@/hooks/useFeeConfigValidation' import { cn, type WalletFileName } from '@/lib/utils' import { authStore } from '@/store/authStore' import { jamSettingsStore } from '@/store/jamSettingsStore' -import { LanguageSelector } from '../ui/jam/LanguageSelector' -import { Spinner } from '../ui/spinner' import { AccountXpubsDialog } from './AccountXpubsDialog' import { SeedPhraseDialog } from './SeedPhraseDialog' import { SettingsItem, SettingsLink, SettingsSwitch } from './SettingsItem' diff --git a/src/components/ui/button-variants.ts b/src/components/ui/button-variants.ts index ee782c92..ec705830 100644 --- a/src/components/ui/button-variants.ts +++ b/src/components/ui/button-variants.ts @@ -40,6 +40,7 @@ const buttonVariants = cva( "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", 'icon-sm': 'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg', 'icon-lg': 'size-9', + 'icon-xxl': 'size-12', }, }, defaultVariants: { diff --git a/src/components/ui/jam/LanguageSelector.tsx b/src/components/ui/jam/LanguageSelector.tsx index dc75b54a..d0553a45 100644 --- a/src/components/ui/jam/LanguageSelector.tsx +++ b/src/components/ui/jam/LanguageSelector.tsx @@ -1,4 +1,4 @@ -import type { ComponentProps } from 'react' +import type { ComponentProps, PropsWithChildren } from 'react' import { LanguagesIcon } from 'lucide-react' import { useTranslation } from 'react-i18next' import { @@ -11,8 +11,22 @@ import { SelectLabel, } from '@/components/ui/select' import languages from '@/i18n/languages' +import { cn } from '@/lib/utils' +import { Button } from '../button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuTrigger, +} from '../dropdown-menu' -export const LanguageSelector = (props: ComponentProps = {}) => { +export const LanguageSelector = ({ + className, + ...props +}: ComponentProps & { className?: string } = {}) => { const { i18n, t } = useTranslation() const currentLanguage = languages.find((lang) => lang.key === i18n.resolvedLanguage) const currentLanguageDescription = currentLanguage?.description || 'English' @@ -25,7 +39,10 @@ export const LanguageSelector = (props: ComponentProps = {}) => { }} {...props} > - + @@ -44,3 +61,56 @@ export const LanguageSelector = (props: ComponentProps = {}) => { ) } + +export const LanguageSelectorDropdownMenu = ({ + className, + align, + loop = true, + ...props +}: PropsWithChildren< + ComponentProps & { + className?: string + align?: ComponentProps['align'] + loop?: ComponentProps['loop'] + } +> = {}) => { + const { i18n, t } = useTranslation() + + return ( + + + {props.children ?? ( + + )} + + + + + + {t('settings.label_select_language')} + + { + void i18n.changeLanguage(value) + }} + > + {languages.map((language) => ( + + {language.description} + + ))} + + + + + ) +} diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 13bac7c6..b5bb57a2 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -293,6 +293,8 @@ "use_address_chunking_enabled": "Address display chunking enabled", "use_address_chunking_disabled": "Address display chunking disabled", "label_select_language": "Language", + "label_select_language_title": "Select language", + "label_select_language_aria_label": "$t(settings.label_select_language_title)", "show_seed": "Show seed phrase", "show_xpubs": "Show account xpubs", "reveal_seed": "Reveal seed phrase",