diff --git a/README.md b/README.md index 3c0710d2..fdceb95a 100644 --- a/README.md +++ b/README.md @@ -1 +1,15 @@ -# react-todo-list-precourse \ No newline at end of file +# react-todo-list-precourse + +## 기능 요구 사항 +하루 또는 한 주의 할일 목록을 업데이트 하는 할일 목록을 구현한다. +React 라이브러리를 사용하여 웹 앱으로 구현한다. +1. 할 일을 추가하고 삭제할 수 있다. + - 할 일을 추가할 때 사용자는 Enter 키나 추가 버튼을 사용하여 할 일을 목록에 추가할 수 있어야 한다. + - 사용자가 아무것도 입력하지 않은 경우에는 할일을 추가할 수 없다. +2. 할 일의 목록을 볼 수 있다. +3. 할 일의 완료 상태를 전환할 수 있다. + +### 선택 요구 사항 +1. 현재 진행 중인 할 일, 완료된 할 일, 모든 할 일을 필터링할 수 있다. +2. 해야 할 일의 총개수를 확인할 수 있다. +3. 새로고침을 하여도 이전에 작성한 데이터는 유지되어야 한다. \ No newline at end of file diff --git a/index.html b/index.html index b021b5c8..c282a61f 100644 --- a/index.html +++ b/index.html @@ -3,10 +3,10 @@ - + TodoMVC: React -
- +
+ diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 00000000..1c015500 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,6 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; +import Main from "./components/main"; + +const root = createRoot(document.getElementById("root")); +root.render(React.createElement(Main)); \ No newline at end of file diff --git a/src/components/FilterButtons.jsx b/src/components/FilterButtons.jsx new file mode 100644 index 00000000..c83fb15f --- /dev/null +++ b/src/components/FilterButtons.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +const FilterButtons = ({ setFilter }) => { + return ( +
+ + + +
+ ); +}; + +export default FilterButtons; diff --git a/src/components/TodoInput.jsx b/src/components/TodoInput.jsx new file mode 100644 index 00000000..e42b62f3 --- /dev/null +++ b/src/components/TodoInput.jsx @@ -0,0 +1,25 @@ +import React, { useState } from 'react'; + +const TodoInput = ({ addTodo }) => { + const [input, setInput] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + addTodo(input); + setInput(''); + }; + + return ( +
+ setInput(e.target.value)} + placeholder="Add a new todo" + /> + +
+ ); +}; + +export default TodoInput; diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx new file mode 100644 index 00000000..c66a9ebd --- /dev/null +++ b/src/components/TodoItem.jsx @@ -0,0 +1,19 @@ +import React from 'react'; + +const TodoItem = ({ todo, toggleTodo, deleteTodo }) => { + return ( +
  • + + + {todo.text} + + +
  • + ); +}; + +export default TodoItem; diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx new file mode 100644 index 00000000..cea0460b --- /dev/null +++ b/src/components/TodoList.jsx @@ -0,0 +1,19 @@ +import React from 'react'; +import TodoItem from './TodoItem'; + +const TodoList = ({ todos, toggleTodo, deleteTodo }) => { + return ( + + ); +}; + +export default TodoList; diff --git a/src/components/main.css b/src/components/main.css new file mode 100644 index 00000000..16d710c5 --- /dev/null +++ b/src/components/main.css @@ -0,0 +1,117 @@ +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; + margin: 0; + padding: 0; +} + +.todoapp { + background: #fff; + margin: 30px auto; + padding: 20px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1), 0 25px 50px rgba(0,0,0,0.1); + width: 300px; +} + +.header { + font-size: 24px; + text-align: center; + color: #333; +} + +.todo-input { + display: flex; + align-items: center; + margin-bottom: 20px; +} + +.todo-input input[type="text"] { + flex: 1; + padding: 10px; + border: 1px solid #ddd; + font-size: 14px; + border-radius: 4px 0 0 4px; + margin-right: -1px; +} + +.todo-input button { + padding: 10px; + border: none; + background: #5ebec1; + color: white; + cursor: pointer; + font-size: 14px; + border-radius: 0 4px 4px 0; +} + +.todo-input button:hover { + background: #3da6a9; +} + +ul { + list-style: none; + padding: 0; +} + +li { + padding: 10px; + border-bottom: 1px solid #ddd; + display: flex; + align-items: center; + justify-content: space-between; +} + +li input[type="checkbox"] { + margin-right: 10px; +} + +li span { + flex: 1; + cursor: pointer; +} + +li span.completed { + text-decoration: line-through; + color: #999; +} + +li button { + background: white; + border: none; + padding: 10px; + cursor: pointer; + color: black; +} + +li button:hover { + background: #ff4d4d; + color: white; +} + +.footer { + display: flex; + justify-content: space-between; + align-items: center; + color: #666; + font-size: 14px; + margin-top: 20px; +} + +.filter-buttons { + display: flex; + gap: 10px; +} + +.filter-buttons button { + padding: 10px; + border: none; + background: #ffffff; + border-radius: 4px 4px 4px 4px; + cursor: pointer; + font-size: 14px; +} + +.filter-buttons button:hover { + background: #ffffff; + border: 1px solid #ddd; +} diff --git a/src/components/main.jsx b/src/components/main.jsx new file mode 100644 index 00000000..b9639239 --- /dev/null +++ b/src/components/main.jsx @@ -0,0 +1,56 @@ +import React, { useState, useEffect } from 'react'; +import TodoInput from './TodoInput'; +import TodoList from './TodoList'; +import FilterButtons from './FilterButtons'; +import './main.css'; + +const Main = () => { + const [todos, setTodos] = useState(() => { + // 페이지 로드 시 로컬 스토리지에서 데이터를 가져옴 + const savedTodos = localStorage.getItem('todos'); + return savedTodos ? JSON.parse(savedTodos) : []; + }); + const [filter, setFilter] = useState('all'); + + useEffect(() => { + // todos 상태가 변경될 때마다 로컬 스토리지에 저장 + localStorage.setItem('todos', JSON.stringify(todos)); + }, [todos]); + + const addTodo = (text) => { + if (text.trim() === '') return; + setTodos([...todos, { text, completed: false }]); + }; + + const toggleTodo = (index) => { + const newTodos = [...todos]; + newTodos[index].completed = !newTodos[index].completed; + setTodos(newTodos); + }; + + const deleteTodo = (index) => { + const newTodos = todos.filter((_, i) => i !== index); + setTodos(newTodos); + }; + + const filteredTodos = todos.filter(todo => { + if (filter === 'completed') return todo.completed; + if (filter === 'active') return !todo.completed; + return true; + }); + + return ( +
    +

    Todo List

    + + +
    + {todos.filter(todo => !todo.completed).length} items left + +
    +
    + ); +}; + +export default Main; + diff --git a/src/main.js b/src/main.js deleted file mode 100644 index e69de29b..00000000