Skip to content

Commit d644470

Browse files
author
Duc Le
committed
Add some styling
1 parent 933eff9 commit d644470

File tree

9 files changed

+823
-38
lines changed

9 files changed

+823
-38
lines changed

package-lock.json

Lines changed: 631 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"@testing-library/react": "^9.3.2",
88
"@testing-library/user-event": "^7.1.2",
99
"formik": "^2.1.4",
10+
"node-sass": "^4.14.1",
1011
"react": "^16.13.1",
1112
"react-dom": "^16.13.1",
1213
"react-scripts": "3.4.1",

src/Todo.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ import TodoList from './components/TodoList';
55
import AddNewTodoForm from './components/AddNewTodoForm';
66

77
// HELPER
8-
import { getTodoItemsFromLocalStorage, saveTodoItemsToLocalStorage } from './helpers'
8+
import { getTodoItemsFromLocalStorage, saveTodoItemsToLocalStorage } from './helpers';
9+
10+
// STYLE
11+
import style from './Todo.module.scss';
912

1013
const Todo = () => {
1114
// Initialize todoItems state with the JSON string stored under todo key in localStorage, if it's falsy, use an empty array instead
1215
const [todoItems, setTodoItems] = useState(getTodoItemsFromLocalStorage('todo') || [])
16+
17+
const [customError, setCustomError] = useState(null)
1318

1419
// Event handler for adding new Todo, using useCallback to return a memoized callback.
1520
const addTodoHandler = useCallback(todo => {
@@ -75,10 +80,9 @@ const Todo = () => {
7580
}, [todoItems])
7681

7782
return (
78-
<div className="todo">
79-
<h1>Todo</h1>
80-
83+
<div className={style.todo}>
8184
<AddNewTodoForm
85+
customError={customError} // Passing down customError
8286
onAddTodo={addTodoHandler} // Passing down addTodoHandler
8387
/>
8488

@@ -87,6 +91,7 @@ const Todo = () => {
8791
onRemoveTodo={removeTodoHandler} // Passing down removeTodoHandler
8892
onToggleTodoDone={toggleTodoDoneHandler} // Passing down toggleTodoDoneHandler
8993
onEditTodo={editTodoHandler} // Passing down editTodoHandler
94+
setCustomError={setCustomError} // Passing down setCustomError
9095
/>
9196
</div>
9297
);

src/Todo.module.scss

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
@import url('https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap');
2+
3+
body {
4+
margin: 0;
5+
&::before {
6+
content: '';
7+
position: fixed;
8+
z-index: -1;
9+
top: 0;
10+
left: 0;
11+
background: #37ecba;
12+
background: linear-gradient(to top, #37ecba 0%, #72afd3 100%);
13+
width: 100%;
14+
height: 100%;
15+
}
16+
}
17+
18+
input, button, span {
19+
font-family: 'Roboto', cursive;
20+
}
21+
22+
input, button {
23+
outline: none;
24+
}
25+
26+
button {
27+
cursor: pointer;
28+
}
29+
30+
.todo {
31+
margin: 0 auto;
32+
min-width: 500px;
33+
max-height: 100vh;
34+
position: absolute;
35+
top: 50%;
36+
left: 50%;
37+
transform: translate(-50%, -50%);
38+
> form {
39+
background-color: #0097A7;
40+
display: flex;
41+
justify-content: space-between;
42+
padding: 15px;
43+
> input {
44+
font-size: 25px;
45+
font-weight: bold;
46+
font-style: italic;
47+
width: calc(100% - 80px);
48+
color: white;
49+
background: none;
50+
border: none;
51+
&::placeholder {
52+
color: white;
53+
}
54+
}
55+
> button {
56+
padding: 5px 10px;
57+
font-size: 20px;
58+
background-color: #00BCD4;
59+
border: none;
60+
color: white;
61+
}
62+
}
63+
> span {
64+
padding: 15px;
65+
background-color: red;
66+
color: white;
67+
display: block;
68+
text-align: center;
69+
}
70+
> ul {
71+
margin: 0;
72+
padding: 0;
73+
list-style: none;
74+
> li {
75+
padding: 15px;
76+
line-height: 25px;
77+
position: relative;
78+
cursor: pointer;
79+
background-color: white;
80+
button {
81+
font-size: 20px;
82+
float: right;
83+
border: none;
84+
background: none;
85+
}
86+
&:hover {
87+
span {
88+
background-color: #eee;
89+
}
90+
}
91+
> input[type="text"] {
92+
background: none;
93+
border: none;
94+
margin-left: 30px;
95+
width: calc(100% - 100px);
96+
}
97+
> input[type="checkbox"] {
98+
position: absolute;
99+
opacity: 0;
100+
cursor: pointer;
101+
height: 0;
102+
width: 0;
103+
&:checked {
104+
~ span {
105+
background-color: #00BCD4;
106+
&::after {
107+
display: block;
108+
}
109+
}
110+
}
111+
}
112+
> span {
113+
position: absolute;
114+
top: 15px;
115+
left: 15px;
116+
height: 25px;
117+
width: 25px;
118+
background-color: #eee;
119+
&::after {
120+
content: "";
121+
position: absolute;
122+
display: none;
123+
left: 9px;
124+
top: 5px;
125+
width: 5px;
126+
height: 10px;
127+
border: solid white;
128+
border-width: 0 3px 3px 0;
129+
transform: rotate(45deg);
130+
}
131+
}
132+
}
133+
}
134+
}

src/components/AddNewTodoForm.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import { useFormik } from 'formik';
33
import * as Yup from 'yup';
44

5-
const AddNewTodoForm = ({ onAddTodo }) => {
5+
const AddNewTodoForm = ({ onAddTodo, customError }) => {
66
// Initialize a form instance with useFormik hook
77
const formik = useFormik({
88
// Disable validation onChange and onBlur for keeping validation errors less annoying
@@ -35,20 +35,24 @@ const AddNewTodoForm = ({ onAddTodo }) => {
3535

3636
const aFormikError = errorKeys.length > 0 ? formik.errors[errorKeys[0]] : null;
3737

38+
const error = customError || aFormikError
39+
3840
return (
41+
<>
3942
<form onSubmit={formik.handleSubmit}>
40-
<label>Todo:</label>
4143
<input
4244
id="todo"
4345
name="todo"
4446
type="text"
4547
onChange={formik.handleChange}
4648
value={formik.values.todo}
4749
autoComplete="off"
50+
placeholder="What needs to be done?"
4851
/>
49-
<button type="submit">Submit</button>
50-
{aFormikError && <span>{aFormikError}</span>}
52+
<button type="submit">Add</button>
5153
</form>
54+
{error && <span>{error}</span>}
55+
</>
5256
)
5357
};
5458

src/components/TodoItem.js

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,56 @@
1-
import React, { useCallback } from 'react';
1+
import React, { useCallback, useRef } from 'react';
22

3-
const TodoItem = ({ todo, id, onRemoveTodo, onToggleTodoDone, onEditTodo, isDone }) => {
4-
const removeTodoHandler = useCallback(() => onRemoveTodo(id), [id, onRemoveTodo])
3+
const TodoItem = ({ todo, id, onRemoveTodo, onToggleTodoDone, onEditTodo, isDone, setCustomError }) => {
4+
const removeTodoHandler = useCallback(() => onRemoveTodo(id), [id, onRemoveTodo]);
5+
6+
const toggleTodoDoneHandler = useCallback(() => onToggleTodoDone(id), [id, onToggleTodoDone]);
57

6-
const toggleTodoDoneHandler = useCallback(() => onToggleTodoDone(id), [id, onToggleTodoDone])
78
const editTodoHandler = useCallback(event => {
89
if (event.keyCode === 13) { // Detect ENTER key down
910
event.preventDefault(); // Prevent adding a new line because it's supposed to be single line
11+
12+
const { value } = event.target;
13+
14+
if (value.length < 3) {
15+
setCustomError('Todo text is too short.');
16+
17+
return;
18+
}
19+
20+
if (value.length > 20) {
21+
setCustomError('Todo text is too long.');
22+
23+
return;
24+
}
25+
1026
onEditTodo(id, event.target.value);
27+
28+
setCustomError(null) // Reset customError
29+
1130
event.target.blur(); // Make the current input lost focus after finishing onEditTodo
1231
}
13-
}, [id, onEditTodo])
32+
}, [id, onEditTodo, setCustomError]);
33+
34+
const checkboxRef = useRef(null);
1435

1536
return (
1637
<li>
1738
<input
39+
type="checkbox"
40+
ref={checkboxRef}
41+
checked={!!isDone}
42+
onChange={toggleTodoDoneHandler}
43+
/>
44+
<span onClick={() => checkboxRef.current.click()} />
45+
<input
46+
type="text"
1847
defaultValue={todo} // innerHTML of the editable div
19-
disabled={false} // use true to disable editing
2048
onKeyDown={editTodoHandler} // handle innerHTML change
21-
tagName='span' // Use a custom HTML tag (uses a div by default)
2249
style={{textDecoration: isDone ? 'line-through' : 'none'}}
2350
/>
24-
<button onClick={removeTodoHandler}>Remove</button>
25-
<button onClick={toggleTodoDoneHandler}>{`Mark as ${isDone ? 'pending': 'done'}`}</button>
51+
<button onClick={removeTodoHandler}>
52+
<span role="img" aria-labelledby="trash" />🗑️
53+
</button>
2654
</li>
2755
)
2856
};

src/components/TodoList.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
// COMPONENT
44
import TodoItem from './TodoItem';
55

6-
const TodoList = ({ todoItems, onRemoveTodo, onToggleTodoDone, onEditTodo }) => {
6+
const TodoList = ({ todoItems, onRemoveTodo, onToggleTodoDone, onEditTodo, setCustomError }) => {
77
return (
88
<ul>
99
{
@@ -12,13 +12,14 @@ const TodoList = ({ todoItems, onRemoveTodo, onToggleTodoDone, onEditTodo }) =>
1212
todoItems.length > 0 && // The array should not be empty
1313
todoItems.map(({ id, todo, isDone }) => ( // If all conditions are met, we render a list of todo items
1414
<TodoItem
15-
key={id}
15+
key={id}s
1616
id={id}
1717
todo={todo}
1818
onRemoveTodo={onRemoveTodo}
1919
onToggleTodoDone={onToggleTodoDone}
2020
onEditTodo={onEditTodo}
2121
isDone={isDone}
22+
setCustomError={setCustomError}
2223
/>
2324
))
2425
}

src/index.css

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom';
3-
import './index.css';
43
import Todo from './Todo';
54
import * as serviceWorker from './serviceWorker';
65

0 commit comments

Comments
 (0)