1
- import { useRef , useEffect , useState } from "react" ;
1
+ import { useRef , useEffect , useState , useMemo } from "react" ;
2
2
3
3
import { useAppContext } from "@contexts/AppContext" ;
4
4
import { useKeyboardNavigation } from "@hooks/useKeyboardNavigation" ;
5
5
import { useLanguages } from "@hooks/useLanguages" ;
6
6
import { LanguageType } from "@types" ;
7
7
8
+ import SubLanguageSelector from "./SubLanguageSelector" ;
9
+
8
10
// Inspired by https://blog.logrocket.com/creating-custom-select-dropdown-css/
9
11
10
12
const LanguageSelector = ( ) => {
11
13
const { language, setLanguage } = useAppContext ( ) ;
12
14
const { fetchedLanguages, loading, error } = useLanguages ( ) ;
15
+ const allLanguages = useMemo (
16
+ ( ) =>
17
+ fetchedLanguages . flatMap ( ( lang ) =>
18
+ lang . subLanguages . length > 0
19
+ ? [
20
+ lang ,
21
+ ...lang . subLanguages . map ( ( subLang ) => ( {
22
+ ...subLang ,
23
+ mainLanguage : lang ,
24
+ subLanguages : [ ] ,
25
+ } ) ) ,
26
+ ]
27
+ : [ lang ]
28
+ ) ,
29
+ [ fetchedLanguages ]
30
+ ) ;
13
31
14
32
const dropdownRef = useRef < HTMLDivElement > ( null ) ;
15
33
const [ isOpen , setIsOpen ] = useState ( false ) ;
34
+ const [ openedLanguages , setOpenedLanguages ] = useState < LanguageType [ ] > ( [ ] ) ;
16
35
17
36
const handleSelect = ( selected : LanguageType ) => {
18
37
setLanguage ( selected ) ;
19
38
setIsOpen ( false ) ;
39
+ setOpenedLanguages ( [ ] ) ;
20
40
} ;
21
41
22
42
const { focusedIndex, handleKeyDown, resetFocus, focusFirst } =
23
43
useKeyboardNavigation ( {
24
- items : fetchedLanguages ,
44
+ items : allLanguages ,
25
45
isOpen,
46
+ openedLanguages,
47
+ toggleDropdown : ( openedLang ) => handleToggleSublanguage ( openedLang ) ,
26
48
onSelect : handleSelect ,
27
49
onClose : ( ) => setIsOpen ( false ) ,
28
50
} ) ;
@@ -38,6 +60,20 @@ const LanguageSelector = () => {
38
60
} , 0 ) ;
39
61
} ;
40
62
63
+ const handleToggleSublanguage = ( openedLang : LanguageType ) => {
64
+ const isAlreadyOpened = openedLanguages . some (
65
+ ( lang ) => lang . name === openedLang . name
66
+ ) ;
67
+
68
+ if ( ! isAlreadyOpened ) {
69
+ setOpenedLanguages ( ( prev ) => [ ...prev , openedLang ] ) ;
70
+ } else {
71
+ setOpenedLanguages ( ( prev ) =>
72
+ prev . filter ( ( lang ) => lang . name !== openedLang . name )
73
+ ) ;
74
+ }
75
+ } ;
76
+
41
77
const toggleDropdown = ( ) => {
42
78
setIsOpen ( ( prev ) => {
43
79
if ( ! prev ) setTimeout ( focusFirst , 0 ) ;
@@ -52,6 +88,13 @@ const LanguageSelector = () => {
52
88
// eslint-disable-next-line react-hooks/exhaustive-deps
53
89
} , [ isOpen ] ) ;
54
90
91
+ useEffect ( ( ) => {
92
+ if ( language . mainLanguage ) {
93
+ handleToggleSublanguage ( language . mainLanguage ) ;
94
+ }
95
+ // eslint-disable-next-line react-hooks/exhaustive-deps
96
+ } , [ language ] ) ;
97
+
55
98
useEffect ( ( ) => {
56
99
if ( isOpen && focusedIndex >= 0 ) {
57
100
const element = document . querySelector (
@@ -90,23 +133,35 @@ const LanguageSelector = () => {
90
133
onKeyDown = { handleKeyDown }
91
134
tabIndex = { - 1 }
92
135
>
93
- { fetchedLanguages . map ( ( lang , index ) => (
94
- < li
95
- key = { lang . name }
96
- role = "option"
97
- tabIndex = { - 1 }
98
- onClick = { ( ) => handleSelect ( lang ) }
99
- className = { `selector__item ${
100
- language . name === lang . name ? "selected" : ""
101
- } ${ focusedIndex === index ? "focused" : "" } `}
102
- aria-selected = { language . name === lang . name }
103
- >
104
- < label >
105
- < img src = { lang . icon } alt = "" />
106
- < span > { lang . name } </ span >
107
- </ label >
108
- </ li >
109
- ) ) }
136
+ { fetchedLanguages . map ( ( lang , index ) =>
137
+ lang . subLanguages . length > 0 ? (
138
+ < SubLanguageSelector
139
+ key = { index }
140
+ mainLanguage = { lang }
141
+ afterSelect = { ( ) => {
142
+ setIsOpen ( false ) ;
143
+ } }
144
+ opened = { openedLanguages . includes ( lang ) }
145
+ onDropdownToggle = { handleToggleSublanguage }
146
+ />
147
+ ) : (
148
+ < li
149
+ key = { lang . name }
150
+ role = "option"
151
+ tabIndex = { - 1 }
152
+ onClick = { ( ) => handleSelect ( lang ) }
153
+ className = { `selector__item ${
154
+ language . name === lang . name ? "selected" : ""
155
+ } ${ focusedIndex === index ? "focused" : "" } `}
156
+ aria-selected = { language . name === lang . name }
157
+ >
158
+ < label >
159
+ < img src = { lang . icon } alt = "" />
160
+ < span > { lang . name } </ span >
161
+ </ label >
162
+ </ li >
163
+ )
164
+ ) }
110
165
</ ul >
111
166
) }
112
167
</ div >
0 commit comments