├── 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 |
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 |
--------------------------------------------------------------------------------