From b0e1973da151a33b42790f2082efb3a566e47267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 22 Mar 2024 01:33:26 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feature:=20=ED=95=A0=20=EC=9D=BC=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EA=B3=BC=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc | 6 ++ package-lock.json | 163 ++++++++++++++++++++++++++++++++++ package.json | 2 + public/index.html | 2 +- src/App.js | 16 ++-- src/assets/img/grayCheck.svg | 3 + src/assets/img/greenCheck.svg | 3 + src/assets/img/plus.svg | 3 + src/assets/img/trash.svg | 3 + src/components/ToDoInput.js | 45 ++++++++++ src/pages/ToDoList.js | 107 ++++++++++++++++++++++ 11 files changed, 346 insertions(+), 7 deletions(-) create mode 100644 .prettierrc create mode 100644 src/assets/img/grayCheck.svg create mode 100644 src/assets/img/greenCheck.svg create mode 100644 src/assets/img/plus.svg create mode 100644 src/assets/img/trash.svg create mode 100644 src/components/ToDoInput.js create mode 100644 src/pages/ToDoList.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0a72520 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} diff --git a/package-lock.json b/package-lock.json index e523af2..6aa4307 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,9 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", + "styled-components": "^6.1.8", "web-vitals": "^2.1.4" } }, @@ -2283,6 +2285,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", @@ -3352,6 +3372,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -4534,6 +4562,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 +6070,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 +6510,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 +6708,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", @@ -15143,6 +15202,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "dependencies": { + "@remix-run/router": "1.15.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "dependencies": { + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -15998,6 +16087,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 +16624,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 +16703,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..58c8180 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", + "styled-components": "^6.1.8", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/public/index.html b/public/index.html index aa069f2..b115c18 100644 --- a/public/index.html +++ b/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + To-Do-List diff --git a/src/App.js b/src/App.js index 3b90819..dad6931 100644 --- a/src/App.js +++ b/src/App.js @@ -1,9 +1,13 @@ -function App() { +import React from 'react'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import ToDoList from './pages/ToDoList'; + +export default function App() { return ( -
-

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

-
+ + + } /> + + ); } - -export default App; diff --git a/src/assets/img/grayCheck.svg b/src/assets/img/grayCheck.svg new file mode 100644 index 0000000..120713c --- /dev/null +++ b/src/assets/img/grayCheck.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/greenCheck.svg b/src/assets/img/greenCheck.svg new file mode 100644 index 0000000..a401f1f --- /dev/null +++ b/src/assets/img/greenCheck.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/plus.svg b/src/assets/img/plus.svg new file mode 100644 index 0000000..7751c75 --- /dev/null +++ b/src/assets/img/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/trash.svg b/src/assets/img/trash.svg new file mode 100644 index 0000000..d46e9e1 --- /dev/null +++ b/src/assets/img/trash.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/ToDoInput.js b/src/components/ToDoInput.js new file mode 100644 index 0000000..2bab116 --- /dev/null +++ b/src/components/ToDoInput.js @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; + +const InputStyle = styled.input` + background-color: #d9d9d9d9; + border: none; + outline: none; + border-radius: 10px; + padding: 5px 5px 5px 30px; /*텍스트와 이미지 겹침 방지를 위한 패딩 조정*/ + width: 345px; + height: 20px; + line-height: 20px; + cursor: pointer; + + background-image: url('../assets/img/plus.svg'); + background-position: 5px center; /*이미지 위치 조정*/ + background-repeat: no-repeat; + background-size: 20px 20px; +`; + +export default function ToDoInput({ onAddTodo }) { + const [value, setValue] = useState(''); + + const handleInput = (event) => { + setValue(event.target.value); + }; + + const handleSubmit = (event) => { + event.preventDefault(); + if (!value.trim()) return; // 공백 입력 방지 + onAddTodo(value); // 부모 컴포넌트의 함수 호출 + setValue(''); // 입력 필드 초기화 + }; + + return ( +
+ +
+ ); +} diff --git a/src/pages/ToDoList.js b/src/pages/ToDoList.js new file mode 100644 index 0000000..cc5e3dd --- /dev/null +++ b/src/pages/ToDoList.js @@ -0,0 +1,107 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import deleteBtn from '../assets/img/trash.svg'; +import grayCheckBtn from '../assets/img/grayCheck.svg'; +import greenCheckBtn from '../assets/img/greenCheck.svg'; +import plusImg from '../assets/img/plus.svg'; +import ToDoInput from '../components/ToDoInput'; + +const Container = styled.div` + margin: auto; + max-width: 375px; + padding: 20px; + background-color: #f5f5f5; + border-radius: 15px; + box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11); +`; + +const ToDoBlock = styled.div` + background-color: white; + border-radius: 10px; + max-width: 375px; + height: 510px; + display: flex; + flex-direction: column; + justify-content: flex-start; + overflow-y: auto; +`; + +const ItemContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 10px 10px 30px; + position: relative; + + &::after { + content: ''; /* 가상 요소에 내용이 없음을 명시 */ + position: absolute; /* 상위 요소인 ItemContainer에 대해 절대 위치 설정 */ + bottom: 0; /* 하단에 위치 */ + left: 0; /* 왼쪽 시작점 */ + width: calc(100% - 30px); /* 전체 너비에서 20px만큼 줄임 */ + margin-left: 30px; /* 왼쪽 여백 설정 */ + border-bottom: 1px solid #ccc; /* 하단 테두리 스타일 */ + } +`; + +const ButtonsContainer = styled.div` + display: flex; +`; + +const DeleteButtonImg = styled.img` + cursor: pointer; + width: 20px; + height: 20px; +`; + +const CheckButtonImg = styled.img` + width: 20px; + height: 20px; + cursor: pointer; + margin-right: 7px; +`; + +const Title = styled.div``; + +export default function ToDoList() { + const [toDoData, setToDoData] = useState([]); + + const handleClick = (id) => { + let newToDoData = toDoData.filter((data) => data.id !== id); + setToDoData(newToDoData); + }; + + const addTodo = (newToDoTitle) => { + let newToDo = { + id: Date.now(), + title: newToDoTitle, + completed: false, + }; + + setToDoData((prev) => [...prev, newToDo]); // 새로운 할 일을 할 일 목록에 추가 + }; + + return ( + + + <h1>할일 목록</h1> + + + + {toDoData.map((data) => ( + + {data.title} + + + handleClick(data.id)} + /> + + + ))} + + + ); +} From 463a4a29f06628f978b9ee300456e27419501b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 22 Mar 2024 01:59:42 +0900 Subject: [PATCH 02/11] =?UTF-8?q?docs:=20=ED=95=A0=20=EC=9D=BC=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/{ToDoInput.js => InputForm.js} | 13 ++-- src/components/List.js | 75 +++++++++++++++++++ src/pages/ToDoList.js | 73 +----------------- 3 files changed, 85 insertions(+), 76 deletions(-) rename src/components/{ToDoInput.js => InputForm.js} (79%) create mode 100644 src/components/List.js diff --git a/src/components/ToDoInput.js b/src/components/InputForm.js similarity index 79% rename from src/components/ToDoInput.js rename to src/components/InputForm.js index 2bab116..f51c9af 100644 --- a/src/components/ToDoInput.js +++ b/src/components/InputForm.js @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import styled from 'styled-components'; +import plusImg from '../assets/img/plus.svg'; const InputStyle = styled.input` background-color: #d9d9d9d9; @@ -7,18 +8,18 @@ const InputStyle = styled.input` outline: none; border-radius: 10px; padding: 5px 5px 5px 30px; /*텍스트와 이미지 겹침 방지를 위한 패딩 조정*/ - width: 345px; + width: 340px; height: 20px; line-height: 20px; cursor: pointer; - background-image: url('../assets/img/plus.svg'); + background-image: url('${plusImg}'); background-position: 5px center; /*이미지 위치 조정*/ background-repeat: no-repeat; background-size: 20px 20px; `; -export default function ToDoInput({ onAddTodo }) { +export default function InputForm({ onAddTodo }) { const [value, setValue] = useState(''); const handleInput = (event) => { @@ -28,13 +29,13 @@ export default function ToDoInput({ onAddTodo }) { const handleSubmit = (event) => { event.preventDefault(); if (!value.trim()) return; // 공백 입력 방지 - onAddTodo(value); // 부모 컴포넌트의 함수 호출 - setValue(''); // 입력 필드 초기화 + onAddTodo(value); + setValue(''); }; return (
- { + let newToDoData = toDoData.filter((data) => data.id !== id); + setToDoData(newToDoData); + }; + + return ( + + {toDoData.map((data) => ( + + {data.title} + + + handleClick(data.id)} + /> + + + ))} + + ); +} diff --git a/src/pages/ToDoList.js b/src/pages/ToDoList.js index cc5e3dd..5d997ef 100644 --- a/src/pages/ToDoList.js +++ b/src/pages/ToDoList.js @@ -1,10 +1,8 @@ import React, { useState } from 'react'; import styled from 'styled-components'; -import deleteBtn from '../assets/img/trash.svg'; -import grayCheckBtn from '../assets/img/grayCheck.svg'; import greenCheckBtn from '../assets/img/greenCheck.svg'; -import plusImg from '../assets/img/plus.svg'; -import ToDoInput from '../components/ToDoInput'; +import ToDoInput from '../components/InputForm'; +import List from '../components/List'; const Container = styled.div` margin: auto; @@ -15,62 +13,11 @@ const Container = styled.div` box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11); `; -const ToDoBlock = styled.div` - background-color: white; - border-radius: 10px; - max-width: 375px; - height: 510px; - display: flex; - flex-direction: column; - justify-content: flex-start; - overflow-y: auto; -`; - -const ItemContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px 10px 10px 30px; - position: relative; - - &::after { - content: ''; /* 가상 요소에 내용이 없음을 명시 */ - position: absolute; /* 상위 요소인 ItemContainer에 대해 절대 위치 설정 */ - bottom: 0; /* 하단에 위치 */ - left: 0; /* 왼쪽 시작점 */ - width: calc(100% - 30px); /* 전체 너비에서 20px만큼 줄임 */ - margin-left: 30px; /* 왼쪽 여백 설정 */ - border-bottom: 1px solid #ccc; /* 하단 테두리 스타일 */ - } -`; - -const ButtonsContainer = styled.div` - display: flex; -`; - -const DeleteButtonImg = styled.img` - cursor: pointer; - width: 20px; - height: 20px; -`; - -const CheckButtonImg = styled.img` - width: 20px; - height: 20px; - cursor: pointer; - margin-right: 7px; -`; - const Title = styled.div``; export default function ToDoList() { const [toDoData, setToDoData] = useState([]); - const handleClick = (id) => { - let newToDoData = toDoData.filter((data) => data.id !== id); - setToDoData(newToDoData); - }; - const addTodo = (newToDoTitle) => { let newToDo = { id: Date.now(), @@ -87,21 +34,7 @@ export default function ToDoList() {

할일 목록

- - {toDoData.map((data) => ( - - {data.title} - - - handleClick(data.id)} - /> - - - ))} - + ); } From f2e821e947122028aecfd9c2a7490145941050f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 22 Mar 2024 03:27:03 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20=EC=99=84=EB=A3=8C=ED=95=9C=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98,=20=EB=82=A0=EC=A7=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/InputForm.js | 6 ++---- src/components/List.js | 28 ++++++++++++++++++++++------ src/components/TodayDate.js | 25 +++++++++++++++++++++++++ src/pages/ToDoList.js | 26 +++++++++++++++++++------- 4 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 src/components/TodayDate.js diff --git a/src/components/InputForm.js b/src/components/InputForm.js index f51c9af..fdb2cdd 100644 --- a/src/components/InputForm.js +++ b/src/components/InputForm.js @@ -19,9 +19,7 @@ const InputStyle = styled.input` background-size: 20px 20px; `; -export default function InputForm({ onAddTodo }) { - const [value, setValue] = useState(''); - +export default function InputForm({ value, setValue, onAddToDo }) { const handleInput = (event) => { setValue(event.target.value); }; @@ -29,7 +27,7 @@ export default function InputForm({ onAddTodo }) { const handleSubmit = (event) => { event.preventDefault(); if (!value.trim()) return; // 공백 입력 방지 - onAddTodo(value); + onAddToDo(value); setValue(''); }; diff --git a/src/components/List.js b/src/components/List.js index 835c566..a3e38be 100644 --- a/src/components/List.js +++ b/src/components/List.js @@ -2,6 +2,7 @@ import React from 'react'; import styled from 'styled-components'; import deleteBtn from '../assets/img/trash.svg'; import grayCheckBtn from '../assets/img/grayCheck.svg'; +import greenCheckBtn from '../assets/img/greenCheck.svg'; const ToDoBlock = styled.div` background-color: white; @@ -20,6 +21,7 @@ const ItemContainer = styled.div` align-items: center; padding: 10px 10px 10px 30px; position: relative; + text-decoration: ${(props) => (props.completed ? 'line-through' : 'none')}; &::after { content: ''; /* 가상 요소에 내용이 없음을 명시 */ @@ -51,21 +53,35 @@ const CheckButtonImg = styled.img` export default function List({ toDoData, setToDoData }) { const handleClick = (id) => { - let newToDoData = toDoData.filter((data) => data.id !== id); + let newToDoData = toDoData.filter((item) => item.id !== id); + setToDoData(newToDoData); + }; + + const handleComplete = (id) => { + let newToDoData = toDoData.map((item) => { + if (item.id === id) { + return { ...item, completed: !item.completed }; + } + return item; + }); setToDoData(newToDoData); }; return ( - {toDoData.map((data) => ( - - {data.title} + {toDoData.map((item) => ( + + {item.title} - + handleComplete(item.id)} + /> handleClick(data.id)} + onClick={() => handleClick(item.id)} /> diff --git a/src/components/TodayDate.js b/src/components/TodayDate.js new file mode 100644 index 0000000..02f39d6 --- /dev/null +++ b/src/components/TodayDate.js @@ -0,0 +1,25 @@ +import React from 'react'; + +export default function TodayDate() { + const today = new Date(); + const days = [ + '일요일', + '월요일', + '화요일', + '수요일', + '목요일', + '금요일', + '토요일', + ]; + + const date = `${today.getFullYear()}-${(today.getMonth() + 1) + .toString() + .padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`; + const day = days[today.getDay()]; + + return ( +

+ {date} {day} +

+ ); +} diff --git a/src/pages/ToDoList.js b/src/pages/ToDoList.js index 5d997ef..d9a6f2a 100644 --- a/src/pages/ToDoList.js +++ b/src/pages/ToDoList.js @@ -1,8 +1,8 @@ import React, { useState } from 'react'; import styled from 'styled-components'; -import greenCheckBtn from '../assets/img/greenCheck.svg'; -import ToDoInput from '../components/InputForm'; +import InputForm from '../components/InputForm'; import List from '../components/List'; +import TodayDate from '../components/TodayDate'; const Container = styled.div` margin: auto; @@ -13,12 +13,18 @@ const Container = styled.div` box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11); `; -const Title = styled.div``; - +const Subtitle = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; export default function ToDoList() { const [toDoData, setToDoData] = useState([]); + const [value, setValue] = useState(''); + let totalItems = toDoData.length; + let completedItems = toDoData.filter((item) => item.completed).length; - const addTodo = (newToDoTitle) => { + const addToDo = (newToDoTitle) => { let newToDo = { id: Date.now(), title: newToDoTitle, @@ -31,9 +37,15 @@ export default function ToDoList() { return ( - <h1>할일 목록</h1> + <h1>할 일 목록</h1> - + + + +

+ {completedItems}/{totalItems} +

+
); From dd636d066e968ca3bef2f1ff7267eaf58774c013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 22 Mar 2024 03:54:29 +0900 Subject: [PATCH 04/11] =?UTF-8?q?style:=20=ED=8F=B0=ED=8A=B8=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 12 ++++++++++++ package.json | 1 + public/index.html | 4 ++++ src/App.js | 2 ++ src/GlobalStyle.js | 37 +++++++++++++++++++++++++++++++++++++ src/pages/ToDoList.js | 8 ++------ 6 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 src/GlobalStyle.js diff --git a/package-lock.json b/package-lock.json index 6aa4307..e6eb084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "styled-components": "^6.1.8", + "styled-reset": "^4.5.2", "web-vitals": "^2.1.4" } }, @@ -16688,6 +16689,17 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, + "node_modules/styled-reset": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/styled-reset/-/styled-reset-4.5.2.tgz", + "integrity": "sha512-dbAaaVEhweBs2FGfqGBdW6oMcMK8238C2X5KCxBhUQJX92m/QyUfzRADOXhdXiXNkIPELtMCd72YY9eCdORfIw==", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "styled-components": ">=4.0.0 || >=5.0.0 || >=6.0.0" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", diff --git a/package.json b/package.json index 58c8180..99de6d7 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "styled-components": "^6.1.8", + "styled-reset": "^4.5.2", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/public/index.html b/public/index.html index b115c18..c37e2f5 100644 --- a/public/index.html +++ b/public/index.html @@ -24,6 +24,10 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> + To-Do-List diff --git a/src/App.js b/src/App.js index dad6931..ae4550a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,10 +1,12 @@ import React from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import ToDoList from './pages/ToDoList'; +import GlobalStyles from '../src/GlobalStyle'; export default function App() { return ( + } /> diff --git a/src/GlobalStyle.js b/src/GlobalStyle.js new file mode 100644 index 0000000..8994d64 --- /dev/null +++ b/src/GlobalStyle.js @@ -0,0 +1,37 @@ +import { createGlobalStyle } from 'styled-components'; +import reset from 'styled-reset'; // style-reset 패키지 + +const GlobalStyles = createGlobalStyle` + ${reset} + a{ + text-decoration: none; + color: inherit; + } + *{ + box-sizing: border-box; + } + html, body, div, span, h1, h2, h3, h4, h5, h6, p, + a, dl, dt, dd, ol, ul, li, form, label, table{ + margin: 0; + padding: 0; + border: 0; + font-size: 10px; + vertical-align: baseline; + } + body{ + line-height: 1; + font-family: pretendard; + background-color: #F6F9F0; + margin-bottom: 100px; + } + ol, ul{ + list-style: none; + } + button { + border: 0; + background: transparent; + cursor: pointer; + } +`; + +export default GlobalStyles; diff --git a/src/pages/ToDoList.js b/src/pages/ToDoList.js index d9a6f2a..5b713b4 100644 --- a/src/pages/ToDoList.js +++ b/src/pages/ToDoList.js @@ -36,15 +36,11 @@ export default function ToDoList() { return ( - - <h1>할 일 목록</h1> - +

할 일 목록

-

- {completedItems}/{totalItems} -

+ {completedItems}/{totalItems}
From 4edf5ac68e9f626dd294f03989b3db75533ad5d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 22 Mar 2024 05:08:26 +0900 Subject: [PATCH 05/11] =?UTF-8?q?style:=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/GlobalStyle.js | 11 +++++++---- src/components/InputForm.js | 9 ++++++--- src/components/List.js | 17 +++++++++++++++-- src/pages/ToDoList.js | 25 ++++++++++++++++++++++--- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/GlobalStyle.js b/src/GlobalStyle.js index 8994d64..d4eca44 100644 --- a/src/GlobalStyle.js +++ b/src/GlobalStyle.js @@ -15,14 +15,17 @@ const GlobalStyles = createGlobalStyle` margin: 0; padding: 0; border: 0; - font-size: 10px; + font-size: 90%; vertical-align: baseline; } body{ - line-height: 1; font-family: pretendard; - background-color: #F6F9F0; - margin-bottom: 100px; + background-color: rgb(231, 244, 253); + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + margin: 0; } ol, ul{ list-style: none; diff --git a/src/components/InputForm.js b/src/components/InputForm.js index fdb2cdd..8c3020d 100644 --- a/src/components/InputForm.js +++ b/src/components/InputForm.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import styled from 'styled-components'; import plusImg from '../assets/img/plus.svg'; @@ -8,8 +8,7 @@ const InputStyle = styled.input` outline: none; border-radius: 10px; padding: 5px 5px 5px 30px; /*텍스트와 이미지 겹침 방지를 위한 패딩 조정*/ - width: 340px; - height: 20px; + width: 100%; line-height: 20px; cursor: pointer; @@ -17,6 +16,10 @@ const InputStyle = styled.input` background-position: 5px center; /*이미지 위치 조정*/ background-repeat: no-repeat; background-size: 20px 20px; + + ::placeholder { + font-size: 1px; + } `; export default function InputForm({ value, setValue, onAddToDo }) { diff --git a/src/components/List.js b/src/components/List.js index a3e38be..d603c9e 100644 --- a/src/components/List.js +++ b/src/components/List.js @@ -7,12 +7,24 @@ import greenCheckBtn from '../assets/img/greenCheck.svg'; const ToDoBlock = styled.div` background-color: white; border-radius: 10px; - max-width: 375px; - height: 510px; + width: 100%; + height: 50vh; display: flex; flex-direction: column; justify-content: flex-start; overflow-y: auto; + + /* 웹킷 기반 브라우저를 위한 스타일 */ + &::-webkit-scrollbar { + width: 5px; + } + + /* 파이어폭스 브라우저를 위한 스타일 */ + scrollbar-width: thin; + + @media (max-width: 768px) { + width: 100%; + } `; const ItemContainer = styled.div` @@ -21,6 +33,7 @@ const ItemContainer = styled.div` align-items: center; padding: 10px 10px 10px 30px; position: relative; + font-size: 1.5em; text-decoration: ${(props) => (props.completed ? 'line-through' : 'none')}; &::after { diff --git a/src/pages/ToDoList.js b/src/pages/ToDoList.js index 5b713b4..680cbe2 100644 --- a/src/pages/ToDoList.js +++ b/src/pages/ToDoList.js @@ -6,17 +6,34 @@ import TodayDate from '../components/TodayDate'; const Container = styled.div` margin: auto; - max-width: 375px; + width: 600px; padding: 20px; background-color: #f5f5f5; border-radius: 15px; box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11); + + @media (max-width: 768px) { + width: 100%; + } +`; + +const Title = styled.h1` + margin: 10px 0; + font-size: 2.7em; + font-weight: bold; `; const Subtitle = styled.div` display: flex; justify-content: space-between; align-items: center; + + h3 { + font-size: 1.8em; + font-weight: 700; + margin-top: 20px; + margin-bottom: 10px; + } `; export default function ToDoList() { const [toDoData, setToDoData] = useState([]); @@ -36,11 +53,13 @@ export default function ToDoList() { return ( -

할 일 목록

+ To-Do-List - {completedItems}/{totalItems} +

+ {completedItems}/{totalItems} +

From 806a548f9ce539ad5616781f818cf0768aba1cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 22 Mar 2024 05:52:44 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=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 | 2 +- src/{GlobalStyle.js => GlobalStyles.js} | 0 src/components/List.js | 2 ++ src/pages/ToDoList.js | 7 +++++-- 4 files changed, 8 insertions(+), 3 deletions(-) rename src/{GlobalStyle.js => GlobalStyles.js} (100%) diff --git a/src/App.js b/src/App.js index ae4550a..60ac184 100644 --- a/src/App.js +++ b/src/App.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import ToDoList from './pages/ToDoList'; -import GlobalStyles from '../src/GlobalStyle'; +import GlobalStyles from '../src/GlobalStyles'; export default function App() { return ( diff --git a/src/GlobalStyle.js b/src/GlobalStyles.js similarity index 100% rename from src/GlobalStyle.js rename to src/GlobalStyles.js diff --git a/src/components/List.js b/src/components/List.js index d603c9e..dbee80e 100644 --- a/src/components/List.js +++ b/src/components/List.js @@ -68,6 +68,7 @@ export default function List({ toDoData, setToDoData }) { const handleClick = (id) => { let newToDoData = toDoData.filter((item) => item.id !== id); setToDoData(newToDoData); + localStorage.setItem('toDoData', JSON.stringify(newToDoData)); }; const handleComplete = (id) => { @@ -78,6 +79,7 @@ export default function List({ toDoData, setToDoData }) { return item; }); setToDoData(newToDoData); + localStorage.setItem('toDoData', JSON.stringify(newToDoData)); }; return ( diff --git a/src/pages/ToDoList.js b/src/pages/ToDoList.js index 680cbe2..010ea82 100644 --- a/src/pages/ToDoList.js +++ b/src/pages/ToDoList.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import InputForm from '../components/InputForm'; import List from '../components/List'; @@ -35,8 +35,10 @@ const Subtitle = styled.div` margin-bottom: 10px; } `; + export default function ToDoList() { - const [toDoData, setToDoData] = useState([]); + const savedToDos = JSON.parse(localStorage.getItem('toDoData') || '[]'); // 로컬 스토리지에서 불러오기 + const [toDoData, setToDoData] = useState(savedToDos); const [value, setValue] = useState(''); let totalItems = toDoData.length; let completedItems = toDoData.filter((item) => item.completed).length; @@ -49,6 +51,7 @@ export default function ToDoList() { }; setToDoData((prev) => [...prev, newToDo]); // 새로운 할 일을 할 일 목록에 추가 + localStorage.setItem('toDoData', JSON.stringify([...toDoData, newToDo])); }; return ( From 13df877e2c35cc0091676391401a7075b19b35d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 22 Mar 2024 14:01:06 +0900 Subject: [PATCH 07/11] =?UTF-8?q?style:=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A4=84=EB=B0=94=EA=BF=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/GlobalStyles.js | 6 +++--- src/components/InputForm.js | 11 ++++++----- src/components/List.js | 35 ++++++++++++++++++++++------------- src/components/TodayDate.js | 1 - src/pages/ToDoList.js | 19 ++++++++++++++++--- 5 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/GlobalStyles.js b/src/GlobalStyles.js index d4eca44..f0c698f 100644 --- a/src/GlobalStyles.js +++ b/src/GlobalStyles.js @@ -1,5 +1,5 @@ import { createGlobalStyle } from 'styled-components'; -import reset from 'styled-reset'; // style-reset 패키지 +import reset from 'styled-reset'; const GlobalStyles = createGlobalStyle` ${reset} @@ -10,12 +10,11 @@ const GlobalStyles = createGlobalStyle` *{ box-sizing: border-box; } - html, body, div, span, h1, h2, h3, h4, h5, h6, p, + html, body, span, div, h1, h2, h3, h4, h5, h6, p, a, dl, dt, dd, ol, ul, li, form, label, table{ margin: 0; padding: 0; border: 0; - font-size: 90%; vertical-align: baseline; } body{ @@ -31,6 +30,7 @@ const GlobalStyles = createGlobalStyle` list-style: none; } button { + display: flex; border: 0; background: transparent; cursor: pointer; diff --git a/src/components/InputForm.js b/src/components/InputForm.js index 8c3020d..f746389 100644 --- a/src/components/InputForm.js +++ b/src/components/InputForm.js @@ -16,20 +16,21 @@ const InputStyle = styled.input` background-position: 5px center; /*이미지 위치 조정*/ background-repeat: no-repeat; background-size: 20px 20px; - - ::placeholder { - font-size: 1px; - } `; export default function InputForm({ value, setValue, onAddToDo }) { + //입력 필드의 입력값 상태 업데이트하는 함수 const handleInput = (event) => { setValue(event.target.value); }; + //폼 제출 시 입력데이터 처리하는 함수 const handleSubmit = (event) => { event.preventDefault(); - if (!value.trim()) return; // 공백 입력 방지 + if (!value.trim()) { + alert('공백 없이 입력해주세요.'); + return; + } onAddToDo(value); setValue(''); }; diff --git a/src/components/List.js b/src/components/List.js index dbee80e..3f082af 100644 --- a/src/components/List.js +++ b/src/components/List.js @@ -33,17 +33,27 @@ const ItemContainer = styled.div` align-items: center; padding: 10px 10px 10px 30px; position: relative; - font-size: 1.5em; - text-decoration: ${(props) => (props.completed ? 'line-through' : 'none')}; &::after { content: ''; /* 가상 요소에 내용이 없음을 명시 */ - position: absolute; /* 상위 요소인 ItemContainer에 대해 절대 위치 설정 */ - bottom: 0; /* 하단에 위치 */ - left: 0; /* 왼쪽 시작점 */ + position: absolute; + bottom: 0; + left: 0; width: calc(100% - 30px); /* 전체 너비에서 20px만큼 줄임 */ - margin-left: 30px; /* 왼쪽 여백 설정 */ - border-bottom: 1px solid #ccc; /* 하단 테두리 스타일 */ + margin-left: 30px; + border-bottom: 1px solid #ccc; + } +`; + +const TextContainer = styled.div` + width: 85%; + font-size: 1.2em; + text-decoration: ${(props) => (props.completed ? 'line-through' : 'none')}; + word-wrap: break-word; + white-space: normal; + + @media (max-width: 768px) { + font-size: 1em; } `; @@ -65,12 +75,12 @@ const CheckButtonImg = styled.img` `; export default function List({ toDoData, setToDoData }) { - const handleClick = (id) => { + const handleDelete = (id) => { let newToDoData = toDoData.filter((item) => item.id !== id); setToDoData(newToDoData); - localStorage.setItem('toDoData', JSON.stringify(newToDoData)); }; + // 할 일 목록의 완료 상태 변경하는 함수 const handleComplete = (id) => { let newToDoData = toDoData.map((item) => { if (item.id === id) { @@ -79,14 +89,13 @@ export default function List({ toDoData, setToDoData }) { return item; }); setToDoData(newToDoData); - localStorage.setItem('toDoData', JSON.stringify(newToDoData)); }; return ( {toDoData.map((item) => ( - - {item.title} + + {item.title} handleClick(item.id)} + onClick={() => handleDelete(item.id)} /> diff --git a/src/components/TodayDate.js b/src/components/TodayDate.js index 02f39d6..d1ff136 100644 --- a/src/components/TodayDate.js +++ b/src/components/TodayDate.js @@ -11,7 +11,6 @@ export default function TodayDate() { '금요일', '토요일', ]; - const date = `${today.getFullYear()}-${(today.getMonth() + 1) .toString() .padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`; diff --git a/src/pages/ToDoList.js b/src/pages/ToDoList.js index 010ea82..c1b6882 100644 --- a/src/pages/ToDoList.js +++ b/src/pages/ToDoList.js @@ -19,8 +19,12 @@ const Container = styled.div` const Title = styled.h1` margin: 10px 0; - font-size: 2.7em; + font-size: 2.5em; font-weight: bold; + + @media (max-width: 768px) { + font-size: 2em; + } `; const Subtitle = styled.div` @@ -29,11 +33,15 @@ const Subtitle = styled.div` align-items: center; h3 { - font-size: 1.8em; + font-size: 1.3em; font-weight: 700; margin-top: 20px; margin-bottom: 10px; } + + @media (max-width: 768px) { + font-size: 1em; + } `; export default function ToDoList() { @@ -43,6 +51,12 @@ export default function ToDoList() { let totalItems = toDoData.length; let completedItems = toDoData.filter((item) => item.completed).length; + // toDoData 수정 시 로컬 스토리지에 저장 + useEffect(() => { + localStorage.setItem('toDoData', JSON.stringify(toDoData)); + }, [toDoData]); + + // 새로운 할 일 객체를 생성하고 현재 할 일 목록에 추가하는 함수로 InputForm 컴포넌트에 prop으로 전달 const addToDo = (newToDoTitle) => { let newToDo = { id: Date.now(), @@ -51,7 +65,6 @@ export default function ToDoList() { }; setToDoData((prev) => [...prev, newToDo]); // 새로운 할 일을 할 일 목록에 추가 - localStorage.setItem('toDoData', JSON.stringify([...toDoData, newToDo])); }; return ( From 0d5baa11b643bc1098d29785983f72e0be1f982a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 22 Mar 2024 16:00:53 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat:=20React.memo=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/InputForm.js | 5 ++- src/components/List.js | 90 ++++++++++++++++++++++++++++--------- src/components/TodayDate.js | 1 + src/pages/ToDoList.js | 5 ++- 4 files changed, 77 insertions(+), 24 deletions(-) diff --git a/src/components/InputForm.js b/src/components/InputForm.js index f746389..65d1fd9 100644 --- a/src/components/InputForm.js +++ b/src/components/InputForm.js @@ -3,14 +3,14 @@ import styled from 'styled-components'; import plusImg from '../assets/img/plus.svg'; const InputStyle = styled.input` - background-color: #d9d9d9d9; + background-color: #d9d9d9; border: none; outline: none; border-radius: 10px; padding: 5px 5px 5px 30px; /*텍스트와 이미지 겹침 방지를 위한 패딩 조정*/ width: 100%; line-height: 20px; - cursor: pointer; + cursor: text; background-image: url('${plusImg}'); background-position: 5px center; /*이미지 위치 조정*/ @@ -19,6 +19,7 @@ const InputStyle = styled.input` `; export default function InputForm({ value, setValue, onAddToDo }) { + console.log('InputForm component'); //입력 필드의 입력값 상태 업데이트하는 함수 const handleInput = (event) => { setValue(event.target.value); diff --git a/src/components/List.js b/src/components/List.js index 3f082af..7f2ca4f 100644 --- a/src/components/List.js +++ b/src/components/List.js @@ -1,5 +1,5 @@ -import React from 'react'; -import styled from 'styled-components'; +import React, { useState } from 'react'; +import styled, { keyframes } from 'styled-components'; import deleteBtn from '../assets/img/trash.svg'; import grayCheckBtn from '../assets/img/grayCheck.svg'; import greenCheckBtn from '../assets/img/greenCheck.svg'; @@ -13,6 +13,7 @@ const ToDoBlock = styled.div` flex-direction: column; justify-content: flex-start; overflow-y: auto; + overflow-x: hidden; /* 웹킷 기반 브라우저를 위한 스타일 */ &::-webkit-scrollbar { @@ -33,32 +34,54 @@ const ItemContainer = styled.div` align-items: center; padding: 10px 10px 10px 30px; position: relative; + min-height: 40px; /* 버튼 컨테이너 상관없이 일관된 높이 */ &::after { content: ''; /* 가상 요소에 내용이 없음을 명시 */ position: absolute; bottom: 0; left: 0; - width: calc(100% - 30px); /* 전체 너비에서 20px만큼 줄임 */ + width: calc(100% - 30px); /* 전체 너비에서 30px만큼 줄임 */ margin-left: 30px; border-bottom: 1px solid #ccc; } + + css @media (max-width: 768px) { + padding: 5px; 5px; 5px; 15px; + &::after { + width: calc(100% - 20px); + margin-left: 20px; + } + } `; const TextContainer = styled.div` - width: 85%; - font-size: 1.2em; + width: 100%; + font-size: 1em; text-decoration: ${(props) => (props.completed ? 'line-through' : 'none')}; word-wrap: break-word; white-space: normal; + cursor: pointer; @media (max-width: 768px) { - font-size: 1em; + font-size: 0.9em; + } +`; + +// 애니메이션 효과 +const SlideIn = keyframes` + from { + transform: translateX(100%); + } + + to { + transform: translateX(0); } `; const ButtonsContainer = styled.div` display: flex; + animation: ${SlideIn} 0.5s forwards; // 0.5초 동안 애니메이션 실행 `; const DeleteButtonImg = styled.img` @@ -74,7 +97,25 @@ const CheckButtonImg = styled.img` margin-right: 7px; `; -export default function List({ toDoData, setToDoData }) { +// React.memo를 사용하여 컴포넌트의 불필요한 렌더링을 방지 +export default React.memo(function List({ toDoData, setToDoData }) { + console.log('List component'); + const [showButtons, setShowButtons] = useState(false); + const [selectedTextId, setSelectedTextId] = useState(null); + + // 텍스트 컨테이너 클릭 핸들러 + const handleTextClick = (id) => { + if (id === selectedTextId) { + // 같은 TextContainer 클릭 시 showButton 상태 변경 + setShowButtons(!showButtons); + } else { + // 다른 TextContainer가 클릭되면, showButtons를 true로 설정하고 selectedTextId를 업데이트 + setShowButtons(true); + setSelectedTextId(id); + } + }; + + // 할 일 목록 삭제하는 함수 const handleDelete = (id) => { let newToDoData = toDoData.filter((item) => item.id !== id); setToDoData(newToDoData); @@ -95,21 +136,28 @@ export default function List({ toDoData, setToDoData }) { {toDoData.map((item) => ( - {item.title} - - handleComplete(item.id)} - /> - handleDelete(item.id)} - /> - + handleTextClick(item.id)} + > + {item.title} + + {selectedTextId === item.id && showButtons && ( + + handleComplete(item.id)} + /> + handleDelete(item.id)} + /> + + )} ))} ); -} +}); diff --git a/src/components/TodayDate.js b/src/components/TodayDate.js index d1ff136..2f035fa 100644 --- a/src/components/TodayDate.js +++ b/src/components/TodayDate.js @@ -1,6 +1,7 @@ import React from 'react'; export default function TodayDate() { + console.log('TodayDate component'); const today = new Date(); const days = [ '일요일', diff --git a/src/pages/ToDoList.js b/src/pages/ToDoList.js index c1b6882..c4526cf 100644 --- a/src/pages/ToDoList.js +++ b/src/pages/ToDoList.js @@ -40,11 +40,14 @@ const Subtitle = styled.div` } @media (max-width: 768px) { - font-size: 1em; + h3 { + font-size: 0.8em; + } } `; export default function ToDoList() { + console.log('ToDoList component'); const savedToDos = JSON.parse(localStorage.getItem('toDoData') || '[]'); // 로컬 스토리지에서 불러오기 const [toDoData, setToDoData] = useState(savedToDos); const [value, setValue] = useState(''); From 154dc6285fdf014ed8011cca5b73f608f92e1d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 22 Mar 2024 16:35:03 +0900 Subject: [PATCH 09/11] =?UTF-8?q?docs:=20readme=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 57 ++++++++++++++++--------------------- src/components/InputForm.js | 1 - src/components/List.js | 1 - src/components/TodayDate.js | 1 - src/pages/ToDoList.js | 1 - 5 files changed, 24 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index a77b4ce..eea726d 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,27 @@ # 2주차 미션: React-Todo -# 서론 - -안녕하세요 🙌🏻 19기 프론트엔드 운영진 **변지혜**입니다. +# 미션 -다들 1주차 미션 Vanilla Todo 만드시느라 수고 많으셨습니다! 1주차 미션을 통해 여러분들께서 본격적인 React 사용에 앞서 Vanilla JS로 SPA를 만들때의 불편한 점을 느끼셨을 것 이라 생각합니다. +## 배포 링크 -그리하여 이번 미션은, 1주차 스터의 미션으로 주어진 Todo list 만들기를 **React**로 리팩토링하는 것 입니다! -기존에 리액트를 잘 아시던 분들께는, 조금 더 효울적인 디자인 패턴에 대해 고민할수 있는 주차가 될 것이고, 리액트를 제대로 접해보지 못하신 분들께는 기존의 어플리케이션을 리액트로 포팅하는 과정을 통해 왜 프론트엔드 시장에 리액트가 등장하게 되었고, 리액트에서 사용하는 여러가지 방식들이 왜 바닐라에 비해 효율적인지 꺠닫는 주차가 될 것이라 생각합니다. +- [배포 링크](https://react-todo-19th.vercel.app/) -비교적 가벼운 미션인 만큼 코드를 짜는 데 있어 여러분의 **창의성**을 충분히 발휘해보시기 바랍니다. _❕작동하기만 하면 되는 것보다 같은 코드를 짜는 여러가지 방식과 패턴에 대해 많이 고민해보시고, 본인이 작성할 수 있는 가장 창의적인 방법으로 코드를 작성해주셨으면 합니다.❕_ 여러분이 미션 수행을 하는 과정에서 한 생각과 고민만큼 스터디에서 더 많은 것을 얻어가실 수 있을 거라 기대합니다! +## 기능 구현 -막히는 부분이 있더라도 우선 스스로 공부하고 찾아보면서 미션을 진행하는 방식을 권고드리지만, 미션과 관련하여 운영진의 도움이 필요하시다면 얼마든지 슬랙 Q&A 채널이나 프론트엔드 카톡방에 질문을 남겨 주세요! +기존 기능 -# 미션 +- 입력창을 통해 할 일 추가 +- 삭제 버튼 클릭으로 할 일 삭제 +- 체크 버튼 클릭으로 완료 표시, 밑줄 그어 완료 표시 +- 날짜 출력 +- 총 할 일 개수와 완료한 일 개수 출력 +- 로컬 스토리지 저장 -## 예시 +추가된 기능 -- [리액트 투두 예시](https://react-todo-18th-lemon.vercel.app/) +- 반응형 +- 버튼 컨테이너 애니메이션 효과 +- React.memo를 이용한 렌더링 최적화 ## 미션 목표 @@ -27,33 +31,20 @@ - React Hooks에 대한 기초를 이해합니다. - Styled-Components를 통한 CSS-in-JS 및 CSS Preprocessor의 사용법을 익힙니다. -## 기한 - -- 2024년 3월 22일 금요일 - ## Key Questions -- Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요? -- 미션을 진행하면서 느낀, React를 사용함으로서 얻을수 있는 장점은 무엇이었나요? -- React에서 상태란 무엇이고 어떻게 관리할 수 있을까요? -- Styled-Components 사용 후기 (CSS와 비교) +1. Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요? + Virtual DOM은 웹 개발에서 사용되는 개념으로, 실제 DOM(Document Object Model)의 가벼운 사본입니다. Virtual DOM은 메모리 내에 존재하며, 실제 DOM과 상호작용하는 대신에 애플리케이션의 UI 상태를 효율적으로 업데이트하는 데 사용됩니다. 애플리케이션의 상태가 변경될 때 이전 Virtual DOM에서 변경된 부분만 업데이트하여 실제 DOM 업데이트가 최소화되어 애플리케이션의 성능이 향상됩니다. -## 필수 요건 +2. 미션을 진행하면서 느낀, React를 사용함으로서 얻을수 있는 장점은 무엇이었나요? + UI를 독립적인 컴포넌트로 나누어 개발할 수 있다는 것이 장점으로 느껴졌습니다. 컴포넌트들은 재사용이 가능하고, 컴포넌트에서 자신이 개별적으로 상태 관리를 할 수 있어 복잡한 UI도 쉽게 관리하고 유지할 수 있습니다. -- 1주차 미션의 결과물을 그대로 React로 구현합니다. (‼️ todo / done 개수 잊지 마세요 ‼️) -- Functional Components를 사용합니다. -- React Hooks만을 사용해 상태를 관리합니다. -- (이번주는 Redux, MobX, Recoil, SWR등의 외부 상태관리 라이브러리를 사용하지 않아도 미션 수행에 지장이 없습니다.) - -## 선택 요건 - -- 기존 Todo-list에 여러분들이 추가하고 싶은 기능과 디자인을 자유롭게 추가해보세요. - -## 로컬 실행방법 - ---- +3. React에서 상태란 무엇이고 어떻게 관리할 수 있을까요? + 상태란 컴포넌트의 상태를 저장하고 관리하는 데이터로 컴포넌트가 동적으로 데이터를 처리하고 UI를 적절히 업데이트할 수 있게 해줍니다. + 클래스 컴포넌트 내에서는 this.state를 사용하여 상태를 초기화하고, this.setState 메소드를 사용해 상태를 업데이트합니다. React 16.8부터 도입된 Hooks는 class없이 state를 사용할 수 있게 하여 함수 컴포넌트에서도 상태 관리를 가능하게 합니다. -`npm start` : 로컬에서 react application을 자동으로 리로드하여 실행시켜줍니다. +4. Styled-Components 사용 후기 (CSS와 비교) + 별도의 css 파일 없이 js 파일 안에서 컴포넌트 단위별로 스타일을 지정해줄 수 있다는 점에서 편리함을 느꼈습니다. 또한 props나 상태에 따라 스타일을 쉽게 처리할 수 있어 편리했습니다. # 링크 및 참고자료 diff --git a/src/components/InputForm.js b/src/components/InputForm.js index 65d1fd9..4e6339c 100644 --- a/src/components/InputForm.js +++ b/src/components/InputForm.js @@ -19,7 +19,6 @@ const InputStyle = styled.input` `; export default function InputForm({ value, setValue, onAddToDo }) { - console.log('InputForm component'); //입력 필드의 입력값 상태 업데이트하는 함수 const handleInput = (event) => { setValue(event.target.value); diff --git a/src/components/List.js b/src/components/List.js index 7f2ca4f..1d13f39 100644 --- a/src/components/List.js +++ b/src/components/List.js @@ -99,7 +99,6 @@ const CheckButtonImg = styled.img` // React.memo를 사용하여 컴포넌트의 불필요한 렌더링을 방지 export default React.memo(function List({ toDoData, setToDoData }) { - console.log('List component'); const [showButtons, setShowButtons] = useState(false); const [selectedTextId, setSelectedTextId] = useState(null); diff --git a/src/components/TodayDate.js b/src/components/TodayDate.js index 2f035fa..d1ff136 100644 --- a/src/components/TodayDate.js +++ b/src/components/TodayDate.js @@ -1,7 +1,6 @@ import React from 'react'; export default function TodayDate() { - console.log('TodayDate component'); const today = new Date(); const days = [ '일요일', diff --git a/src/pages/ToDoList.js b/src/pages/ToDoList.js index c4526cf..cdd86d2 100644 --- a/src/pages/ToDoList.js +++ b/src/pages/ToDoList.js @@ -47,7 +47,6 @@ const Subtitle = styled.div` `; export default function ToDoList() { - console.log('ToDoList component'); const savedToDos = JSON.parse(localStorage.getItem('toDoData') || '[]'); // 로컬 스토리지에서 불러오기 const [toDoData, setToDoData] = useState(savedToDos); const [value, setValue] = useState(''); From 0db396e35c711ba3a7f403383b5cb42bffe2532d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 22 Mar 2024 16:55:48 +0900 Subject: [PATCH 10/11] =?UTF-8?q?docs:=20readme=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eea726d..d4862d9 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,23 @@ 추가된 기능 -- 반응형 +- 반응형 구현 - 버튼 컨테이너 애니메이션 효과 - React.memo를 이용한 렌더링 최적화 +## 느낀 점 + +안녕하세요, 19기 프론트 안혜연입니다! + +1주차 과제에서 추가로 몇몇 기능을 넣으려고 했으나,, 일단 필수 기능 위주로 구현했습니다. +추가로 스타일을 반응형으로 지정해주려고 했습니다. 평소 자주 쓰는 px 단위가 익숙해서 반응형 구현에 있어 어렵게 느껴졌습니다.. +다른 분들은 어떤 단위 위주로 사용하는지,, 피그마로 디자인을 볼 때 px 단위로 나타나는데 직접 다른 단위로 계산해서 사용하는지,, +반응형 구현은 미디어 쿼리로 지정해주시는지 궁금합니다.!! +그리고 Tailwind CSS를 사용하면 반응형 디자인 구현이 훨씬 편리하다는 것 같아 Tailwind CSS도 공부해서 사용해보고 싶습니다. + +할 일을 입력할 때마다 할 일 목록들이 불필요하게 렌더링되어 React.memo를 사용해 이를 방지하였습니다. +또 할 일 텍스트를 선택하면 버튼 컨테이너가 나오게끔 슬라이드 효과를 주고 싶어 애니메이션 효과를 지정해주었습니다. + ## 미션 목표 - VSCode, Prettier를 이용하여 개발환경을 관리합니다. @@ -44,7 +57,7 @@ 클래스 컴포넌트 내에서는 this.state를 사용하여 상태를 초기화하고, this.setState 메소드를 사용해 상태를 업데이트합니다. React 16.8부터 도입된 Hooks는 class없이 state를 사용할 수 있게 하여 함수 컴포넌트에서도 상태 관리를 가능하게 합니다. 4. Styled-Components 사용 후기 (CSS와 비교) - 별도의 css 파일 없이 js 파일 안에서 컴포넌트 단위별로 스타일을 지정해줄 수 있다는 점에서 편리함을 느꼈습니다. 또한 props나 상태에 따라 스타일을 쉽게 처리할 수 있어 편리했습니다. + css 파일로 스타일을 지정하는 것보다 Styled-Components가 더 편리하게 느껴졌습니다. 별도의 css 파일 없이 하나의 js 파일 안에서 컴포넌트 단위별로 스타일을 지정해줄 수 있다는 점에서 스타일을 관리하기 더 쉬웠습니다. 또한 props나 상태에 따라 해당 스타일을 쉽게 처리할 수 있어 편리했습니다. # 링크 및 참고자료 From f4c872ff27e52696a366322582e010a17def6cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Sat, 30 Mar 2024 23:47:14 +0900 Subject: [PATCH 11/11] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html | 2 +- src/App.js | 1 - src/components/InputForm.js | 40 +++++++++++++++++++++++++------------ src/components/List.js | 28 ++++++++++++++------------ src/components/TodayDate.js | 2 -- src/index.js | 8 ++++---- src/pages/ToDoList.js | 9 ++++----- 7 files changed, 51 insertions(+), 39 deletions(-) diff --git a/public/index.html b/public/index.html index c37e2f5..0e7ed0c 100644 --- a/public/index.html +++ b/public/index.html @@ -1,5 +1,5 @@ - + diff --git a/src/App.js b/src/App.js index 60ac184..9d43aa0 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,3 @@ -import React from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import ToDoList from './pages/ToDoList'; import GlobalStyles from '../src/GlobalStyles'; diff --git a/src/components/InputForm.js b/src/components/InputForm.js index 4e6339c..5f61d6c 100644 --- a/src/components/InputForm.js +++ b/src/components/InputForm.js @@ -1,24 +1,35 @@ -import React from 'react'; +import { useState } from 'react'; import styled from 'styled-components'; import plusImg from '../assets/img/plus.svg'; +const InputContainer = styled.div` + position: relative; + width: 100%; +`; + const InputStyle = styled.input` background-color: #d9d9d9; border: none; outline: none; border-radius: 10px; - padding: 5px 5px 5px 30px; /*텍스트와 이미지 겹침 방지를 위한 패딩 조정*/ + padding: 5px 5px 5px 30px; // 텍스트와 이미지 겹침 방지를 위한 패딩 조정 width: 100%; line-height: 20px; cursor: text; +`; - background-image: url('${plusImg}'); - background-position: 5px center; /*이미지 위치 조정*/ - background-repeat: no-repeat; - background-size: 20px 20px; +const PlusImg = styled.img` + position: absolute; + left: 5px; // 이미지 위치 조정 + top: 50%; + transform: translateY(-50%); + width: 20px; + height: 20px; `; -export default function InputForm({ value, setValue, onAddToDo }) { +export default function InputForm({ onAddToDo }) { + const [value, setValue] = useState(''); + //입력 필드의 입력값 상태 업데이트하는 함수 const handleInput = (event) => { setValue(event.target.value); @@ -37,12 +48,15 @@ export default function InputForm({ value, setValue, onAddToDo }) { return ( - + + + + ); } diff --git a/src/components/List.js b/src/components/List.js index 1d13f39..acbf671 100644 --- a/src/components/List.js +++ b/src/components/List.js @@ -34,19 +34,21 @@ const ItemContainer = styled.div` align-items: center; padding: 10px 10px 10px 30px; position: relative; - min-height: 40px; /* 버튼 컨테이너 상관없이 일관된 높이 */ + min-height: 40px; // 버튼 컨테이너 상관없이 일관된 높이 + overflow-y: auto; // 각 항목별 스크롤바 생성 + overflow-x: hidden; &::after { - content: ''; /* 가상 요소에 내용이 없음을 명시 */ + content: ''; // 가상 요소에 내용이 없음을 명시 position: absolute; bottom: 0; left: 0; - width: calc(100% - 30px); /* 전체 너비에서 30px만큼 줄임 */ + width: calc(100% - 30px); // 전체 너비에서 30px만큼 줄임 margin-left: 30px; border-bottom: 1px solid #ccc; } - css @media (max-width: 768px) { + @media (max-width: 768px) { padding: 5px; 5px; 5px; 15px; &::after { width: calc(100% - 20px); @@ -116,19 +118,17 @@ export default React.memo(function List({ toDoData, setToDoData }) { // 할 일 목록 삭제하는 함수 const handleDelete = (id) => { - let newToDoData = toDoData.filter((item) => item.id !== id); + const newToDoData = toDoData.filter((item) => item.id !== id); setToDoData(newToDoData); }; // 할 일 목록의 완료 상태 변경하는 함수 const handleComplete = (id) => { - let newToDoData = toDoData.map((item) => { - if (item.id === id) { - return { ...item, completed: !item.completed }; - } - return item; - }); - setToDoData(newToDoData); + setToDoData((prev) => + prev.map((item) => + item.id === id ? { ...item, completed: !item.completed } : item + ) + ); }; return ( @@ -136,7 +136,7 @@ export default React.memo(function List({ toDoData, setToDoData }) { {toDoData.map((item) => ( handleTextClick(item.id)} > {item.title} @@ -144,11 +144,13 @@ export default React.memo(function List({ toDoData, setToDoData }) { {selectedTextId === item.id && showButtons && ( handleComplete(item.id)} /> handleDelete(item.id)} diff --git a/src/components/TodayDate.js b/src/components/TodayDate.js index d1ff136..d2fa38e 100644 --- a/src/components/TodayDate.js +++ b/src/components/TodayDate.js @@ -1,5 +1,3 @@ -import React from 'react'; - export default function TodayDate() { const today = new Date(); const days = [ diff --git a/src/index.js b/src/index.js index 8db5acb..593edf1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,8 @@ -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'; -const root = ReactDOM.createRoot(document.getElementById("root")); +const root = ReactDOM.createRoot(document.getElementById('root')); root.render( diff --git a/src/pages/ToDoList.js b/src/pages/ToDoList.js index cdd86d2..e40bd95 100644 --- a/src/pages/ToDoList.js +++ b/src/pages/ToDoList.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import styled from 'styled-components'; import InputForm from '../components/InputForm'; import List from '../components/List'; @@ -49,9 +49,8 @@ const Subtitle = styled.div` export default function ToDoList() { const savedToDos = JSON.parse(localStorage.getItem('toDoData') || '[]'); // 로컬 스토리지에서 불러오기 const [toDoData, setToDoData] = useState(savedToDos); - const [value, setValue] = useState(''); - let totalItems = toDoData.length; - let completedItems = toDoData.filter((item) => item.completed).length; + const totalItems = toDoData.length; + const completedItems = toDoData.filter((item) => item.completed).length; // toDoData 수정 시 로컬 스토리지에 저장 useEffect(() => { @@ -72,7 +71,7 @@ export default function ToDoList() { return ( To-Do-List - +