diff --git a/src/App.css b/src/App.css index 74b5e05..6b74067 100644 --- a/src/App.css +++ b/src/App.css @@ -1,38 +1,349 @@ -.App { - text-align: center; +html, +body { + margin: 0; + padding: 0; } -.App-logo { - height: 40vmin; - pointer-events: none; +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + font-weight: inherit; + color: inherit; } -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } +button, input[type="checkbox"] { + outline: none; } -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #f5f5f5; + color: #4d4d4d; + min-width: 230px; + max-width: 550px; + margin: 0 auto; + font-weight: 300; } -.App-link { - color: #61dafb; +.hidden { + display: none; } -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } +.todoapp { + background: #fff; + margin: 130px 0 40px 0; + position: relative; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), + 0 25px 50px 0 rgba(0, 0, 0, 0.1); +} + +.todoapp input::input-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp h1 { + position: absolute; + top: -155px; + width: 100%; + font-size: 100px; + font-weight: 100; + text-align: center; + text-transform: capitalize; + color: #cc9a9a; +} + +.new-todo, +.edit { + position: relative; + margin: 0; + width: 100%; + font-size: 24px; + border: 0; + outline: none; + padding: 6px; + border: 1px solid #999; + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); + box-sizing: border-box; +} + +.new-todo { + padding: 16px 16px 16px 60px; + border: none; + background: rgba(0, 0, 0, 0.003); + box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); +} + +.main { + position: relative; + z-index: 2; + border-top: 1px solid #e6e6e6; +} + +label[for='toggle-all'] { + display: none; +} + +.toggle-all:before { + content: '❯'; + font-size: 22px; + color: #e6e6e6; + padding: 10px 27px 10px 27px; +} + +.toggle-all:checked:before { + color: #737373; +} + +.toggle-all { + position: absolute; + top: 5px; + left: -12px; + width: 60px; + height: 34px; + text-align: center; + border: none; /* Mobile Safari */ + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + -webkit-appearance: none; +} + +.todo-list { + margin: 0; + padding: 0; + list-style: none; +} + +.todo-list li { + position: relative; + font-size: 24px; + border-bottom: 1px solid #ededed; +} + +.todo-list li:last-child { + border-bottom: none; +} + +.todo-list li.checked { + color: #979797; + font-weight: normal; + text-decoration: line-through; + +} + +.todo-list li.checked input[type="checkbox"]:after { + border: 1px solid #166B94; + border-radius: 3px; + color: #fff; + content: ""; + display: block; + height: 16px; + line-height: 16px; + position: absolute; + text-align: center; + visibility: visible; + width: 16px; +} + +.todo-list li.checked input[type=checkbox]:checked:after { + border: 1px solid #979797; + color: #979797; + content: "✓"; + font-size: 25px; + color: green; +} + + + +.todo-list li .itemList { + text-align: center; + height: 20px; + /* auto, since non-WebKit browsers doesn't support input styling */ + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; + +} + +.todo-list li label { + white-space: pre-line; + word-break: break-all; + padding: 15px 60px 15px 15px; + margin-left: 45px; + display: block; + line-height: 1.2; + transition: color 0.4s; +} + +.todo-list input[type=checkbox] { + cursor: pointer; + visibility: hidden; + margin-left: 20px; +} + +.todo-list input[type="checkbox"]:after { + border: 1px solid #166B94; + border-radius: 3px; + color: #fff; + content: ""; + display: block; + height: 16px; + line-height: 16px; + position: absolute; + text-align: center; + visibility: visible; + width: 16px; +} + +.todo-list input[type=checkbox]:checked:after { + border: 1px solid #979797; + color: #979797; + content: "✓"; + font-size: 25px; + color: green; +} + + +.todo-list input[type=checkbox]:checked + label { + color: #979797; + font-weight: normal; + text-decoration: line-through; +} + +.todo-list li .remove { + display: none; + position: absolute; + top: 0; + right: 10px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 30px; + color: #cc9a9a; + margin-bottom: 11px; + transition: color 0.2s ease-out; + cursor: pointer; +} + +.todo-list li .remove:hover { + color: #af5b5e; +} + +.todo-list li .remove:after { + content: '×'; +} + +.todo-list li:hover .remove { + display: block; +} + + +.edit { + display: none; +} + +li.editing { + display: block; + width: 430px; +} + +li.editing > label { + display: none; +} + +li.editing > input.edit { + display: block !important; +} + +li:hover.editing > button.remove { + display: none ; +} +.todo-list li.editing .edit { + display: block; + width: 506px; + padding: 13px 17px 12px 17px; + margin: 0 0 0 43px; +} + +.show-all { + display: block; +} + +.todo-list li.editing:last-child { + margin-bottom: -1px; +} + +.footer { + color: #777; + padding: 10px 15px; + height: 20px; + text-align: center; + border-top: 1px solid #e6e6e6; +} + +.footer:before { + content: ''; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 50px; + overflow: hidden; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), + 0 8px 0 -3px #f6f6f6, + 0 9px 1px -3px rgba(0, 0, 0, 0.2), + 0 16px 0 -6px #f6f6f6, + 0 17px 2px -6px rgba(0, 0, 0, 0.2); +} + +.todo-count { + float: left; + text-align: left; +} + +.filters { + margin: 0; + padding: 0; + list-style: none; + position: absolute; + right: 0; + left: 0; +} + +.filters li { + display: inline; +} + +.filters li a { + color: inherit; + margin: 3px; + padding: 3px 7px; + text-decoration: none; + border: 1px solid transparent; + border-radius: 3px; +} + +.filters li a.selected { + border-color: rgba(175, 47, 47, 0.2); +} + +.clear-completed, html .clear-completed:active { + float: right; + position: relative; + line-height: 20px; + text-decoration: none; + cursor: pointer; +} + +.clear-completed:hover { + text-decoration: underline; } diff --git a/src/App.tsx b/src/App.tsx index a53698a..9f95d2d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,58 @@ -import React from 'react'; -import logo from './logo.svg'; +import React, { useState, useEffect } from 'react'; import './App.css'; +import { ITask } from './utils/types/Task'; +import { setLocalStorage, getLocalStorage } from './utils/common'; +import TodoForm from './components/TodoForm'; +import TodoList from './components/TodoList'; +import TodoOptionShow from './components/TodoOptionShow'; +import { NAME_LOCAL_STORAGE, STATUS_TASK } from './utils/constants'; function App() { - return ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
-
- ); + const [taskList, setTaskList] = useState([]); + + useEffect(() => { + setTaskList(getLocalStorage(NAME_LOCAL_STORAGE)); + }, []); + + /** + * Update list Task in local storage and taskList + * @param newTaskList + */ + const updateNewTaskList = (newTaskList: ITask[]) => { + setLocalStorage(NAME_LOCAL_STORAGE, newTaskList); + setTaskList(getLocalStorage(NAME_LOCAL_STORAGE)); + }; + + /** + * Clear Task Complete + */ + const removeTaskByStatus = (statusNeedRemove: boolean) => { + const newTask: ITask[] = getLocalStorage(NAME_LOCAL_STORAGE).filter((task: ITask) => task.status !== statusNeedRemove); + updateNewTaskList(newTask); + }; + + return ( +
+
+

todo list

+
+ +
+ + + +
+
+ + {taskList?.length ?? 0} item left + + + +
+
+ ); } export default App; diff --git a/src/components/TodoForm.tsx b/src/components/TodoForm.tsx new file mode 100644 index 0000000..cf40f78 --- /dev/null +++ b/src/components/TodoForm.tsx @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import { ITask } from '../utils/types/Task'; +import { STATUS_TASK } from '../utils/constants'; + +type DataProps = { + updateNewTaskList: any; + taskList: ITask[]; +}; + +function TodoForm({ updateNewTaskList, taskList }: DataProps) { + const [taskNew, setTaskNew] = useState(''); + + const clearInput = () => { + setTaskNew(''); + }; + + const handleSubmit = (e: React.SyntheticEvent) => { + e.preventDefault(); + + const target = e.target as typeof e.target & { description: { value: string } }; + + const formRequest = { + id: uuidv4(), + description: target.description.value, + status: STATUS_TASK.ACTIVE, + }; + + updateNewTaskList([...(taskList || []), formRequest]); + + clearInput(); + }; + + return ( +
handleSubmit(e)}> + setTaskNew(e.target.value)} placeholder="What needs to be done?" /> +
+ ); +} + +export default TodoForm; diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx new file mode 100644 index 0000000..8f2ccdd --- /dev/null +++ b/src/components/TodoList.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { ITask } from './../utils/types/Task'; +import { getLocalStorage } from '../utils/common'; +import { NAME_LOCAL_STORAGE, STATUS_TASK } from '../utils/constants'; + +type DataProps = { + updateNewTaskList: any; + taskList: ITask[]; +}; + +function TodoList({ updateNewTaskList, taskList }: DataProps) { + const onDeleteTask = (id: string | undefined = undefined) => { + if (!id) return; + + const newList = taskList.filter((task) => task.id !== id); + + updateNewTaskList(newList); + }; + + const changeStatus = (task: ITask) => { + if (!task) return; + + onDeleteTask(task.id); + + const newTask = { ...task, status: !STATUS_TASK.ACTIVE }; + + const preTaskList = getLocalStorage(NAME_LOCAL_STORAGE); + + updateNewTaskList([...preTaskList, newTask]); + }; + + return ( + + ); +} + +export default TodoList; diff --git a/src/components/TodoOptionShow.tsx b/src/components/TodoOptionShow.tsx new file mode 100644 index 0000000..67f5144 --- /dev/null +++ b/src/components/TodoOptionShow.tsx @@ -0,0 +1,42 @@ +import { NAME_LOCAL_STORAGE, STATUS_TASK } from '../utils/constants'; +import { ITask } from '../utils/types/Task'; +import { getLocalStorage } from '../utils/common'; + +type DataProps = { + setTaskList: any; +}; + +const TodoOptionShow = ({ setTaskList }: DataProps) => { + /** + * Function filter tasks with status + * @param task_status + * @returns + */ + const showTaskWithStatus = (task_status: any) => { + const listTask = getLocalStorage(NAME_LOCAL_STORAGE) as ITask[]; + + setTaskList(listTask.filter((task) => task.status === task_status)); + }; + + return ( + + ); +}; + +export default TodoOptionShow; diff --git a/src/utils/common.ts b/src/utils/common.ts new file mode 100644 index 0000000..c263afd --- /dev/null +++ b/src/utils/common.ts @@ -0,0 +1,7 @@ +export function setLocalStorage(name: string, listData: Array) { + localStorage.setItem(name, JSON.stringify(listData)); +} + +export const getLocalStorage = (name: string) => { + return JSON.parse(localStorage.getItem(name) as string); +}; diff --git a/src/utils/constants/index.tsx b/src/utils/constants/index.tsx new file mode 100644 index 0000000..37dd592 --- /dev/null +++ b/src/utils/constants/index.tsx @@ -0,0 +1,6 @@ +export const NAME_LOCAL_STORAGE = 'todo'; + +export const STATUS_TASK = { + ACTIVE: false, + COMPLETE: true, +}; diff --git a/src/utils/types/Task.d.ts b/src/utils/types/Task.d.ts new file mode 100644 index 0000000..e7159a5 --- /dev/null +++ b/src/utils/types/Task.d.ts @@ -0,0 +1,5 @@ +export interface ITask { + id: string | undefined; + description: string; + status: boolean; +}