Skip to content

Commit

Permalink
added solution
Browse files Browse the repository at this point in the history
  • Loading branch information
AlNeon committed Feb 8, 2025
1 parent c867b63 commit 032b521
Show file tree
Hide file tree
Showing 13 changed files with 416 additions and 146 deletions.
178 changes: 37 additions & 141 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,156 +1,52 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';
import React, { useEffect, useRef, useState } from 'react';

export const App: React.FC = () => {
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>
import { FilterType } from './types/FilterType';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { TodoList } from './components/TodoList';
import { useTodos } from './components/TodosContext';

{/* 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>
export const App: React.FC = () => {
const { todos, setTodos } = useTodos();
const [query, setQuery] = useState<string>('');
const [filter, setFilter] = useState<FilterType>(FilterType.All);

{/* 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 inputRef = useRef<HTMLInputElement | null>(null);

{/* 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>
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, [todos]);

<span data-cy="TodoTitle" className="todo__title">
Todo is being saved now
</span>
const isAllCompleted = todos.every(todo => todo.completed);

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>
</section>
const handleAllTodoCompleted = () => {
const updatedTodos = todos.map(todo => ({
...todo,
completed: !isAllCompleted,
}));

{/* 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>
setTodos(updatedTodos);
};

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

<a
href="#/active"
className="filter__link"
data-cy="FilterLinkActive"
>
Active
</a>
<div className="todoapp__content">
<Header
query={query}
setQuery={setQuery}
isAllCompleted={isAllCompleted}
handleAllTodoCompleted={handleAllTodoCompleted}
inputRef={inputRef}
/>

<a
href="#/completed"
className="filter__link"
data-cy="FilterLinkCompleted"
>
Completed
</a>
</nav>
{todos.length > 0 && <TodoList filter={filter} />}

{/* 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 filter={filter} setFilter={setFilter} />}
</div>
</div>
);
Expand Down
40 changes: 40 additions & 0 deletions src/components/Filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import cn from 'classnames';

import { FilterType } from '../types/FilterType';

const filters = [
{ type: FilterType.All, href: '#/', dataCy: 'FilterLinkAll' },
{ type: FilterType.Active, href: '#/active', dataCy: 'FilterLinkActive' },
{
type: FilterType.Completed,
href: '#/completed',
dataCy: 'FilterLinkCompleted',
},
];

type Props = {
filter: FilterType;
setFilter: (filter: FilterType) => void;
};

export const Filter: React.FC<Props> = ({ filter, setFilter }) => (
<nav className="filter" data-cy="Filter">
{filters.map(({ type, href, dataCy }) => (
<a
key={type}
href={href}
className={cn('filter__link', {
selected: filter === type,
})}
data-cy={dataCy}
onClick={event => {
event.preventDefault();
setFilter(type);
}}
>
{type}
</a>
))}
</nav>
);
42 changes: 42 additions & 0 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';

import { FilterType } from '../types/FilterType';
import { Filter } from './Filter';
import { useTodos } from './TodosContext';

type Props = {
filter: FilterType;
setFilter: (filter: FilterType) => void;
};

export const Footer: React.FC<Props> = ({ filter, setFilter }) => {
const { todos, setTodos } = useTodos();

const numberOfActiveTodos = todos.filter(todo => !todo.completed).length;

const handleDeleteCompletedTodos = () => {
const unCompletedTodos = todos.filter(todo => !todo.completed);

setTodos(unCompletedTodos);
};

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

<Filter filter={filter} setFilter={setFilter} />

<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
disabled={!todos.some(todo => todo.completed)}
onClick={handleDeleteCompletedTodos}
>
Clear completed
</button>
</footer>
);
};
54 changes: 54 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import cn from 'classnames';
import { useTodos } from './TodosContext';

type Props = {
query: string;
setQuery: (query: string) => void;
isAllCompleted: boolean;
handleAllTodoCompleted: () => void;
inputRef: React.RefObject<HTMLInputElement>;
};

export const Header: React.FC<Props> = ({
query,
setQuery,
isAllCompleted,
handleAllTodoCompleted,
inputRef,
}) => {
const { todos, addTodo } = useTodos();

const handleAddTodo = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' && query.trim() !== '') {
addTodo(query.trim());
setQuery('');
}
};

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

<form>
<input
ref={inputRef}
data-cy="NewTodoField"
type="text"
className="todoapp__new-todo"
placeholder="What needs to be done?"
value={query}
onChange={event => setQuery(event.target.value)}
onKeyDown={handleAddTodo}
/>
</form>
</header>
);
};
Loading

0 comments on commit 032b521

Please sign in to comment.