diff --git a/src/App.tsx b/src/App.tsx index a399287bd..bd326e93c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,9 @@ /* eslint-disable jsx-a11y/control-has-associated-label */ -import React from 'react'; +import React, { useContext } from 'react'; +import { TodoHeader } from './components/TodoHeader'; +import { TodoFooter } from './components/TodoFooter'; +import { TodoList } from './components/TodoList'; +import { TodoContext } from './context/TodoContext'; export const App: React.FC = () => { return ( @@ -7,150 +11,12 @@ export const App: React.FC = () => {

todos

-
- {/* this button should have `active` class only if all todos are completed */} -
- -
- {/* This is a completed todo */} -
- - - - Completed Todo - - - {/* Remove button appears only on hover */} - -
- - {/* This todo is an active todo */} -
- - - - Not Completed Todo - - - -
- - {/* This todo is being edited */} -
- - - {/* This form is shown instead of the title and remove button */} -
- -
-
- - {/* This todo is in loadind state */} -
- - - - Todo is being saved now - - - -
-
+ {/* Hide the footer if there are no todos */} -
- - 3 items left - - - {/* Active link should have the 'selected' class */} - - - {/* this button should be disabled if there are no completed todos */} - -
+
); diff --git a/src/components/TodoFooter.tsx b/src/components/TodoFooter.tsx new file mode 100644 index 000000000..fc2bc3d80 --- /dev/null +++ b/src/components/TodoFooter.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +type TodoFooterProps = {}; + +export const TodoFooter: React.FC = () => { + return ( + + ); +}; diff --git a/src/components/TodoHeader.tsx b/src/components/TodoHeader.tsx new file mode 100644 index 000000000..40f05c0c6 --- /dev/null +++ b/src/components/TodoHeader.tsx @@ -0,0 +1,32 @@ +import React, { useCallback, useContext } from 'react'; +import { TodoContext } from '../context/TodoContext'; +import { Todo } from '../type'; + +type TodoHeaderProps = {}; + +export const TodoHeader: React.FC = () => { + const { todos, setTodos } = useContext(TodoContext); + + console.log(todos) + + return ( +
+ {/* this button should have `active` class only if all todos are completed */} +
+ ); +}; diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx new file mode 100644 index 000000000..11ca06f99 --- /dev/null +++ b/src/components/TodoItem.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +type TodoItemProps = {}; + +export const TodoItem: React.FC = () => { + return ( +
+ +
+ ); +}; diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx new file mode 100644 index 000000000..bf762fd80 --- /dev/null +++ b/src/components/TodoList.tsx @@ -0,0 +1,90 @@ +import React from 'react'; + +type TodoListProps = {}; + +export const TodoList: React.FC = () => { + return ( +
+ {/* This is a completed todo */} +
+ + + + Completed Todo + + + {/* Remove button appears only on hover */} + +
+ + {/* This todo is an active todo */} +
+ + + + Not Completed Todo + + + +
+ + {/* This todo is being edited */} +
+ + + {/* This form is shown instead of the title and remove button */} +
+ +
+
+ + {/* This todo is in loadind state */} +
+ + + + Todo is being saved now + + + +
+
+ ); +}; diff --git a/src/context/TodoContext.tsx b/src/context/TodoContext.tsx new file mode 100644 index 000000000..05d997524 --- /dev/null +++ b/src/context/TodoContext.tsx @@ -0,0 +1,66 @@ +import React, { + createContext, + MutableRefObject, + useEffect, + useReducer, + useRef, +} from 'react'; +import { State } from '../types/State'; +import { Todo } from '../types/Todo'; +import { Filters } from '../enums/TodoFilterEnum'; + +interface Props { + state: State; + dispatch: React.Dispatch; + inputRef: MutableRefObject; +} + +export const TodoContext = createContext({ + state: { todos: [], filter: Filters.All }, + dispatch: () => {}, + inputRef: { current: null }, +}); + +type Action = + | { type: 'ADD_TODO'; payload: Todo } + | { type: 'EDIT_TODO'; payload: Todo } + | { type: 'TOGGLE_TODO'; payload: number } + | { type: 'DELETE_TODO'; payload: number } + | { type: 'FILTER_TODOS'; payload: Filters } + | { type: 'DELETE_COMPLETED_TODOS'; payload: number[] } + | { type: 'TOGGLE_ALL_TODOS'; payload: number[] }; + +function reduceTodos(state: State, action: Action): State { + switch (action.type) { + case 'ADD_TODO': + return { ...state, todos: [...state.todos, action.payload] }; + + default: + return state; + } +} + +export const TodoProvider = ({ children }: { children: React.ReactNode }) => { + const inputRef = useRef(null); + + const getStoredTodos = () => { + const stored = localStorage.getItem('todos'); + + return stored ? JSON.parse(stored) : []; + }; + + const [state, dispatch] = useReducer(reduceTodos, { + todos: getStoredTodos(), + filter: Filters.All, + }); + + useEffect(() => { + localStorage.setItem('todos', JSON.stringify(state.todos)); + }, [state.todos]); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/src/enums/TodoFilterEnum.ts b/src/enums/TodoFilterEnum.ts new file mode 100644 index 000000000..d6f410111 --- /dev/null +++ b/src/enums/TodoFilterEnum.ts @@ -0,0 +1,5 @@ +export enum Filters { + All = 'All', + Active = 'Active', + Completed = 'Completed', +} \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index b2c38a17a..a4207b1fc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,7 +3,12 @@ import { createRoot } from 'react-dom/client'; import './styles/index.scss'; import { App } from './App'; +import { TodoProvider } from './context/TodoContext'; const container = document.getElementById('root') as HTMLDivElement; -createRoot(container).render(); +createRoot(container).render( + + + , +); diff --git a/src/type.ts b/src/type.ts new file mode 100644 index 000000000..b624f601a --- /dev/null +++ b/src/type.ts @@ -0,0 +1,5 @@ +export type Todo = { + id: number + title: string + completed: boolean +} \ No newline at end of file diff --git a/src/types/State.ts b/src/types/State.ts new file mode 100644 index 000000000..0b30a3145 --- /dev/null +++ b/src/types/State.ts @@ -0,0 +1,7 @@ +import { Filters } from '../enums/TodoFilterEnum'; +import { Todo } from './Todo'; + +export interface State { + todos: Todo[]; + filter: Filters; +} \ No newline at end of file diff --git a/src/types/Todo.ts b/src/types/Todo.ts new file mode 100644 index 000000000..f9e06b381 --- /dev/null +++ b/src/types/Todo.ts @@ -0,0 +1,5 @@ +export interface Todo { + id: number; + title: string; + completed: boolean; +}