diff --git a/eslint.config.js b/eslint.config.js index 238d2e4..85f281a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -29,10 +29,13 @@ export default [ ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, 'react/jsx-no-target-blank': 'off', + "no-unused-vars" : "off", 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, + ], + 'react/no-unknown-property': ['error', { ignore: ['css'] }], }, }, ] diff --git a/package.json b/package.json index 9f1bcb2..ef82f8c 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.13.3", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/src/App.jsx b/src/App.jsx index 852a9fe..4cd76c4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,57 @@ +/** @jsxImportSource @emotion/react */ +import { useState, useEffect } from 'react'; +import { css } from '@emotion/react'; +import TodoInput from './components/TodoInput'; +import TodoList from './components/TodoList'; +import TodoHeader from './components/TodoHeader'; + + +const containerStyle = css` + max-width: 400px; + margin: 100px auto; + padding: 20px; + background: linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%); + border-radius: 20px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); +`; + + function App() { - return
React Todo
; + const [todos, setTodos] = useState(() => { + // 로컬 스토리지에서 저장된 todos 가져오기 + const savedTodos = localStorage.getItem('todos'); + return savedTodos ? JSON.parse(savedTodos) : []; // 저장된 값이 있으면 파싱하여 사용, 없으면 빈 배열 + }); + + useEffect(() => { + // todos 상태가 변경될 때마다 로컬 스토리지에 저장 + localStorage.setItem('todos', JSON.stringify(todos)); + }, [todos]); + + // 새 할 일을 추가하는 함수 + const addTodo = (text) => { + setTodos(todos.concat({ text, completed: false })); // 새로운 할 일을 기존 할 일 리스트에 추가 + }; + + // 특정 인덱스의 할 일을 삭제하는 함수 + const deleteTodo = (index) => { + setTodos(todos.filter((_, i) => i !== index)); // 해당 인덱스의 할 일을 제외하고 새로운 리스트 생성 + }; + + // 특정 인덱스의 할 일의 완료 상태를 토글하는 함수 + const toggleTodo = (index) => { + setTodos(todos.map((todo, i) => + i === index ? { ...todo, completed: !todo.completed } : todo + )); // 할 일의 완료 상태를 반전시킴 + }; + + return ( +
+ + + +
+ ); } -export default App; +export default App; diff --git a/src/components/TodoButton.jsx b/src/components/TodoButton.jsx new file mode 100644 index 0000000..15e4bab --- /dev/null +++ b/src/components/TodoButton.jsx @@ -0,0 +1,23 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; + +const buttonStyle = css` + padding: 10px; + cursor: pointer; + background-color: #ff7f7f; + border: none; + border-radius: 5px; + color: white; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + margin-left: 8px; +`; + +function TodoButton({ onClick, children }) { + return ( + + ); +} + +export default TodoButton; diff --git a/src/components/TodoHeader.jsx b/src/components/TodoHeader.jsx new file mode 100644 index 0000000..8077879 --- /dev/null +++ b/src/components/TodoHeader.jsx @@ -0,0 +1,14 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; + +const titleStyle = css` + font-size: 24px; + text-align: center; + margin-bottom: 20px; +`; + +function TodoHeader() { + return

📚 TodoList

; +} + +export default TodoHeader; diff --git a/src/components/TodoInput.jsx b/src/components/TodoInput.jsx new file mode 100644 index 0000000..6714f5f --- /dev/null +++ b/src/components/TodoInput.jsx @@ -0,0 +1,57 @@ +//** @jsxImportSource @emotion/react */ +import { useState } from 'react'; +import { css } from '@emotion/react'; + +const inputContainerStyle = css` + margin-bottom: 20px; + display: flex; + justify-content: center; + align-items: center; +`; + +const inputStyle = css` + padding: 10px; + width: 200px; + margin-right: 10px; + border: none; + border-radius: 5px; + outline: none; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +`; + +const buttonStyle = css` + padding: 10px; + cursor: pointer; + background-color: #ff7f7f; + border: none; + border-radius: 5px; + color: white; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); +`; + +function TodoInput({ addTodo }) { + const [input, setInput] = useState(''); // 입력 값을 관리하기 위한 상태 선언 + + // 폼 제출 시 호출되는 함수 + const handleSubmit = (e) => { + e.preventDefault(); // 기본 폼 제출 동작을 막음 + if (!input.trim()) return; // 입력 값이 공백일 경우 아무 작업도 하지 않음 + addTodo(input); + setInput(''); + }; + + return ( +
+ setInput(e.target.value)} // 입력 값이 변경될 때마다 상태 업데이트 + placeholder="할일을 입력하세요" + /> + {/* 할 일을 추가하는 버튼 */} +
+ ); +} + +export default TodoInput; \ No newline at end of file diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx new file mode 100644 index 0000000..ee00191 --- /dev/null +++ b/src/components/TodoItem.jsx @@ -0,0 +1,38 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import TodoButton from './TodoButton'; + +// Todo 항목의 기본 스타일 정의 +const itemStyle = css` + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.5); + cursor: pointer; + color: #fff; +`; + +// 완료된 Todo 항목 스타일 정의 +const completedStyle = css` + text-decoration: line-through; // 완료된 항목을 취소선으로 표시 + color: #aaa; +`; + +// 개별 Todo 항목을 나타내는 컴포넌트 +function TodoItem({ todo, index, deleteTodo, toggleTodo }) { + return ( +
+ {/* 할 일 텍스트 표시 */} + {todo.text} +
+ {/* 할 일 완료 상태를 토글하는 버튼 */} + toggleTodo(index)}>✔️ + {/* 할 일을 삭제하는 버튼 */} + deleteTodo(index)}>🗑️ +
+
+ ); +} + +export default TodoItem; \ No newline at end of file diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx new file mode 100644 index 0000000..6be7e7d --- /dev/null +++ b/src/components/TodoList.jsx @@ -0,0 +1,36 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import TodoListSection from './TodoListSection'; + + +const listContainerStyle = css` + width: 300px; + max-width: 100%; + margin: 0 auto; +`; + +function TodoList({ todos, deleteTodo, toggleTodo }) { + const pendingTodos = todos.filter((todo) => !todo.completed); // 완료되지 않은 할 일만 필터링 + const completedTodos = todos.filter((todo) => todo.completed); // 완료된 할 일만 필터링 + + return ( +
+ {/* TO DO 섹션 */} + + {/* DONE 섹션 */} + +
+ ); +} + +export default TodoList; diff --git a/src/components/TodoListSection.jsx b/src/components/TodoListSection.jsx new file mode 100644 index 0000000..4d1bdc9 --- /dev/null +++ b/src/components/TodoListSection.jsx @@ -0,0 +1,26 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import TodoItem from './TodoItem'; + +const sectionStyle = css` + margin-bottom: 20px; +`; + +function TodoListSection({ title, todos, deleteTodo, toggleTodo }) { + return ( +
+

{title} ({todos.length})

+ {todos.map((todo, index) => ( + + ))} +
+ ); +} + +export default TodoListSection;