diff --git a/README.md b/README.md index 3c0710d2..0e424896 100644 --- a/README.md +++ b/README.md @@ -1 +1,21 @@ -# react-todo-list-precourse \ No newline at end of file +# react-todo-list-precourse + +TodoList: 할 일 목록 렌더링 +Header: 할 일 추가 & 입력창 초기화 +Footer: 필터 + + +App.jsx 파일 내 기능들: + +초기 할 일 List 가져오는 함수 +초기 할 일 List 설정 + +Filter 설정 +할 일 Filtered 목록 가져오는 함수 +(할 일 Filtered 목록 가져오는 함수) 호출 + +할 일 추가 함수 +할 일 완료 업데이트 함수 +할 일 삭제 함수 + +할 일 목록이 변경될 때마다 로컬에 저장 \ No newline at end of file diff --git a/index.html b/index.html index b021b5c8..b0145cb0 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,12 @@ - + - - - - - - -
- - + + + + React Todo List + + +
+ + diff --git a/src/components/App.jsx b/src/components/App.jsx new file mode 100644 index 00000000..be32de71 --- /dev/null +++ b/src/components/App.jsx @@ -0,0 +1,61 @@ +import React, { useState, useEffect } from 'react'; +import Header from './Header'; +import TodoList from './TodoList'; +import Footer from './Footer'; + +// 초기 할 일 List 가져오는 함수 +const InitTodo = () => { + return JSON.parse(localStorage.getItem('todos')) || []; +}; + +// 할 일 완료 업데이트 함수 +const updateTodo = (todos, index) => { + return todos.map((todo, i) => + i === index ? { ...todo, completed: !todo.completed } : todo + ); +}; + +// 할 일 Filtered 목록 가져오는 함수 +const filteredTodo = (todos, filter) => { + return todos.filter(todo => + filter === 'all' ? true : filter === 'completed' ? todo.completed : !todo.completed + ); +}; + +const App = () => { + const [todos, setTodos] = useState(InitTodo()); // 초기 할 일 List 설정 + const [filter, setFilter] = useState('all'); // 필터 설정 + + useEffect(() => { + localStorage.setItem('todos', JSON.stringify(todos)); // 할 일 목록이 변경될 때마다 로컬에 저장 + }, [todos]); + + // 할 일 추가 함수 + const addTodo = (text) => { + if (text.trim().length === 0) return; + setTodos([...todos, { text, completed: false }]); + }; + + // 할 일 완료 업데이트 함수 + const toTodo = (index) => { + setTodos(updateTodo(todos, index)); + }; + + // 할 일 삭제 함수 + const deleteTodo = (index) => { + setTodos(todos.filter((_, i) => i !== index)); + }; + + // (할 일 Filtered 목록 가져오는 함수) 호출 + const filteredTodos = filteredTodo(todos, filter); + + return ( +
+
+ +
+ ); +}; + +export default App; diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx new file mode 100644 index 00000000..a65e55f1 --- /dev/null +++ b/src/components/Footer.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +// 필터 함수 +const Footer = ({ setFilter, filter, totalCount }) => ( + +); + +export default Footer; diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 00000000..295cf154 --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,36 @@ +import React, { useState } from 'react'; + +const Header = ({ addTodo }) => { + const [text, setText] = useState(''); + + // 제출 함수 (할 일 추가 및 입력창 초기화) + const submit = (e) => { + e.preventDefault(); + if (text.trim().length > 0) { + addClear(text, setText); + } + }; + + // 할 일 추가 & 입력창 초기화 함수 + const addClear = (text, setText) => { + addTodo(text); + setText(''); + }; + + return ( +
+

todos

+
+ setText(e.target.value)} + /> + +
+
+ ); +}; + +export default Header; diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx new file mode 100644 index 00000000..08b2e194 --- /dev/null +++ b/src/components/TodoList.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +// 할 일 목록 렌더링 +const TodoList = ({ todos, toTodo, deleteTodo }) => ( + +); + +// 할 일 항목 컴포넌트 +const TodoItem = ({ todo, index, toTodo, deleteTodo }) => ( +
  • + toTodo(index)} /> + toTodo(index)}>{todo.text} + +
  • +); + +export default TodoList; diff --git a/src/main.css b/src/main.css new file mode 100644 index 00000000..3c037974 --- /dev/null +++ b/src/main.css @@ -0,0 +1,79 @@ +body { + background-color: black; + color: white; + font-family: 'Gothic', Arial, sans-serif; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; +} + +#root { + width: 100%; + max-width: 600px; + margin: auto; +} + +header { + text-align: center; +} + +header h1 { + font-size: 2.5em; + font-family: 'Gothic', Arial, sans-serif; +} + +input[type="text"] { + width: calc(100% - 60px); + padding: 10px; + font-size: 16px; + margin: 5px 0; + border: none; + border-radius: 4px; + box-sizing: border-box; + font-family: 'Gothic', Arial, sans-serif; +} + +button { + padding: 10px; + font-size: 16px; + margin: 5px 0; + border: none; + border-radius: 4px; + cursor: pointer; + font-family: 'Gothic', Arial, sans-serif; +} + +.todo-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + margin: 10px 0; + border: 1px solid #444; + border-radius: 4px; + background-color: #222; +} + +.todo-item.completed { + text-decoration: line-through; + color: gray; +} + +.todo-item span { + flex-grow: 1; + padding-left: 10px; + font-family: 'Gothic', Arial, sans-serif; +} + +.todo-item input[type="checkbox"] { + cursor: pointer; +} + +footer { + display: flex; + justify-content: space-between; + align-items: center; + font-family: 'Gothic', Arial, sans-serif; +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 00000000..81752c23 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,7 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './components/App'; +import './main.css'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render();