Skip to content

Commit

Permalink
add solution
Browse files Browse the repository at this point in the history
  • Loading branch information
an-marryKyslenko committed Nov 29, 2024
1 parent f1536f7 commit ab9039e
Show file tree
Hide file tree
Showing 9 changed files with 399 additions and 147 deletions.
152 changes: 9 additions & 143 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,156 +1,22 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';
import TodoList from './components/TodoList/TodoList';
import Footer from './components/Footer/Footer';
import Header from './components/Header/Header';
import { useTodos } from './context/context';

export const App: React.FC = () => {
const { todos } = useTodos();

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>

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

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

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

{/* 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 />}
</div>
</div>
);
Expand Down
46 changes: 46 additions & 0 deletions src/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import { useTodos } from '../../context/context';
import { SelectedBy } from '../../types/SelectedBy';
import classNames from 'classnames';

const Footer: React.FC = () => {
const { todos, selectedBy, setSelectedBy, clearAllCompletedTodos } =
useTodos();
const activeTodos = todos.filter(todo => !todo.completed);

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

<nav className="filter" data-cy="Filter">
{Object.values(SelectedBy).map(value => (
<a
key={value}
href={`#/${value}`}
className={classNames('filter__link', {
selected: selectedBy === value,
})}
data-cy={`FilterLink${value}`}
onClick={() => setSelectedBy(value)}
>
{value}
</a>
))}
</nav>

<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
disabled={activeTodos.length === todos.length}
onClick={clearAllCompletedTodos}
>
Clear completed
</button>
</footer>
);
};

export default Footer;
45 changes: 45 additions & 0 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useState } from 'react';
import { useTodos } from '../../context/context';
import classNames from 'classnames';

const Header: React.FC = () => {
const { todos, addTodo, mainRef, toggleAllTodos } = useTodos();
const [title, setTitle] = useState('');
const addNewTodo = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

if (title.trim()) {
addTodo(title.trim());
setTitle('');
}
};

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

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

export default Header;
120 changes: 120 additions & 0 deletions src/components/TodoItem/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
import React, { useRef, useState } from 'react';
import { Todo } from '../../types/Todo';
import classNames from 'classnames';
import { useTodos } from '../../context/context';

const TodoItem: React.FC<{ todo: Todo }> = ({ todo }) => {
const { deleteTodo, tempTodo, toggleTodo, editTodo } = useTodos();
const [isOpenEditForm, setIsOpenEditForm] = useState(false);
const editInputRef = useRef<HTMLInputElement>(null);

const handleDobleClick = () => {
setIsOpenEditForm(true);

setTimeout(() => {
editInputRef.current?.focus();
}, 0);
};

const editTitle = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

const form = new FormData(e.currentTarget);
const newTitle = form.get('TodoTitleField') as string;

if (!newTitle.trim()) {
deleteTodo(todo.id);
}

if (newTitle !== todo.title) {
editTodo(newTitle.trim(), todo.id);
}

setTimeout(() => {
setIsOpenEditForm(false);
}, 100);
};

const handleTitleBlur = (e: React.ChangeEvent<HTMLInputElement>) => {
const newTitle = e.target.value.trim();

if (!newTitle) {
deleteTodo(todo.id);
}

if (newTitle !== todo.title) {
editTodo(newTitle, todo.id);
}

setIsOpenEditForm(false);
};

const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Escape') {
setIsOpenEditForm(false);
}
};

return (
<div
data-cy="Todo"
className={classNames('todo', {
completed: todo.completed,
})}
>
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
defaultChecked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
</label>

{!isOpenEditForm ? (
tempTodo?.id === todo.id ? (
<span data-cy="TodoTitle" className="todo__title">
Todo is being saved now
</span>
) : (
<span
data-cy="TodoTitle"
className="todo__title"
onDoubleClick={handleDobleClick}
>
{todo.title}
</span>
)
) : (
<form onSubmit={editTitle}>
<input
data-cy="TodoTitleField"
name="TodoTitleField"
type="text"
className="todo__title-field"
placeholder="Empty todo will be deleted"
defaultValue={todo.title}
ref={editInputRef}
onBlur={handleTitleBlur}
onKeyUp={handleKeyUp}
/>
</form>
)}

{!isOpenEditForm && (
<button
type="button"
className="todo__remove"
data-cy="TodoDelete"
onClick={() => deleteTodo(todo.id)}
>
×
</button>
)}
</div>
);
};

export default TodoItem;
Loading

0 comments on commit ab9039e

Please sign in to comment.