diff --git a/src/App.css b/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 00000000..c5ed2a25 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,72 @@ +import { useState, useEffect } from 'react'; +import Header from './components/Header'; +import AddTodo from './components/AddTodo'; +import Category from './components/Category'; +import TodoList from './components/TodoList'; + +function App() { + const [todos, setTodos] = useState(() => { + const savedTodos = localStorage.getItem('todos'); + return savedTodos ? JSON.parse(savedTodos) : [ + { id: 1, text: 'Eat', completed: false }, + { id: 2, text: 'Sleep', completed: false }, + { id: 3, text: 'Repeat', completed: false }, + ]; + }); + const [filter, setFilter] = useState('All'); + + useEffect(() => { + localStorage.setItem('todos', JSON.stringify(todos)); + }, [todos]); + + const addTodo = (text) => { + const newTodo = { + id: Date.now(), + text, + completed: false, + }; + setTodos([newTodo, ...todos]); + }; + + const deleteTodo = (id) => { + setTodos(todos.filter((todo) => todo.id !== id)); + }; + + const toggleComplete = (id) => { + setTodos( + todos.map((todo) => + todo.id === id ? { ...todo, completed: !todo.completed } : todo + ) + ); + }; + + const editTodo = (id, newText) => { + setTodos( + todos.map((todo) => + todo.id === id ? { ...todo, text: newText } : todo + ) + ); + }; + + const filteredTodos = todos.filter((todo) => { + if (filter === 'Active') return !todo.completed; + if (filter === 'Completed') return todo.completed; + return true; + }); + + return ( +
+
+ + + +
+ ); +} + +export default App; \ No newline at end of file diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/AddTodo.jsx b/src/components/AddTodo.jsx new file mode 100644 index 00000000..c02c1b80 --- /dev/null +++ b/src/components/AddTodo.jsx @@ -0,0 +1,24 @@ +import { useState } from 'react'; + +export default function AddTodo({ onAdd }) { + const [input, setInput] = useState(''); + + const handleAdd = () => { + if (input.trim()) { + onAdd(input); + setInput(''); + } + }; + + return ( +
+ setInput(e.target.value)} + placeholder="Enter a task" + /> + +
+ ); +} \ No newline at end of file diff --git a/src/components/Category.jsx b/src/components/Category.jsx new file mode 100644 index 00000000..42ec996e --- /dev/null +++ b/src/components/Category.jsx @@ -0,0 +1,9 @@ +export default function Category({ current, onChange }) { + return ( +
+ + + +
+ ); +} diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 00000000..a84507f0 --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,9 @@ +export default function Header() { + return ( +
+

TodoMatic

+

What needs to be done?

+
+ ); + } + \ No newline at end of file diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx new file mode 100644 index 00000000..8501c2ea --- /dev/null +++ b/src/components/TodoItem.jsx @@ -0,0 +1,55 @@ +import { useState } from 'react'; + +export default function TodoItem({ todo, onDelete, onToggle, onEdit }) { + const [isEditing, setIsEditing] = useState(false); + const [editText, setEditText] = useState(todo.text); + + const handleEdit = () => { + if (isEditing) { + if (editText.trim() !== '') { + onEdit(todo.id, editText); + } + } + setIsEditing(!isEditing); + }; + + const handleCancel = () => { + setEditText(todo.text); + setIsEditing(false); + }; + + return ( +
+ onToggle(todo.id)} + /> + {isEditing ? ( + <> + setEditText(e.target.value)} + className="edit-input" + /> + + + + ) : ( + <> + + {todo.text} + + + + + )} +
+ ); +} diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx new file mode 100644 index 00000000..0dcc34a8 --- /dev/null +++ b/src/components/TodoList.jsx @@ -0,0 +1,18 @@ +import TodoItem from './TodoItem'; + +export default function TodoList({ todos, onDelete, onToggle, onEdit }) { + return ( +
+

{todos.length} tasks remaining

+ {todos.map((todo) => ( + + ))} +
+ ); +} \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 00000000..bc793e70 --- /dev/null +++ b/src/index.css @@ -0,0 +1,98 @@ +body { + font-family: 'Arial', sans-serif; + background-color: #f7f7f7; + margin: 0; + padding: 2rem; + display: flex; + justify-content: center; +} + +.App { + background: white; + padding: 2rem; + width: 400px; + box-shadow: 0 4px 10px rgba(0,0,0,0.1); + border-radius: 10px; +} + +.header { + text-align: center; + margin-bottom: 1.5rem; +} + +.add-todo { + display: flex; + gap: 0.5rem; + margin-bottom: 1.5rem; +} + +.add-todo input { + flex: 1; + padding: 0.5rem; + font-size: 1rem; +} + +.add-todo button { + background-color: black; + color: white; + border: none; + padding: 0 1rem; + font-size: 1rem; + cursor: pointer; +} + +.category { + display: flex; + justify-content: space-between; + margin-bottom: 1.5rem; +} + +.category button { + flex: 1; + padding: 0.5rem; + margin: 0 0.2rem; + border: 1px solid #ccc; + cursor: pointer; + background-color: white; +} + +.todo-list p { + font-weight: bold; + margin-bottom: 1rem; +} + +.todo-item { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 1rem; +} + +.todo-item input[type="checkbox"] { + width: 16px; + height: 16px; +} + +.todo-item span { + flex: 1; +} + +.todo-item button { + padding: 0.3rem 0.8rem; + border: none; + cursor: pointer; +} + +.todo-item button.delete { + background-color: #d9534f; + color: white; +} + +.todo-item .edit-input { + flex: 1; + padding: 0.3rem; + margin-right: 0.5rem; + font-size: 1rem; + border: 1px solid #ccc; + border-radius: 4px; +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 00000000..b9a1a6de --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +)