Skip to content

Commit

Permalink
copy src from develop branch
Browse files Browse the repository at this point in the history
  • Loading branch information
nikalaiii committed Jan 31, 2025
1 parent c867b63 commit 25b198a
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 137 deletions.
175 changes: 39 additions & 136 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,156 +1,59 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';

export const App: React.FC = () => {
return (
<div className="todoapp">
<h1 className="todoapp__title">todos</h1>
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';

<div className="todoapp__content">
<header className="todoapp__header">
{/* this button should have `active` class only if all todos are completed */}
<button
type="button"
className="todoapp__toggle-all active"
data-cy="ToggleAllButton"
/>
export const App: React.FC = () => {
const context = useContext(TodosContext);

{/* Add a todo on form submit */}
<form>
<input
data-cy="NewTodoField"
type="text"
className="todoapp__new-todo"
placeholder="What needs to be done?"
/>
</form>
</header>
if (!context) {
throw new Error('useTodos must be used within a TodosProvider');
}

<section className="todoapp__main" data-cy="TodoList">
{/* This is a completed todo */}
<div data-cy="Todo" className="todo completed">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
checked
/>
</label>
const { todos } = context;

<span data-cy="TodoTitle" className="todo__title">
Completed Todo
</span>
const [filterMethod, setFilterMethod] = useState<FilterMethods>(
FilterMethods.all,
);

{/* Remove button appears only on hover */}
<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>
function filterTodos(method: FilterMethods) {
let currentTodos = [...todos];

{/* This todo is an active todo */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>
switch (method) {
case FilterMethods.all:
return currentTodos;

<span data-cy="TodoTitle" className="todo__title">
Not Completed Todo
</span>
case FilterMethods.active:
currentTodos = currentTodos.filter(todo => !todo.completed);
break;

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>
case FilterMethods.completed:
currentTodos = currentTodos.filter(todo => todo.completed);
break;
}

{/* This todo is being edited */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>
return currentTodos;
}

{/* This form is shown instead of the title and remove button */}
<form>
<input
data-cy="TodoTitleField"
type="text"
className="todo__title-field"
placeholder="Empty todo will be deleted"
value="Todo is being edited now"
/>
</form>
</div>
const visibleTodos = filterTodos(filterMethod);

{/* This todo is in loadind state */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>
return (
<div className="todoapp">
<h1 className="todoapp__title">todos</h1>

<span data-cy="TodoTitle" className="todo__title">
Todo is being saved now
</span>
<div className="todoapp__content">
<Header />

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>
</section>
<Main visibleTodos={visibleTodos} />

{/* Hide the footer if there are no todos */}
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
3 items left
</span>

{/* Active link should have the 'selected' class */}
<nav className="filter" data-cy="Filter">
<a
href="#/"
className="filter__link selected"
data-cy="FilterLinkAll"
>
All
</a>

<a
href="#/active"
className="filter__link"
data-cy="FilterLinkActive"
>
Active
</a>

<a
href="#/completed"
className="filter__link"
data-cy="FilterLinkCompleted"
>
Completed
</a>
</nav>

{/* this button should be disabled if there are no completed todos */}
<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
>
Clear completed
</button>
</footer>
{todos.length !== 0 && (
<Footer onFilter={setFilterMethod} filterList={filterMethod} />
)}
</div>
</div>
);
Expand Down
81 changes: 81 additions & 0 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -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<Props> = ({ 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 (
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
{`${todos.filter(el => !el.completed).length} items left`}
</span>

{/* Active link should have the 'selected' class */}
<nav className="filter" data-cy="Filter">
<a
onClick={() => {
onFilter(FilterMethods.all);
}}
className={classNames('filter__link', {
selected: filterList === FilterMethods.all,
})}
data-cy="FilterLinkAll"
>
All
</a>

<a
onClick={() => onFilter(FilterMethods.active)}
className={classNames('filter__link', {
selected: filterList === FilterMethods.active,
})}
data-cy="FilterLinkActive"
>
Active
</a>

<a
onClick={() => onFilter(FilterMethods.completed)}
className={classNames('filter__link', {
selected: filterList === FilterMethods.completed,
})}
data-cy="FilterLinkCompleted"
>
Completed
</a>
</nav>

{/* this button should be disabled if there are no completed todos */}

<button
disabled={todos.every(el => !el.completed)}
onClick={() => clearCompleted()}
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
>
Clear completed
</button>
</footer>
);
};
43 changes: 43 additions & 0 deletions src/components/Form.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<form
onSubmit={e => {
e.preventDefault();
if (!title.trim()) {
return;
}

addTodo({
id: +new Date(),
title: title.trim(),
completed: false,
});

setTitle('');
}}
>
<input
autoFocus
ref={inputFocus}
data-cy="NewTodoField"
type="text"
className="todoapp__new-todo"
placeholder="What needs to be done?"
value={title}
onChange={e => setTitle(e.target.value)}
/>
</form>
);
};
42 changes: 42 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<header className="todoapp__header">
{/* this button should have `active` class only if all todos are completed */}
{todos.length !== 0 && (
<button
onClick={toggleAll}
type="button"
className={classNames('todoapp__toggle-all', {
active: todos.every(el => el.completed),
})}
data-cy="ToggleAllButton"
/>
)}

{/* Add a todo on form submit */}
<Form />
</header>
);
};
24 changes: 24 additions & 0 deletions src/components/Main.tsx
Original file line number Diff line number Diff line change
@@ -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<Props> = ({ visibleTodos }) => {
const [editingTodo, setEditingTodo] = useState<Date | number | null>(null);

return (
<section className="todoapp__main" data-cy="TodoList">
{visibleTodos?.map(todo => (
<TodoItem
key={`${todo.id}-${todo.completed}`}
todo={todo}
editing={editingTodo}
onEdit={setEditingTodo}
/>
))}
</section>
);
};
Loading

0 comments on commit 25b198a

Please sign in to comment.