Created
March 23, 2025 14:05
-
-
Save AniruddhaAdak/d9616412abf5d43b7abf87021b847973 to your computer and use it in GitHub Desktop.
Description: A classic app to manage tasks. Key Features:Add, edit, and delete tasks. Mark tasks as completed with a checkbox. Display tasks in a simple list.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useEffect, useRef } from 'react'; | |
import { Button } from "@/components/ui/button" | |
import { Input } from "@/components/ui/input" | |
import { Checkbox } from "@/components/ui/checkbox" | |
import { Trash2, Edit, CheckCircle, Circle, XCircle } from 'lucide-react'; | |
import { cn } from "@/lib/utils" | |
import { motion, AnimatePresence } from 'framer-motion'; | |
interface Todo { | |
id: string; | |
text: string; | |
completed: boolean; | |
} | |
const TodoApp = () => { | |
const [todos, setTodos] = useState<Todo[]>([]); | |
const [newTodo, setNewTodo] = useState(''); | |
const [editingId, setEditingId] = useState<string | null>(null); | |
const [editText, setEditText] = useState(''); | |
const editInputRef = useRef<HTMLInputElement>(null); | |
// Focus on edit input when editing starts | |
useEffect(() => { | |
if (editingId && editInputRef.current) { | |
editInputRef.current.focus(); | |
} | |
}, [editingId]); | |
const handleAddTodo = () => { | |
if (newTodo.trim()) { | |
const todo: Todo = { | |
id: crypto.randomUUID(), | |
text: newTodo.trim(), | |
completed: false, | |
}; | |
setTodos([...todos, todo]); | |
setNewTodo(''); | |
} | |
}; | |
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
setNewTodo(e.target.value); | |
}; | |
const handleToggleComplete = (id: string) => { | |
setTodos( | |
todos.map((todo) => | |
todo.id === id ? { ...todo, completed: !todo.completed } : todo | |
) | |
); | |
}; | |
const handleDeleteTodo = (id: string) => { | |
setTodos(todos.filter((todo) => todo.id !== id)); | |
if (editingId === id) { | |
setEditingId(null); | |
} | |
}; | |
const handleEditTodo = (id: string, currentText: string) => { | |
setEditingId(id); | |
setEditText(currentText); | |
}; | |
const handleUpdateTodo = (id: string) => { | |
if (editText.trim()) { | |
setTodos( | |
todos.map((todo) => | |
todo.id === id ? { ...todo, text: editText.trim() } : todo | |
) | |
); | |
} | |
setEditingId(null); | |
setEditText(''); | |
}; | |
const handleCancelEdit = () => { | |
setEditingId(null); | |
setEditText(''); | |
}; | |
return ( | |
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 p-4 sm:p-8"> | |
<div className="max-w-2xl mx-auto bg-white/10 backdrop-blur-md rounded-xl shadow-lg p-4 sm:p-6 border border-white/10"> | |
<h1 className="text-3xl sm:text-4xl font-bold text-white mb-6 text-center"> | |
My To-Do List | |
</h1> | |
{/* Input and Add Button */} | |
<div className="flex flex-col sm:flex-row gap-4 mb-6"> | |
<Input | |
type="text" | |
placeholder="Add a new task..." | |
value={newTodo} | |
onChange={handleInputChange} | |
className="bg-black/20 text-white border-gray-700 placeholder:text-gray-400" | |
onKeyDown={(e) => { | |
if (e.key === 'Enter') { | |
handleAddTodo(); | |
} | |
}} | |
/> | |
<Button | |
onClick={handleAddTodo} | |
className="bg-blue-500/90 hover:bg-blue-500 text-white font-semibold px-6 py-3 rounded-md transition-colors duration-300 | |
shadow-lg hover:shadow-blue-500/50" | |
disabled={!newTodo.trim()} | |
> | |
Add Task | |
</Button> | |
</div> | |
{/* Todo List */} | |
<AnimatePresence> | |
{todos.map((todo) => ( | |
<motion.div | |
key={todo.id} | |
initial={{ opacity: 0, y: -10, scale: 0.95 }} | |
animate={{ opacity: 1, y: 0, scale: 1 }} | |
exit={{ opacity: 0, x: -20, scale: 0.9 }} | |
transition={{ duration: 0.2 }} | |
className={cn( | |
"flex items-center justify-between gap-4 mb-4 p-3 sm:p-4 rounded-lg", | |
"bg-black/20 border border-white/10", | |
"hover:bg-black/30 transition-colors duration-300", | |
"shadow-md" | |
)} | |
> | |
<div className="flex items-center gap-4 flex-1 min-w-0"> | |
<Checkbox | |
checked={todo.completed} | |
onCheckedChange={() => handleToggleComplete(todo.id)} | |
className={cn( | |
"border-gray-700", | |
todo.completed | |
? "bg-green-500/20 text-green-400" | |
: "text-white" | |
)} | |
aria-label="Mark as completed" | |
> | |
{todo.completed ? ( | |
<CheckCircle className="h-5 w-5" /> | |
) : ( | |
<Circle className="h-5 w-5" /> | |
)} | |
</Checkbox> | |
{editingId === todo.id ? ( | |
<Input | |
ref={editInputRef} | |
type="text" | |
value={editText} | |
onChange={(e) => setEditText(e.target.value)} | |
className="bg-black/20 text-white border-gray-700 placeholder:text-gray-400 flex-1 min-w-0" | |
onKeyDown={(e) => { | |
if (e.key === 'Enter') { | |
handleUpdateTodo(todo.id); | |
} else if (e.key === 'Escape') { | |
handleCancelEdit(); | |
} | |
}} | |
/> | |
) : ( | |
<span | |
className={cn( | |
"text-white text-base sm:text-lg truncate", | |
todo.completed && "line-through text-gray-400" | |
)} | |
> | |
{todo.text} | |
</span> | |
)} | |
</div> | |
<div className="flex gap-2"> | |
{editingId === todo.id ? ( | |
<> | |
<Button | |
onClick={() => handleUpdateTodo(todo.id)} | |
className="bg-green-500/90 hover:bg-green-500 text-white p-2 rounded-md transition-colors duration-200" | |
size="icon" | |
aria-label="Confirm Edit" | |
> | |
<CheckCircle className="h-4 w-4" /> | |
</Button> | |
<Button | |
onClick={handleCancelEdit} | |
className="bg-gray-500/90 hover:bg-gray-500 text-white p-2 rounded-md transition-colors duration-200" | |
size="icon" | |
aria-label="Cancel Edit" | |
> | |
<XCircle className="h-4 w-4" /> | |
</Button> | |
</> | |
) : ( | |
<> | |
<Button | |
onClick={() => handleEditTodo(todo.id, todo.text)} | |
className="bg-blue-500/90 hover:bg-blue-500 text-white p-2 rounded-md transition-colors duration-200" | |
size="icon" | |
aria-label="Edit" | |
> | |
<Edit className="h-4 w-4" /> | |
</Button> | |
<Button | |
onClick={() => handleDeleteTodo(todo.id)} | |
className="bg-red-500/90 hover:bg-red-500 text-white p-2 rounded-md transition-colors duration-200" | |
size="icon" | |
aria-label="Delete" | |
> | |
<Trash2 className="h-4 w-4" /> | |
</Button> | |
</> | |
)} | |
</div> | |
</motion.div> | |
))} | |
</AnimatePresence> | |
{todos.length === 0 && ( | |
<div className="text-center text-gray-400 py-6"> | |
No tasks yet. Add some! | |
</div> | |
)} | |
</div> | |
</div> | |
); | |
}; | |
export default TodoApp; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment