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 ( + + ); +} + +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 ( +
+ 온도를 입력해주세요(단위:{scaleNames[props.scale]}); + +
+ ); +} + +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( - + );