Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
# react-todo-list-precourse
# 🖥️ react-todo-list-precourse
카카오 테크 캠퍼스 2기 2회차 미니과제 - 할 일 목록

<br>

## ⚙️ 구현할 기능 목록

### UI 구성

<br>

### 일정 입력 기능

- [x] Enter 키나 추가 버튼을 사용하여 할 일 추가
- [x] 아무것도 입력하지 않은 경우에는 할 일을 추가 불가
- [x] 할일 목록 렌더링

<br>

### 할 일 삭제
- [x] "X" 버튼 클릭하여 삭제

<br>

### 할 일 상태 전환
- [x] 완료, 미완료 상태 전환

<br>

### 추가 기능
- [x] 미완료 일정, 완료 일정, 모든 일정 필터링
- [x] 미완료 할 일 총 개수 확인 가능
- [x] 모든 완료 일정 한 번에 삭제 가능

4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<title></title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
16 changes: 16 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<TodoProvider>
<FilterProvider>
<TodoTemplate/>
</FilterProvider>
</TodoProvider>
);
}

export default App;
31 changes: 31 additions & 0 deletions src/components/TodoInput.jsx
Original file line number Diff line number Diff line change
@@ -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(
<input
className="todo-input"
type="text"
value={inputValue}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
/>
);
}

export default TodoInput;
14 changes: 14 additions & 0 deletions src/components/TodoItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";
import "../styles/TodoItem.css";

function TodoItem({ todo, index, removeTodo, toggleTodo }) {
return (
<div className="todo" style={{ textDecoration: todo.done ? "line-through" : "none" }}>
<input type="checkbox" onClick={() => toggleTodo(index)}/>
{todo.text}
<div onClick={() => removeTodo(index)}>X</div>
</div>
);
}

export default TodoItem;
36 changes: 36 additions & 0 deletions src/components/TodoList.jsx
Original file line number Diff line number Diff line change
@@ -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(
<div className="todo-list">
{filterTodos.map((todo, index) => (
<TodoItem
key={index}
todo={todo}
index={index}
removeTodo={removeTodo}
toggleTodo={toggleTodo}
/>
))}
</div>
);
}

export default TodoList;
35 changes: 35 additions & 0 deletions src/components/TodoOption.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="footer">
{todos.length > 0 ? (
<div className="option-menu">
<div>{activeTodos} items left!</div>
<select value={filter} onChange={handleFilterChange}>
<option value="All">All</option>
<option value="Active">Active</option>
<option value="Completed">Completed</option>
</select>
<div className="removeall" onClick={removeAllTodo}>Clear completed </div>
</div>
) : (
<></>
)}
</div>
);
}

export default TodoOption;
19 changes: 19 additions & 0 deletions src/components/TodoTemplate.jsx
Original file line number Diff line number Diff line change
@@ -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(
<div className="container">
<div className="title">TO DO</div>
<TodoInput/>
<TodoList/>
<TodoOption/>
</div>
);
}

export default TodoTemplate;
16 changes: 16 additions & 0 deletions src/context/FilterContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, { createContext, useContext, useState } from "react";

const FilterContext = createContext();

const FilterProvider = ({ children }) => {
const [filter, setFilter] = useState("All");

return (
<FilterContext.Provider value={{filter, setFilter}}>
{children}
</FilterContext.Provider>
);
};


export { FilterContext, FilterProvider };
36 changes: 36 additions & 0 deletions src/context/TodosContext.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<TodoContext.Provider value={{ todos, addTodo, removeTodo, removeAllTodo, toggleTodo }}>
{children}
</TodoContext.Provider>
);
};

export { TodoContext, TodoProvider };
7 changes: 7 additions & 0 deletions src/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
body{
height: 100%;
background-color: #000;
font-family: sans-serif;
margin: 0;
padding: 0;
}
Empty file removed src/main.js
Empty file.
6 changes: 6 additions & 0 deletions src/main.jsx
Original file line number Diff line number Diff line change
@@ -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(<App />);
7 changes: 7 additions & 0 deletions src/styles/TodoInput.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.todo-input{
height: 60px;
width: 500px;
border: 2px solid #000;
margin: 10px;
font-size: 30px;
}
30 changes: 30 additions & 0 deletions src/styles/TodoItem.css
Original file line number Diff line number Diff line change
@@ -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;
}
7 changes: 7 additions & 0 deletions src/styles/TodoList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.todo-list{
display: flex;
flex-direction: column;
width: 500px;
height: 250px;
overflow-y: auto;
}
25 changes: 25 additions & 0 deletions src/styles/TodoOption.css
Original file line number Diff line number Diff line change
@@ -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;
}
20 changes: 20 additions & 0 deletions src/styles/TodoTemplate.css
Original file line number Diff line number Diff line change
@@ -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;
}