diff --git a/index.html b/index.html
index 5ef976f33..25b67361f 100644
--- a/index.html
+++ b/index.html
@@ -1,24 +1,19 @@
-
- 서윤의 블로그
-
-
-
- 제 블로그에 오신 것을 환영합니다!
+
+ 서윤의 블로그
+
+
+
+ 제 블로그에 오신 것을 환영합니다!
-
+
-
-
-
+
+
+
-
-
-
+
+
+
+
diff --git a/my-app/package-lock.json b/my-app/package-lock.json
index 8e8b0f239..381ced5f1 100644
--- a/my-app/package-lock.json
+++ b/my-app/package-lock.json
@@ -15,6 +15,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-scripts": "^5.0.1",
+ "styled-components": "^6.1.17",
"web-vitals": "^2.1.4"
}
},
@@ -2354,6 +2355,27 @@
"postcss-selector-parser": "^6.0.10"
}
},
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+ "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+ "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
+ "license": "MIT"
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz",
@@ -3899,6 +3921,12 @@
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
"license": "MIT"
},
+ "node_modules/@types/stylis": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==",
+ "license": "MIT"
+ },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
@@ -5463,6 +5491,15 @@
"node": ">= 6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -6006,6 +6043,15 @@
"postcss": "^8.4"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/css-declaration-sorter": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
@@ -6155,6 +6201,17 @@
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==",
"license": "MIT"
},
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/css-tree": {
"version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -6363,6 +6420,12 @@
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
"license": "MIT"
},
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -14890,6 +14953,12 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -15559,6 +15628,68 @@
"webpack": "^5.0.0"
}
},
+ "node_modules/styled-components": {
+ "version": "6.1.17",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.17.tgz",
+ "integrity": "sha512-97D7DwWanI7nN24v0D4SvbfjLE9656umNSJZkBkDIWL37aZqG/wRQ+Y9pWtXyBIM/NSfcBzHLErEsqHmJNSVUg==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.2",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.5",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.3",
+ "postcss": "8.4.49",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.2",
+ "tslib": "2.6.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/postcss": {
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/styled-components/node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+ "license": "0BSD"
+ },
"node_modules/stylehacks": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
@@ -15575,6 +15706,12 @@
"postcss": "^8.2.15"
}
},
+ "node_modules/stylis": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+ "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==",
+ "license": "MIT"
+ },
"node_modules/sucrase": {
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
diff --git a/my-app/package.json b/my-app/package.json
index 75a8a4863..6c296dfe8 100644
--- a/my-app/package.json
+++ b/my-app/package.json
@@ -10,6 +10,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-scripts": "^5.0.1",
+ "styled-components": "^6.1.17",
"web-vitals": "^2.1.4"
},
"scripts": {
diff --git a/my-app/src/chapter_03/Library.jsx b/my-app/src/chapter_03/Library.jsx
index 9f426b0e5..2570fdf9f 100644
--- a/my-app/src/chapter_03/Library.jsx
+++ b/my-app/src/chapter_03/Library.jsx
@@ -2,13 +2,13 @@ import React from "react";
import Book from "./Book";
function Library(props) {
- return (
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+ );
}
-export default Library;
+export default Library;
\ No newline at end of file
diff --git a/my-app/src/chapter_03/efub5-first-react-study.code-workspace b/my-app/src/chapter_03/efub5-first-react-study.code-workspace
new file mode 100644
index 000000000..e446dc1bc
--- /dev/null
+++ b/my-app/src/chapter_03/efub5-first-react-study.code-workspace
@@ -0,0 +1,8 @@
+{
+ "folders": [
+ {
+ "path": "../../.."
+ }
+],
+ "settings": {}
+}
diff --git a/my-app/src/chapter_08/ConfirmButton.jsx b/my-app/src/chapter_08/ConfirmButton.jsx
new file mode 100644
index 000000000..9c9887bc2
--- /dev/null
+++ b/my-app/src/chapter_08/ConfirmButton.jsx
@@ -0,0 +1,17 @@
+import React, { useState } from "react";
+
+function ConfirmButton(props) {
+ const [isConfirmed, setIsConfirmed] = useState(false);
+
+ const handleConfirm = () => {
+ setIsConfirmed((prevIsConfirmed) => !prevIsConfirmed);
+ };
+
+ return (
+
+ );
+}
+
+export default ConfirmButton;
diff --git a/my-app/src/chapter_09/LandingPage.jsx b/my-app/src/chapter_09/LandingPage.jsx
new file mode 100644
index 000000000..f32c145ca
--- /dev/null
+++ b/my-app/src/chapter_09/LandingPage.jsx
@@ -0,0 +1,27 @@
+import React, { useState } from "react";
+import Toolbar from "./Toolbar";
+
+function LandingPage(props) {
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+
+ const onClickLogin = () => {
+ setIsLoggedIn(true);
+ };
+
+ const onClickLogout = () => {
+ setIsLoggedIn(false);
+ };
+
+ return (
+
+ );
+}
+
+export default LandingPage;
diff --git a/my-app/src/chapter_09/Toolbar.jsx b/my-app/src/chapter_09/Toolbar.jsx
new file mode 100644
index 000000000..f902ffbac
--- /dev/null
+++ b/my-app/src/chapter_09/Toolbar.jsx
@@ -0,0 +1,31 @@
+import React from "react";
+
+const styles = {
+ wrapper: {
+ padding: 16,
+ display: "flex",
+ flexDirection: "row",
+ borderBottom: "1px solid grey",
+ },
+ greeting: {
+ marginRight: 8,
+ },
+};
+
+function Toolbar(props) {
+ const { isLoggedIn, onClickLogin, onClickLogout } = props;
+
+ return (
+
+ {isLoggedIn && 환영합니다!}
+
+ {isLoggedIn ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+
+export default Toolbar;
diff --git a/my-app/src/chapter_10/AttendanceBook.jsx b/my-app/src/chapter_10/AttendanceBook.jsx
new file mode 100644
index 000000000..ebc8676ee
--- /dev/null
+++ b/my-app/src/chapter_10/AttendanceBook.jsx
@@ -0,0 +1,32 @@
+import React from "react";
+
+const students = [
+ {
+ id: 1,
+ name: "Inje",
+ },
+ {
+ id: 2,
+ name: "Steve",
+ },
+ {
+ id: 3,
+ name: "Bill",
+ },
+ {
+ id: 4,
+ name: "Jeff",
+ },
+];
+
+function AttendanceBook(props) {
+ return (
+
+ {students.map((student) => {
+ return - {student.name}
;
+ })}
+
+ );
+}
+
+export default AttendanceBook;
diff --git a/my-app/src/chapter_11/SignUp.jsx b/my-app/src/chapter_11/SignUp.jsx
new file mode 100644
index 000000000..837c59be3
--- /dev/null
+++ b/my-app/src/chapter_11/SignUp.jsx
@@ -0,0 +1,39 @@
+import React, { useState } from "react";
+
+function SignUp(props) {
+ const [name, setName] = useState("");
+ const [gender, setGender] = useState("남자");
+
+ const handleChangeName = (event) => {
+ setName(event.target.value);
+ };
+
+ const handleChangeGender = (event) => {
+ setGender(event.target.value);
+ };
+
+ const handleSubmit = (event) => {
+ alert(`이름: ${name}, 성별: ${gender}`);
+ event.preventDefault();
+ };
+
+ return (
+
+ );
+}
+
+export default SignUp;
diff --git a/my-app/src/chapter_12/Calculator.jsx b/my-app/src/chapter_12/Calculator.jsx
new file mode 100644
index 000000000..999c55636
--- /dev/null
+++ b/my-app/src/chapter_12/Calculator.jsx
@@ -0,0 +1,65 @@
+import React, { useState } from "react";
+import TemperatureInput from "./TemperatureInput";
+
+function BoilindVerdict(props) {
+ if (props.celsius >= 100) {
+ return 물이 끓습니다.
;
+ }
+ return 물이 끓지 않습니다.
;
+}
+
+function toCelsius(fahrenheit) {
+ return ((fahrenheit - 32) * 5) / 9;
+}
+
+function toFahrenheit(celsius) {
+ return (celsius * 9) / 5 + 32;
+}
+
+function tryConvert(temperature, convert) {
+ const input = parseFloat(temperature);
+ if (Number.isNaN(input)) {
+ return "";
+ }
+ const output = convert(input);
+ const rounded = Math.round(output * 1000) / 1000;
+ return rounded.toString();
+}
+
+function Calculator(props) {
+ const [temperature, setTemperature] = useState("");
+ const [scale, setScale] = useState("c");
+
+ const handleCelsiusChange = (temperature) => {
+ setTemperature(temperature);
+ setScale("c");
+ };
+
+ const handleFahrenheitChange = (temperature) => {
+ setTemperature(temperature);
+ setScale("f");
+ };
+
+ const celsius =
+ scale === "f" ? tryConvert(temperature, toCelsius) : temperature;
+ const fahrenheit =
+ scale === "c" ? tryConvert(temperature, toFahrenheit) : temperature;
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default Calculator;
diff --git a/my-app/src/chapter_12/TemperatureInput.jsx b/my-app/src/chapter_12/TemperatureInput.jsx
new file mode 100644
index 000000000..a268aafdd
--- /dev/null
+++ b/my-app/src/chapter_12/TemperatureInput.jsx
@@ -0,0 +1,19 @@
+const scaleNames = {
+ c: "섭씨",
+ f: "화씨",
+};
+
+function TemperatureInput(props) {
+ const handleChange = (event) => {
+ props.onTemperatureChange(event.target.value);
+ };
+
+ return (
+
+ );
+}
+
+export default TemperatureInput;
diff --git a/my-app/src/chapter_13/Card.jsx b/my-app/src/chapter_13/Card.jsx
new file mode 100644
index 000000000..0615fc429
--- /dev/null
+++ b/my-app/src/chapter_13/Card.jsx
@@ -0,0 +1,20 @@
+function Card(props) {
+ const { title, backgroundColor, children } = props;
+
+ return (
+
+ {title &&
{title}
}
+ {children}
+
+ );
+}
+
+export default Card;
diff --git a/my-app/src/chapter_13/ProfileCard.jsx b/my-app/src/chapter_13/ProfileCard.jsx
new file mode 100644
index 000000000..4ec7622b5
--- /dev/null
+++ b/my-app/src/chapter_13/ProfileCard.jsx
@@ -0,0 +1,12 @@
+import Card from "./Card";
+
+function ProfileCard(props) {
+ return (
+
+ 안녕하세요, 소플입니다.
+ 저는 리액트를 사용해서 개발하고 있습니다.
+
+ );
+}
+
+export default ProfileCard;
diff --git a/my-app/src/chapter_14/DarkOrLight.jsx b/my-app/src/chapter_14/DarkOrLight.jsx
new file mode 100644
index 000000000..54156f8a3
--- /dev/null
+++ b/my-app/src/chapter_14/DarkOrLight.jsx
@@ -0,0 +1,23 @@
+import { useState, useCallback } from "react";
+import ThemeContext from "./ThemeContext";
+import MainContext from "./MainContext";
+
+function DarkOrLight(props) {
+ const [theme, setTheme] = useState("light");
+
+ const toggleTheme = useCallback(() => {
+ if (theme == "light") {
+ setTheme("dark");
+ } else if (theme == "dark") {
+ setTheme("light");
+ }
+ }, [theme]);
+
+ return (
+
+
+
+ );
+}
+
+export default DarkOrLight;
diff --git a/my-app/src/chapter_14/MainContext.jsx b/my-app/src/chapter_14/MainContext.jsx
new file mode 100644
index 000000000..28974a410
--- /dev/null
+++ b/my-app/src/chapter_14/MainContext.jsx
@@ -0,0 +1,23 @@
+import { useContext } from "react";
+import ThemeContext from "./ThemeContext";
+
+function MainContext(props) {
+ const { theme, toggleTheme } = useContext(ThemeContext);
+
+ return (
+
+
안녕하세요, 테마 변경이 가능한 웹사이트입니다.
+
+
+ );
+}
+
+export default MainContext;
diff --git a/my-app/src/chapter_14/ThemeContext.jsx b/my-app/src/chapter_14/ThemeContext.jsx
new file mode 100644
index 000000000..81d9a691c
--- /dev/null
+++ b/my-app/src/chapter_14/ThemeContext.jsx
@@ -0,0 +1,6 @@
+import React from "react";
+
+const ThemeContext = React.createContext();
+ThemeContext.displayName = "ThemeContext";
+
+export default ThemeContext;
diff --git a/my-app/src/chapter_15/Blocks.jsx b/my-app/src/chapter_15/Blocks.jsx
new file mode 100644
index 000000000..6b7a9d739
--- /dev/null
+++ b/my-app/src/chapter_15/Blocks.jsx
@@ -0,0 +1,58 @@
+import styled from "styled-components";
+
+const Wrapper = styled.div`
+ padding: 1rem;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: flex-start;
+ background-color: lightgrey;
+`;
+
+const Block = styled.div`
+ padding: ${(props) => props.padding};
+ border: 1px solid black;
+ border-radius: 1rem;
+ background-color: ${(props) => props.backgroundColor};
+ color: white;
+ font-size: 2rem;
+ font-weight: bold;
+ text-align: center;
+`;
+
+const blockItems = [
+ {
+ label: "1",
+ padding: "1rem",
+ backgroundColor: "red",
+ },
+ {
+ label: "2",
+ padding: "3rem",
+ backgroundColor: "green",
+ },
+ {
+ label: "3",
+ padding: "2rem",
+ backgroundColor: "blue",
+ },
+];
+
+function Blocks(props) {
+ return (
+
+ {blockItems.map((blockItem) => {
+ return (
+
+ {blockItem.label}
+
+ );
+ })}
+
+ );
+}
+
+export default Blocks;
diff --git a/my-app/src/index.js b/my-app/src/index.js
index 99d508413..9b79a1193 100644
--- a/my-app/src/index.js
+++ b/my-app/src/index.js
@@ -8,13 +8,24 @@ import Library from "./chapter_03/Library";
import Clock from "./chapter_04/Clock";
import CommentList from "./chapter_05/CommentList";
import NotificationList from "./chapter_06/NotificationList";
+
import Accommodate from "./chapter_07/Accommodate";
+import ConfirmButton from "./chapter_08/ConfirmButton";
+import LandingPage from "./chapter_09/LandingPage";
+import AttendanceBook from "./chapter_10/AttendanceBook";
+
+import SignUp from "./chapter_11/SignUp";
+import Calculator from "./chapter_12/Calculator";
+import ProfileCard from "./chapter_13/ProfileCard";
+
+import DarkOrLight from "./chapter_14/DarkOrLight";
+import Blocks from "./chapter_15/Blocks";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
-
+
);