+import { useContext, useState } from 'react';
+import { TodosContext } from './components/TodosContext';
+import { Footer } from './components/Footer';
+import { FilterMethods } from './types/FilterMethods';
+import { Header } from './components/Header';
+import { Main } from './components/Main';
-
-
- {/* this button should have `active` class only if all todos are completed */}
-
+export const App: React.FC = () => {
+ const context = useContext(TodosContext);
- {/* Add a todo on form submit */}
-
-
+ if (!context) {
+ throw new Error('useTodos must be used within a TodosProvider');
+ }
-
- {/* This is a completed todo */}
-
-
+ const { todos } = context;
-
- Completed Todo
-
+ const [filterMethod, setFilterMethod] = useState(
+ FilterMethods.all,
+ );
- {/* Remove button appears only on hover */}
-
-
+ function filterTodos(method: FilterMethods) {
+ let currentTodos = [...todos];
- {/* This todo is an active todo */}
-
-
+ switch (method) {
+ case FilterMethods.all:
+ return currentTodos;
-
- Not Completed Todo
-
+ case FilterMethods.active:
+ currentTodos = currentTodos.filter(todo => !todo.completed);
+ break;
-
-
+ case FilterMethods.completed:
+ currentTodos = currentTodos.filter(todo => todo.completed);
+ break;
+ }
- {/* This todo is being edited */}
-
-
+ return currentTodos;
+ }
- {/* This form is shown instead of the title and remove button */}
-
-
+ const visibleTodos = filterTodos(filterMethod);
- {/* This todo is in loadind state */}
-
-
+ return (
+
+
todos
-
- Todo is being saved now
-
+
+
-
-
-
+
{/* Hide the footer if there are no todos */}
-
+ {todos.length !== 0 && (
+
+ )}
);
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
new file mode 100644
index 000000000..504575975
--- /dev/null
+++ b/src/components/Footer.tsx
@@ -0,0 +1,81 @@
+import { useContext } from 'react';
+import { TodosContext } from './TodosContext';
+import { FilterMethods } from '../types/FilterMethods';
+import classNames from 'classnames';
+
+type Props = {
+ onFilter: (v: FilterMethods) => void;
+ filterList: FilterMethods | null;
+};
+
+export const Footer: React.FC = ({ onFilter, filterList }) => {
+ const context = useContext(TodosContext);
+
+ if (!context) {
+ throw new Error('useTodos must be used within a TodosProvider');
+ }
+
+ const { todos, setTodos, inputFocus } = context;
+
+ function clearCompleted() {
+ const clearedTodos = todos.filter(todo => !todo.completed);
+
+ setTodos(clearedTodos);
+ inputFocus.current?.focus();
+ }
+
+ return (
+
+ );
+};
diff --git a/src/components/Form.tsx b/src/components/Form.tsx
new file mode 100644
index 000000000..7bc496b42
--- /dev/null
+++ b/src/components/Form.tsx
@@ -0,0 +1,43 @@
+import { useContext, useState } from 'react';
+import { TodosContext } from './TodosContext';
+
+export const Form = () => {
+ const [title, setTitle] = useState('');
+ const context = useContext(TodosContext);
+
+ if (!context) {
+ throw new Error('useTodos must be used within a TodosProvider');
+ }
+
+ const { addTodo, inputFocus } = context;
+
+ return (
+
+ );
+};
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 000000000..9383fb7b2
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,42 @@
+import classNames from 'classnames';
+import { useContext } from 'react';
+import { TodosContext } from './TodosContext';
+import { Form } from './Form';
+
+export const Header: React.FC = () => {
+ const context = useContext(TodosContext);
+
+ if (!context) {
+ throw new Error('useTodos must be used within a TodosProvider');
+ }
+
+ const { todos, updateTodo } = context;
+
+ const toggleAll = () => {
+ const allCompleted = todos.every(todo => todo.completed);
+ const newStatus = !allCompleted;
+
+ todos.forEach(todo => {
+ updateTodo(todo.id, { completed: newStatus });
+ });
+ };
+
+ return (
+
+ {/* this button should have `active` class only if all todos are completed */}
+ {todos.length !== 0 && (
+
+ );
+};
diff --git a/src/components/Main.tsx b/src/components/Main.tsx
new file mode 100644
index 000000000..ef3da5213
--- /dev/null
+++ b/src/components/Main.tsx
@@ -0,0 +1,24 @@
+import { useState } from 'react';
+import { Todo } from '../types/Todo';
+import { TodoItem } from './TodoItem';
+
+type Props = {
+ visibleTodos: Todo[];
+};
+
+export const Main: React.FC = ({ visibleTodos }) => {
+ const [editingTodo, setEditingTodo] = useState(null);
+
+ return (
+
+ {visibleTodos?.map(todo => (
+
+ ))}
+
+ );
+};
diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx
new file mode 100644
index 000000000..5e2a0904a
--- /dev/null
+++ b/src/components/TodoItem.tsx
@@ -0,0 +1,100 @@
+import { useContext, useState } from 'react';
+import { Todo } from '../types/Todo';
+import { TodosContext } from './TodosContext';
+import classNames from 'classnames';
+
+type Props = {
+ todo: Todo;
+ editing: Date | number | null;
+ onEdit: (v: number | null) => void;
+};
+
+export const TodoItem: React.FC = ({ todo, editing, onEdit }) => {
+ const context = useContext(TodosContext);
+
+ if (!context) {
+ throw new Error('useTodos must be used within a TodosProvider');
+ }
+
+ const { updateTodo, deleteTodo } = context;
+ const [value, setValue] = useState(todo.title);
+
+ function handleSumbit() {
+ const normalizedValue = value.trim();
+
+ if (normalizedValue.length === 0) {
+ deleteTodo(todo.id);
+
+ return;
+ }
+
+ updateTodo(todo.id, { title: normalizedValue });
+ setValue(normalizedValue);
+ onEdit(null);
+ }
+
+ return (
+