├── src ├── index.js ├── App.js ├── components │ ├── Todo.js │ ├── TodoList.js │ └── TodoForm.js └── App.css └── package.json /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import TodoList from './components/TodoList'; 4 | 5 | function App() { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-list-v1", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "react": "^16.13.1", 10 | "react-dom": "^16.13.1", 11 | "react-icons": "^3.11.0", 12 | "react-scripts": "3.4.3" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": "react-app" 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Todo.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import TodoForm from './TodoForm'; 3 | import { RiCloseCircleLine } from 'react-icons/ri'; 4 | import { TiEdit } from 'react-icons/ti'; 5 | 6 | const Todo = ({ todos, completeTodo, removeTodo, updateTodo }) => { 7 | const [edit, setEdit] = useState({ 8 | id: null, 9 | value: '' 10 | }); 11 | 12 | const submitUpdate = value => { 13 | updateTodo(edit.id, value); 14 | setEdit({ 15 | id: null, 16 | value: '' 17 | }); 18 | }; 19 | 20 | if (edit.id) { 21 | return ; 22 | } 23 | 24 | return todos.map((todo, index) => ( 25 |
29 |
completeTodo(todo.id)}> 30 | {todo.text} 31 |
32 |
33 | removeTodo(todo.id)} 35 | className='delete-icon' 36 | /> 37 | setEdit({ id: todo.id, value: todo.text })} 39 | className='edit-icon' 40 | /> 41 |
42 |
43 | )); 44 | }; 45 | 46 | export default Todo; 47 | -------------------------------------------------------------------------------- /src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import TodoForm from './TodoForm'; 3 | import Todo from './Todo'; 4 | 5 | function TodoList() { 6 | const [todos, setTodos] = useState([]); 7 | 8 | const addTodo = todo => { 9 | if (!todo.text || /^\s*$/.test(todo.text)) { 10 | return; 11 | } 12 | 13 | const newTodos = [todo, ...todos]; 14 | 15 | setTodos(newTodos); 16 | console.log(...todos); 17 | }; 18 | 19 | const updateTodo = (todoId, newValue) => { 20 | if (!newValue.text || /^\s*$/.test(newValue.text)) { 21 | return; 22 | } 23 | 24 | setTodos(prev => prev.map(item => (item.id === todoId ? newValue : item))); 25 | }; 26 | 27 | const removeTodo = id => { 28 | const removedArr = [...todos].filter(todo => todo.id !== id); 29 | 30 | setTodos(removedArr); 31 | }; 32 | 33 | const completeTodo = id => { 34 | let updatedTodos = todos.map(todo => { 35 | if (todo.id === id) { 36 | todo.isComplete = !todo.isComplete; 37 | } 38 | return todo; 39 | }); 40 | setTodos(updatedTodos); 41 | }; 42 | 43 | return ( 44 | <> 45 |

What's the Plan for Today?

46 | 47 | 53 | 54 | ); 55 | } 56 | 57 | export default TodoList; 58 | -------------------------------------------------------------------------------- /src/components/TodoForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | 3 | function TodoForm(props) { 4 | const [input, setInput] = useState(props.edit ? props.edit.value : ''); 5 | 6 | const inputRef = useRef(null); 7 | 8 | useEffect(() => { 9 | inputRef.current.focus(); 10 | }); 11 | 12 | const handleChange = e => { 13 | setInput(e.target.value); 14 | }; 15 | 16 | const handleSubmit = e => { 17 | e.preventDefault(); 18 | 19 | props.onSubmit({ 20 | id: Math.floor(Math.random() * 10000), 21 | text: input 22 | }); 23 | setInput(''); 24 | }; 25 | 26 | return ( 27 |
28 | {props.edit ? ( 29 | <> 30 | 38 | 41 | 42 | ) : ( 43 | <> 44 | 52 | 55 | 56 | )} 57 |
58 | ); 59 | } 60 | 61 | export default TodoForm; 62 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 6 | 'Lucida Sans', Arial, sans-serif; 7 | } 8 | 9 | body { 10 | background: linear-gradient( 11 | 90deg, 12 | rgba(48, 16, 255, 1) 0%, 13 | rgba(100, 115, 255, 1) 100% 14 | ); 15 | } 16 | .todo-app { 17 | display: flex; 18 | flex-direction: column; 19 | justify-content: start; 20 | width: 520px; 21 | min-height: 600px; 22 | background: #161a2b; 23 | text-align: center; 24 | margin: 128px auto; 25 | border-radius: 10px; 26 | padding-bottom: 32px; 27 | } 28 | 29 | h1 { 30 | margin: 32px 0; 31 | color: #fff; 32 | font-size: 24px; 33 | } 34 | 35 | .complete { 36 | text-decoration: line-through; 37 | opacity: 0.4; 38 | } 39 | 40 | .todo-form { 41 | margin-bottom: 32px; 42 | } 43 | 44 | .todo-input { 45 | padding: 14px 32px 14px 16px; 46 | border-radius: 4px 0 0 4px; 47 | border: 2px solid #5d0cff; 48 | outline: none; 49 | width: 320px; 50 | background: transparent; 51 | color: #fff; 52 | } 53 | 54 | .todo-input::placeholder { 55 | color: #e2e2e2; 56 | } 57 | 58 | .todo-button { 59 | padding: 16px; 60 | border: none; 61 | border-radius: 0 4px 4px 0; 62 | cursor: pointer; 63 | outline: none; 64 | background: linear-gradient( 65 | 90deg, 66 | rgba(93, 12, 255, 1) 0%, 67 | rgba(155, 0, 250, 1) 100% 68 | ); 69 | color: #fff; 70 | text-transform: capitalize; 71 | } 72 | 73 | .todo-input.edit { 74 | border: 2px solid #149fff; 75 | } 76 | 77 | .todo-button.edit { 78 | background: linear-gradient( 79 | 90deg, 80 | rgba(20, 159, 255, 1) 0%, 81 | rgba(17, 122, 255, 1) 100% 82 | ); 83 | padding: 16px 22px; 84 | } 85 | 86 | .todo-container { 87 | display: flex; 88 | flex-direction: row; 89 | position: relative; 90 | } 91 | 92 | .todo-row { 93 | display: flex; 94 | justify-content: space-between; 95 | align-items: center; 96 | margin: 4px auto; 97 | color: #fff; 98 | background: linear-gradient( 99 | 90deg, 100 | rgba(255, 118, 20, 1) 0%, 101 | rgba(255, 84, 17, 1) 100% 102 | ); 103 | 104 | padding: 16px; 105 | border-radius: 5px; 106 | width: 90%; 107 | } 108 | 109 | .todo-row:nth-child(4n + 1) { 110 | background: linear-gradient( 111 | 90deg, 112 | rgba(93, 12, 255, 1) 0%, 113 | rgba(155, 0, 250, 1) 100% 114 | ); 115 | } 116 | 117 | .todo-row:nth-child(4n + 2) { 118 | background: linear-gradient( 119 | 90deg, 120 | rgba(255, 12, 241, 1) 0%, 121 | rgba(250, 0, 135, 1) 100% 122 | ); 123 | } 124 | 125 | .todo-row:nth-child(4n + 3) { 126 | background: linear-gradient( 127 | 90deg, 128 | rgba(20, 159, 255, 1) 0%, 129 | rgba(17, 122, 255, 1) 100% 130 | ); 131 | } 132 | 133 | .icons { 134 | display: flex; 135 | align-items: center; 136 | font-size: 24px; 137 | cursor: pointer; 138 | } 139 | 140 | .delete-icon { 141 | margin-right: 5px; 142 | color: #fff; 143 | } 144 | 145 | .edit-icon { 146 | color: #fff; 147 | } 148 | --------------------------------------------------------------------------------