diff --git a/README.md b/README.md
index 3c0710d2..d71614da 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,36 @@
-# react-todo-list-precourse
\ No newline at end of file
+# π ν μΌ λͺ©λ‘ - λ―Έλκ³Όμ
+
+## κ³Όμ μ§ν μꡬ μ¬ν β
+
+- [x] λ―Έμ
μ ν μΌ λͺ©λ‘ μ μ₯μλ₯Ό ν¬ν¬νκ³ ν΄λ‘ νλ κ²μΌλ‘ μμνλ€.
+- [x] κΈ°λ₯μ ꡬννκΈ° μ README.md μ ꡬνν κΈ°λ₯ λͺ©λ‘μ μ λ¦¬ν΄ μΆκ°νλ€.
+- [x] Gitμ μ»€λ° λ¨μλ μ λ¨κ³μμ README.md μ μ 리ν κΈ°λ₯ λͺ©λ‘ λ¨μλ‘ μΆκ°νλ€.
+
+## π κΈ°λ₯ μꡬ μ¬ν β
+
+ν루 λλ ν μ£Όμ ν μΌ λͺ©λ‘μ μ
λ°μ΄νΈνλ ν μΌ λͺ©λ‘μ ꡬννλ€. **React λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νμ¬ μΉ μ±μΌλ‘ ꡬννλ€.**
+
+- [x] ν μΌμ μΆκ°νκ³ μμ ν μ μλ€.
+- [x] ν μΌμ μΆκ°ν λ μ¬μ©μλ Enter ν€λ μΆκ° λ²νΌμ μ¬μ©νμ¬ ν μΌμ λͺ©λ‘μ μΆκ°ν μ μμ΄μΌ νλ€.
+- [x] μ¬μ©μκ° μ무κ²λ μ
λ ₯νμ§ μμ κ²½μ°μλ ν μΌμ μΆκ°ν μ μλ€.
+- [x] ν μΌμ λͺ©λ‘μ λ³Ό μ μλ€.
+- [x] ν μΌμ μλ£ μνλ₯Ό μ νν μ μλ€.
+
+## π μ ν μꡬ μ¬ν β
+
+- [ ] νμ¬ μ§ν μ€μΈ ν μΌ, μλ£λ ν μΌ, λͺ¨λ ν μΌμ νν°λ§ν μ μλ€.
+- [x] ν΄μΌ ν μΌμ μ΄κ°μλ₯Ό νμΈν μ μλ€.
+- [ ] μλ‘κ³ μΉ¨μ νμ¬λ μ΄μ μ μμ±ν λ°μ΄ν°λ μ μ§λμ΄μΌ νλ€.
+
+## π― μ£Όμ ν΄μΌ ν νλ‘κ·Έλλ° μꡬ μ¬ν β
+
+- [x] νλ‘κ·Έλ¨ μ€νμ μμμ μ App.js μ΄λ€.
+- [x] package.json νμΌμ λ³κ²½ν μ μμΌλ©°, μ 곡λ λΌμ΄λΈλ¬λ¦¬μ μ€νμΌ λΌμ΄λΈλ¬λ¦¬ μ΄μΈμ μΈλΆ λΌμ΄λΈλ¬λ¦¬λ μ¬μ©νμ§ μμμΌ νλ€.
+- [x] νλ‘κ·Έλ¨ μ’
λ£ μ process.exit() λ₯Ό νΈμΆνμ§ μλλ€.
+- [x] indent(μΈλ΄νΈ, λ€μ¬μ°κΈ°) depthλ₯Ό 3μ΄ λμ§ μλλ‘ κ΅¬ννλ€. 2κΉμ§λ§ νμ©νλ€.
+- [x] ν¨μ(λλ λ©μλ)μ κΈΈμ΄κ° 15λΌμΈμ λμ΄κ°μ§ μλλ‘ κ΅¬ννλ€.
+
+### κ³Όμ μ μΆ μ 체ν¬λ¦¬μ€νΈ
+
+- [x] ν°λ―Έλμμ node --version μ μ€ννμ¬ Node.js λ²μ μ΄ 18.17.1 μ΄μμΈμ§ νμΈνλ€.
+- [x] npm install , npm run start λͺ
λ Ή μ
λ ₯νμ¬ ν¨ν€μ§λ₯Ό μ€μΉν ν μ€ννλ λ° λ¬Έμ κ° μμ΄μΌ νλ€.
diff --git a/index.html b/index.html
index b021b5c8..351ba609 100644
--- a/index.html
+++ b/index.html
@@ -1,12 +1,12 @@
-
-
+
+
-
-
+
+
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 00000000..973947cd
--- /dev/null
+++ b/src/App.css
@@ -0,0 +1,3 @@
+body {
+ background: #d3d3d3;
+}
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 00000000..2d83c8ec
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,21 @@
+import React from "react";
+import "./App.css";
+import TodoBox from "./components/TodoBox";
+import TodoHead from "./components/TodoHead";
+import TodoList from "./components/TodoList";
+import TodoCreate from "./components/TodoCreate";
+import { TodoProvider } from "./TodoContext";
+
+function App() {
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/TodoContext.jsx b/src/TodoContext.jsx
new file mode 100644
index 00000000..ea7560f2
--- /dev/null
+++ b/src/TodoContext.jsx
@@ -0,0 +1,70 @@
+import React, { useReducer, createContext, useContext, useRef } from "react";
+
+const initialTodos = [
+ {
+ id: 1,
+ text: "μ€μκ° μμ€ν
κ³Όμ μμ±",
+ done: true,
+ },
+ {
+ id: 2,
+ text: "μΊ‘μ€ν€ λμμΈ μ΅μ’
λ³΄κ³ μ μμ±",
+ done: true,
+ },
+ {
+ id: 3,
+ text: "곡μλ° λ°ν μλ£ μμ±",
+ done: false,
+ },
+ {
+ id: 4,
+ text: "μΉ΄ν
μΊ λ΄μ© 볡μ΅",
+ done: false,
+ },
+];
+
+function todoReducer(state, action) {
+ switch (action.type) {
+ case "CREATE":
+ return state.concat(action.todo);
+ case "TOGGLE":
+ return state.map((todo) =>
+ todo.id === action.id ? { ...todo, done: !todo.done } : todo
+ );
+ case "REMOVE":
+ return state.filter((todo) => todo.id !== action.id);
+ default:
+ throw new Error(`Unhandled action type: ${action.type}`);
+ }
+}
+
+const TodoStateContext = createContext();
+const TodoDispatchContext = createContext();
+const TodoNextIdContext = createContext();
+
+export function TodoProvider({ children }) {
+ const [state, dispatch] = useReducer(todoReducer, initialTodos);
+ const nextId = useRef(5);
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
+
+export function useTodoState() {
+ return useContext(TodoStateContext);
+}
+
+export function useTodoDispatch() {
+ return useContext(TodoDispatchContext);
+}
+
+export function useTodoNextId() {
+ return useContext(TodoNextIdContext);
+}
diff --git a/src/components/TodoBox.jsx b/src/components/TodoBox.jsx
new file mode 100644
index 00000000..0eebb760
--- /dev/null
+++ b/src/components/TodoBox.jsx
@@ -0,0 +1,8 @@
+import React from "react";
+import "../styles/TodoBox.css";
+
+function TodoBox({ children }) {
+ return {children}
;
+}
+
+export default TodoBox;
diff --git a/src/components/TodoCreate.jsx b/src/components/TodoCreate.jsx
new file mode 100644
index 00000000..2703d810
--- /dev/null
+++ b/src/components/TodoCreate.jsx
@@ -0,0 +1,57 @@
+import React, { useState } from "react";
+import "../styles/TodoCreate.css";
+import addImage from "../images/add.png";
+import { useTodoDispatch, useTodoNextId } from "../TodoContext";
+
+function TodoCreate() {
+ const [open, setOpen] = useState(false);
+ const [value, setValue] = useState("");
+ const dispatch = useTodoDispatch();
+ const nextId = useTodoNextId();
+ const onToggle = () => setOpen(!open);
+ const onChange = (e) => setValue(e.target.value);
+ const onSubmit = (e) => {
+ e.preventDefault();
+ if (value.trim().length > 0) {
+ // 곡백 μ
λ ₯ λ°©μ§
+ dispatch({
+ type: "CREATE",
+ todo: {
+ id: nextId.current,
+ text: value,
+ done: false,
+ },
+ });
+ setValue("");
+ setOpen(false);
+ nextId.current += 1;
+ }
+ };
+
+ return (
+ <>
+ {open && (
+
+
+
+ )}
+
+ >
+ );
+}
+
+export default React.memo(TodoCreate);
diff --git a/src/components/TodoHead.jsx b/src/components/TodoHead.jsx
new file mode 100644
index 00000000..95bae74c
--- /dev/null
+++ b/src/components/TodoHead.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import "../styles/TodoHead.css";
+import { useTodoState } from "../TodoContext";
+
+function TodoHead() {
+ const todos = useTodoState();
+ const undoneTasks = todos.filter((todo) => !todo.done);
+
+ const today = new Date();
+ const dateString = today.toLocaleDateString("ko-KR", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ });
+ const dayName = today.toLocaleDateString("ko-KR", { weekday: "long" });
+
+ return (
+
+
Todos
+
+ {dateString} {dayName}
+
+
λ¨μ μΌ : {undoneTasks.length}κ°
+
+ );
+}
+
+export default TodoHead;
diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx
new file mode 100644
index 00000000..ad2fbdf3
--- /dev/null
+++ b/src/components/TodoItem.jsx
@@ -0,0 +1,25 @@
+import React from "react";
+import "../styles/TodoItem.css";
+import doneImage from "../images/done.png";
+import deleteImage from "../images/delete.png";
+import { useTodoDispatch } from "../TodoContext";
+
+function TodoItem({ id, done, text }) {
+ const dispatch = useTodoDispatch();
+ const onToggle = () => dispatch({ type: "TOGGLE", id });
+ const onRemove = () => dispatch({ type: "REMOVE", id });
+ return (
+
+
+ {done &&

}
+
+
{text}
+
+

+
+
+ );
+}
+
+export default React.memo(TodoItem);
+// λ€λ₯Έ νλͺ© μ
λ°μ΄νΈ -> λΆνμν 리λ λλ§ λ°©μ§λ‘ μ±λ₯ μ΅μ ν
diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx
new file mode 100644
index 00000000..cf96d478
--- /dev/null
+++ b/src/components/TodoList.jsx
@@ -0,0 +1,22 @@
+import React from "react";
+import "../styles/TodoList.css";
+import TodoItem from "./TodoItem";
+import { useTodoState } from "../TodoContext";
+
+function TodoList() {
+ const todos = useTodoState();
+ return (
+
+ {todos.map((todo) => (
+
+ ))}
+
+ );
+}
+
+export default TodoList;
diff --git a/src/images/add.png b/src/images/add.png
new file mode 100644
index 00000000..8ce7c053
Binary files /dev/null and b/src/images/add.png differ
diff --git a/src/images/delete.png b/src/images/delete.png
new file mode 100644
index 00000000..a564a544
Binary files /dev/null and b/src/images/delete.png differ
diff --git a/src/images/done.png b/src/images/done.png
new file mode 100644
index 00000000..bdd61728
Binary files /dev/null and b/src/images/done.png differ
diff --git a/src/main.js b/src/main.js
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/main.jsx b/src/main.jsx
new file mode 100644
index 00000000..8db5acb8
--- /dev/null
+++ b/src/main.jsx
@@ -0,0 +1,10 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App";
+
+const root = ReactDOM.createRoot(document.getElementById("root"));
+root.render(
+
+
+
+);
diff --git a/src/styles/TodoBox.css b/src/styles/TodoBox.css
new file mode 100644
index 00000000..202d17ad
--- /dev/null
+++ b/src/styles/TodoBox.css
@@ -0,0 +1,13 @@
+.todo-box {
+ width: 512px;
+ height: 768px;
+ position: relative;
+ background: white;
+ border-radius: 16px;
+ box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.04);
+ margin: 0 auto;
+ margin-top: 96px;
+ margin-bottom: 32px;
+ display: flex;
+ flex-direction: column;
+}
diff --git a/src/styles/TodoCreate.css b/src/styles/TodoCreate.css
new file mode 100644
index 00000000..4d8b1d25
--- /dev/null
+++ b/src/styles/TodoCreate.css
@@ -0,0 +1,75 @@
+.circleButton {
+ background: #38d9a9;
+ z-index: 5;
+ cursor: pointer;
+ width: 80px;
+ height: 80px;
+ display: block;
+ align-items: center;
+ justify-content: center;
+ font-size: 60px;
+ position: absolute;
+ left: 50%;
+ bottom: 0px;
+ transform: translate(-50%, 50%);
+ color: white;
+ border-radius: 50%;
+ border: none;
+ outline: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: 0.125s all ease-in;
+}
+
+.circleButton:hover {
+ background: #63e6be;
+}
+
+.circleButton:active {
+ background: #20c997;
+}
+
+.circleButton.open {
+ background: #ff6b6b;
+}
+
+.circleButton.open:hover {
+ background: #ff8787;
+}
+
+.circleButton.open:active {
+ background: #fa5252;
+}
+
+.circleButton.open {
+ transform: translate(-50%, 50%) rotate(45deg);
+}
+
+.Input {
+ padding: 12px;
+ border-radius: 4px;
+ border: 1px solid rgb(254, 254, 254);
+ width: 100%;
+ outline: none;
+ font-size: 18px;
+ box-sizing: border-box;
+}
+
+.InsertFormPositioner {
+ width: 100%;
+ bottom: 0;
+ left: 0;
+ position: absolute;
+}
+
+.InsertForm {
+ background: #f8f9fa;
+ padding-left: 32px;
+ padding-top: 32px;
+ padding-right: 32px;
+ padding-bottom: 72px;
+ border-bottom-left-radius: 16px;
+ border-bottom-right-radius: 16px;
+ border-top: 1px solid #e9ecef;
+}
diff --git a/src/styles/TodoHead.css b/src/styles/TodoHead.css
new file mode 100644
index 00000000..6c2e19e8
--- /dev/null
+++ b/src/styles/TodoHead.css
@@ -0,0 +1,26 @@
+.todo-head-block {
+ padding-top: 48px;
+ padding-left: 32px;
+ padding-right: 32px;
+ padding-bottom: 24px;
+ border-bottom: 1px solid #e9ecef;
+}
+
+.todo-head-block h1 {
+ margin: 0;
+ font-size: 36px;
+ color: #343a40;
+}
+
+.todo-head-block .day {
+ margin-top: 4px;
+ color: #868e96;
+ font-size: 21px;
+}
+
+.todo-head-block .tasks-left {
+ color: #20c997;
+ font-size: 18px;
+ margin-top: 40px;
+ font-weight: bold;
+}
diff --git a/src/styles/TodoItem.css b/src/styles/TodoItem.css
new file mode 100644
index 00000000..00a4570b
--- /dev/null
+++ b/src/styles/TodoItem.css
@@ -0,0 +1,53 @@
+.remove {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #dee2e6;
+ font-size: 24px;
+ cursor: pointer;
+ display: none;
+}
+
+.todo-item-block {
+ display: flex;
+ align-items: center;
+ padding-top: 12px;
+ padding-bottom: 12px;
+}
+
+.todo-item-block:hover .remove {
+ display: initial;
+}
+
+.check-circle {
+ width: 32px;
+ height: 32px;
+ border-radius: 16px;
+ border: 1px solid #ced4da;
+ font-size: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 20px;
+ cursor: pointer;
+}
+
+.check-circle.done {
+ border: 1px solid #38d9a9;
+ color: #38d9a9;
+}
+
+.text {
+ flex: 1;
+ font-size: 21px;
+ color: #495057;
+}
+
+.text.done {
+ color: #ced4da;
+}
+
+img {
+ width: 24px;
+ height: 24px;
+}
diff --git a/src/styles/TodoList.css b/src/styles/TodoList.css
new file mode 100644
index 00000000..455f4a16
--- /dev/null
+++ b/src/styles/TodoList.css
@@ -0,0 +1,6 @@
+.todo-list {
+ flex: 1;
+ padding: 20px 32px;
+ padding-bottom: 48px;
+ overflow-y: auto;
+}