From 79ed2cb0d9fb3451cf75dfc4263b7c372946de52 Mon Sep 17 00:00:00 2001 From: eunsu Date: Mon, 18 Mar 2024 14:53:37 +0900 Subject: [PATCH 01/15] =?UTF-8?q?chore:=20.prettierrc=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1dc7164 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} \ No newline at end of file From 995bc452214c16ee7fce31587a137ef715dbe5c4 Mon Sep 17 00:00:00 2001 From: eunsu Date: Mon, 18 Mar 2024 15:10:59 +0900 Subject: [PATCH 02/15] =?UTF-8?q?chore:=20styled-components=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 124 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 125 insertions(+) diff --git a/package-lock.json b/package-lock.json index e523af2..115138b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "styled-components": "^6.1.8", "web-vitals": "^2.1.4" } }, @@ -2283,6 +2284,24 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "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==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -4534,6 +4553,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -6037,6 +6061,14 @@ "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==", + "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", @@ -6469,6 +6501,14 @@ "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==", + "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", @@ -6659,6 +6699,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "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==", + "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", @@ -15998,6 +16048,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "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==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -16530,6 +16585,70 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "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/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -16545,6 +16664,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/package.json b/package.json index 6c9c71f..eefbbf2 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "styled-components": "^6.1.8", "web-vitals": "^2.1.4" }, "scripts": { From 122f0efb9bae7f4ea62995cb275990715ed998b8 Mon Sep 17 00:00:00 2001 From: eunsu Date: Tue, 19 Mar 2024 13:13:05 +0900 Subject: [PATCH 03/15] =?UTF-8?q?feat:=20Globalc=20ss=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.js | 10 +++++---- src/style/GlobalStyle.js | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 src/style/GlobalStyle.js diff --git a/src/index.js b/src/index.js index 8db5acb..c04339a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,12 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import App from "./App"; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import GlobalStyle from './style/GlobalStyle'; -const root = ReactDOM.createRoot(document.getElementById("root")); +const root = ReactDOM.createRoot(document.getElementById('root')); root.render( + ); diff --git a/src/style/GlobalStyle.js b/src/style/GlobalStyle.js new file mode 100644 index 0000000..ba1c2c6 --- /dev/null +++ b/src/style/GlobalStyle.js @@ -0,0 +1,46 @@ +import { createGlobalStyle } from 'styled-components'; + +const GlobalStyle = createGlobalStyle` + *, +*::before, +*::after { + box-sizing: border-box; +} +* { + margin: 0; + padding: 0; +} +body { + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} +input, +button, +textarea, +select { + font: inherit; +} +p, +h1, +h2, +h3, +h4, +h5, +h6 { + overflow-wrap: break-word; +} + +.scroll-box::-webkit-scrollbar { + display: none; +} +`; + +export default GlobalStyle; From af5e8883e28c4ca6288a79f0642264cba7fddf6b Mon Sep 17 00:00:00 2001 From: eunsu Date: Tue, 19 Mar 2024 13:29:02 +0900 Subject: [PATCH 04/15] =?UTF-8?q?feat:=20TodoCard=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 33 +++++++++++++++++-- src/assets/svg/CheckBox.svg | 3 ++ src/assets/svg/CheckBoxOutline.svg | 3 ++ src/assets/svg/Delete.svg | 3 ++ src/components/TodoCard.js | 52 ++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/assets/svg/CheckBox.svg create mode 100644 src/assets/svg/CheckBoxOutline.svg create mode 100644 src/assets/svg/Delete.svg create mode 100644 src/components/TodoCard.js diff --git a/src/App.js b/src/App.js index 3b90819..20c026e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,8 +1,35 @@ +import styled from 'styled-components'; +import TodoCard from './components/TodoCard'; + +const DUMMYTOOLIST = [ + { todo: '공부하기', isdone: false }, + { todo: '세수하기', isdone: true }, +]; + +let DUMMYCONTAINERLAYOUT = styled.div` + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + background-color: #efd8d8; +`; + +let TodoListLayout = styled.div` + width: 100%; + max-width: 840px; + background-color: white; +`; + function App() { return ( -
-

CEOS 19기 프론트엔드 파이팅!( ¨̮ )و✧🔥

