From 74249e304e70ee323768dc1e24faf6b7b52b7524 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Tue, 27 Oct 2026 17:00:19 +0700 Subject: [PATCH] fix(menus): move language into its own mobile view; fit crew tabs --- src/components/LanguageToggle.tsx | 59 +++++--------------- src/components/Layout.tsx | 68 +++++++++++++++++++----- src/components/crew/CordnNativePanel.tsx | 48 ++++++++++------- src/lib/languageOptions.ts | 43 +++++++++++++++ src/pages/CrewDetailPage.tsx | 19 ++++--- 5 files changed, 154 insertions(+), 83 deletions(-) create mode 100644 src/lib/languageOptions.ts diff --git a/src/components/LanguageToggle.tsx b/src/components/LanguageToggle.tsx index 12ef4e5..3a50883 100644 --- a/src/components/LanguageToggle.tsx +++ b/src/components/LanguageToggle.tsx @@ -7,47 +7,10 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { useTranslation, type Language } from '@/contexts/language'; +import { useTranslation } from '@/contexts/language'; +import { LANGUAGE_OPTIONS } from '@/lib/languageOptions'; import { cn } from '@/lib/utils'; -interface LanguageOption { - code: Language; - name: string; - nativeName: string; - flag: string; -} - -const languages: LanguageOption[] = [ - { code: 'en', name: 'English', nativeName: 'English', flag: '/flags/gb-eng.svg' }, - { code: 'es', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸' }, - { code: 'ca', name: 'Catalan', nativeName: 'Català', flag: '/flags/ca.svg' }, - { code: 'eu', name: 'Basque', nativeName: 'Euskara', flag: '/flags/eu.svg' }, - { code: 'gl', name: 'Galician', nativeName: 'Galego', flag: '/flags/gl.svg' }, - { code: 'pt', name: 'Portuguese (Brazil)', nativeName: 'Português (Brasil)', flag: '🇧🇷' }, - { code: 'pt-PT', name: 'Portuguese (Portugal)', nativeName: 'Português (Portugal)', flag: '🇵🇹' }, - { code: 'fr', name: 'French', nativeName: 'Français', flag: '🇫🇷' }, - { code: 'de', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪' }, - { code: 'it', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹' }, - { code: 'nl', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱' }, - { code: 'pl', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱' }, - { code: 'ru', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺' }, - { code: 'ko', name: 'Korean', nativeName: '한국어', flag: '🇰🇷' }, - { code: 'ko-KP', name: 'Korean (North)', nativeName: '조선어', flag: '🇰🇵' }, - { code: 'th', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭' }, - { code: 'ja', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵' }, - { code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文', flag: '🇨🇳' }, - { code: 'zh-TW', name: 'Chinese (Traditional)', nativeName: '繁體中文', flag: '🇹🇼' }, - { code: 'uk', name: 'Ukrainian', nativeName: 'Українська', flag: '🇺🇦' }, - { code: 'id', name: 'Indonesian', nativeName: 'Bahasa Indonesia', flag: '🇮🇩' }, - { code: 'cs', name: 'Czech', nativeName: 'Čeština', flag: '🇨🇿' }, - { code: 'ro', name: 'Romanian', nativeName: 'Română', flag: '🇷🇴' }, - { code: 'sv', name: 'Swedish', nativeName: 'Svenska', flag: '🇸🇪' }, - { code: 'el', name: 'Greek', nativeName: 'Ελληνικά', flag: '🇬🇷' }, - { code: 'vi', name: 'Vietnamese', nativeName: 'Tiếng Việt', flag: '🇻🇳' }, - { code: 'tr', name: 'Turkish', nativeName: 'Türkçe', flag: '🇹🇷' }, - { code: 'hi', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳' }, -]; - interface LanguageToggleProps { /** Show only the flag icon (compact mode) */ compact?: boolean; @@ -93,17 +56,23 @@ export function LanguageToggle({ compact = false, inline = false, className, var const { language, setLanguage, t } = useTranslation(); const [query, setQuery] = useState(''); + // Auto-focusing the search on a phone pops the keyboard over the list before + // the user can see the languages. Keep it for mouse/pen, not touch. + const autoFocusSearch = + typeof window !== 'undefined' && !!window.matchMedia?.('(pointer: fine)').matches; + const safeQuery = query.trim().toLowerCase(); const filteredLanguages = safeQuery - ? languages.filter( + ? LANGUAGE_OPTIONS.filter( (l) => l.name.toLowerCase().includes(safeQuery) || l.nativeName.toLowerCase().includes(safeQuery) || l.code.toLowerCase().includes(safeQuery) ) - : languages; + : LANGUAGE_OPTIONS; - const currentLanguage = languages.find(l => l.code === language) || languages[0]; + const currentLanguage = + LANGUAGE_OPTIONS.find((l) => l.code === language) || LANGUAGE_OPTIONS[0]; if (inline) { return ( @@ -177,7 +146,7 @@ export function LanguageToggle({ compact = false, inline = false, className, var onPointerDown={(e) => e.stopPropagation()} > setQuery(e.target.value)} placeholder={t('nav.searchLanguages')} @@ -189,7 +158,7 @@ export function LanguageToggle({ compact = false, inline = false, className, var key={lang.code} onClick={() => setLanguage(lang.code)} className={cn( - 'flex items-center gap-3 cursor-pointer', + 'flex min-h-11 items-center gap-3 cursor-pointer', language === lang.code && 'bg-primary/10 text-primary' )} > @@ -225,7 +194,7 @@ export function LanguageSelector() {
- {languages.map((lang) => ( + {LANGUAGE_OPTIONS.map((lang) => ( - + {/* User info header */}
@@ -513,6 +517,7 @@ export function Layout({ children, noPadding }: LayoutProps) { // padding even though it shares this one persistent layout with every route. const isFullBleed = noPadding || location.pathname === '/' || location.pathname === '/map'; const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + const [mobileLanguageView, setMobileLanguageView] = useState(false); const [showStatusDialog, setShowStatusDialog] = useState(false); const [showCustomizeNav, setShowCustomizeNav] = useState(false); const [showAddAccount, setShowAddAccount] = useState(false); @@ -539,7 +544,8 @@ export function Layout({ children, noPadding }: LayoutProps) { const timer = window.setTimeout(() => setShowWelcomeLogin(true), 900); return () => window.clearTimeout(timer); }, [user]); - const { t } = useTranslation(); + const { t, language } = useTranslation(); + const currentLanguage = LANGUAGE_OPTIONS.find((item) => item.code === language) ?? LANGUAGE_OPTIONS[0]; const { currentUser: currentAccount, removeLogin } = useLoggedInAccounts(); const author = useAuthor(user?.pubkey || ''); const displayName = author.data?.metadata?.name ?? genUserName(user?.pubkey); @@ -624,7 +630,7 @@ export function Layout({ children, noPadding }: LayoutProps) { {t('nav.more')} - + {/* Catalog items the user hasn't pinned to the top bar. Auth-only items (stash/collections/contacts/wallet/messages) only show when logged in. */} @@ -822,7 +828,13 @@ export function Layout({ children, noPadding }: LayoutProps) {
- + { + setMobileMenuOpen(open); + if (!open) setMobileLanguageView(false); + }} + > +
+

+ {t('settings.selectLanguage')} +

+ +
+
+ ) : (
+ {/* Language picker lives in its own view — 28 languages inline + made this sheet ~3,200px tall and buried the control. */} + + {/* Wallet Card - Mobile Menu */} {user && ( {t('chromeUi.customizeNavBar')}
- {/* Language Toggle in Mobile Menu — inline (a portaled dropdown - is unclickable inside a Radix Sheet) */} -
- -
{/* Login/Logout Section */}
{user ? ( @@ -1063,6 +1106,7 @@ export function Layout({ children, noPadding }: LayoutProps) { )}
+ )}
diff --git a/src/components/crew/CordnNativePanel.tsx b/src/components/crew/CordnNativePanel.tsx index 3d31fb6..29b88b1 100644 --- a/src/components/crew/CordnNativePanel.tsx +++ b/src/components/crew/CordnNativePanel.tsx @@ -5,6 +5,7 @@ import { Check, ChevronDown, Copy, + ExternalLink, ImagePlus, KeyRound, Loader2, @@ -780,25 +781,36 @@ function AdvancedPanel({ {groupRef && ( -
- {t('cordnUi.groupRef')} - - {groupRef} - - +
+ - {copied ? ( - - ) : ( - - )} - {copied ? t('cordnUi.copied') : t('cordnUi.copyRef')} - - + {' '} + {t('socialUi.crewCoordinator.openCordnGroup')} + + )} diff --git a/src/lib/languageOptions.ts b/src/lib/languageOptions.ts new file mode 100644 index 0000000..933be53 --- /dev/null +++ b/src/lib/languageOptions.ts @@ -0,0 +1,43 @@ +import type { Language } from '@/contexts/language'; + +export interface LanguageOption { + code: Language; + name: string; + nativeName: string; + flag: string; +} + +/** + * Every locale the picker can show. Kept out of the component file so Fast + * Refresh still works (a component module must only export components). + */ +export const LANGUAGE_OPTIONS: LanguageOption[] = [ + { code: 'en', name: 'English', nativeName: 'English', flag: '/flags/gb-eng.svg' }, + { code: 'es', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸' }, + { code: 'ca', name: 'Catalan', nativeName: 'Català', flag: '/flags/ca.svg' }, + { code: 'eu', name: 'Basque', nativeName: 'Euskara', flag: '/flags/eu.svg' }, + { code: 'gl', name: 'Galician', nativeName: 'Galego', flag: '/flags/gl.svg' }, + { code: 'pt', name: 'Portuguese (Brazil)', nativeName: 'Português (Brasil)', flag: '🇧🇷' }, + { code: 'pt-PT', name: 'Portuguese (Portugal)', nativeName: 'Português (Portugal)', flag: '🇵🇹' }, + { code: 'fr', name: 'French', nativeName: 'Français', flag: '🇫🇷' }, + { code: 'de', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪' }, + { code: 'it', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹' }, + { code: 'nl', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱' }, + { code: 'pl', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱' }, + { code: 'ru', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺' }, + { code: 'ko', name: 'Korean', nativeName: '한국어', flag: '🇰🇷' }, + { code: 'ko-KP', name: 'Korean (North)', nativeName: '조선어', flag: '🇰🇵' }, + { code: 'th', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭' }, + { code: 'ja', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵' }, + { code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文', flag: '🇨🇳' }, + { code: 'zh-TW', name: 'Chinese (Traditional)', nativeName: '繁體中文', flag: '🇹🇼' }, + { code: 'uk', name: 'Ukrainian', nativeName: 'Українська', flag: '🇺🇦' }, + { code: 'id', name: 'Indonesian', nativeName: 'Bahasa Indonesia', flag: '🇮🇩' }, + { code: 'cs', name: 'Czech', nativeName: 'Čeština', flag: '🇨🇿' }, + { code: 'ro', name: 'Romanian', nativeName: 'Română', flag: '🇷🇴' }, + { code: 'sv', name: 'Swedish', nativeName: 'Svenska', flag: '🇸🇪' }, + { code: 'el', name: 'Greek', nativeName: 'Ελληνικά', flag: '🇬🇷' }, + { code: 'vi', name: 'Vietnamese', nativeName: 'Tiếng Việt', flag: '🇻🇳' }, + { code: 'tr', name: 'Turkish', nativeName: 'Türkçe', flag: '🇹🇷' }, + { code: 'hi', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳' }, +]; diff --git a/src/pages/CrewDetailPage.tsx b/src/pages/CrewDetailPage.tsx index f5fb44b..072f94a 100644 --- a/src/pages/CrewDetailPage.tsx +++ b/src/pages/CrewDetailPage.tsx @@ -347,18 +347,21 @@ export default function CrewDetailPage() { }} className="w-full" > - - - {t('socialUi.crew.eventsTab')} + + + {' '} + {t('socialUi.crew.eventsTab')} {crewEvents && crewEvents.length > 0 && ( - {crewEvents.length} + {crewEvents.length} )} - - {t('socialUi.crew.chatTab')} + + {' '} + {t('socialUi.crew.chatTab')} - - {t('cordnUi.title')} + + {' '} + {t('cordnUi.title')}