diff --git a/README.md b/README.md index 3c0710d2..e6ef7180 100644 --- a/README.md +++ b/README.md @@ -1 +1,34 @@ -# react-todo-list-precourse \ No newline at end of file +# ๐Ÿ–ฅ๏ธ react-todo-list-precourse +์นด์นด์˜ค ํ…Œํฌ ์บ ํผ์Šค 2๊ธฐ 2ํšŒ์ฐจ ๋ฏธ๋‹ˆ๊ณผ์ œ - ํ•  ์ผ ๋ชฉ๋ก + +
+ +## โš™๏ธ ๊ตฌํ˜„ํ•  ๊ธฐ๋Šฅ ๋ชฉ๋ก + +### UI ๊ตฌ์„ฑ + +
+ +### ์ผ์ • ์ž…๋ ฅ ๊ธฐ๋Šฅ + +- [x] Enter ํ‚ค๋‚˜ ์ถ”๊ฐ€ ๋ฒ„ํŠผ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•  ์ผ ์ถ”๊ฐ€ +- [x] ์•„๋ฌด๊ฒƒ๋„ ์ž…๋ ฅํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” ํ•  ์ผ์„ ์ถ”๊ฐ€ ๋ถˆ๊ฐ€ +- [x] ํ• ์ผ ๋ชฉ๋ก ๋ Œ๋”๋ง + +
+ +### ํ•  ์ผ ์‚ญ์ œ +- [x] "X" ๋ฒ„ํŠผ ํด๋ฆญํ•˜์—ฌ ์‚ญ์ œ + +
+ +### ํ•  ์ผ ์ƒํƒœ ์ „ํ™˜ +- [x] ์™„๋ฃŒ, ๋ฏธ์™„๋ฃŒ ์ƒํƒœ ์ „ํ™˜ + +
+ +### ์ถ”๊ฐ€ ๊ธฐ๋Šฅ +- [x] ๋ฏธ์™„๋ฃŒ ์ผ์ •, ์™„๋ฃŒ ์ผ์ •, ๋ชจ๋“  ์ผ์ • ํ•„ํ„ฐ๋ง +- [x] ๋ฏธ์™„๋ฃŒ ํ•  ์ผ ์ด ๊ฐœ์ˆ˜ ํ™•์ธ ๊ฐ€๋Šฅ +- [x] ๋ชจ๋“  ์™„๋ฃŒ ์ผ์ • ํ•œ ๋ฒˆ์— ์‚ญ์ œ ๊ฐ€๋Šฅ + diff --git a/index.html b/index.html index b021b5c8..82b6987d 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ -
- +
+ diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 00000000..a1967ec5 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,16 @@ +import React from "react"; +import { FilterProvider } from "./context/FilterContext"; +import { TodoProvider } from "./context/TodosContext"; +import TodoTemplate from "./components/TodoTemplate"; + +function App() { + return ( + + + + + + ); +} + +export default App; diff --git a/src/components/TodoInput.jsx b/src/components/TodoInput.jsx new file mode 100644 index 00000000..69c85979 --- /dev/null +++ b/src/components/TodoInput.jsx @@ -0,0 +1,31 @@ +import React, { useState, useContext } from "react"; +import { TodoContext } from "../context/TodosContext"; +import "../styles/TodoInput.css"; + +function TodoInput() { + const [inputValue, setInputValue] = useState(""); + const { addTodo } = useContext(TodoContext); + + const handleInputChange = (event) => { + setInputValue(event.target.value); + }; + + const handleKeyDown = (event) => { + if (event.key === "Enter" && inputValue.trim() !== "" ) { + addTodo(inputValue.trim()); + setInputValue(""); + } + }; + + return( + + ); +} + +export default TodoInput; \ No newline at end of file diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx new file mode 100644 index 00000000..a0537d50 --- /dev/null +++ b/src/components/TodoItem.jsx @@ -0,0 +1,14 @@ +import React from "react"; +import "../styles/TodoItem.css"; + +function TodoItem({ todo, index, removeTodo, toggleTodo }) { + return ( +
+ toggleTodo(index)}/> + {todo.text} +
removeTodo(index)}>X
+
+ ); +} + +export default TodoItem; diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx new file mode 100644 index 00000000..29edd2c3 --- /dev/null +++ b/src/components/TodoList.jsx @@ -0,0 +1,36 @@ +import React, { useContext } from "react"; +import { FilterContext } from "../context/FilterContext"; +import { TodoContext } from "../context/TodosContext"; +import TodoItem from "./TodoItem"; +import "../styles/TodoList.css"; + +function TodoList() { + const { todos, removeTodo, toggleTodo } = useContext(TodoContext); + const { filter } = useContext(FilterContext); + + const filterTodos = todos.filter((todo) => { + if (filter === "Active") { + return !todo.done; + } else if (filter === "Completed") { + return todo.done; + } else { + return true; + } + }); + + return( +
+ {filterTodos.map((todo, index) => ( + + ))} +
+ ); +} + +export default TodoList; \ No newline at end of file diff --git a/src/components/TodoOption.jsx b/src/components/TodoOption.jsx new file mode 100644 index 00000000..be786671 --- /dev/null +++ b/src/components/TodoOption.jsx @@ -0,0 +1,35 @@ +import React, { useContext } from "react"; +import { FilterContext } from "../context/FilterContext"; +import { TodoContext } from "../context/TodosContext"; +import "../styles/TodoOption.css"; + +function TodoOption() { + const { filter, setFilter } = useContext(FilterContext); + const { todos, removeAllTodo } = useContext(TodoContext); + + const handleFilterChange = (e) => { + setFilter(e.target.value); + }; + + const activeTodos = todos.filter((todo) =>!todo.done).length; + + return ( +
+ {todos.length > 0 ? ( +
+
{activeTodos} items left!
+ +
Clear completed
+
+ ) : ( + <> + )} +
+ ); +} + +export default TodoOption; diff --git a/src/components/TodoTemplate.jsx b/src/components/TodoTemplate.jsx new file mode 100644 index 00000000..68440e90 --- /dev/null +++ b/src/components/TodoTemplate.jsx @@ -0,0 +1,19 @@ +import React from "react"; +import TodoInput from "./TodoInput"; +import TodoList from "./TodoList"; +import TodoOption from "./TodoOption"; +import "../styles/TodoTemplate.css"; + +function TodoTemplate() { + + return( +
+
TO DO
+ + + +
+ ); +} + +export default TodoTemplate; \ No newline at end of file diff --git a/src/context/FilterContext.jsx b/src/context/FilterContext.jsx new file mode 100644 index 00000000..fa4de4e1 --- /dev/null +++ b/src/context/FilterContext.jsx @@ -0,0 +1,16 @@ +import React, { createContext, useContext, useState } from "react"; + +const FilterContext = createContext(); + +const FilterProvider = ({ children }) => { + const [filter, setFilter] = useState("All"); + + return ( + + {children} + + ); +}; + + +export { FilterContext, FilterProvider }; diff --git a/src/context/TodosContext.jsx b/src/context/TodosContext.jsx new file mode 100644 index 00000000..7da85d36 --- /dev/null +++ b/src/context/TodosContext.jsx @@ -0,0 +1,36 @@ +import React, { createContext, useContext, useState } from "react"; + +const TodoContext = createContext(); + +const TodoProvider = ({ children }) => { + const [todos, setTodos] = useState([]); + + const addTodo = (todo) => { + const newTodo = { text: todo, done: false }; + setTodos([...todos, newTodo]); + }; + + const removeTodo = (index) => { + setTodos(todos.filter((todo, i) => i !== index)); + }; + + const removeAllTodo = () => { + setTodos(todos.filter(todo => !todo.done)); + }; + + const toggleTodo = (index) => { + setTodos( + todos.map((todo, i) => + i === index ? { ...todo, done: !todo.done } : todo + ) + ); + }; + + return ( + + {children} + + ); +}; + +export { TodoContext, TodoProvider }; diff --git a/src/main.css b/src/main.css new file mode 100644 index 00000000..2e3ec449 --- /dev/null +++ b/src/main.css @@ -0,0 +1,7 @@ +body{ + height: 100%; + background-color: #000; + font-family: sans-serif; + margin: 0; + padding: 0; +} \ No newline at end of file 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..5037d53f --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,6 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./App.jsx"; +import "./main.css"; + +ReactDOM.createRoot(document.getElementById("root")).render(); \ No newline at end of file diff --git a/src/styles/TodoInput.css b/src/styles/TodoInput.css new file mode 100644 index 00000000..36ad31e5 --- /dev/null +++ b/src/styles/TodoInput.css @@ -0,0 +1,7 @@ +.todo-input{ + height: 60px; + width: 500px; + border: 2px solid #000; + margin: 10px; + font-size: 30px; +} \ No newline at end of file diff --git a/src/styles/TodoItem.css b/src/styles/TodoItem.css new file mode 100644 index 00000000..484d9232 --- /dev/null +++ b/src/styles/TodoItem.css @@ -0,0 +1,30 @@ +.todo { + position: relative; + display: flex; + justify-content: flex-start; + align-items: center; + height: 30px; + margin: 5px 0; + padding-bottom: 5px; + padding-left: 5px; + font-size: 30px; + font-weight: 500; + border-bottom: 0.5px solid grey; +} + +.todo > *:nth-child(1) { + margin-right: 10px; +} + +.todo > *:nth-child(2) { + position: absolute; + right: 8px; + border: none; + color: gray; + font-size: 15px; + border: 400; +} + +.todo > *:nth-child(2):hover { + color: #000; +} \ No newline at end of file diff --git a/src/styles/TodoList.css b/src/styles/TodoList.css new file mode 100644 index 00000000..c0973b20 --- /dev/null +++ b/src/styles/TodoList.css @@ -0,0 +1,7 @@ +.todo-list{ + display: flex; + flex-direction: column; + width: 500px; + height: 250px; + overflow-y: auto; +} \ No newline at end of file diff --git a/src/styles/TodoOption.css b/src/styles/TodoOption.css new file mode 100644 index 00000000..d26cb999 --- /dev/null +++ b/src/styles/TodoOption.css @@ -0,0 +1,25 @@ +.footer { + position: absolute; + display: flex; + justify-content: flex-end; + align-items: center; + bottom: 0; + height: 70px; + width: 700px; + font-size: 20px; + font-weight: 600; +} + +.option-menu { + display: flex; + flex-direction: row; + margin-right: 20px; +} + +.option-menu > *:nth-child(2) { + margin: 0 20px; +} + +.removeall{ + cursor: pointer; +} \ No newline at end of file diff --git a/src/styles/TodoTemplate.css b/src/styles/TodoTemplate.css new file mode 100644 index 00000000..c42bdb41 --- /dev/null +++ b/src/styles/TodoTemplate.css @@ -0,0 +1,20 @@ +.container{ + position: absolute; + display: flex; + flex-direction: column; + align-items: center; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + height: 500px; + width: 700px; + background-color: #fff; + border-radius: 10px; +} + +.title { + font-size: 40px; + font-weight: 600; + margin: 10px; + text-align: center; +} \ No newline at end of file