Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
# react-todo-list-precourse
# react-todo-list-precourse

하루 또는 한 주의 목록을 업데이트하는 할 일 목록을 구현한다.

### 할 일 추가/삭제
- 추가할 때 사용자는 Enter나 '추가'버튼을 사용하여 할 일을 목록에 추가할 수 있어야 한다.
- 사용자가 아무것도 입력하지 않은 경우, 할 일을 추가할 수 없다.
- 할 일의 목록을 볼 수 있다.
- 할 일의 완료 상태를 전환할 수 있다.

### 선택 요구 사항
- 현재 진행 중인 할 일, 완료된 할 일, 모든 할 일을 필터링할 수 있다.
- 해야할 일의 총 개수를 확인할 수 있다.
- 새로고침을 하여도 이전에 작성한 데이터는 유지되어야 한다.

### UI는 자유롭게 만드는 것이 가능하다.
28 changes: 28 additions & 0 deletions components/AddTodo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { useState } from 'react';

function AddTodo({ addTodo }) {
const [value, setValue] = useState('');

const handleSubmit = (e) => {
e.preventDefault();
if (!value.trim()) return; // 공백 입력 방지
addTodo(value.trim()); // 공백 제거한 값 전달
setValue(''); // 입력 필드 초기화
};

return (
<form onSubmit={handleSubmit}>
<div className="input-container">
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="What needs to be done?"
/>
<button type="submit">Add Todo</button>
</div>
</form>
);
}

export default AddTodo;
13 changes: 13 additions & 0 deletions components/TodoFilter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

function TodoFilter({ filter, setFilter }) {
return (
<div>
<button onClick={() => setFilter('all')}>All</button>
<button onClick={() => setFilter('active')}>Active</button>
<button onClick={() => setFilter('completed')}>Completed</button>
</div>
);
}

export default TodoFilter;
19 changes: 19 additions & 0 deletions components/TodoItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

function TodoItem({ todo, index, deleteTodo, toggleComplete }) {
return (
<div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
<span onClick={() => toggleComplete(index)} style={{ cursor: 'pointer' }}>
{todo.text}
</span>
<div className="todo-buttons">
<button onClick={() => toggleComplete(index)}>
{todo.completed ? 'Undo' : 'Complete'}
</button>
<button onClick={() => deleteTodo(index)}>Delete</button>
</div>
</div>
);
}

export default TodoItem;
20 changes: 20 additions & 0 deletions components/TodoList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import TodoItem from './TodoItem';

function TodoList({ todos, deleteTodo, toggleComplete }) {
return (
<div>
{todos.map((todo, index) => (
<TodoItem
key={index}
index={index}
todo={todo}
deleteTodo={deleteTodo}
toggleComplete={toggleComplete}
/>
))}
</div>
);
}

export default TodoList;
6 changes: 3 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
<title>Todo List</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>
98 changes: 98 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
.App {
text-align: center;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
width: 400px;
margin: 40px auto;
font-family: Arial, sans-serif;
}

.input-container {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
}

input[type="text"] {
flex: 1;
padding: 10px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
}

button {
background-color: #5cb85c;
color: white;
border: none;
padding: 10px;
font-size: 16px;
border-radius: 0 4px 4px 0;
cursor: pointer;
transition: background-color 0.3s;
}

button:hover {
background-color: #4cae4c;
}

.todo-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #ededed;
font-size: 16px;
}

.todo-item.completed {
text-decoration: line-through;
color: #d9d9d9;
}

.todo-buttons {
display: flex;
gap: 10px;
}

.filters {
display: flex;
justify-content: center;
margin-top: 20px;
}

.filters button {
margin: 0 5px;
padding: 5px 10px;
border: 1px solid #e6e6e6;
background: none;
font-size: 14px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.3s, color 0.3s;
}

.filters button:hover {
background-color: #f0f0f0;
}

.filters button.selected {
border-color: rgba(175, 47, 47, 0.2);
color: #5cb85c;
}

.clear-completed {
background: none;
border: none;
color: #777;
cursor: pointer;
font-size: 14px;
margin-top: 20px;
}

.clear-completed:hover {
text-decoration: underline;
}
55 changes: 55 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useState, useEffect } from 'react';
import TodoList from '../components/TodoList';
import AddTodo from '../components/AddTodo';
import TodoFilter from '../components/TodoFilter';
import './App.css';

function App() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');

useEffect(() => {
const savedTodos = JSON.parse(localStorage.getItem('todos'));
if (savedTodos) {
setTodos(savedTodos);
}
}, []);

useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);

const addTodo = (text) => {
setTodos([...todos, { text, completed: false }]);
};

const deleteTodo = (index) => {
const newTodos = [...todos];
newTodos.splice(index, 1);
setTodos(newTodos);
};

const toggleComplete = (index) => {
const newTodos = [...todos];
newTodos[index].completed = !newTodos[index].completed;
setTodos(newTodos);
};

const filteredTodos = todos.filter(todo => {
if (filter === 'all') return true;
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});

return (
<div className="App">
<h1>Todo List</h1>
<AddTodo addTodo={addTodo} />
<TodoList todos={filteredTodos} deleteTodo={deleteTodo} toggleComplete={toggleComplete} />
<TodoFilter filter={filter} setFilter={setFilter} />
</div>
);
}

export default App;
29 changes: 29 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
box-sizing: border-box;
background-color: #f5f5f5;
color: #4d4d4d;
}

#root {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}

input[type="text"]::placeholder {
color: #d9d9d9;
font-style: italic;
}

input[type="text"]:focus {
outline: none;
border-color: #5cb85c;
}

input[type="text"]:focus::placeholder {
color: transparent;
}
11 changes: 11 additions & 0 deletions src/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);