Skip to content

Commit

Permalink
solution
Browse files Browse the repository at this point in the history
  • Loading branch information
yuliia-nudyk committed Oct 5, 2024
1 parent f1536f7 commit a05c0ba
Show file tree
Hide file tree
Showing 13 changed files with 439 additions and 147 deletions.
170 changes: 27 additions & 143 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,156 +1,40 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';
// #region imports
import React, { useContext, useState } from 'react';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { TodoList } from './components/TodoList';
import { TodosContext } from './components/TodosContext';
import { getFilteredTodos } from './services/getFilteredTodos';
import { FilterStatus } from './types/FilterStatus';
// #endregion

export const App: React.FC = () => {
const { todos } = useContext(TodosContext);
const sortedTodos = {
active: todos.filter(({ completed }) => !completed),
completed: todos.filter(({ completed }) => completed),
};
const [filterStatus, setFilterStatus] = useState(FilterStatus.All);

const filteredTodos = getFilteredTodos(todos, sortedTodos, filterStatus);

return (
<div className="todoapp">
<h1 className="todoapp__title">todos</h1>

<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"
/>

{/* 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>

<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>

<span data-cy="TodoTitle" className="todo__title">
Completed Todo
</span>

{/* Remove button appears only on hover */}
<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>

{/* 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>

<span data-cy="TodoTitle" className="todo__title">
Not Completed Todo
</span>

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>
<Header />

{/* 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>
<TodoList todos={filteredTodos} />

{/* 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>

{/* 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>

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

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

{/* 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
sortedTodos={sortedTodos}
filterStatus={filterStatus}
onStatusChange={setFilterStatus}
/>
)}
</div>
</div>
);
Expand Down
66 changes: 66 additions & 0 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// #region imports
import cn from 'classnames';
import { useContext } from 'react';
import { FilterStatus } from '../types/FilterStatus';
import { Todo } from '../types/Todo';
import { TodosContext } from './TodosContext';
// #endregion

type Props = {
sortedTodos: {
active: Todo[];
completed: Todo[];
};
filterStatus: FilterStatus;
onStatusChange: (status: FilterStatus) => void;
};

export const Footer: React.FC<Props> = ({
sortedTodos,
filterStatus,
onStatusChange,
}) => {
const { changeTodos } = useContext(TodosContext);
const { active, completed } = sortedTodos;
const filterLinks: {
[key: string]: string;
} = {
All: '#/',
Active: '#/active',
Completed: '#/completed',
};

return (
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
{`${active.length} items left`}
</span>

<nav className="filter" data-cy="Filter">
{Object.values(FilterStatus).map(status => (
<a
key={status}
href={filterLinks[status]}
className={cn('filter__link', {
selected: status === filterStatus,
})}
data-cy={`FilterLink${status}`}
onClick={() => onStatusChange(status)}
>
{status}
</a>
))}
</nav>

<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
disabled={!completed.length}
onClick={() => changeTodos(active)}
>
Clear completed
</button>
</footer>
);
};
84 changes: 84 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// #region imports
import classNames from 'classnames';
import {
FormEvent,
memo,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { TodosContext } from './TodosContext';
// #endregion

export const Header = memo(function Header() {
// #region hooks
const { todos, changeTodos } = useContext(TodosContext);
const [newTitle, setNewTitle] = useState('');
const titleInput = useRef<HTMLInputElement>(null);

useEffect(() => {
titleInput.current?.focus();
});
// #endregion

const areTodosCompleted = todos.every(todo => todo.completed);

// #region handlings
const handleTodosToggle = () => {
changeTodos(
todos.map(todo => ({
...todo,
completed: !areTodosCompleted,
})),
);
};

const handleSubmit = (e: FormEvent) => {
e.preventDefault();

const trimmedTitle = newTitle.trim();

if (!trimmedTitle) {
return;
}

changeTodos([
...todos,
{
id: +new Date(),
title: trimmedTitle,
completed: false,
},
]);
setNewTitle('');
};
// #endregion

return (
<header className="todoapp__header">
{todos.length > 0 && (
<button
type="button"
className={classNames('todoapp__toggle-all', {
active: areTodosCompleted,
})}
onClick={handleTodosToggle}
data-cy="ToggleAllButton"
/>
)}

<form onSubmit={handleSubmit}>
<input
ref={titleInput}
data-cy="NewTodoField"
type="text"
className="todoapp__new-todo"
placeholder="What needs to be done?"
value={newTitle}
onChange={e => setNewTitle(e.target.value)}
/>
</form>
</header>
);
});
Loading

0 comments on commit a05c0ba

Please sign in to comment.