Skip to content

Commit 6e7b4dd

Browse files
authored
Merge pull request #198 from ACR1209/frameworks-frontend
Frameworks frontend implementation
2 parents 40c29cb + da71b16 commit 6e7b4dd

File tree

16 files changed

+283
-28
lines changed

16 files changed

+283
-28
lines changed

cspell-dict.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
quicksnip
22
slugified
33
slugifyed
4+
sublanguage
5+
fastapi
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
title: Hello, World!
3+
description: Show Hello World on the page.
4+
author: ACR1209
5+
tags: printing,hello-world
6+
---
7+
8+
```tsx
9+
import React from 'react';
10+
import ReactDOM from 'react-dom';
11+
12+
const App = () => {
13+
return (
14+
<div>
15+
<h1>Hello, World!</h1>
16+
</div>
17+
);
18+
};
19+
20+
ReactDOM.render(<App />, document.getElementById('root'));
21+
```

snippets/javascript/[react]/icon.svg

Lines changed: 9 additions & 0 deletions
Loading
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
title: Hello, World!
3+
description: Returns Hello, World! when it recives a GET request made to the root endpoint.
4+
author: ACR1209
5+
tags: printing,hello-world,web,api
6+
---
7+
8+
```py
9+
from typing import Union
10+
from fastapi import FastAPI
11+
12+
app = FastAPI()
13+
14+
15+
@app.get("/")
16+
def read_root():
17+
return {"msg": "Hello, World!"}
18+
19+
# Usage:
20+
# -> Go to http://127.0.0.1:8000/ and you'll see {"msg", "Hello, World!"}
21+
```

snippets/python/[fastapi]/icon.svg

Lines changed: 1 addition & 0 deletions
Loading

src/components/LanguageSelector.tsx

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,50 @@
1-
import { useRef, useEffect, useState } from "react";
1+
import { useRef, useEffect, useState, useMemo } from "react";
22

33
import { useAppContext } from "@contexts/AppContext";
44
import { useKeyboardNavigation } from "@hooks/useKeyboardNavigation";
55
import { useLanguages } from "@hooks/useLanguages";
66
import { LanguageType } from "@types";
77

8+
import SubLanguageSelector from "./SubLanguageSelector";
9+
810
// Inspired by https://blog.logrocket.com/creating-custom-select-dropdown-css/
911

1012
const 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>

src/components/SnippetList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const SnippetList = () => {
8787
<SnippetModal
8888
snippet={snippet}
8989
handleCloseModal={handleCloseModal}
90-
language={language.name}
90+
language={snippet.extension}
9191
/>
9292
)}
9393
</AnimatePresence>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { useAppContext } from "@contexts/AppContext";
2+
import { LanguageType } from "@types";
3+
4+
type SubLanguageSelectorProps = {
5+
mainLanguage: LanguageType;
6+
afterSelect: () => void;
7+
onDropdownToggle: (openedLang: LanguageType) => void;
8+
opened: boolean;
9+
};
10+
11+
const SubLanguageSelector = ({
12+
mainLanguage,
13+
afterSelect,
14+
onDropdownToggle,
15+
opened,
16+
}: SubLanguageSelectorProps) => {
17+
const { language, setLanguage } = useAppContext();
18+
19+
const handleSelect = (selected: LanguageType) => {
20+
setLanguage(selected);
21+
onDropdownToggle(mainLanguage);
22+
afterSelect();
23+
};
24+
25+
return (
26+
<>
27+
<li
28+
key={mainLanguage.name}
29+
role="option"
30+
tabIndex={-1}
31+
className={`selector__item ${
32+
language.name === mainLanguage.name ? "selected" : ""
33+
}`}
34+
aria-selected={language.name === mainLanguage.name}
35+
onClick={() => setLanguage(mainLanguage)}
36+
>
37+
<label>
38+
<img src={mainLanguage.icon} alt={mainLanguage.name} />
39+
<span>{mainLanguage.name}</span>
40+
<button
41+
className="sublanguage__button"
42+
tabIndex={-1}
43+
aria-expanded={opened}
44+
aria-haspopup="listbox"
45+
onClick={(e) => {
46+
e.stopPropagation();
47+
onDropdownToggle(mainLanguage);
48+
}}
49+
>
50+
<span className="sublanguage__arrow" />
51+
</button>
52+
</label>
53+
</li>
54+
55+
{opened && (
56+
<>
57+
{mainLanguage.subLanguages.map((subLanguage) => (
58+
<li
59+
key={subLanguage.name}
60+
role="option"
61+
tabIndex={-1}
62+
className={`selector__item sublanguage__item ${
63+
language.name === subLanguage.name ? "selected" : ""
64+
}`}
65+
aria-selected={language.name === subLanguage.name}
66+
onClick={() => {
67+
handleSelect({
68+
...subLanguage,
69+
mainLanguage: mainLanguage,
70+
subLanguages: [],
71+
});
72+
}}
73+
>
74+
<label>
75+
<img src={subLanguage.icon} alt={subLanguage.name} />
76+
<span>{subLanguage.name}</span>
77+
</label>
78+
</li>
79+
))}
80+
</>
81+
)}
82+
</>
83+
);
84+
};
85+
86+
export default SubLanguageSelector;

src/contexts/AppContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { AppState, LanguageType, SnippetType } from "@types";
66
const defaultLanguage: LanguageType = {
77
name: "JAVASCRIPT",
88
icon: "/icons/javascript.svg",
9-
subIndexes: [],
9+
subLanguages: [],
1010
};
1111

1212
// TODO: add custom loading and error handling

src/hooks/useCategories.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useFetch } from "./useFetch";
99
export const useCategories = () => {
1010
const { language } = useAppContext();
1111
const { data, loading, error } = useFetch<CategoryType[]>(
12-
`/consolidated/${slugify(language.name)}.json`
12+
`/consolidated/${language.mainLanguage ? `${slugify(language.mainLanguage.name)}--${slugify(language.name)}` : slugify(language.name)}.json`
1313
);
1414

1515
const fetchedCategories = useMemo(() => {

0 commit comments

Comments
 (0)