@@ -2,28 +2,32 @@ import { useEffect, useState } from 'react'
22import {
33 Box ,
44 Button ,
5- Menu ,
6- MenuButton ,
7- MenuItem ,
8- MenuList ,
5+ Divider ,
6+ Input ,
7+ Link ,
8+ Popover ,
9+ PopoverBody ,
10+ PopoverContent ,
11+ PopoverHeader ,
12+ PopoverTrigger ,
913 Text ,
10- useColorModeValue
14+ useColorModeValue ,
15+ VStack
1116} from '@chakra-ui/react'
1217import { CheckIcon , ChevronDownIcon } from '@chakra-ui/icons'
1318import { IoLanguageOutline } from 'react-icons/io5'
19+ import { getShortLanguageLabel , languages } from '../lib/languages'
1420
1521const ORIGINAL_ORIGIN = 'https://matthewvaishnav.github.io'
1622const TRANSLATED_ORIGIN = 'https://matthewvaishnav-github-io.translate.goog'
17-
18- const languages = [
19- { code : 'en' , shortLabel : 'EN' , nativeLabel : 'English' , englishLabel : 'English' } ,
20- { code : 'zh-CN' , shortLabel : '中' , nativeLabel : '简体中文' , englishLabel : 'Chinese' } ,
21- { code : 'ja' , shortLabel : '日' , nativeLabel : '日本語' , englishLabel : 'Japanese' } ,
22- { code : 'ko' , shortLabel : '한' , nativeLabel : '한국어' , englishLabel : 'Korean' }
23- ]
24-
23+ const featuredCodes = [ 'en' , 'fr' , 'fr-CA' , 'zh-CN' , 'zh-TW' , 'ja' , 'ko' , 'es' , 'de' ]
2524const supportedCodes = new Set ( languages . map ( language => language . code ) )
2625
26+ const orderedLanguages = [
27+ ...featuredCodes . map ( code => languages . find ( language => language . code === code ) ) ,
28+ ...languages . filter ( language => ! featuredCodes . includes ( language . code ) )
29+ ] . filter ( Boolean )
30+
2731const getActiveLanguage = ( ) => {
2832 if ( typeof window === 'undefined' ) return 'en'
2933
@@ -58,10 +62,11 @@ const getLanguageUrl = languageCode => {
5862
5963const LanguageSelector = ( ) => {
6064 const [ activeLanguage , setActiveLanguage ] = useState ( 'en' )
65+ const [ search , setSearch ] = useState ( '' )
6166 const buttonVariant = useColorModeValue ( 'solid' , 'outline' )
6267 const buttonBorder = useColorModeValue ( 'transparent' , 'orange.300' )
63- const menuBg = useColorModeValue ( 'white' , 'gray.800' )
64- const menuBorder = useColorModeValue ( 'blackAlpha.200' , 'whiteAlpha.200' )
68+ const popoverBg = useColorModeValue ( 'white' , 'gray.800' )
69+ const popoverBorder = useColorModeValue ( 'blackAlpha.200' , 'whiteAlpha.200' )
6570 const activeBg = useColorModeValue ( 'teal.50' , 'whiteAlpha.100' )
6671 const muted = useColorModeValue ( 'gray.500' , 'whiteAlpha.600' )
6772
@@ -71,74 +76,124 @@ const LanguageSelector = () => {
7176
7277 const activeOption =
7378 languages . find ( language => language . code === activeLanguage ) || languages [ 0 ]
79+ const normalizedSearch = search . trim ( ) . toLocaleLowerCase ( )
80+ const visibleLanguages = normalizedSearch
81+ ? languages . filter ( language =>
82+ `${ language . englishLabel } ${ language . nativeLabel } ${ language . code } `
83+ . toLocaleLowerCase ( )
84+ . includes ( normalizedSearch )
85+ )
86+ : orderedLanguages
7487
7588 const selectLanguage = languageCode => {
7689 if ( languageCode === activeLanguage ) return
7790 window . location . assign ( getLanguageUrl ( languageCode ) )
7891 }
7992
8093 return (
81- < Menu placement = "bottom-end" isLazy >
82- < MenuButton
83- as = { Button }
84- aria-label = { `Select language. Current language: ${ activeOption . englishLabel } ` }
85- colorScheme = { useColorModeValue ( 'teal' , 'orange' ) }
86- variant = { buttonVariant }
87- borderColor = { buttonBorder }
88- minW = { { base : '40px' , md : '84px' } }
89- px = { { base : 0 , md : 3 } }
90- >
91- < Box as = "span" display = { { base : 'none' , md : 'inline-flex' } } mr = { 2 } >
92- < IoLanguageOutline />
93- </ Box >
94- < Box as = "span" > { activeOption . shortLabel } </ Box >
95- < ChevronDownIcon display = { { base : 'none' , md : 'inline-block' } } ml = { 1.5 } />
96- </ MenuButton >
97-
98- < MenuList
99- minW = "210px"
100- p = { 2 }
101- mt = { 1 }
102- bg = { menuBg }
103- borderColor = { menuBorder }
94+ < Popover placement = "bottom-end" isLazy >
95+ < PopoverTrigger >
96+ < Button
97+ aria-label = { `Select language. Current language: ${ activeOption . englishLabel } ` }
98+ colorScheme = { useColorModeValue ( 'teal' , 'orange' ) }
99+ variant = { buttonVariant }
100+ borderColor = { buttonBorder }
101+ minW = { { base : '40px' , md : '84px' } }
102+ px = { { base : 0 , md : 3 } }
103+ className = "notranslate"
104+ translate = "no"
105+ >
106+ < Box as = "span" display = { { base : 'none' , md : 'inline-flex' } } mr = { 2 } >
107+ < IoLanguageOutline />
108+ </ Box >
109+ < Box as = "span" > { getShortLanguageLabel ( activeOption . code ) } </ Box >
110+ < ChevronDownIcon display = { { base : 'none' , md : 'inline-block' } } ml = { 1.5 } />
111+ </ Button >
112+ </ PopoverTrigger >
113+
114+ < PopoverContent
115+ w = { { base : 'calc(100vw - 24px)' , sm : '330px' } }
116+ maxW = "330px"
117+ bg = { popoverBg }
118+ borderColor = { popoverBorder }
104119 borderRadius = "xl"
105120 boxShadow = "xl"
121+ className = "notranslate"
122+ translate = "no"
106123 >
107- { languages . map ( language => {
108- const isActive = language . code === activeLanguage
109-
110- return (
111- < MenuItem
112- key = { language . code }
113- onClick = { ( ) => selectLanguage ( language . code ) }
114- bg = { isActive ? activeBg : 'transparent' }
115- borderRadius = "lg"
116- px = { 3 }
117- py = { 2.5 }
118- _hover = { { bg : activeBg } }
119- _focus = { { bg : activeBg } }
120- >
121- < Box w = "18px" mr = { 3 } color = "teal.400" >
122- { isActive && < CheckIcon boxSize = { 3 } /> }
123- </ Box >
124- < Box flex = "1" >
125- < Text fontWeight = { isActive ? 700 : 600 } lineHeight = "short" >
126- { language . nativeLabel }
127- </ Text >
128- { language . nativeLabel !== language . englishLabel && (
129- < Text mt = { 1 } color = { muted } fontSize = "xs" lineHeight = "short" >
130- { language . englishLabel }
124+ < PopoverHeader border = "0" px = { 4 } pt = { 4 } pb = { 2 } >
125+ < Text fontWeight = { 800 } > Choose a language</ Text >
126+ < Text mt = { 1 } color = { muted } fontSize = "xs" >
127+ { languages . length } translation options
128+ </ Text >
129+ </ PopoverHeader >
130+ < PopoverBody px = { 3 } pt = { 2 } pb = { 3 } >
131+ < Input
132+ value = { search }
133+ onChange = { event => setSearch ( event . target . value ) }
134+ placeholder = "Search languages"
135+ aria-label = "Search languages"
136+ mb = { 2 }
137+ borderRadius = "lg"
138+ />
139+
140+ < VStack align = "stretch" spacing = { 1 } maxH = "320px" overflowY = "auto" pr = { 1 } >
141+ { visibleLanguages . map ( language => {
142+ const isActive = language . code === activeLanguage
143+
144+ return (
145+ < Button
146+ key = { language . code }
147+ onClick = { ( ) => selectLanguage ( language . code ) }
148+ variant = "ghost"
149+ bg = { isActive ? activeBg : 'transparent' }
150+ justifyContent = "flex-start"
151+ h = "auto"
152+ minH = "44px"
153+ px = { 3 }
154+ py = { 2 }
155+ borderRadius = "lg"
156+ fontWeight = "normal"
157+ _hover = { { bg : activeBg } }
158+ _focusVisible = { { boxShadow : 'outline' } }
159+ >
160+ < Box w = "18px" mr = { 3 } color = "teal.400" flexShrink = { 0 } >
161+ { isActive && < CheckIcon boxSize = { 3 } /> }
162+ </ Box >
163+ < Box flex = "1" minW = { 0 } textAlign = "left" >
164+ < Text fontWeight = { isActive ? 700 : 600 } lineHeight = "short" noOfLines = { 1 } >
165+ { language . nativeLabel }
166+ </ Text >
167+ { language . nativeLabel !== language . englishLabel && (
168+ < Text mt = { 1 } color = { muted } fontSize = "xs" lineHeight = "short" noOfLines = { 1 } >
169+ { language . englishLabel }
170+ </ Text >
171+ ) }
172+ </ Box >
173+ < Text ml = { 3 } color = { muted } fontSize = "xs" fontWeight = { 700 } flexShrink = { 0 } >
174+ { language . code }
131175 </ Text >
132- ) }
133- </ Box >
134- < Text color = { muted } fontSize = "xs" fontWeight = { 700 } >
135- { language . shortLabel }
176+ </ Button >
177+ )
178+ } ) }
179+
180+ { visibleLanguages . length === 0 && (
181+ < Text px = { 3 } py = { 5 } color = { muted } fontSize = "sm" textAlign = "center" >
182+ No matching language
136183 </ Text >
137- </ MenuItem >
138- )
139- } ) }
140- </ MenuList >
141- </ Menu >
184+ ) }
185+ </ VStack >
186+
187+ < Divider my = { 3 } />
188+ < Text color = { muted } fontSize = "xs" textAlign = "center" >
189+ Translations powered by{ ' ' }
190+ < Link href = "https://translate.google.com" target = "_blank" rel = "noopener noreferrer" >
191+ Google Translate
192+ </ Link >
193+ </ Text >
194+ </ PopoverBody >
195+ </ PopoverContent >
196+ </ Popover >
142197 )
143198}
144199
0 commit comments