Skip to content

Commit 634ab12

Browse files
Merge branch '21-25' into main
2 parents 046d807 + 7f97af8 commit 634ab12

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+283
-21
lines changed

package-lock.json

+41
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"@testing-library/jest-dom": "^5.14.1",
77
"@testing-library/react": "^11.2.7",
88
"@testing-library/user-event": "^12.8.3",
9+
"copy-to-clipboard": "^3.3.1",
10+
"js-cookie": "^3.0.1",
911
"lodash": "^4.17.21",
1012
"react": "^17.0.2",
1113
"react-dom": "^17.0.2",
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useRef, useState } from "react"
2+
import useClickOutside from "./useClickOutside"
3+
4+
export default function ClickOutsideComponent() {
5+
const [open, setOpen] = useState(false)
6+
const modalRef = useRef()
7+
8+
useClickOutside(modalRef, () => {
9+
if (open) setOpen(false)
10+
})
11+
12+
return (
13+
<>
14+
<button onClick={() => setOpen(true)}>Open</button>
15+
<div
16+
ref={modalRef}
17+
style={{
18+
display: open ? "block" : "none",
19+
backgroundColor: "blue",
20+
color: "white",
21+
width: "100px",
22+
height: "100px",
23+
position: "absolute",
24+
top: "calc(50% - 50px)",
25+
left: "calc(50% - 50px)",
26+
}}
27+
>
28+
<span>Modal</span>
29+
</div>
30+
</>
31+
)
32+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import useEventListener from "../1-20/13-useEventListener/useEventListener"
2+
3+
export default function useClickOutside(ref, cb) {
4+
useEventListener(
5+
"click",
6+
e => {
7+
if (ref.current == null || ref.current.contains(e.target)) return
8+
cb(e)
9+
},
10+
document
11+
)
12+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import useDarkMode from "./useDarkMode"
2+
import "./body.css"
3+
4+
export default function DarkModeComponent() {
5+
const [darkMode, setDarkMode] = useDarkMode()
6+
7+
return (
8+
<button
9+
onClick={() => setDarkMode(prevDarkMode => !prevDarkMode)}
10+
style={{
11+
border: `1px solid ${darkMode ? "white" : "black"}`,
12+
background: "none",
13+
color: darkMode ? "white" : "black",
14+
}}
15+
>
16+
Toggle Dark Mode
17+
</button>
18+
)
19+
}

src/22-useDarkMode/body.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
body.dark-mode {
2+
background-color: #333;
3+
}

src/22-useDarkMode/useDarkMode.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useEffect } from "react"
2+
import useMediaQuery from "../1-20/16-useMediaQuery/useMediaQuery"
3+
import { useLocalStorage } from "../1-20/8-useStorage/useStorage"
4+
5+
export default function useDarkMode() {
6+
const [darkMode, setDarkMode] = useLocalStorage("useDarkMode")
7+
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)")
8+
const enabled = darkMode ?? prefersDarkMode
9+
10+
useEffect(() => {
11+
document.body.classList.toggle("dark-mode", enabled)
12+
}, [enabled])
13+
14+
return [enabled, setDarkMode]
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import useCopyToClipboard from "./useCopyToClipboard"
2+
3+
export default function CopyToClipboardComponent() {
4+
const [copyToClipboard, { success }] = useCopyToClipboard()
5+
6+
return (
7+
<>
8+
<button onClick={() => copyToClipboard("This was copied")}>
9+
{success ? "Copied" : "Copy Text"}
10+
</button>
11+
<input type="text" />
12+
</>
13+
)
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useState } from "react"
2+
import copy from "copy-to-clipboard"
3+
4+
export default function useCopyToClipboard() {
5+
const [value, setValue] = useState()
6+
const [success, setSuccess] = useState()
7+
8+
const copyToClipboard = (text, options) => {
9+
const result = copy(text, options)
10+
if (result) setValue(text)
11+
setSuccess(result)
12+
}
13+
14+
return [copyToClipboard, { value, success }]
15+
}

src/24-useCookie/CookieComponent.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import useCookie from "./useCookie"
2+
3+
export default function CookieComponent() {
4+
const [value, update, remove] = useCookie("name", "John")
5+
6+
return (
7+
<>
8+
<div>{value}</div>
9+
<button onClick={() => update("Sally")}>Change Name To Sally</button>
10+
<button onClick={remove}>Delete Name</button>
11+
</>
12+
)
13+
}

src/24-useCookie/useCookie.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useState, useCallback } from "react"
2+
import Cookies from "js-cookie"
3+
4+
export default function useCookie(name, defaultValue) {
5+
const [value, setValue] = useState(() => {
6+
const cookie = Cookies.get(name)
7+
if (cookie) return cookie
8+
Cookies.set(name, defaultValue)
9+
return defaultValue
10+
})
11+
12+
const updateCookie = useCallback(
13+
(newValue, options) => {
14+
Cookies.set(name, newValue, options)
15+
setValue(newValue)
16+
},
17+
[name]
18+
)
19+
20+
const deleteCookie = useCallback(() => {
21+
Cookies.remove(name)
22+
setValue(null)
23+
}, [name])
24+
25+
return [value, updateCookie, deleteCookie]
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import useTranslation from "./useTranslation"
2+
3+
export default function TranslationComponent() {
4+
const { language, setLanguage, setFallbackLanguage, t } = useTranslation()
5+
6+
return (
7+
<>
8+
<div>{language}</div>
9+
<div>{t("hi")}</div>
10+
<div>{t("bye")}</div>
11+
<div>{t("nested.value")}</div>
12+
<button onClick={() => setLanguage("sp")}>Change To Spanish</button>
13+
<button onClick={() => setLanguage("en")}>Change To English</button>
14+
</>
15+
)
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"hi": "Hello",
3+
"bye": "Goodbye",
4+
"nested": { "value": "Test" }
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * as en from "./en.json"
2+
export * as sp from "./sp.json"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"hi": "Hola"
3+
}
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useLocalStorage } from "../1-20/8-useStorage/useStorage"
2+
import * as translations from "./translations"
3+
4+
export default function useTranslation() {
5+
const [language, setLanguage] = useLocalStorage("language", "en")
6+
const [fallbackLanguage, setFallbackLanguage] = useLocalStorage(
7+
"fallbackLanguage",
8+
"en"
9+
)
10+
11+
const translate = key => {
12+
const keys = key.split(".")
13+
14+
return (
15+
getNestedTranslation(language, keys) ??
16+
getNestedTranslation(fallbackLanguage, keys) ??
17+
key
18+
)
19+
}
20+
21+
return {
22+
language,
23+
setLanguage,
24+
fallbackLanguage,
25+
setFallbackLanguage,
26+
t: translate,
27+
}
28+
}
29+
30+
function getNestedTranslation(language, keys) {
31+
return keys.reduce((obj, key) => {
32+
return obj?.[key]
33+
}, translations[language])
34+
}

