diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..6c4516e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true + } \ No newline at end of file diff --git a/README.md b/README.md index a77b4ce..2db285a 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,66 @@ -# 2์ฃผ์ฐจ ๋ฏธ์…˜: React-Todo +## ๐Ÿ”—ย ๋ฐฐํฌ ๋งํฌ -# ์„œ๋ก  +[https://react-todo-19th-psi.vercel.app/](https://react-todo-19th-psi.vercel.app/) -์•ˆ๋…•ํ•˜์„ธ์š” ๐Ÿ™Œ๐Ÿป 19๊ธฐ ํ”„๋ก ํŠธ์—”๋“œ ์šด์˜์ง„ **๋ณ€์ง€ํ˜œ**์ž…๋‹ˆ๋‹ค. +## ๐Ÿ•ถ๏ธย ๊ธฐ๋Šฅ๊ตฌํ˜„ -๋‹ค๋“ค 1์ฃผ์ฐจ ๋ฏธ์…˜ Vanilla Todo ๋งŒ๋“œ์‹œ๋А๋ผ ์ˆ˜๊ณ  ๋งŽ์œผ์…จ์Šต๋‹ˆ๋‹ค! 1์ฃผ์ฐจ ๋ฏธ์…˜์„ ํ†ตํ•ด ์—ฌ๋Ÿฌ๋ถ„๋“ค๊ป˜์„œ ๋ณธ๊ฒฉ์ ์ธ React ์‚ฌ์šฉ์— ์•ž์„œ Vanilla JS๋กœ SPA๋ฅผ ๋งŒ๋“ค๋•Œ์˜ ๋ถˆํŽธํ•œ ์ ์„ ๋А๋ผ์…จ์„ ๊ฒƒ ์ด๋ผ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. +![screenshot](./public/img/screenshot.png) -๊ทธ๋ฆฌํ•˜์—ฌ ์ด๋ฒˆ ๋ฏธ์…˜์€, 1์ฃผ์ฐจ ์Šคํ„ฐ์˜ ๋ฏธ์…˜์œผ๋กœ ์ฃผ์–ด์ง„ Todo list ๋งŒ๋“ค๊ธฐ๋ฅผ **React**๋กœ ๋ฆฌํŒฉํ† ๋งํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค! -๊ธฐ์กด์— ๋ฆฌ์•กํŠธ๋ฅผ ์ž˜ ์•„์‹œ๋˜ ๋ถ„๋“ค๊ป˜๋Š”, ์กฐ๊ธˆ ๋” ํšจ์šธ์ ์ธ ๋””์ž์ธ ํŒจํ„ด์— ๋Œ€ํ•ด ๊ณ ๋ฏผํ• ์ˆ˜ ์žˆ๋Š” ์ฃผ์ฐจ๊ฐ€ ๋  ๊ฒƒ์ด๊ณ , ๋ฆฌ์•กํŠธ๋ฅผ ์ œ๋Œ€๋กœ ์ ‘ํ•ด๋ณด์ง€ ๋ชปํ•˜์‹  ๋ถ„๋“ค๊ป˜๋Š” ๊ธฐ์กด์˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฆฌ์•กํŠธ๋กœ ํฌํŒ…ํ•˜๋Š” ๊ณผ์ •์„ ํ†ตํ•ด ์™œ ํ”„๋ก ํŠธ์—”๋“œ ์‹œ์žฅ์— ๋ฆฌ์•กํŠธ๊ฐ€ ๋“ฑ์žฅํ•˜๊ฒŒ ๋˜์—ˆ๊ณ , ๋ฆฌ์•กํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ์‹๋“ค์ด ์™œ ๋ฐ”๋‹๋ผ์— ๋น„ํ•ด ํšจ์œจ์ ์ธ์ง€ ๊บ ๋‹ซ๋Š” ์ฃผ์ฐจ๊ฐ€ ๋  ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. +- todo ์ถ”๊ฐ€ํ•˜๊ธฐ +- local storage ๊ตฌํ˜„ +- check๋ฒ„ํŠผ ํด๋ฆญ์‹œ todo โ†” done์œผ๋กœ ์ด๋™ +- delete๋ฒ„ํŠผ ํด๋ฆญ์‹œ local storage์—์„œ ์‚ญ์ œ +- text overflow ๊ด€๋ฆฌ +- scroll ๊ฐ€๋Šฅ -๋น„๊ต์  ๊ฐ€๋ฒผ์šด ๋ฏธ์…˜์ธ ๋งŒํผ ์ฝ”๋“œ๋ฅผ ์งœ๋Š” ๋ฐ ์žˆ์–ด ์—ฌ๋Ÿฌ๋ถ„์˜ **์ฐฝ์˜์„ฑ**์„ ์ถฉ๋ถ„ํžˆ ๋ฐœํœ˜ํ•ด๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. _โ•์ž‘๋™ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๋Š” ๊ฒƒ๋ณด๋‹ค ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์งœ๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ์‹๊ณผ ํŒจํ„ด์— ๋Œ€ํ•ด ๋งŽ์ด ๊ณ ๋ฏผํ•ด๋ณด์‹œ๊ณ , ๋ณธ์ธ์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ์ฐฝ์˜์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์…จ์œผ๋ฉด ํ•ฉ๋‹ˆ๋‹ค.โ•_ ์—ฌ๋Ÿฌ๋ถ„์ด ๋ฏธ์…˜ ์ˆ˜ํ–‰์„ ํ•˜๋Š” ๊ณผ์ •์—์„œ ํ•œ ์ƒ๊ฐ๊ณผ ๊ณ ๋ฏผ๋งŒํผ ์Šคํ„ฐ๋””์—์„œ ๋” ๋งŽ์€ ๊ฒƒ์„ ์–ป์–ด๊ฐ€์‹ค ์ˆ˜ ์žˆ์„ ๊ฑฐ๋ผ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค! +## ๐Ÿ‘ฉโ€๐Ÿ’ปย ๋А๋‚€์  -๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋”๋ผ๋„ ์šฐ์„  ์Šค์Šค๋กœ ๊ณต๋ถ€ํ•˜๊ณ  ์ฐพ์•„๋ณด๋ฉด์„œ ๋ฏธ์…˜์„ ์ง„ํ–‰ํ•˜๋Š” ๋ฐฉ์‹์„ ๊ถŒ๊ณ ๋“œ๋ฆฌ์ง€๋งŒ, ๋ฏธ์…˜๊ณผ ๊ด€๋ จํ•˜์—ฌ ์šด์˜์ง„์˜ ๋„์›€์ด ํ•„์š”ํ•˜์‹œ๋‹ค๋ฉด ์–ผ๋งˆ๋“ ์ง€ ์Šฌ๋ž™ Q&A ์ฑ„๋„์ด๋‚˜ ํ”„๋ก ํŠธ์—”๋“œ ์นดํ†ก๋ฐฉ์— ์งˆ๋ฌธ์„ ๋‚จ๊ฒจ ์ฃผ์„ธ์š”! +์ง€๋‚œ ๋ฏธ์…˜์—์„œ local storage๋ฅผ ํฌํ•จํ•ด์„œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ตฌํ˜„ํ•˜์ง€ ๋ชปํ•ด ์•„์‰ฌ์› ๋˜ ์ ์„ ๋งŽ์ด ๋ณด์™„ํ•˜๊ณ ์ž ๋…ธ๋ ฅํ–ˆ์Šต๋‹ˆ๋‹ค..! local storage๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋‹ค ๋ณด๋‹ˆ๊นŒ ๋ฐฐ์—ด ํ˜•ํƒœ๋ฅผ ๊ฐ€๊ณตํ•˜๋Š” ๊ฒƒ์ด ๋งŽ์ด ์–ด๋ ค์›Œ์„œ ํ—ค๋งค๋‹ค ๋ณด๋‹ˆ ์‹œ๊ฐ„์ด ๋ถ€์กฑํ•ด์„œ ๋‹ค๋ฅธ ๋ถ„๋“ค์˜ ์ฝ”๋“œ๋ฅผ ๋งŽ์ด ์ฐธ๊ณ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ์ „ํžˆ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์— ๋” ์ง‘์ค‘ ํ•ด์•ผ๊ฒ ๋‹ค๊ณ  ๋‹ค์งํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌ์•กํŠธ๊ฐ€ ๋” ํŽธํ• ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ hook๊ณผ ์นœํ•ด์ง€์ง€ ๋ชปํ•ด์„œ์ธ์ง€ ์˜คํžˆ๋ ค ๋” ์–ด๋ ค์› ๋˜ ๋ฏธ์…˜์ด์—ˆ์Šต๋‹ˆ๋‹ค..๐Ÿฅน -# ๋ฏธ์…˜ +## ๐Ÿ‘ปย ์–ด๋ ค์› ๋˜์  -## ์˜ˆ์‹œ +1. deleteTodo() ๊ฐ€ toggleTodo() ์ดํ›„์— ๋ถˆ๋ ค์˜ค๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚จ +deleteTodo ํ•จ์ˆ˜๊ฐ€ filter๋ฅผ ํ†ตํ•ด ์ƒˆ๋กญ๊ฒŒ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ด ํ•จ์ˆ˜๊ฐ€ ๋จผ์ € ์˜ค๋ฉด undefined๊ฐ€ ๋œธ. +๋”ฐ๋ผ์„œ useEffect๋ฅผ ํ†ตํ•ด ์ง์ ‘ ์ œ์–ดํ•ด ์ค„ ์ˆ˜ ์žˆ์œผ๋ฉด ๋” ์ข‹์•˜์„๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ•˜์ง€๋งŒ, ์ •ํ™•ํ•œ ๊ตฌํ˜„์€ ํ•˜์ง€ ๋ชปํ•ด ์ƒ๊ฐ์„ ๋” ํ•ด๋ณด์•„์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค. ๋ฐฐ์—ด ์ž์ฒด๋ฅผ todolist์— ๋“ค์–ด๊ฐˆ ๊ฒƒ๊ณผ donelist์— ๋“ค์–ด๊ฐˆ๊ฒƒ์œผ๋กœ ๋‚˜๋ˆ„์–ด์„œ ์•„์˜ˆ ๋ถ„๋ฆฌ๋œ ๋ฐฐ์—ด์„ ๋งŒ๋“œ๋Š” ๊ฒƒ๋„ ๊ณ ๋ คํ•ด ๋ดค์ง€๋งŒ, ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ํฌ๊ธฐ ์ž์ฒด๊ฐ€ ์ž‘์•„์„œ ํšจ์œจ์ ์ด์ง€ ์•Š๋‹ค๊ณ  ์ƒ๊ฐํ•จ. +2. deleteTodoํ•จ์ˆ˜์—์„œ ์ง์ ‘ ๋ฐฐ์—ด ์ธ๋ฑ์Šค๋ฅผ ์ฐพ์•„์„œ sliceํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ๊ณ ๋ฏผํ•ด๋ณด๊ณ  ์ง์ ‘ ๊ตฌํ˜„ํ•ด ๋ณด์•˜์ง€๋งŒ, ์ธ๋ฑ์Šค๋กœ ์ฐพ๊ฒŒ๋˜๋ฉด ๋งค๋ฒˆ ๋งˆ์šดํŒ… ๋  ๋•Œ ๋งˆ๋‹ค id ๊ฐ’์ด ์ผ์น˜ํ•˜๋Š” ๊ฒƒ์„ ์ฐพ๊ณ , ๋˜ ์ธ๋ฑ์Šค ๊ฐ’์„ ๋งค๋ฒˆ ๊ณ„์‚ฐํ•˜๊ฒŒ ๋˜์–ด ์—ฌ๋Ÿฌ๋ฒˆ์˜ ์ˆœ์ฐจํƒ์ƒ‰์„ ํ•˜๊ฒŒ๋˜๋ฏ€๋กœ ํšจ์œจ์„ฑ์ด ๋–จ์–ด์ง„๋‹ค๊ณ  ์ƒ๊ฐํ•จ. +3. ์•„์ง props์™€ ์นœํ•ด์ง€์ง€ ๋ชปํ•ด์„œ ToDo ์ปดํฌ๋„ŒํŠธ์— ๊ฑฐ์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ๋ชฐ์•„๋„ฃ๊ณ  ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— ํ•˜๋‚˜ํ•˜๋‚˜ props๋กœ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋งŒ ์ผ์œผ๋‚˜, ์ข€๋” ํšจ์œจ์ ์ธ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์„ฑ์„ ๊ณ ๋ฏผํ•˜๊ณ  ๋ฐฐ์น˜ํ•˜์—ฌ ํ•œ ์ปดํฌ๋„ŒํŠธ์— ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์ด ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๊ฒŒ ์žฌ์‚ฌ์šฉ์„ฑ์ด ์ข‹์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด์•ผ๊ฒ ๋‹ค๊ณ  ๋ฐ˜์„ฑํ•จ. +4. ๋ง‰์ƒ html์—์„œ react๋กœ ๋„˜์–ด๊ฐ€๋‹ˆ ์ต์ˆ™ํ•˜์ง€ ์•Š์•˜๋‹ค. useState ๋ฟ ์•„๋‹ˆ๋ผ useEffect๋กœ๋„ ๊ด€๋ฆฌ ํ•  ์ˆ˜ ์žˆ์—ˆ์œผ๋ฉด ํ›จ์”ฌ ์ข‹์•˜์„ ๊ฒƒ ๊ฐ™๋‹ค. +5. ์‚ฌ์†Œํ•œ ๋ถ€๋ถ„์ด์ง€๋งŒ, ๋‚ ์งœ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ ๋งŽ์ด ์“ฐ์ด๋˜ moment.js๋Š” ์ž˜ ์•ˆ์“ฐ์ด๋Š” ์ถ”์„ธ์ด๊ณ  ํ›จ์”ฌ ๊ฐ€๋ฒผ์šด days ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. ํฌ๋งท ๋ณ€ํ™˜๋„ ์–ด๋ ค์› ๋Š”๋ฐ days๋Š” ํ•œ๊ตญ์–ด๋„ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— days github ๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ํฌ๋งทํ•˜๋ฉด ์œ ์šฉํ•˜๋‹ค. -- [๋ฆฌ์•กํŠธ ํˆฌ๋‘ ์˜ˆ์‹œ](https://react-todo-18th-lemon.vercel.app/) +## ๐Ÿ”‘ย key questions -## ๋ฏธ์…˜ ๋ชฉํ‘œ + -## Key Questions + -- 1์ฃผ์ฐจ ๋ฏธ์…˜์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ๊ทธ๋Œ€๋กœ React๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. (โ€ผ๏ธ todo / done ๊ฐœ์ˆ˜ ์žŠ์ง€ ๋งˆ์„ธ์š” โ€ผ๏ธ) -- Functional Components๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -- React Hooks๋งŒ์„ ์‚ฌ์šฉํ•ด ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. -- (์ด๋ฒˆ์ฃผ๋Š” Redux, MobX, Recoil, SWR๋“ฑ์˜ ์™ธ๋ถ€ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋ฏธ์…˜ ์ˆ˜ํ–‰์— ์ง€์žฅ์ด ์—†์Šต๋‹ˆ๋‹ค.) + ---- + diff --git a/package-lock.json b/package-lock.json index e523af2..f327cb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,15 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "dayjs": "^1.11.10", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "styled-components": "^6.1.8", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -643,9 +648,17 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, "engines": { "node": ">=6.9.0" }, @@ -1888,6 +1901,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2283,6 +2307,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 +4576,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 +6084,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 +6524,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 +6722,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", @@ -6922,6 +6995,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -15998,6 +16076,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 +16613,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 +16692,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..3bec54f 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,11 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "dayjs": "^1.11.10", "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": { @@ -34,5 +36,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11" } } diff --git a/public/img/screenshot.png b/public/img/screenshot.png new file mode 100644 index 0000000..939945b Binary files /dev/null and b/public/img/screenshot.png differ diff --git a/public/img/state.png b/public/img/state.png new file mode 100644 index 0000000..e3c7c5e Binary files /dev/null and b/public/img/state.png differ diff --git a/public/img/virtualDom.png b/public/img/virtualDom.png new file mode 100644 index 0000000..bad16d2 Binary files /dev/null and b/public/img/virtualDom.png differ diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 3b90819..0000000 --- a/src/App.js +++ /dev/null @@ -1,9 +0,0 @@ -function App() { - return ( -
-

