Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit92f0df0

Browse files
committed
Import hooks, some components & simplified context
1 parentc945b7c commit92f0df0

File tree

14 files changed

+702
-12
lines changed

14 files changed

+702
-12
lines changed

‎src/app/layout.tsx‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
importtype{Metadata}from"next";
22
import{Source_Sans_3}from"next/font/google";
33
import"@/styles/globals.css";
4+
import{AppProvider}from"@/contexts/AppContext";
45

56
constsourceSans3=Source_Sans_3({
67
subsets:["latin"],
@@ -18,8 +19,10 @@ export default function RootLayout({
1819
children:React.ReactNode;
1920
}>){
2021
return(
21-
<htmllang="en">
22-
<bodyclassName={`${sourceSans3.className}`}>{children}</body>
23-
</html>
22+
<AppProvider>
23+
<htmllang="en">
24+
<bodyclassName={`${sourceSans3.className}`}>{children}</body>
25+
</html>
26+
</AppProvider>
2427
);
2528
}

‎src/components/CategoryList.tsx‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"use client";
2+
3+
import{FC}from"react";
4+
import{useRouter}from"next/navigation";
5+
import{useAppContext}from"@/contexts/AppContext";
6+
import{useCategories}from"@/hooks/useCategories";
7+
import{defaultCategoryName}from"@/utils/consts";
8+
import{slugify}from"@/utils/slugify";
9+
10+
interfaceCategoryListItemProps{
11+
name:string;
12+
}
13+
14+
constCategoryListItem:FC<CategoryListItemProps>=({ name})=>{
15+
constrouter=useRouter();
16+
17+
const{ selectedLanguage, selectedCategory}=useAppContext();
18+
19+
consthandleSelect=()=>{
20+
router.push(
21+
`/${slugify(selectedLanguage.name)}/${slugify(
22+
selectedCategory.categoryName
23+
)}`
24+
);
25+
};
26+
27+
return(
28+
<liclassName="category">
29+
<button
30+
className={`category__btn${
31+
slugify(name)===slugify(selectedCategory.categoryName)
32+
?"category__btn--active"
33+
:""
34+
}`}
35+
onClick={handleSelect}
36+
>
37+
{name}
38+
</button>
39+
</li>
40+
);
41+
};
42+
43+
constCategoryList:FC=()=>{
44+
const{ fetchedCategories, loading, error}=useCategories();
45+
46+
if(loading)return<div>Loading...</div>;
47+
if(error)return<div>Error occurred:{error}</div>;
48+
49+
return(
50+
<ulrole="list"className="categories">
51+
<CategoryListItemname={defaultCategoryName}/>
52+
{fetchedCategories.map((name,idx)=>(
53+
<CategoryListItemkey={idx}name={name}/>
54+
))}
55+
</ul>
56+
);
57+
};
58+
59+
exportdefaultCategoryList;
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/**
2+
* Inspired by https://blog.logrocket.com/creating-custom-select-dropdown-css/
3+
*/
4+
5+
import{useRef,useEffect,useState,useMemo}from"react";
6+
7+
import{useAppContext}from"@/contexts/AppContext";
8+
import{useKeyboardNavigation}from"@/hooks/useKeyboardNavigation";
9+
import{useLanguages}from"@/hooks/useLanguages";
10+
import{LanguageType}from"@/types";
11+
// import { configureUserSelection } from "@utils/configureUserSelection";
12+
// import {
13+
// getLanguageDisplayLogo,
14+
// getLanguageDisplayName,
15+
// } from "@utils/languageUtils";
16+
import{slugify}from"@/utils/slugify";
17+
18+
// import SubLanguageSelector from "./SubLanguageSelector";
19+
import{useRouter}from"next/router";
20+
21+
constLanguageSelector=()=>{
22+
constrouter=useRouter();
23+
24+
const{ selectedLanguage, selectedCategory, setSearchText}=useAppContext();
25+
const{ fetchedLanguages, loading, error}=useLanguages();
26+
27+
constdropdownRef=useRef<HTMLDivElement>(null);
28+
const[isOpen,setIsOpen]=useState<boolean>(false);
29+
const[openedLanguages,setOpenedLanguages]=useState<LanguageType[]>([]);
30+
31+
constkeyboardItems=useMemo(()=>{
32+
returnfetchedLanguages.flatMap((lang)=>
33+
openedLanguages.map((ol)=>ol.name).includes(lang.languageName)
34+
?[
35+
{languageName:lang.languageName},
36+
...lang.subLanguages.map((sl)=>({
37+
languageName:lang.name,
38+
subLanguageName:sl.name,
39+
})),
40+
]
41+
:[{languageName:lang.name}]
42+
);
43+
},[fetchedLanguages,openedLanguages]);
44+
45+
constdisplayName=useMemo(
46+
()=>getLanguageDisplayName(language.name,subLanguage),
47+
[language.name,subLanguage]
48+
);
49+
50+
constdisplayLogo=useMemo(
51+
()=>getLanguageDisplayLogo(language.name,subLanguage),
52+
[language.name,subLanguage]
53+
);
54+
55+
consthandleToggleSubLanguage=(name:LanguageType["name"])=>{
56+
constisAlreadyOpened=openedLanguages.some((lang)=>lang.name===name);
57+
constopenedLang=fetchedLanguages.find(
58+
(lang)=>lang.languageName===name
59+
);
60+
if(openedLang===undefined||openedLang.subLanguages.length===0){
61+
return;
62+
}
63+
64+
if(!isAlreadyOpened){
65+
setOpenedLanguages((prev)=>[...prev,openedLang]);
66+
}else{
67+
setOpenedLanguages((prev)=>
68+
prev.filter((lang)=>lang.name!==openedLang.name)
69+
);
70+
}
71+
};
72+
73+
/**
74+
* When setting a new language we need to ensure that a category
75+
* has been set given this new language.
76+
* Ensure that the search text is cleared.
77+
*/
78+
consthandleSelect=async(selected:LanguageType)=>{
79+
const{
80+
language:newLanguage,
81+
subLanguage:newSubLanguage,
82+
category:newCategory,
83+
}=awaitconfigureUserSelection({
84+
languageName:selected.name,
85+
});
86+
87+
setSearchText("");
88+
router.push(`/${slugify(newLanguage.name)}/${slugify(newCategory)}`);
89+
setIsOpen(false);
90+
setOpenedLanguages([]);
91+
};
92+
93+
constafterSelect=()=>{
94+
setIsOpen(false);
95+
};
96+
97+
consthandleSubLanguageSelect=async(
98+
selectedLanguageName:LanguageType["name"],
99+
selectedSubLanguageName:
100+
|LanguageType["subLanguages"][number]["name"]
101+
|undefined
102+
)=>{
103+
const{
104+
language:newLanguage,
105+
subLanguage:newSubLanguage,
106+
category:newCategory,
107+
}=awaitconfigureUserSelection({
108+
languageName:selectedLanguageName,
109+
subLanguageName:selectedSubLanguageName,
110+
});
111+
112+
setSearchText("");
113+
router.push(`/${slugify(newLanguage.name)}/${slugify(newCategory)}`);
114+
afterSelect();
115+
};
116+
117+
const{ focusedIndex, handleKeyDown, resetFocus, focusFirst}=
118+
useKeyboardNavigation({
119+
items:keyboardItems,
120+
isOpen,
121+
toggleDropdown:(l)=>handleToggleSubLanguage(l),
122+
onSelect:(l,sl)=>handleSubLanguageSelect(l,sl),
123+
onClose:()=>setIsOpen(false),
124+
});
125+
126+
consthandleBlur=()=>{
127+
setTimeout(()=>{
128+
if(
129+
dropdownRef.current&&
130+
!dropdownRef.current.contains(document.activeElement)
131+
){
132+
setIsOpen(false);
133+
}
134+
},0);
135+
};
136+
137+
consttoggleDropdown=()=>{
138+
setIsOpen((prev)=>{
139+
if(!prev)setTimeout(focusFirst,0);
140+
return!prev;
141+
});
142+
};
143+
144+
useEffect(()=>{
145+
if(!isOpen){
146+
resetFocus();
147+
}
148+
// eslint-disable-next-line react-hooks/exhaustive-deps
149+
},[isOpen]);
150+
151+
useEffect(()=>{
152+
if(isOpen&&focusedIndex>=0){
153+
constelements=Array.from(
154+
document.querySelectorAll(".selector__item")
155+
)asHTMLElement[];
156+
constfocusableElements=elements.filter(
157+
(el)=>el.getAttribute("tabIndex")!=="-1"
158+
);
159+
constelement=focusableElements[focusedIndex];
160+
element?.focus();
161+
}
162+
},[isOpen,focusedIndex]);
163+
164+
if(loading){
165+
return<p>Loading languages...</p>;
166+
}
167+
168+
if(error){
169+
return<p>Error fetching languages:{error}</p>;
170+
}
171+
172+
return(
173+
<div
174+
className={`selector${isOpen ?"selector--open" :""}`}
175+
ref={dropdownRef}
176+
onBlur={handleBlur}
177+
>
178+
<button
179+
className="selector__button"
180+
aria-label="select button"
181+
aria-haspopup="listbox"
182+
aria-expanded={isOpen}
183+
onClick={toggleDropdown}
184+
>
185+
<divclassName="selector__value">
186+
<imgsrc={displayLogo}alt=""/>
187+
<span>{displayName}</span>
188+
</div>
189+
<spanclassName="selector__arrow"/>
190+
</button>
191+
<ul
192+
className={`selector__dropdown${isOpen ?"" :" hidden"}`}
193+
role="listbox"
194+
onKeyDown={handleKeyDown}
195+
tabIndex={0}
196+
>
197+
{fetchedLanguages.map((lang,index)=>
198+
lang.subLanguages.length>0 ?(
199+
<SubLanguageSelector
200+
key={lang.name}
201+
opened={openedLanguages.includes(lang)}
202+
parentLanguage={lang}
203+
onDropdownToggle={handleToggleSubLanguage}
204+
handleParentSelect={handleSelect}
205+
afterSelect={afterSelect}
206+
/>
207+
) :(
208+
<li
209+
key={lang.name}
210+
role="option"
211+
tabIndex={0}
212+
onClick={()=>handleSelect(lang)}
213+
className={`selector__item${
214+
language.name===lang.name ?"selected" :""
215+
}${focusedIndex===index ?"focused" :""}`}
216+
aria-selected={language.name===lang.name}
217+
>
218+
<label>
219+
<imgsrc={lang.icon}alt=""/>
220+
<span>{lang.name}</span>
221+
</label>
222+
</li>
223+
)
224+
)}
225+
</ul>
226+
</div>
227+
);
228+
};
229+
230+
exportdefaultLanguageSelector;

‎src/components/Sidebar.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
//import CategoryList from "@components/CategoryList";
1+
importCategoryListfrom"@/components/CategoryList";
22
// import LanguageSelector from "@components/LanguageSelector";
33

44
constSidebar=()=>{
55
return(
66
<asideclassName="sidebar flow">
7-
{/* <LanguageSelector />
8-
<CategoryList /> */}
7+
{/* <LanguageSelector /> */}
8+
<CategoryList/>
99
</aside>
1010
);
1111
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp