1- import { useRef , useEffect , useState } from "react" ;
1+ import { useRef , useEffect , useState , useMemo } from "react" ;
22
33import { useAppContext } from "@contexts/AppContext" ;
44import { useKeyboardNavigation } from "@hooks/useKeyboardNavigation" ;
55import { useLanguages } from "@hooks/useLanguages" ;
66import { LanguageType } from "@types" ;
77
8+ import SubLanguageSelector from "./SubLanguageSelector" ;
9+
810// Inspired by https://blog.logrocket.com/creating-custom-select-dropdown-css/
911
1012const LanguageSelector = ( ) => {
1113 const { language, setLanguage } = useAppContext ( ) ;
1214 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+ ) ;
1331
1432 const dropdownRef = useRef < HTMLDivElement > ( null ) ;
1533 const [ isOpen , setIsOpen ] = useState ( false ) ;
34+ const [ openedLanguages , setOpenedLanguages ] = useState < LanguageType [ ] > ( [ ] ) ;
1635
1736 const handleSelect = ( selected : LanguageType ) => {
1837 setLanguage ( selected ) ;
1938 setIsOpen ( false ) ;
39+ setOpenedLanguages ( [ ] ) ;
2040 } ;
2141
2242 const { focusedIndex, handleKeyDown, resetFocus, focusFirst } =
2343 useKeyboardNavigation ( {
24- items : fetchedLanguages ,
44+ items : allLanguages ,
2545 isOpen,
46+ openedLanguages,
47+ toggleDropdown : ( openedLang ) => handleToggleSublanguage ( openedLang ) ,
2648 onSelect : handleSelect ,
2749 onClose : ( ) => setIsOpen ( false ) ,
2850 } ) ;
@@ -38,6 +60,20 @@ const LanguageSelector = () => {
3860 } , 0 ) ;
3961 } ;
4062
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+
4177 const toggleDropdown = ( ) => {
4278 setIsOpen ( ( prev ) => {
4379 if ( ! prev ) setTimeout ( focusFirst , 0 ) ;
@@ -52,6 +88,13 @@ const LanguageSelector = () => {
5288 // eslint-disable-next-line react-hooks/exhaustive-deps
5389 } , [ isOpen ] ) ;
5490
91+ useEffect ( ( ) => {
92+ if ( language . mainLanguage ) {
93+ handleToggleSublanguage ( language . mainLanguage ) ;
94+ }
95+ // eslint-disable-next-line react-hooks/exhaustive-deps
96+ } , [ language ] ) ;
97+
5598 useEffect ( ( ) => {
5699 if ( isOpen && focusedIndex >= 0 ) {
57100 const element = document . querySelector (
@@ -90,23 +133,35 @@ const LanguageSelector = () => {
90133 onKeyDown = { handleKeyDown }
91134 tabIndex = { - 1 }
92135 >
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+ ) }
110165 </ ul >
111166 ) }
112167 </ div >
0 commit comments