src/App.js

+31-21
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
1-
import ToggleComponent from "./1-useToggle/ToggleComponent"
2-
import TimeoutComponent from "./2-useTimeout/TimeoutComponent"
3-
import DebounceComponent from "./3-useDebounce/DebounceComponent"
4-
import UpdateEffectComponent from "./4-useUpdateEffect/UpdateEffectComponent"
5-
import ArrayComponent from "./5-useArray/ArrayComponent"
6-
import PreviousComponent from "./6-usePrevious/PreviousComponent"
7-
import StateWithHistoryComponent from "./7-useStateWithHistory/StateWithHistoryComponent"
8-
import StorageComponent from "./8-useStorage/StorageComponent"
9-
import AsyncComponent from "./9-useAsync/AsyncComponent"
10-
import FetchComponent from "./10-useFetch/FetchComponent"
11-
import ScriptComponent from "./11-useScript/ScriptComponent"
12-
import DeepCompareEffectComponent from "./12-useDeepCompareEffect/DeepCompareEffectComponent"
13-
import EventListenerComponent from "./13-useEventListener/EventListenerComponent"
14-
import OnScreenComponentComponent from "./14-useOnScreen/OnScreenComponent"
15-
import WindowSizeComponent from "./15-useWindowSize/WindowSizeComponent"
16-
import MediaQueryComponent from "./16-useMediaQuery/MediaQueryComponent"
17-
import GeolocationComponent from "./17-useGeolocation/GeolocationComponent"
18-
import StateWithValidationComponent from "./18-useStateWithValidation/StateWithValidationComponent"
19-
import SizeComponent from "./19-useSize/SizeComponent"
20-
import EffectOnceComponent from "./20-useEffectOnce/EffectOnceComponent"
1+
import ToggleComponent from "./1-20/1-useToggle/ToggleComponent"
2+
import TimeoutComponent from "./1-20/2-useTimeout/TimeoutComponent"
3+
import DebounceComponent from "./1-20/3-useDebounce/DebounceComponent"
4+
import UpdateEffectComponent from "./1-20/4-useUpdateEffect/UpdateEffectComponent"
5+
import ArrayComponent from "./1-20/5-useArray/ArrayComponent"
6+
import PreviousComponent from "./1-20/6-usePrevious/PreviousComponent"
7+
import StateWithHistoryComponent from "./1-20/7-useStateWithHistory/StateWithHistoryComponent"
8+
import StorageComponent from "./1-20/8-useStorage/StorageComponent"
9+
import AsyncComponent from "./1-20/9-useAsync/AsyncComponent"
10+
import FetchComponent from "./1-20/10-useFetch/FetchComponent"
11+
import ScriptComponent from "./1-20/11-useScript/ScriptComponent"
12+
import DeepCompareEffectComponent from "./1-20/12-useDeepCompareEffect/DeepCompareEffectComponent"
13+
import EventListenerComponent from "./1-20/13-useEventListener/EventListenerComponent"
14+
import OnScreenComponentComponent from "./1-20/14-useOnScreen/OnScreenComponent"
15+
import WindowSizeComponent from "./1-20/15-useWindowSize/WindowSizeComponent"
16+
import MediaQueryComponent from "./1-20/16-useMediaQuery/MediaQueryComponent"
17+
import GeolocationComponent from "./1-20/17-useGeolocation/GeolocationComponent"
18+
import StateWithValidationComponent from "./1-20/18-useStateWithValidation/StateWithValidationComponent"
19+
import SizeComponent from "./1-20/19-useSize/SizeComponent"
20+
import EffectOnceComponent from "./1-20/20-useEffectOnce/EffectOnceComponent"
21+
import ClickOutsideComponent from "./21-useClickOutside/ClickOutsideComponent"
22+
import DarkModeComponent from "./22-useDarkMode/DarkModeComponent"
23+
import CopyToClipboardComponent from "./23-useCopyToClipboard/CopyToClipboardComponent"
24+
import CookieComponent from "./24-useCookie/CookieComponent"
25+
import TranslationComponent from "./25-useTranslation/TranslationComponent"
2126

2227
function App() {
2328
// return <ToggleComponent />
@@ -39,7 +44,12 @@ function App() {
3944
// return <GeolocationComponent />
4045
// return <StateWithValidationComponent />
4146
// return <SizeComponent />
42-
return <EffectOnceComponent />
47+
// return <EffectOnceComponent />
48+
return <ClickOutsideComponent />
49+
// return <DarkModeComponent />
50+
// return <CopyToClipboardComponent />
51+
// return <CookieComponent />
52+
// return <TranslationComponent />
4353
}
4454

4555
export default App

0 commit comments

Comments
 (0)