-
+ + + {DUMMYTOOLIST.map((todo, index) => { + return ; + })} + + ); } diff --git a/src/assets/svg/CheckBox.svg b/src/assets/svg/CheckBox.svg new file mode 100644 index 0000000..98fedba --- /dev/null +++ b/src/assets/svg/CheckBox.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/CheckBoxOutline.svg b/src/assets/svg/CheckBoxOutline.svg new file mode 100644 index 0000000..1531751 --- /dev/null +++ b/src/assets/svg/CheckBoxOutline.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/Delete.svg b/src/assets/svg/Delete.svg new file mode 100644 index 0000000..bb70fe7 --- /dev/null +++ b/src/assets/svg/Delete.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/TodoCard.js b/src/components/TodoCard.js new file mode 100644 index 0000000..df457de --- /dev/null +++ b/src/components/TodoCard.js @@ -0,0 +1,52 @@ +import React from 'react'; +import styled from 'styled-components'; +import { ReactComponent as CheckBoxSvg } from '../assets/svg/CheckBox.svg'; +import { ReactComponent as CheckBoxOutlineSvg } from '../assets/svg/CheckBoxOutline.svg'; +import { ReactComponent as DeleteSvg } from '../assets/svg/Delete.svg'; + +let TodoCardLayout = styled.div` + width: 100%; + height: 2.5rem; + display: flex; + align-items: center; + padding: 0.5rem 1rem; + gap: 0.5rem; + &:hover { + background-color: #f5f5f5; + cursor: pointer; + button { + display: flex; + } + } +`; + +let TodoCardParagraph = styled.p` + flex-grow: 1; +`; + +let TodoDeleteButton = styled.button` + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: none; + cursor: pointer; + &:hover { + background-color: gray; + } + display: none; +`; + +export default function TodoCard({ isdone, todo }) { + return ( + + {isdone ? : } + {todo} + + + + + ); +} From a53b8bfe4e571a1de743ff6916c07ca1014b11bb Mon Sep 17 00:00:00 2001 From: eunsu Date: Wed, 20 Mar 2024 23:27:34 +0900 Subject: [PATCH 05/15] =?UTF-8?q?feat:=20layout=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/App.js b/src/App.js index 20c026e..aef47c4 100644 --- a/src/App.js +++ b/src/App.js @@ -6,30 +6,35 @@ const DUMMYTOOLIST = [ { todo: '세수하기', isdone: true }, ]; -let DUMMYCONTAINERLAYOUT = styled.div` +let Layout = styled.div` width: 100%; height: 100vh; display: flex; flex-direction: column; align-items: center; + justify-content: center; background-color: #efd8d8; `; let TodoListLayout = styled.div` width: 100%; + height: 80%; max-width: 840px; background-color: white; + @media (max-width: 768px) { + height: 100%; + } `; function App() { return ( - + {DUMMYTOOLIST.map((todo, index) => { return ; })} - + ); } From 8e29f33ec1914f40d6b473a97bd1856078472bdc Mon Sep 17 00:00:00 2001 From: eunsu Date: Wed, 20 Mar 2024 23:45:01 +0900 Subject: [PATCH 06/15] =?UTF-8?q?feat:=20TodoHeader=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 2 ++ src/components/TodoHeader.js | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/components/TodoHeader.js diff --git a/src/App.js b/src/App.js index aef47c4..1ed4df0 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,6 @@ import styled from 'styled-components'; import TodoCard from './components/TodoCard'; +import TodoHeader from './components/TodoHeader'; const DUMMYTOOLIST = [ { todo: '공부하기', isdone: false }, @@ -30,6 +31,7 @@ function App() { return ( + {DUMMYTOOLIST.map((todo, index) => { return ; })} diff --git a/src/components/TodoHeader.js b/src/components/TodoHeader.js new file mode 100644 index 0000000..c5d99ac --- /dev/null +++ b/src/components/TodoHeader.js @@ -0,0 +1,39 @@ +import React from 'react'; +import styled from 'styled-components'; + +let TodoHeaderLayout = styled.div` + width: 100%; + height: 70px; + padding: 0 1rem; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid #e0e0e0; +`; + +let TodayStringSpan = styled.span` + font-size: 1.5rem; + font-weight: 700; +`; + +let DaySpan = styled.span` + font-size: 1.5rem; + font-weight: 600; +`; + +export default function TodoHeader() { + const today = new Date(); + const options = { + year: 'numeric', + month: 'long', + day: 'numeric', + }; + const todayString = today.toLocaleDateString('kr-kR', options); + const day = today.toLocaleDateString('en-US', { weekday: 'long' }); + return ( + + {todayString} + {day} + + ); +} From 894d517058c2a98a9f9e9b0d54a8dd59d906ce0b Mon Sep 17 00:00:00 2001 From: eunsu Date: Fri, 22 Mar 2024 11:11:10 +0900 Subject: [PATCH 07/15] =?UTF-8?q?feat:=20TodoSection=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 20 ++++++++++++++++---- src/components/DoneSection.js | 27 +++++++++++++++++++++++++++ src/components/TodoSection.js | 27 +++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/components/DoneSection.js create mode 100644 src/components/TodoSection.js diff --git a/src/App.js b/src/App.js index 1ed4df0..9c3e76f 100644 --- a/src/App.js +++ b/src/App.js @@ -1,12 +1,23 @@ import styled from 'styled-components'; +import DoneSection from './components/DoneSection'; import TodoCard from './components/TodoCard'; import TodoHeader from './components/TodoHeader'; +import TodoSection from './components/TodoSection'; -const DUMMYTOOLIST = [ +const DUMMYLOCALSTROAGETODOLIST = [ { todo: '공부하기', isdone: false }, { todo: '세수하기', isdone: true }, ]; +const DUMMYTODOLIST = DUMMYLOCALSTROAGETODOLIST.filter( + (todo) => todo.isdone === false +); +const DUMMYDONELIST = DUMMYLOCALSTROAGETODOLIST.filter( + (todo) => todo.isdone === true +); +console.log(DUMMYTODOLIST); +console.log(DUMMYDONELIST); + let Layout = styled.div` width: 100%; height: 100vh; @@ -21,6 +32,8 @@ let TodoListLayout = styled.div` width: 100%; height: 80%; max-width: 840px; + display: flex; + flex-direction: column; background-color: white; @media (max-width: 768px) { height: 100%; @@ -32,9 +45,8 @@ function App() { - {DUMMYTOOLIST.map((todo, index) => { - return ; - })} + + ); diff --git a/src/components/DoneSection.js b/src/components/DoneSection.js new file mode 100644 index 0000000..4d9d377 --- /dev/null +++ b/src/components/DoneSection.js @@ -0,0 +1,27 @@ +import React from 'react'; +import styled from 'styled-components'; +import TodoCard from './TodoCard'; + +let DoneSectionLayout = styled.div` + width: 100%; + display: flex; + flex-direction: column; + flex: 1 1; +`; +let TodoListHeaderBox = styled.div` + font-size: 0.75rem; + padding: 0.25rem 1rem; + font-weight: 700; + color: #9e9e9e; +`; + +export default function DoneSection({ doneList }) { + return ( + + done + {doneList.map((todo, index) => { + return ; + })} + + ); +} diff --git a/src/components/TodoSection.js b/src/components/TodoSection.js new file mode 100644 index 0000000..7c1dd7a --- /dev/null +++ b/src/components/TodoSection.js @@ -0,0 +1,27 @@ +import React from 'react'; +import styled from 'styled-components'; +import TodoCard from './TodoCard'; + +let TodoSectionLayout = styled.div` + width: 100%; + display: flex; + flex-direction: column; + flex: 1 1; +`; +let TodoListHeaderBox = styled.div` + font-size: 0.75rem; + padding: 0.25rem 1rem; + font-weight: 700; + color: #9e9e9e; +`; + +export default function TodoSection({ todoList }) { + return ( + + todo + {todoList.map((todo, index) => { + return ; + })} + + ); +} From 6258efd5240e596d597c07215774f599fe492722 Mon Sep 17 00:00:00 2001 From: eunsu Date: Fri, 22 Mar 2024 11:18:26 +0900 Subject: [PATCH 08/15] =?UTF-8?q?feat:=20TodoFooter=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 3 ++- src/components/TodoFooter.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/components/TodoFooter.js diff --git a/src/App.js b/src/App.js index 9c3e76f..2f55a02 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,6 @@ import styled from 'styled-components'; import DoneSection from './components/DoneSection'; -import TodoCard from './components/TodoCard'; +import TodoFooter from './components/TodoFooter'; import TodoHeader from './components/TodoHeader'; import TodoSection from './components/TodoSection'; @@ -47,6 +47,7 @@ function App() { + ); diff --git a/src/components/TodoFooter.js b/src/components/TodoFooter.js new file mode 100644 index 0000000..6106add --- /dev/null +++ b/src/components/TodoFooter.js @@ -0,0 +1,35 @@ +import React from 'react'; +import styled from 'styled-components'; + +let TodoFooterLayout = styled.div` + width: 100%; + height: 50px; + padding: 0 1rem; + display: flex; + align-items: center; + justify-content: space-between; + border-top: 1px solid #e0e0e0; +`; + +let TodoProgressCountSpan = styled.span` + font-size: 1rem; + font-weight: 600; + color: #9e9e9e; +`; + +let TodoProgressTextSpan = styled.span` + font-size: 1.5rem; + font-weight: 600; + color: #9e9e9e; +`; + +export default function TodoFooter({ todoTotalCount, todoDoneCount }) { + return ( + + + {todoDoneCount}개 / {todoTotalCount}개 + + TodoList + + ); +} From 4a468aea4fbacf8743ec8390877ce305bbc98911 Mon Sep 17 00:00:00 2001 From: eunsu Date: Fri, 22 Mar 2024 12:44:29 +0900 Subject: [PATCH 09/15] =?UTF-8?q?feat:=20localstorage=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=ED=9B=85=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useLocalStorage.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/hooks/useLocalStorage.js diff --git a/src/hooks/useLocalStorage.js b/src/hooks/useLocalStorage.js new file mode 100644 index 0000000..6a868f2 --- /dev/null +++ b/src/hooks/useLocalStorage.js @@ -0,0 +1,11 @@ +import React from 'react'; + +export default function useLocalStorage() { + const getTodoFromLocalStorage = (listName) => { + return JSON.parse(localStorage.getItem(listName)) || []; + }; + const setTodoToLocalStorage = (listName, todoList) => { + localStorage.setItem(listName, JSON.stringify(todoList)); + }; + return { getTodoFromLocalStorage, setTodoToLocalStorage }; +} From a7f0614cb47b7575d7bc9cc1e25e8399c901d421 Mon Sep 17 00:00:00 2001 From: eunsu Date: Fri, 22 Mar 2024 13:49:24 +0900 Subject: [PATCH 10/15] =?UTF-8?q?feat:=20todo=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EC=B0=BD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/AddTodo.js | 71 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/components/AddTodo.js diff --git a/src/components/AddTodo.js b/src/components/AddTodo.js new file mode 100644 index 0000000..7abfcc2 --- /dev/null +++ b/src/components/AddTodo.js @@ -0,0 +1,71 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import useLocalStorage from '../hooks/useLocalStorage'; + +let AddTodoLayout = styled.div` + width: 100%; + height: 50px; + padding: 0 1rem; + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; +`; + +let TodoInput = styled.input` + width: 100%; + border: none; + border: 1px solid #e0e0e0; + font-size: 1rem; + flex-grow: 1; + outline: none; + border-radius: 0.5rem; + padding: 0.5rem 1rem; +`; + +let AddButton = styled.button` + width: 2rem; + height: 2rem; + font-size: 1.5rem; + border: none; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + cursor: pointer; + &:hover { + background-color: #f5f5f5; + } +`; + +export default function AddTodo() { + const { getTodoFromLocalStorage, setTodoToLocalStorage } = useLocalStorage(); + const [inputValue, setInputValue] = useState(''); + const addButtonHandler = () => { + if (inputValue.trim() === '') { + return; + } + let todoList = getTodoFromLocalStorage('todoList'); + todoList.push({ todo: inputValue, isdone: false }); + setTodoToLocalStorage('todoList', todoList); + setInputValue(''); + }; + return ( + + { + setInputValue((prev) => e.target.value); + }} + onKeyPress={(e) => { + if (e.key === 'Enter') { + addButtonHandler(); + } + }} + /> + + + + ); +} From 8eaa40a0d3010c9a0503272c402b08fd924b69d5 Mon Sep 17 00:00:00 2001 From: eunsu Date: Fri, 22 Mar 2024 14:58:12 +0900 Subject: [PATCH 11/15] =?UTF-8?q?feat:=20todoCard=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=82=AD=EC=A0=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 45 ++++++++++++++++++++++------------- src/components/AddTodo.js | 21 ++++++++++------ src/components/DoneSection.js | 11 +++++++-- src/components/TodoCard.js | 22 +++++++++++++++-- src/components/TodoSection.js | 15 +++++++++--- src/hooks/useLocalStorage.js | 2 -- 6 files changed, 83 insertions(+), 33 deletions(-) diff --git a/src/App.js b/src/App.js index 2f55a02..93182ba 100644 --- a/src/App.js +++ b/src/App.js @@ -1,22 +1,11 @@ +import { useEffect, useState } from 'react'; import styled from 'styled-components'; +import AddTodo from './components/AddTodo'; import DoneSection from './components/DoneSection'; import TodoFooter from './components/TodoFooter'; import TodoHeader from './components/TodoHeader'; import TodoSection from './components/TodoSection'; - -const DUMMYLOCALSTROAGETODOLIST = [ - { todo: '공부하기', isdone: false }, - { todo: '세수하기', isdone: true }, -]; - -const DUMMYTODOLIST = DUMMYLOCALSTROAGETODOLIST.filter( - (todo) => todo.isdone === false -); -const DUMMYDONELIST = DUMMYLOCALSTROAGETODOLIST.filter( - (todo) => todo.isdone === true -); -console.log(DUMMYTODOLIST); -console.log(DUMMYDONELIST); +import useLocalStorage from './hooks/useLocalStorage'; let Layout = styled.div` width: 100%; @@ -41,13 +30,35 @@ let TodoListLayout = styled.div` `; function App() { + const [todoList, setTodoList] = useState([]); + const [doneList, setDoneList] = useState([]); + const { getTodoFromLocalStorage, setTodoToLocalStorage } = useLocalStorage(); + useEffect(() => { + setTodoList(getTodoFromLocalStorage('todoList')); + setDoneList(getTodoFromLocalStorage('doneList')); + }, []); + const addTodo = (todo) => { + setTodoList((prev) => { + return [...prev, todo]; + }); + // setTodoToLocalStorage('todoList', todoList);2 + }; + const deleteTodo = (todo) => { + setTodoList((prev) => { + return prev.filter((item) => item.todo !== todo); + }); + }; return ( - - - + + + + ); diff --git a/src/components/AddTodo.js b/src/components/AddTodo.js index 7abfcc2..c7eccdf 100644 --- a/src/components/AddTodo.js +++ b/src/components/AddTodo.js @@ -39,17 +39,24 @@ let AddButton = styled.button` } `; -export default function AddTodo() { +export default function AddTodo({ addTodo }) { const { getTodoFromLocalStorage, setTodoToLocalStorage } = useLocalStorage(); const [inputValue, setInputValue] = useState(''); const addButtonHandler = () => { + let todoList = getTodoFromLocalStorage('todoList') || []; if (inputValue.trim() === '') { - return; + alert('할 일을 입력해주세요.'); + console.log(1); + } else if (todoList.some((item) => item.todo === inputValue)) { + alert('이미 등록된 할 일입니다.'); + } else { + todoList.push({ todo: inputValue, isdone: false }); + console.log(todoList); + setTodoToLocalStorage('todoList', todoList); + addTodo({ todo: inputValue, isdone: false }); + setInputValue(''); + console.log(3); } - let todoList = getTodoFromLocalStorage('todoList'); - todoList.push({ todo: inputValue, isdone: false }); - setTodoToLocalStorage('todoList', todoList); - setInputValue(''); }; return ( @@ -65,7 +72,7 @@ export default function AddTodo() { } }} /> - + + + ); } diff --git a/src/components/DoneSection.js b/src/components/DoneSection.js index 4d9d377..ac4cac2 100644 --- a/src/components/DoneSection.js +++ b/src/components/DoneSection.js @@ -15,12 +15,19 @@ let TodoListHeaderBox = styled.div` color: #9e9e9e; `; -export default function DoneSection({ doneList }) { +export default function DoneSection({ doneList, deleteTodo }) { return ( done {doneList.map((todo, index) => { - return ; + return ( + + ); })} ); diff --git a/src/components/TodoCard.js b/src/components/TodoCard.js index df457de..4ce2110 100644 --- a/src/components/TodoCard.js +++ b/src/components/TodoCard.js @@ -3,6 +3,7 @@ import styled from 'styled-components'; import { ReactComponent as CheckBoxSvg } from '../assets/svg/CheckBox.svg'; import { ReactComponent as CheckBoxOutlineSvg } from '../assets/svg/CheckBoxOutline.svg'; import { ReactComponent as DeleteSvg } from '../assets/svg/Delete.svg'; +import useLocalStorage from '../hooks/useLocalStorage'; let TodoCardLayout = styled.div` width: 100%; @@ -39,12 +40,29 @@ let TodoDeleteButton = styled.button` display: none; `; -export default function TodoCard({ isdone, todo }) { +export default function TodoCard({ isdone, todo, deleteTodo }) { + const { getTodoFromLocalStorage, setTodoToLocalStorage } = useLocalStorage(); + const deleteHandler = () => { + isdone + ? setTodoToLocalStorage( + 'doneList', + getTodoFromLocalStorage('doneList').filter( + (item) => item.todo !== todo + ) + ) + : setTodoToLocalStorage( + 'todoList', + getTodoFromLocalStorage('todoList').filter( + (item) => item.todo !== todo + ) + ); + deleteTodo(todo); + }; return ( {isdone ? : } {todo} - + diff --git a/src/components/TodoSection.js b/src/components/TodoSection.js index 7c1dd7a..9f20a71 100644 --- a/src/components/TodoSection.js +++ b/src/components/TodoSection.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import styled from 'styled-components'; import TodoCard from './TodoCard'; @@ -15,12 +15,21 @@ let TodoListHeaderBox = styled.div` color: #9e9e9e; `; -export default function TodoSection({ todoList }) { +export default function TodoSection({ todoList, deleteTodo }) { + const clickHandler = (isdone, index) => {}; return ( todo {todoList.map((todo, index) => { - return ; + return ( + + ); })} ); diff --git a/src/hooks/useLocalStorage.js b/src/hooks/useLocalStorage.js index 6a868f2..ac97230 100644 --- a/src/hooks/useLocalStorage.js +++ b/src/hooks/useLocalStorage.js @@ -1,5 +1,3 @@ -import React from 'react'; - export default function useLocalStorage() { const getTodoFromLocalStorage = (listName) => { return JSON.parse(localStorage.getItem(listName)) || []; From f16e3fe8e5d591a6141d5876d011dbbf1196a944 Mon Sep 17 00:00:00 2001 From: eunsu Date: Fri, 22 Mar 2024 16:53:31 +0900 Subject: [PATCH 12/15] =?UTF-8?q?feat:=20todoCard=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20-=20localstorage=20=ED=95=98=EB=82=98?= =?UTF-8?q?=EC=9D=98=20=EB=B0=B0=EC=97=B4=EB=A1=9C=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?-=20overflox,=20flex-shrink=20=EB=93=B1=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 46 +++++++++++++++++++++++++++-------- src/components/AddTodo.js | 5 ++-- src/components/DoneSection.js | 32 +++++++++++++++--------- src/components/TodoCard.js | 40 ++++++++++++++++-------------- src/components/TodoHeader.js | 4 +-- src/components/TodoSection.js | 37 ++++++++++++++++++---------- src/hooks/useLocalStorage.js | 8 +++--- src/style/GlobalStyle.js | 4 --- 8 files changed, 112 insertions(+), 64 deletions(-) diff --git a/src/App.js b/src/App.js index 93182ba..e19745d 100644 --- a/src/App.js +++ b/src/App.js @@ -32,28 +32,54 @@ let TodoListLayout = styled.div` function App() { const [todoList, setTodoList] = useState([]); const [doneList, setDoneList] = useState([]); - const { getTodoFromLocalStorage, setTodoToLocalStorage } = useLocalStorage(); + const { getTodoFromLocalStorage } = useLocalStorage(); useEffect(() => { - setTodoList(getTodoFromLocalStorage('todoList')); - setDoneList(getTodoFromLocalStorage('doneList')); + const list = getTodoFromLocalStorage('todoList'); + if (list) { + setTodoList(list.filter((item) => !item.isdone)); + setDoneList(list.filter((item) => item.isdone)); + } }, []); const addTodo = (todo) => { setTodoList((prev) => { return [...prev, todo]; }); - // setTodoToLocalStorage('todoList', todoList);2 + // setTodoToLocalStorage('todoList', todoList); }; - const deleteTodo = (todo) => { - setTodoList((prev) => { - return prev.filter((item) => item.todo !== todo); - }); + const deleteTodo = (todo, isDone) => { + if (isDone) { + setDoneList((prev) => { + return prev.filter((item) => item.todo !== todo); + }); + } else { + setTodoList((prev) => { + return prev.filter((item) => item.todo !== todo); + }); + } + }; + const toggleTodo = (todo, isDone) => { + if (isDone) { + setDoneList((prev) => prev.filter((item) => item.todo !== todo)); + setTodoList((prev) => [...prev, { todo, isdone: false }]); + } else { + setTodoList((prev) => prev.filter((item) => item.todo !== todo)); + setDoneList((prev) => [...prev, { todo, isdone: true }]); + } }; return ( - - + + { - let todoList = getTodoFromLocalStorage('todoList') || []; + let todoList = getTodoFromLocalStorage() || []; if (inputValue.trim() === '') { alert('할 일을 입력해주세요.'); console.log(1); } else if (todoList.some((item) => item.todo === inputValue)) { alert('이미 등록된 할 일입니다.'); + setInputValue(''); } else { todoList.push({ todo: inputValue, isdone: false }); console.log(todoList); - setTodoToLocalStorage('todoList', todoList); + setTodoToLocalStorage(todoList); addTodo({ todo: inputValue, isdone: false }); setInputValue(''); console.log(3); diff --git a/src/components/DoneSection.js b/src/components/DoneSection.js index ac4cac2..5510d6b 100644 --- a/src/components/DoneSection.js +++ b/src/components/DoneSection.js @@ -7,6 +7,13 @@ let DoneSectionLayout = styled.div` display: flex; flex-direction: column; flex: 1 1; + overflow-y: scroll; +`; +let DoneContentBox = styled.div` + width: 100%; + display: flex; + flex-direction: column; + overflow-y: scroll; `; let TodoListHeaderBox = styled.div` font-size: 0.75rem; @@ -15,20 +22,23 @@ let TodoListHeaderBox = styled.div` color: #9e9e9e; `; -export default function DoneSection({ doneList, deleteTodo }) { +export default function DoneSection({ doneList, deleteTodo, toggleTodo }) { return ( done - {doneList.map((todo, index) => { - return ( - - ); - })} + + {doneList.map((todo, index) => { + return ( + + ); + })} + ); } diff --git a/src/components/TodoCard.js b/src/components/TodoCard.js index 4ce2110..f3b8293 100644 --- a/src/components/TodoCard.js +++ b/src/components/TodoCard.js @@ -24,6 +24,9 @@ let TodoCardLayout = styled.div` let TodoCardParagraph = styled.p` flex-grow: 1; `; +let CheckboxBox = styled.div` + flex-shrink: 0; +`; let TodoDeleteButton = styled.button` width: 1.5rem; @@ -33,6 +36,7 @@ let TodoDeleteButton = styled.button` align-items: center; justify-content: center; border: none; + flex-shrink: 0; cursor: pointer; &:hover { background-color: gray; @@ -40,27 +44,27 @@ let TodoDeleteButton = styled.button` display: none; `; -export default function TodoCard({ isdone, todo, deleteTodo }) { +export default function TodoCard({ isdone, todo, deleteTodo, toggleTodo }) { const { getTodoFromLocalStorage, setTodoToLocalStorage } = useLocalStorage(); - const deleteHandler = () => { - isdone - ? setTodoToLocalStorage( - 'doneList', - getTodoFromLocalStorage('doneList').filter( - (item) => item.todo !== todo - ) - ) - : setTodoToLocalStorage( - 'todoList', - getTodoFromLocalStorage('todoList').filter( - (item) => item.todo !== todo - ) - ); - deleteTodo(todo); + const deleteHandler = (e) => { + e.stopPropagation(); + setTodoToLocalStorage( + getTodoFromLocalStorage().filter((item) => item.todo !== todo) + ); + deleteTodo(todo, isdone); + }; + const toggleHandler = () => { + let todoList = getTodoFromLocalStorage(); + let target = todoList.find((item) => item.todo === todo); + target.isdone = !target.isdone; + setTodoToLocalStorage(todoList); + toggleTodo(todo, isdone); }; return ( - - {isdone ? : } + + + {isdone ? : } + {todo} diff --git a/src/components/TodoHeader.js b/src/components/TodoHeader.js index c5d99ac..b1c9a0c 100644 --- a/src/components/TodoHeader.js +++ b/src/components/TodoHeader.js @@ -28,8 +28,8 @@ export default function TodoHeader() { month: 'long', day: 'numeric', }; - const todayString = today.toLocaleDateString('kr-kR', options); - const day = today.toLocaleDateString('en-US', { weekday: 'long' }); + const todayString = today.toLocaleDateString('kr-KR', options); + const day = today.toLocaleDateString('kr-KR', { weekday: 'long' }); return ( {todayString} diff --git a/src/components/TodoSection.js b/src/components/TodoSection.js index 9f20a71..f8b8b1c 100644 --- a/src/components/TodoSection.js +++ b/src/components/TodoSection.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import styled from 'styled-components'; import TodoCard from './TodoCard'; @@ -7,6 +7,14 @@ let TodoSectionLayout = styled.div` display: flex; flex-direction: column; flex: 1 1; + overflow-y: scroll; +`; +let TodoContentBox = styled.div` + width: 100%; + display: flex; + flex-direction: column; + flex: 1 1; + overflow-y: scroll; `; let TodoListHeaderBox = styled.div` font-size: 0.75rem; @@ -15,22 +23,25 @@ let TodoListHeaderBox = styled.div` color: #9e9e9e; `; -export default function TodoSection({ todoList, deleteTodo }) { +export default function TodoSection({ todoList, deleteTodo, toggleTodo }) { const clickHandler = (isdone, index) => {}; return ( todo - {todoList.map((todo, index) => { - return ( - - ); - })} + + {todoList.map((todo, index) => { + return ( + + ); + })} + ); } diff --git a/src/hooks/useLocalStorage.js b/src/hooks/useLocalStorage.js index ac97230..f760ef2 100644 --- a/src/hooks/useLocalStorage.js +++ b/src/hooks/useLocalStorage.js @@ -1,9 +1,9 @@ export default function useLocalStorage() { - const getTodoFromLocalStorage = (listName) => { - return JSON.parse(localStorage.getItem(listName)) || []; + const getTodoFromLocalStorage = () => { + return JSON.parse(localStorage.getItem('todoList')) || []; }; - const setTodoToLocalStorage = (listName, todoList) => { - localStorage.setItem(listName, JSON.stringify(todoList)); + const setTodoToLocalStorage = (todoList) => { + localStorage.setItem('todoList', JSON.stringify(todoList)); }; return { getTodoFromLocalStorage, setTodoToLocalStorage }; } diff --git a/src/style/GlobalStyle.js b/src/style/GlobalStyle.js index ba1c2c6..f932497 100644 --- a/src/style/GlobalStyle.js +++ b/src/style/GlobalStyle.js @@ -37,10 +37,6 @@ h5, h6 { overflow-wrap: break-word; } - -.scroll-box::-webkit-scrollbar { - display: none; -} `; export default GlobalStyle; From b815e76a8f8155036106c93863f7b984770cfa80 Mon Sep 17 00:00:00 2001 From: eunsu Date: Fri, 22 Mar 2024 17:10:08 +0900 Subject: [PATCH 13/15] =?UTF-8?q?refactor:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0,=20useEffec?= =?UTF-8?q?t=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 8 +++++--- src/components/TodoSection.js | 2 -- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/App.js b/src/App.js index e19745d..c1489f3 100644 --- a/src/App.js +++ b/src/App.js @@ -24,8 +24,11 @@ let TodoListLayout = styled.div` display: flex; flex-direction: column; background-color: white; + border-radius: 1rem; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); @media (max-width: 768px) { height: 100%; + border-radius: 0; } `; @@ -33,18 +36,17 @@ function App() { const [todoList, setTodoList] = useState([]); const [doneList, setDoneList] = useState([]); const { getTodoFromLocalStorage } = useLocalStorage(); + const list = getTodoFromLocalStorage('todoList'); useEffect(() => { - const list = getTodoFromLocalStorage('todoList'); if (list) { setTodoList(list.filter((item) => !item.isdone)); setDoneList(list.filter((item) => item.isdone)); } - }, []); + }, [list]); const addTodo = (todo) => { setTodoList((prev) => { return [...prev, todo]; }); - // setTodoToLocalStorage('todoList', todoList); }; const deleteTodo = (todo, isDone) => { if (isDone) { diff --git a/src/components/TodoSection.js b/src/components/TodoSection.js index f8b8b1c..723416e 100644 --- a/src/components/TodoSection.js +++ b/src/components/TodoSection.js @@ -24,7 +24,6 @@ let TodoListHeaderBox = styled.div` `; export default function TodoSection({ todoList, deleteTodo, toggleTodo }) { - const clickHandler = (isdone, index) => {}; return ( todo @@ -35,7 +34,6 @@ export default function TodoSection({ todoList, deleteTodo, toggleTodo }) { key={index} todo={todo.todo} isdone={todo.isdone} - onClick={clickHandler(todo.isdone, index)} deleteTodo={deleteTodo} toggleTodo={toggleTodo} /> From ddf341ff60e06724a354c8a98d305f75f6fcf720 Mon Sep 17 00:00:00 2001 From: eunsu Date: Fri, 22 Mar 2024 18:50:49 +0900 Subject: [PATCH 14/15] =?UTF-8?q?feat:=20README=20=EC=9E=91=EC=84=B1=20-?= =?UTF-8?q?=20refactor:=20console.log()=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 74 +++++++++++++++------------------------ src/components/AddTodo.js | 3 -- 2 files changed, 28 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index a77b4ce..373c2c6 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,50 @@ # 2주차 미션: React-Todo -# 서론 +## 기능구현 -안녕하세요 🙌🏻 19기 프론트엔드 운영진 **변지혜**입니다. +- todo 추가 및 삭제 +- 유효성 체크, 중복 체크 +- todo 완료 여부에 따른 이동 +- 날짜 표시 +- 갯수체크 +- 반응형 구현 -다들 1주차 미션 Vanilla Todo 만드시느라 수고 많으셨습니다! 1주차 미션을 통해 여러분들께서 본격적인 React 사용에 앞서 Vanilla JS로 SPA를 만들때의 불편한 점을 느끼셨을 것 이라 생각합니다. +## 리뷰 -그리하여 이번 미션은, 1주차 스터의 미션으로 주어진 Todo list 만들기를 **React**로 리팩토링하는 것 입니다! -기존에 리액트를 잘 아시던 분들께는, 조금 더 효울적인 디자인 패턴에 대해 고민할수 있는 주차가 될 것이고, 리액트를 제대로 접해보지 못하신 분들께는 기존의 어플리케이션을 리액트로 포팅하는 과정을 통해 왜 프론트엔드 시장에 리액트가 등장하게 되었고, 리액트에서 사용하는 여러가지 방식들이 왜 바닐라에 비해 효율적인지 꺠닫는 주차가 될 것이라 생각합니다. +리액트의 hooks, 그리고 상태를 사용해 변경되면 자동으로 리렌더링을 해주는 방식 덕분에 JS만을 사용했을때보다 직접 신경써줘야 할 부분이 적었습니다. -비교적 가벼운 미션인 만큼 코드를 짜는 데 있어 여러분의 **창의성**을 충분히 발휘해보시기 바랍니다. _❕작동하기만 하면 되는 것보다 같은 코드를 짜는 여러가지 방식과 패턴에 대해 많이 고민해보시고, 본인이 작성할 수 있는 가장 창의적인 방법으로 코드를 작성해주셨으면 합니다.❕_ 여러분이 미션 수행을 하는 과정에서 한 생각과 고민만큼 스터디에서 더 많은 것을 얻어가실 수 있을 거라 기대합니다! +선연형 프로그래밍 패러다임을 지키기 위해 각 기능별로 컴포넌트화 시키다보니 각 컴포넌트에서 todoList들을 따로 접근해야 했고, 하나의 컴포넌트에서 변경이 이루어지면 다른 컴포넌트들도 변경이 일어난 걸 알아야 하니 이를 어떻게 처리할 지 고민이 되었습니다. -막히는 부분이 있더라도 우선 스스로 공부하고 찾아보면서 미션을 진행하는 방식을 권고드리지만, 미션과 관련하여 운영진의 도움이 필요하시다면 얼마든지 슬랙 Q&A 채널이나 프론트엔드 카톡방에 질문을 남겨 주세요! +일반적으로 props 전달방식, 혹은 전역상태관리로 해결하겠지만 이번에는 Localstorage를 사용하기 떄문에 이것만으로 해결할 수 있지 않을까 싶었습니다. 하지만 직접해보니 localstorage는 리액트의 상태가 아니기 떄문에 변경이 되어도 리렌더링이 되지 않았고 새로고침을 해야 정상적으로 변경사항이 반영됐습니다. -# 미션 - -## 예시 - -- [리액트 투두 예시](https://react-todo-18th-lemon.vercel.app/) - -## 미션 목표 - -- VSCode, Prettier를 이용하여 개발환경을 관리합니다. -- React의 기초를 이해합니다. -- React를 통한 어플리케이션 상태 관리 방법을 이해합니다. -- React Hooks에 대한 기초를 이해합니다. -- Styled-Components를 통한 CSS-in-JS 및 CSS Preprocessor의 사용법을 익힙니다. - -## 기한 - -- 2024년 3월 22일 금요일 +localstorage에 있는 값이 변경될때마다 모든 컴포넌트에서 localstorage의 값을 다시 받아오는 과정은 굉장히 비효율적인 거 같아 결국 처음에 생각했던 방법을 사용하기로 했습니다. 프로젝트가 그렇게 크지 않기 때문에 전역상태관리 없이 props drilling으로도 해결이 가능할 거 같아 props로 전달하는 방식을 선택했습니다. 구현해보니 생각보다 drilling이 많이 생겨 전역상태관리로 해결했어도 좋았겠다 싶었습니다. ## Key Questions - Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요? -- 미션을 진행하면서 느낀, React를 사용함으로서 얻을수 있는 장점은 무엇이었나요? -- React에서 상태란 무엇이고 어떻게 관리할 수 있을까요? -- Styled-Components 사용 후기 (CSS와 비교) + 리액트에서 사용하는 가상 돔으로, 리액트 변경점이 발생하면 가상 돔이 변경되고, 실제 DOM과 차이점을 확인 후 해당하는 부분만 변경이 이루어집니다. 이를 통해 DOM의 변경을 최소화 시킬 수 있습니다. -## 필수 요건 + 리액트는 마운트가 된 이후 변경사항이 발생하면 리렌더링이 발생합니다. js에서 속성변경 등으로 이루어지는 리렌더링 역시 수정되는 부분만 수정되지만, 이는 개발자가 직접 컨트롤 해줘야 합니다. 하지만 react는 선언적 UI를 사용하기 때문에 상태가 변하면 자동으로 UI를 필요한 부분만 업데이트 해줍니다. -- 1주차 미션의 결과물을 그대로 React로 구현합니다. (‼️ todo / done 개수 잊지 마세요 ‼️) -- Functional Components를 사용합니다. -- React Hooks만을 사용해 상태를 관리합니다. -- (이번주는 Redux, MobX, Recoil, SWR등의 외부 상태관리 라이브러리를 사용하지 않아도 미션 수행에 지장이 없습니다.) +- 미션을 진행하면서 느낀, React를 사용함으로서 얻을수 있는 장점은 무엇이었나요? + html 태그들 대신 컴포넌트들을 볼 수 있기 때문에 해당 코드(컴포넌트)가 무엇을 하려고 하는 지 알기 쉬웠습니다. 코딩하는 과정에서도 어느 부분을 내가 짜고 있는지, 어디서 에러가 났는지 찾지 편했습니다. -## 선택 요건 + 상태라는 개념을 통해 변경사항이 자동으로 변경되고, hooks들을 통해 이미 만들어진 함수들을 사용해 개발자의 부담을 덜어줬습니다. -- 기존 Todo-list에 여러분들이 추가하고 싶은 기능과 디자인을 자유롭게 추가해보세요. +- React에서 상태란 무엇이고 어떻게 관리할 수 있을까요? + 컴포넌트의 현재 상태를 의미합니다. `useState`를 사용해 상태변수를 생성하고, 관리할 수 있습니다. -## 로컬 실행방법 + 리액트는 변수를 관리하지 않습니다. `useState`hook을 사용해 상태를 선언하면 이를 관리하며 변경사항이 생기면 이를 반영해 리렌더링을 시킵니다. ---- + 주의할 점은 `useState`는 비동기처리 된다는 것입니다. set함수를 호출하면 이는 스케쥴링되는데 스케쥴링 되어있는 상태에서 또 set함수를 호출하면 변경이 이루어지지 않은 값을 사용합니다. 따라서 리렌더링 되지 않았을 때 값에 접근하는 것을 주의해야 하고, 그럼에도 set함수를 호출해야 한다면 -`npm start` : 로컬에서 react application을 자동으로 리로드하여 실행시켜줍니다. + ```js + set함수((prev)=>{...}) + ``` -# 링크 및 참고자료 + 방식을 활용해 이전 값에 올바르게 접근할 수 있게 해줘야합니다. ---- +- Styled-Components 사용 후기 (CSS와 비교) + CSS를 JS파일 안에서 컨트롤 할 수 있기 때문에 수정하고 싶을때 CSS-IN-CSS 방식을 사용할 때보다 빠르게 찾아 수정할 수 있었습니다. 또한 props를 통해 동적인 스타일링을 할 수 있는 것도 장점입니다. -- [create react app (CRA)](https://create-react-app.dev/docs/getting-started/) -- [리액트 docs 주요 개념 1-12](https://react.dev/learn) -- [리액트 docs Hook 1-3](https://react.dev/reference/react) -- [리액트 useEffect 완벽 가이드](https://overreacted.io/ko/a-complete-guide-to-useeffect/) -- [컴포넌트 네이밍을 위한 자바스크립트 네이밍 컨벤션](https://velog.io/@cada/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%ED%83%80%EC%9D%BC-%EA%B0%80%EC%9D%B4%EB%93%9C-%EB%84%A4%EC%9D%B4%EB%B0%8D-%EC%BB%A8%EB%B2%A4%EC%85%98-%ED%8E%B8) -- [useState, useEffect hooks](https://velog.io/@velopert/react-hooks#1-usestate) -- [styled-component](https://styled-components.com/docs/basics#getting-started) + 다만 스타일드 컴포넌트와 일반적인 컴포넌트를 구분할 수 없어서 이 부분이 불편했습니다. diff --git a/src/components/AddTodo.js b/src/components/AddTodo.js index 355a6c9..14b06c4 100644 --- a/src/components/AddTodo.js +++ b/src/components/AddTodo.js @@ -46,17 +46,14 @@ export default function AddTodo({ addTodo }) { let todoList = getTodoFromLocalStorage() || []; if (inputValue.trim() === '') { alert('할 일을 입력해주세요.'); - console.log(1); } else if (todoList.some((item) => item.todo === inputValue)) { alert('이미 등록된 할 일입니다.'); setInputValue(''); } else { todoList.push({ todo: inputValue, isdone: false }); - console.log(todoList); setTodoToLocalStorage(todoList); addTodo({ todo: inputValue, isdone: false }); setInputValue(''); - console.log(3); } }; return ( From 2f10cad19d707645503037eb32cf7a819c747f58 Mon Sep 17 00:00:00 2001 From: eunsu Date: Tue, 2 Apr 2024 12:59:32 +0900 Subject: [PATCH 15/15] =?UTF-8?q?style:=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 5 ++++- src/components/DoneSection.js | 6 ++++++ src/components/TodoCard.js | 4 ++-- src/components/TodoSection.js | 6 ++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/App.js b/src/App.js index c1489f3..431b25e 100644 --- a/src/App.js +++ b/src/App.js @@ -9,12 +9,15 @@ import useLocalStorage from './hooks/useLocalStorage'; let Layout = styled.div` width: 100%; - height: 100vh; + height: 100dvh; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: #efd8d8; + @media (max-width: 768px) { + background-color: white; + } `; let TodoListLayout = styled.div` diff --git a/src/components/DoneSection.js b/src/components/DoneSection.js index 5510d6b..d24edce 100644 --- a/src/components/DoneSection.js +++ b/src/components/DoneSection.js @@ -8,12 +8,18 @@ let DoneSectionLayout = styled.div` flex-direction: column; flex: 1 1; overflow-y: scroll; + & ::-webkit-scrollbar { + display: none; + } `; let DoneContentBox = styled.div` width: 100%; display: flex; flex-direction: column; overflow-y: scroll; + & ::-webkit-scrollbar { + display: none; + } `; let TodoListHeaderBox = styled.div` font-size: 0.75rem; diff --git a/src/components/TodoCard.js b/src/components/TodoCard.js index f3b8293..94b6b73 100644 --- a/src/components/TodoCard.js +++ b/src/components/TodoCard.js @@ -6,8 +6,8 @@ import { ReactComponent as DeleteSvg } from '../assets/svg/Delete.svg'; import useLocalStorage from '../hooks/useLocalStorage'; let TodoCardLayout = styled.div` - width: 100%; - height: 2.5rem; + width: 840px; + height: fit-content; display: flex; align-items: center; padding: 0.5rem 1rem; diff --git a/src/components/TodoSection.js b/src/components/TodoSection.js index 723416e..332a1a3 100644 --- a/src/components/TodoSection.js +++ b/src/components/TodoSection.js @@ -8,6 +8,9 @@ let TodoSectionLayout = styled.div` flex-direction: column; flex: 1 1; overflow-y: scroll; + & ::-webkit-scrollbar { + display: none; + } `; let TodoContentBox = styled.div` width: 100%; @@ -15,6 +18,9 @@ let TodoContentBox = styled.div` flex-direction: column; flex: 1 1; overflow-y: scroll; + & ::-webkit-scrollbar { + display: none; + } `; let TodoListHeaderBox = styled.div` font-size: 0.75rem;