Skip to content

Commit c037d18

Browse files
committed
rework the sharing and search logic to also handle sub languages
1 parent 2215ba9 commit c037d18

13 files changed

+274
-153
lines changed

src/AppRouter.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ const AppRouter = () => {
99
<Route element={<App />}>
1010
<Route path="/" element={<SnippetList />} />
1111
<Route path="/:languageName" element={<SnippetList />} />
12-
<Route path="/:languageName/:categoryName" element={<SnippetList />} />
12+
<Route
13+
path="/:languageName/:subLanguageName/:categoryName"
14+
element={<SnippetList />}
15+
/>
1316
</Route>
1417
</Routes>
1518
);

src/components/CategoryList.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ const CategoryListItem: FC<CategoryListItemProps> = ({ name }) => {
1414
const navigate = useNavigate();
1515
const [searchParams] = useSearchParams();
1616

17-
const { language, category } = useAppContext();
17+
const { language, subLanguage, category } = useAppContext();
1818

1919
const handleSelect = () => {
2020
navigate({
21-
pathname: `/${slugify(language.name)}/${slugify(name)}`,
21+
pathname: `/${slugify(language.name)}/${slugify(subLanguage)}/${slugify(name)}`,
2222
search: searchParams.toString(),
2323
});
2424
};

src/components/LanguageSelector.tsx

+92-56
Original file line numberDiff line numberDiff line change
@@ -10,60 +10,118 @@ import { useKeyboardNavigation } from "@hooks/useKeyboardNavigation";
1010
import { useLanguages } from "@hooks/useLanguages";
1111
import { LanguageType } from "@types";
1212
import { configureUserSelection } from "@utils/configureUserSelection";
13+
import {
14+
getLanguageDisplayLogo,
15+
getLanguageDisplayName,
16+
} from "@utils/languageUtils";
1317
import { slugify } from "@utils/slugify";
1418

1519
import SubLanguageSelector from "./SubLanguageSelector";
1620

1721
const LanguageSelector = () => {
1822
const navigate = useNavigate();
1923

20-
const { language, setSearchText } = useAppContext();
24+
const { language, subLanguage, setSearchText } = useAppContext();
2125
const { fetchedLanguages, loading, error } = useLanguages();
22-
const allLanguages = useMemo(
23-
() =>
24-
fetchedLanguages.flatMap((lang) =>
25-
lang.subLanguages.length > 0
26-
? [
27-
lang,
28-
...lang.subLanguages.map((subLang) => ({
29-
...subLang,
30-
mainLanguage: lang,
31-
subLanguages: [],
32-
})),
33-
]
34-
: [lang]
35-
),
36-
[fetchedLanguages]
37-
);
3826

3927
const dropdownRef = useRef<HTMLDivElement>(null);
40-
const [isOpen, setIsOpen] = useState(false);
28+
const [isOpen, setIsOpen] = useState<boolean>(false);
4129
const [openedLanguages, setOpenedLanguages] = useState<LanguageType[]>([]);
4230

31+
const keyboardItems = useMemo(() => {
32+
return fetchedLanguages.flatMap((lang) =>
33+
openedLanguages.map((ol) => ol.name).includes(lang.name)
34+
? [
35+
{ languageName: lang.name },
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+
const displayName = useMemo(
46+
() => getLanguageDisplayName(language.name, subLanguage),
47+
[language.name, subLanguage]
48+
);
49+
50+
const displayLogo = useMemo(
51+
() => getLanguageDisplayLogo(language.name, subLanguage),
52+
[language.name, subLanguage]
53+
);
54+
55+
const handleToggleSubLanguage = (name: LanguageType["name"]) => {
56+
const isAlreadyOpened = openedLanguages.some((lang) => lang.name === name);
57+
const openedLang = fetchedLanguages.find((lang) => lang.name === name);
58+
if (openedLang === undefined || openedLang.subLanguages.length === 0) {
59+
return;
60+
}
61+
62+
if (!isAlreadyOpened) {
63+
setOpenedLanguages((prev) => [...prev, openedLang]);
64+
} else {
65+
setOpenedLanguages((prev) =>
66+
prev.filter((lang) => lang.name !== openedLang.name)
67+
);
68+
}
69+
};
70+
4371
/**
4472
* When setting a new language we need to ensure that a category
4573
* has been set given this new language.
4674
* Ensure that the search text is cleared.
4775
*/
4876
const handleSelect = async (selected: LanguageType) => {
49-
const { language: newLanguage, category: newCategory } =
50-
await configureUserSelection({
51-
languageName: selected.name,
52-
});
77+
const {
78+
language: newLanguage,
79+
subLanguage: newSubLanguage,
80+
category: newCategory,
81+
} = await configureUserSelection({
82+
languageName: selected.name,
83+
});
5384

5485
setSearchText("");
55-
navigate(`/${slugify(newLanguage.name)}/${slugify(newCategory)}`);
86+
navigate(
87+
`/${slugify(newLanguage.name)}/${slugify(newSubLanguage)}/${slugify(newCategory)}`
88+
);
5689
setIsOpen(false);
5790
setOpenedLanguages([]);
5891
};
5992

93+
const afterSelect = () => {
94+
setIsOpen(false);
95+
};
96+
97+
const handleSubLanguageSelect = 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+
} = await configureUserSelection({
108+
languageName: selectedLanguageName,
109+
subLanguageName: selectedSubLanguageName,
110+
});
111+
112+
setSearchText("");
113+
navigate(
114+
`/${slugify(newLanguage.name)}/${slugify(newSubLanguage)}/${slugify(newCategory)}`
115+
);
116+
afterSelect();
117+
};
118+
60119
const { focusedIndex, handleKeyDown, resetFocus, focusFirst } =
61120
useKeyboardNavigation({
62-
items: allLanguages,
121+
items: keyboardItems,
63122
isOpen,
64-
openedLanguages,
65-
toggleDropdown: (openedLang) => handleToggleSublanguage(openedLang),
66-
onSelect: handleSelect,
123+
toggleDropdown: (l) => handleToggleSubLanguage(l),
124+
onSelect: (l, sl) => handleSubLanguageSelect(l, sl),
67125
onClose: () => setIsOpen(false),
68126
});
69127

@@ -78,20 +136,6 @@ const LanguageSelector = () => {
78136
}, 0);
79137
};
80138

81-
const handleToggleSublanguage = (openedLang: LanguageType) => {
82-
const isAlreadyOpened = openedLanguages.some(
83-
(lang) => lang.name === openedLang.name
84-
);
85-
86-
if (!isAlreadyOpened) {
87-
setOpenedLanguages((prev) => [...prev, openedLang]);
88-
} else {
89-
setOpenedLanguages((prev) =>
90-
prev.filter((lang) => lang.name !== openedLang.name)
91-
);
92-
}
93-
};
94-
95139
const toggleDropdown = () => {
96140
setIsOpen((prev) => {
97141
if (!prev) setTimeout(focusFirst, 0);
@@ -106,13 +150,6 @@ const LanguageSelector = () => {
106150
// eslint-disable-next-line react-hooks/exhaustive-deps
107151
}, [isOpen]);
108152

109-
useEffect(() => {
110-
if (language.mainLanguage) {
111-
handleToggleSublanguage(language.mainLanguage);
112-
}
113-
// eslint-disable-next-line react-hooks/exhaustive-deps
114-
}, [language]);
115-
116153
useEffect(() => {
117154
if (isOpen && focusedIndex >= 0) {
118155
const element = document.querySelector(
@@ -144,8 +181,8 @@ const LanguageSelector = () => {
144181
onClick={toggleDropdown}
145182
>
146183
<div className="selector__value">
147-
<img src={language.icon} alt="" />
148-
<span>{language.name || "Select a language"}</span>
184+
<img src={displayLogo} alt="" />
185+
<span>{displayName}</span>
149186
</div>
150187
<span className="selector__arrow" />
151188
</button>
@@ -159,13 +196,12 @@ const LanguageSelector = () => {
159196
{fetchedLanguages.map((lang, index) =>
160197
lang.subLanguages.length > 0 ? (
161198
<SubLanguageSelector
162-
key={index}
163-
mainLanguage={lang}
164-
afterSelect={() => {
165-
setIsOpen(false);
166-
}}
199+
key={lang.name}
167200
opened={openedLanguages.includes(lang)}
168-
onDropdownToggle={handleToggleSublanguage}
201+
parentLanguage={lang}
202+
onDropdownToggle={handleToggleSubLanguage}
203+
handleParentSelect={handleSelect}
204+
afterSelect={afterSelect}
169205
/>
170206
) : (
171207
<li

src/components/SnippetList.tsx

+11-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import { useAppContext } from "@contexts/AppContext";
66
import { useSnippets } from "@hooks/useSnippets";
77
import { SnippetType } from "@types";
88
import { QueryParams } from "@utils/enums";
9+
import {
10+
getLanguageDisplayLogo,
11+
getLanguageDisplayName,
12+
} from "@utils/languageUtils";
913
import { slugify } from "@utils/slugify";
1014

1115
import { LeftAngleArrowIcon } from "./Icons";
@@ -15,7 +19,7 @@ const SnippetList = () => {
1519
const [searchParams, setSearchParams] = useSearchParams();
1620
const shouldReduceMotion = useReducedMotion();
1721

18-
const { language, snippet, setSnippet } = useAppContext();
22+
const { language, subLanguage, snippet, setSnippet } = useAppContext();
1923
const { fetchedSnippets } = useSnippets();
2024

2125
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
@@ -64,8 +68,8 @@ const SnippetList = () => {
6468
<>
6569
<motion.ul role="list" className="snippets">
6670
<AnimatePresence mode="popLayout">
67-
{fetchedSnippets.map((snippet, _idx) => {
68-
const uniqueId = `${language.name}-${snippet.title}`;
71+
{fetchedSnippets.map((snippet, idx) => {
72+
const uniqueId = `${language.name}-${snippet.title}-${idx}`;
6973
return (
7074
<motion.li
7175
key={uniqueId}
@@ -75,15 +79,13 @@ const SnippetList = () => {
7579
opacity: 1,
7680
y: 0,
7781
transition: {
78-
// delay: shouldReduceMotion ? 0 : 0.09 + idx * 0.05,
7982
duration: shouldReduceMotion ? 0 : 0.2,
8083
},
8184
}}
8285
exit={{
8386
opacity: 0,
8487
y: -20,
8588
transition: {
86-
// delay: idx * 0.01,
8789
duration: shouldReduceMotion ? 0 : 0.09,
8890
},
8991
}}
@@ -100,7 +102,10 @@ const SnippetList = () => {
100102
whileTap={{ scale: 0.98 }}
101103
>
102104
<div className="snippet__preview">
103-
<img src={language.icon} alt={language.name} />
105+
<img
106+
src={getLanguageDisplayLogo(language.name, subLanguage)}
107+
alt={getLanguageDisplayName(language.name, subLanguage)}
108+
/>
104109
</div>
105110
<h3 className="snippet__title">{snippet.title}</h3>
106111
</motion.button>

0 commit comments

Comments
 (0)