diff --git a/README.md b/README.md index 3c0710d2..bc6ac07a 100644 --- a/README.md +++ b/README.md @@ -1 +1,16 @@ -# react-todo-list-precourse \ No newline at end of file +# react-todo-list-precourse + +하루 또는 한 주의 목록을 업데이트하는 할 일 목록을 구현한다. + +### 할 일 추가/삭제 +- 추가할 때 사용자는 Enter나 '추가'버튼을 사용하여 할 일을 목록에 추가할 수 있어야 한다. +- 사용자가 아무것도 입력하지 않은 경우, 할 일을 추가할 수 없다. +- 할 일의 목록을 볼 수 있다. +- 할 일의 완료 상태를 전환할 수 있다. + +### 선택 요구 사항 +- 현재 진행 중인 할 일, 완료된 할 일, 모든 할 일을 필터링할 수 있다. +- 해야할 일의 총 개수를 확인할 수 있다. +- 새로고침을 하여도 이전에 작성한 데이터는 유지되어야 한다. + +### UI는 자유롭게 만드는 것이 가능하다. \ No newline at end of file diff --git a/components/AddTodo.jsx b/components/AddTodo.jsx new file mode 100644 index 00000000..b45a1149 --- /dev/null +++ b/components/AddTodo.jsx @@ -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 ( +
+
+ setValue(e.target.value)} + placeholder="What needs to be done?" + /> + +
+
+ ); +} + +export default AddTodo; diff --git a/components/TodoFilter.jsx b/components/TodoFilter.jsx new file mode 100644 index 00000000..5b444aad --- /dev/null +++ b/components/TodoFilter.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +function TodoFilter({ filter, setFilter }) { + return ( +
+ + + +
+ ); +} + +export default TodoFilter; diff --git a/components/TodoItem.jsx b/components/TodoItem.jsx new file mode 100644 index 00000000..b19b75ff --- /dev/null +++ b/components/TodoItem.jsx @@ -0,0 +1,19 @@ +import React from 'react'; + +function TodoItem({ todo, index, deleteTodo, toggleComplete }) { + return ( +
+ toggleComplete(index)} style={{ cursor: 'pointer' }}> + {todo.text} + +
+ + +
+
+ ); +} + +export default TodoItem; diff --git a/components/TodoList.jsx b/components/TodoList.jsx new file mode 100644 index 00000000..ff1833f5 --- /dev/null +++ b/components/TodoList.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import TodoItem from './TodoItem'; + +function TodoList({ todos, deleteTodo, toggleComplete }) { + return ( +
+ {todos.map((todo, index) => ( + + ))} +
+ ); +} + +export default TodoList; diff --git a/index.html b/index.html index b021b5c8..3008ae8b 100644 --- a/index.html +++ b/index.html @@ -3,10 +3,10 @@ - + Todo List -
- +
+ diff --git a/src/App.css b/src/App.css new file mode 100644 index 00000000..3244d27d --- /dev/null +++ b/src/App.css @@ -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; +} diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 00000000..3d4b99bd --- /dev/null +++ b/src/App.jsx @@ -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 ( +
+

Todo List

+ + + +
+ ); +} + +export default App; diff --git a/src/index.css b/src/index.css new file mode 100644 index 00000000..546d573d --- /dev/null +++ b/src/index.css @@ -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; +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 00000000..498a00c6 --- /dev/null +++ b/src/main.jsx @@ -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( + + + +);