├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
└── robots.txt
├── readme
├── example.png
└── lighthouse.png
└── src
├── App.js
├── components
├── AddTask.js
├── DeleteTask.js
├── UpdateTask.js
└── tasks.js
├── images
└── empty.svg
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # dependencies
3 | /node_modules
4 | /.pnp
5 | .pnp.js
6 |
7 | # testing
8 | /coverage
9 |
10 | # production
11 | /build
12 |
13 | # misc
14 | .DS_Store
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 | package-lock.json
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Todo List
2 | A simple to-do list app powered by React, Chakra UI, React icons.
3 | Save in Local Storage
4 |
5 | ##### [Live Demo](https://todo-app.erfjs.com/) on Vercel
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Lighthouse Speed
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-todo-list",
3 | "version": "1.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "@chakra-ui/react": "^1.8.5",
7 | "@emotion/react": "^11.8.1",
8 | "@emotion/styled": "^11.8.1",
9 | "@testing-library/jest-dom": "^5.16.2",
10 | "@testing-library/react": "^12.1.3",
11 | "@testing-library/user-event": "^13.5.0",
12 | "framer-motion": "^6.2.8",
13 | "nanoid": "^3.3.1",
14 | "react": "17.0.2",
15 | "react-dom": "17.0.2",
16 | "react-icons": "^4.3.1",
17 | "react-scripts": "5.0.0",
18 | "web-vitals": "^2.1.4"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erfjs/Todo-list/eb0e9f74a7b2fc43e04e7422bc1ab0c747d674c5/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Todo list
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/readme/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erfjs/Todo-list/eb0e9f74a7b2fc43e04e7422bc1ab0c747d674c5/readme/example.png
--------------------------------------------------------------------------------
/readme/lighthouse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erfjs/Todo-list/eb0e9f74a7b2fc43e04e7422bc1ab0c747d674c5/readme/lighthouse.png
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import {
2 | Heading,
3 | IconButton,
4 | VStack,
5 | useColorMode,
6 | useToast,
7 | } from "@chakra-ui/react";
8 | import TaskList from "./components/tasks";
9 | import AddTask from "./components/AddTask";
10 | import { FaSun, FaMoon } from "react-icons/fa";
11 | import { useState, useEffect } from "react";
12 |
13 | function App() {
14 | const toast = useToast();
15 | const [tasks, setTasks] = useState(
16 | () => JSON.parse(localStorage.getItem("tasks")) || []
17 | );
18 |
19 | useEffect(() => {
20 | localStorage.setItem("tasks", JSON.stringify(tasks));
21 | }, [tasks]);
22 |
23 | function deleteTask(id) {
24 | const newTasks = tasks.filter((task) => {
25 | return task.id !== id;
26 | });
27 | setTasks(newTasks);
28 | }
29 |
30 | function deleteTaskAll() {
31 | setTasks([]);
32 | }
33 |
34 | function checkTask(id) {
35 | const newTasksCheck = tasks.map((task, index, array) => {
36 | if (task.id === id) {
37 | task.check = !task.check;
38 | }
39 | return task;
40 | });
41 | setTasks(newTasksCheck);
42 | }
43 |
44 | function updateTask(id, body, onClose) {
45 | const info = body.trim();
46 |
47 | if (!info) {
48 | toast({
49 | title: "Enter your task",
50 | position: "top",
51 | status: "warning",
52 | duration: 2000,
53 | isClosable: true,
54 | });
55 |
56 | return;
57 | }
58 |
59 | const newTasksUpdate = tasks.map((task, index, array) => {
60 | if (task.id === id) {
61 | task.body = body;
62 | task.check = false;
63 | }
64 | return task;
65 | });
66 |
67 | setTasks(newTasksUpdate);
68 |
69 | onClose();
70 | }
71 |
72 | function addTask(task) {
73 | setTasks([...tasks, task]);
74 | }
75 |
76 | const { colorMode, toggleColorMode } = useColorMode();
77 |
78 | return (
79 |
80 | : }
82 | isRound='true'
83 | size='md'
84 | alignSelf='flex-end'
85 | onClick={toggleColorMode}
86 | aria-label='toogle-dark-mode'
87 | />
88 |
89 |
96 | Todo list
97 |
98 |
99 |
106 |
107 | );
108 | }
109 |
110 | export default App;
111 |
--------------------------------------------------------------------------------
/src/components/AddTask.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Button, HStack, Input, useToast } from "@chakra-ui/react";
3 | import { nanoid } from "nanoid";
4 |
5 | function AddTask({ addTask }) {
6 | const toast = useToast();
7 | const [content, setContent] = useState("");
8 | const [statusInput, setStatusInput] = useState(true);
9 |
10 | function handleSubmit(e) {
11 | e.preventDefault();
12 |
13 | const taskText = content.trim();
14 |
15 | if (!taskText) {
16 | toast({
17 | title: "Enter your task",
18 | position: "top",
19 | status: "warning",
20 | duration: 2000,
21 | isClosable: true,
22 | });
23 | setStatusInput(false);
24 |
25 | return setContent("");
26 | }
27 |
28 | const task = {
29 | id: nanoid(),
30 | body: taskText,
31 | check: false,
32 | };
33 |
34 | addTask(task);
35 | setContent("");
36 | }
37 |
38 | if (content && !statusInput) {
39 | setStatusInput(true);
40 | }
41 |
42 | return (
43 |
65 | );
66 | }
67 |
68 | export default AddTask;
69 |
--------------------------------------------------------------------------------
/src/components/DeleteTask.js:
--------------------------------------------------------------------------------
1 | import {
2 | Modal,
3 | ModalOverlay,
4 | ModalContent,
5 | ModalHeader,
6 | ModalFooter,
7 | ModalBody,
8 | Button,
9 | Text,
10 | useDisclosure,
11 | IconButton,
12 | } from "@chakra-ui/react";
13 | import React from "react";
14 | import { FiTrash2 } from "react-icons/fi";
15 |
16 | function DeleteAllTask({ deleteTaskAll }) {
17 | const { isOpen, onOpen, onClose } = useDisclosure();
18 | return (
19 | <>
20 |
30 |
31 |
32 |
33 |
34 | Do you really want to delete all tasks?
35 |
36 |
39 |
42 |
43 |
44 |
45 | >
46 | );
47 | }
48 |
49 | function DeleteTask({ task, deleteTask }) {
50 | const { isOpen, onOpen, onClose } = useDisclosure();
51 |
52 | return (
53 | <>
54 | } isRound='true' onClick={onOpen} />
55 |
56 |
57 |
58 |
59 | Do you really want to delete the task?
60 |
61 | {task.body}
62 |
63 |
64 |
67 |
73 |
74 |
75 |
76 | >
77 | );
78 | }
79 |
80 | export { DeleteTask, DeleteAllTask };
81 |
--------------------------------------------------------------------------------
/src/components/UpdateTask.js:
--------------------------------------------------------------------------------
1 | import {
2 | Modal,
3 | ModalOverlay,
4 | ModalContent,
5 | ModalHeader,
6 | ModalFooter,
7 | ModalBody,
8 | ModalCloseButton,
9 | Button,
10 | Input,
11 | FormControl,
12 | useDisclosure,
13 | IconButton,
14 | } from "@chakra-ui/react";
15 | import { useState } from "react";
16 | import React from "react";
17 | import { FiEdit } from "react-icons/fi";
18 |
19 | function UpdateTask({ task, updateTask }) {
20 | const { isOpen, onOpen, onClose } = useDisclosure();
21 | const [body, setBody] = useState("");
22 |
23 | const initialRef = React.useRef();
24 |
25 | return (
26 | <>
27 | } isRound='true' onClick={onOpen} />
28 |
34 |
35 |
36 | Update your task
37 |
38 |
39 |
40 | setBody(e.target.value)}
45 | onFocus={(e) => setBody(e.target.value)}
46 | />
47 |
48 |
49 |
50 |
51 |
54 |
60 |
61 |
62 |
63 | >
64 | );
65 | }
66 |
67 | export default UpdateTask;
68 |
--------------------------------------------------------------------------------
/src/components/tasks.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import UpdateTask from "./UpdateTask";
3 | import { DeleteTask, DeleteAllTask } from "./DeleteTask";
4 | import {
5 | HStack,
6 | Box,
7 | VStack,
8 | Flex,
9 | Text,
10 | StackDivider,
11 | } from "@chakra-ui/react";
12 | import { Image } from "@chakra-ui/react";
13 | import img from "../images/empty.svg";
14 |
15 | function TaskList({ tasks, updateTask, deleteTask, deleteTaskAll, checkTask }) {
16 | if (!tasks.length) {
17 | return (
18 | <>
19 |
20 |
27 |
28 | >
29 | );
30 | }
31 | return (
32 | <>
33 | }
35 | borderColor='gray.100'
36 | borderWidth='2px'
37 | p='5'
38 | borderRadius='lg'
39 | w='100%'
40 | maxW={{ base: "90vw", sm: "80vw", lg: "50vw", xl: "30vw" }}
41 | alignItems='stretch'
42 | >
43 | {tasks.map((task) => (
44 |
45 | checkTask(task.id)}
52 | >
53 | {task.body}
54 |
55 |
60 |
61 |
62 | ))}
63 |
64 |
65 |
66 |
67 |
68 | >
69 | );
70 | }
71 |
72 | export default TaskList;
73 |
--------------------------------------------------------------------------------
/src/images/empty.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
347 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import { ChakraProvider, ColorModeScript, extendTheme } from "@chakra-ui/react";
5 |
6 | const config = {
7 | initialColorMode: "dark",
8 | };
9 |
10 | const theme = extendTheme({
11 | config,
12 | });
13 |
14 | ReactDOM.render(
15 |
16 |
17 |
18 |
19 |
20 | ,
21 | document.getElementById("root")
22 | );
23 |
--------------------------------------------------------------------------------