Skip to content

Commit

Permalink
add task solution
Browse files Browse the repository at this point in the history
  • Loading branch information
tonni004 committed Nov 12, 2024
1 parent f1536f7 commit 3355fef
Show file tree
Hide file tree
Showing 13 changed files with 444 additions and 165 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ Implement a simple [TODO app](https://mate-academy.github.io/react_todo-app/) th
- Implement a solution following the [React task guidelines](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline).
- Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript).
- Open another terminal and run tests with `npm test` to ensure your solution is correct.
- Replace `<your_account>` with your GitHub username in the [DEMO LINK](https://<your_account>.github.io/react_todo-app/) and add it to the PR description.
- Replace `<your_account>` with your GitHub username in the [DEMO LINK](https://tonni004.github.io/react_todo-app/) and add it to the PR description.
12 changes: 7 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"devDependencies": {
"@cypress/react18": "^2.0.1",
"@mate-academy/scripts": "^1.8.5",
"@mate-academy/scripts": "^1.9.12",
"@mate-academy/students-ts-config": "*",
"@mate-academy/stylelint-config": "*",
"@types/node": "^20.14.10",
Expand Down
163 changes: 8 additions & 155 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,157 +1,10 @@
/* 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>

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

<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>
</div>
</div>
);
};
import { TodosProvider } from './TodosContext';
import { TodoApp } from './components/TodoApp';

export const App: React.FC = () => (
<TodosProvider>
<TodoApp />
</TodosProvider>
);
54 changes: 54 additions & 0 deletions src/FocusContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, {
createContext,
useContext,
useRef,
useCallback,
useEffect,
} from 'react';
import { useTodos } from './TodosContext';

interface FocusContextType {
focusInput: () => void;
inputRef: React.RefObject<HTMLInputElement>;
}

const FocusContext = createContext<FocusContextType | undefined>(undefined);

export const FocusProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const inputRef = useRef<HTMLInputElement>(null);
const { todos } = useTodos();

const focusInput = useCallback(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);

useEffect(() => {
if (todos.length) {
const input = document.querySelector(
'[data-cy="NewTodoField"]',
) as HTMLInputElement;

input?.focus();
}
}, [todos.length]);

return (
<FocusContext.Provider value={{ focusInput, inputRef }}>
{children}
</FocusContext.Provider>
);
};

export const useFocus = () => {
const context = useContext(FocusContext);

if (!context) {
throw new Error('useFocus must be used within a FocusProvider');
}

return context;
};
93 changes: 93 additions & 0 deletions src/TodosContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { createContext, useContext, useState, useEffect } from 'react';

interface Todo {
id: number;
title: string;
completed: boolean;
}

interface TodosContextProps {
todos: Todo[];
addTodo: (title: string) => void;
toggleTodo: (id: number) => void;
deleteTodo: (id: number) => void;
clearCompleted: () => void;
updateTodo: (id: number, title: string) => void;
toggleAllTodos: () => void;
}

const TodosContext = createContext<TodosContextProps | undefined>(undefined);

export const TodosProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [todos, setTodos] = useState<Todo[]>(() => {
const savedTodos = localStorage.getItem('todos');

return savedTodos ? JSON.parse(savedTodos) : [];
});

useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);

const addTodo = (title: string) => {
setTodos([...todos, { id: +new Date(), title, completed: false }]);
};

const toggleTodo = (id: number) => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo,
),
);
};

const toggleAllTodos = () => {
const allCompleted = todos.every(todo => todo.completed);

setTodos(todos.map(todo => ({ ...todo, completed: !allCompleted })));
};

const deleteTodo = (id: number) => {
setTodos(todos.filter(todo => todo.id !== id));
};

const clearCompleted = () => {
setTodos(todos.filter(todo => !todo.completed));
};

const updateTodo = (id: number, title: string) => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, title: title.trim() || 'Untitled' } : todo,
),
);
};

return (
<TodosContext.Provider
value={{
todos,
addTodo,
toggleTodo,
deleteTodo,
clearCompleted,
updateTodo,
toggleAllTodos,
}}
>
{children}
</TodosContext.Provider>
);
};

export const useTodos = () => {
const context = useContext(TodosContext);

if (!context) {
throw new Error('useTodos must be used within a TodosProvider');
}

return context;
};
Loading

0 comments on commit 3355fef

Please sign in to comment.