CEOS 19๊ธฐ ํ”„๋ก ํŠธ์—”๋“œ ํŒŒ์ดํŒ…!( ยจฬฎ )ูˆโœง๐Ÿ”ฅ

-
- ); -} - -export default App; diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..f019276 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,12 @@ +import Todos from "./components/Todos"; + + +function App() { + return ( +
+ +
+ ); +} + +export default App; diff --git a/src/assets/circle-check-regular.svg b/src/assets/circle-check-regular.svg new file mode 100644 index 0000000..3b860b8 --- /dev/null +++ b/src/assets/circle-check-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/circle-check-solid.svg b/src/assets/circle-check-solid.svg new file mode 100644 index 0000000..3ff3458 --- /dev/null +++ b/src/assets/circle-check-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/circle-minus-solid.svg b/src/assets/circle-minus-solid.svg new file mode 100644 index 0000000..4910cd2 --- /dev/null +++ b/src/assets/circle-minus-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Date.jsx b/src/components/Date.jsx new file mode 100644 index 0000000..0f54680 --- /dev/null +++ b/src/components/Date.jsx @@ -0,0 +1,47 @@ +import React from 'react' +import styled from 'styled-components' +import dayjs from 'dayjs' +import 'dayjs/locale/ko' +dayjs.locale('ko') + +const DateWrapper = styled.div` + width: 100%; + height: 8.583rem; + display: flex; + flex-direction: column; + border-bottom: 0.05rem solid #968E8E; +` +const Title = styled.div` + text-align: end; + color: #968E8E; + font-weight: 200; + font-size: 1.3rem; +` +const GetDate = styled.h1` + margin: 0; + font-size: 3rem; + font-weight: bold; +` +const GetWeek = styled.h3` + margin: 0.5rem 0; + font-size: 2rem; + font-weight: normal; +` + + +function Date() { +const date = dayjs(); +const dateFormat = date.format('YYYY๋…„ MM์›” DD์ผ'); +const weekFormat = date.format('dddd') + + + return ( + + To do List + {dateFormat} + {weekFormat} + + ) +} + +export default Date \ No newline at end of file diff --git a/src/components/Lists/ListsItems.jsx b/src/components/Lists/ListsItems.jsx new file mode 100644 index 0000000..549f8bd --- /dev/null +++ b/src/components/Lists/ListsItems.jsx @@ -0,0 +1,60 @@ +import React from 'react' +import {styled, css} from 'styled-components' +import iconLineCheck from '../../assets/circle-check-regular.svg' +import iconSolidCheck from '../../assets/circle-check-solid.svg' +import iconDelete from '../../assets/circle-minus-solid.svg' + + + +const ItemsWrapper = styled.div` + background-color: #fff; + width: 90%; + margin: 0.9rem auto; + border: 0.05rem solid #CBC0C0; + border-radius: 10px; + box-shadow: 0px 4px 4px #CBC0C0; + display: flex; + flex-direction: row; +` +const TodoText = styled.div` + display: flex; + align-items: center; + flex-grow: 1; + margin: 1.2rem 0; + padding: 0 1rem; + font-size: 1.2rem; + font-weight: normal; + word-break:break-all; +` +export const button = css` + width: 1.2rem; + height: 1.2rem; + margin: auto 1rem auto 0rem; + cursor: pointer; +` + +const DeleteButton = styled.img` + ${button} +` +const CheckButton = styled.img` + ${button} +` + +function ListsItems({todo, data, deleteTodo, toggleTodo}) { + + + return ( + + {todo} + toggleTodo(data.id)} + src = {data.completed ? iconSolidCheck: iconLineCheck} + > + deleteTodo(data.id)} + src = {iconDelete}/> + + ) +} + +export default ListsItems \ No newline at end of file diff --git a/src/components/Lists/TodoInputField.jsx b/src/components/Lists/TodoInputField.jsx new file mode 100644 index 0000000..7c2b511 --- /dev/null +++ b/src/components/Lists/TodoInputField.jsx @@ -0,0 +1,78 @@ +import styled from 'styled-components' +import dayjs from 'dayjs' + +const TodoInputFieldWrapper = styled.form` + height: 3.5rem; + width: 90%; + display: flex; + flex-direction: row; + margin: 0 auto; + border-bottom: 0.1rem solid #968E8E; +` +const InputField = styled.input` + border: none; + padding: 0; + flex-grow: 1; + color: black; + font-size: 1.3rem; + font-weight: normal; + &::placeholder{ + color: black; + font-size: 1.3rem; + font-weight: normal; + } +` +const SubmitButton = styled.button` + border: none; + margin-left: auto; + width: 10%; + background-color: #fff; + color: #2F82FE; + font-size: 3rem; + font-weight: normal; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +` + +function TodoInputField({ lists, setLists, value, setValue}) { +//inputfield์— ๊ฐ’ ๋„ฃ๊ธฐ +const handleChange = (e) =>{ + setValue(e.target.value); +} + +const onSubmit = (e) => { + e.preventDefault(); + if(!value.trim()){ + alert('ํ• ์ผ์„ ์ž…๋ ฅ ํ•ด ์ฃผ์„ธ์š”') + } + getList(value); + setValue(""); +} + +//๊ฐ์ฒด ์ƒ์„ฑ +function getList(todo) { + const newList = { + id: dayjs(), + title: todo, + completed: false + } + setLists([...lists, newList]); + localStorage.setItem("lists", JSON.stringify([...lists, newList])) +} + + return ( + + + + + + ) + +} + +export default TodoInputField \ No newline at end of file diff --git a/src/components/Todos.jsx b/src/components/Todos.jsx new file mode 100644 index 0000000..1c8655b --- /dev/null +++ b/src/components/Todos.jsx @@ -0,0 +1,138 @@ +import React, { useEffect } from 'react'; +import Date from './Date'; +import styled from 'styled-components'; +import {css} from 'styled-components'; +import TodoInputField from './Lists/TodoInputField'; +import { useState } from 'react'; +import ListsItems from './Lists/ListsItems'; +//์ „์ฒด ํ‹€ ์žก๊ธฐ + +const TodoContainer = styled.div` + display: flex; + flex-direction: column; + width: 70vw; + height: 70vh; + display: flex; + margin: 10vh auto; + border: 0.05rem solid #CBC0C0; + border-radius: 25px; + box-shadow: 5px 4px 4px #CBC0C0; + padding: 2rem; +` + +//list๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ํ•˜๋‹จ๋ถ€ +export const textCenter = css` + width: 90%; + display: flex; + align-items: center; + font-size: 1.3rem; + font-weight: normal; +` + +const Wrapper = styled.div` + display: flex; + flex-direction: row; + width: 100%; + height: 54vh; +` + +const TodoLists = styled.div` + display: flex; + flex-direction: column; + width:50%; +` +const DoneLists = styled.div` + display: flex; + flex-direction: column; + width:50%; + h4{ + ${textCenter} + color: black; + margin: 0 auto; + height: 3.6rem; + } +` + +const ListsWrapper = styled.div` + flex-grow: 1; + overflow: scroll; +` +const ListsCount = styled.div` + ${textCenter} + color: #968E8E; + justify-content: center; + margin-top: 0.9rem; +` + +export default function Todos() { +//๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ๊ธฐ์กด ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ +const savedLists = localStorage.getItem("lists")? JSON.parse(localStorage.getItem("lists")):[]; +const [lists, setLists] = useState(savedLists); +const [value, setValue] = useState(""); + + +const deleteTodo = (id) => { + let deletedTodo = lists.filter((data) => data.id !== id); + setLists(deletedTodo); + localStorage.setItem("lists", JSON.stringify(deletedTodo)); + }; + +const toggleTodo = (id) => { + let toggledTodo = lists.map((data) => + data.id === id ? { ...data, completed: !data.completed } : data + ); + setLists(toggledTodo); + localStorage.setItem("lists", JSON.stringify(toggledTodo)); + }; + + +const todoCount = lists.filter((data) => !data.completed).length; +const doneCount = lists.filter((data) => data.completed).length; + + + return ( + + + + + + + {lists.map((data)=> + data.completed ? <>: + + )} + + {todoCount} lists are left + + + +

Done

+ + {lists.map((data)=> + data.completed ? + : <> + )} + + {doneCount} lists are done! way to go : ) +
+
+
+ ) +}