diff --git a/.eslintrc.js b/.eslintrc.js index 1a0050e..2bc29df 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,7 +21,12 @@ module.exports = { sourceType: 'module', }, rules: { - 'prettier/prettier': 'error', + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + }, + ], 'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }], 'react/jsx-uses-react': 'off', 'react/react-in-jsx-scope': 'off', diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 95957a4..0b90ba7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,4 +20,3 @@ jobs: run: npm run lint - name: test run: npm test - \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js index 2252fdc..171a241 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -13,4 +13,6 @@ module.exports = { printWidth: 80, // 화살표 함수가 하나의 매개변수를 받을 때 괄호를 생략 arrowParens: 'avoid', + + endOfLine: 'lf', }; diff --git a/public/index.html b/public/index.html index aa069f2..e65acb3 100644 --- a/public/index.html +++ b/public/index.html @@ -1,4 +1,4 @@ - + diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..9c6296f --- /dev/null +++ b/src/App.css @@ -0,0 +1,8 @@ +body { + display: flex; + flex-direction: column; + align-items: center; +} +h1 { + text-align: center; +} diff --git a/src/App.js b/src/App.js index ae5ce50..3286e49 100644 --- a/src/App.js +++ b/src/App.js @@ -1,13 +1,52 @@ +import { useState, useRef } from 'react'; import Form from './components/Form'; import Item from './components/Item'; +import './App.css'; function App() { + const [todos, setTodos] = useState([]); + + const nextId = useRef(1); + + const handleAddTodo = todo => { + const newTodo = { + id: nextId.current, + todo, + isCompleted: false, + }; + setTodos([newTodo, ...todos]); + nextId.current += 1; + }; + + const handleCompleteTodo = (id, isCompleted) => { + setTodos( + todos.map(todo => (todo.id === id ? { ...todo, isCompleted } : todo)), + ); + }; + + const handleDeleteTodo = id => { + setTodos(todos.filter(todo => todo.id !== id)); + }; + return (

Todo List

-
+
); diff --git a/src/components/Form.css b/src/components/Form.css new file mode 100644 index 0000000..6d9042a --- /dev/null +++ b/src/components/Form.css @@ -0,0 +1,6 @@ +.addButton { + width: 100px; + border: 0; + border-radius: 10px; + padding: 10px 20px; +} diff --git a/src/components/Form.js b/src/components/Form.js index cb405e1..b07466d 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -1,11 +1,32 @@ import PropTypes from 'prop-types'; +import { useState } from 'react'; +import './Form.css'; function Form({ onAddTodo }) { + const [value, setValue] = useState(''); + const onChange = e => { + setValue(e.target.value); + }; + const onAdd = e => { + e.preventDefault(); + if (value.trim() !== '') { + onAddTodo(value); + setValue(''); + } + }; return ( -
- - -
+ + + + ); } diff --git a/src/components/Item.css b/src/components/Item.css new file mode 100644 index 0000000..bf6b9ec --- /dev/null +++ b/src/components/Item.css @@ -0,0 +1,20 @@ +.inputTodo { + width: 400px; + border: 1px solid #e0e0e0; + margin-right: 10px; + padding: 10px 20px; +} +.deleteButton { + width: 70px; + border: 0; + border-radius: 10px; + padding: 10px 20px; + margin-top: 10px; +} +.todo { + margin: 20px 10px; +} +li { + margin: 10px 0; + list-style: none; +} diff --git a/src/components/Item.js b/src/components/Item.js index a4bd48e..7062400 100644 --- a/src/components/Item.js +++ b/src/components/Item.js @@ -1,32 +1,34 @@ import PropTypes from 'prop-types'; +import './Item.css'; -function Item({ id, todo, isCompleted, onDeleteTodo, onCompleteTodo }) { +function Item({ id, todo, isCompleted, onCompleteTodo, onDeleteTodo }) { return (
  • - -

    {todo}

    + onCompleteTodo(id, !isCompleted)} + /> + {todo}
    - +
  • ); } Item.propTypes = { - id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + id: PropTypes.number.isRequired, todo: PropTypes.string.isRequired, isCompleted: PropTypes.bool.isRequired, - /** - * 할 일 삭제 함수 - * @param {number|string} id 삭제할 할 일의 id - */ - onDeleteTodo: PropTypes.func.isRequired, - /** - * 할 일 완료 함수 - * @param {number|string} id 완료할 할 일의 id - * @param {boolean} isCompleted 완료 여부 - */ onCompleteTodo: PropTypes.func.isRequired, + onDeleteTodo: PropTypes.func.isRequired, }; export default Item;