Skip to content

Instantly share code, notes, and snippets.

@AniruddhaAdak
Created March 23, 2025 14:05
Show Gist options
  • Save AniruddhaAdak/d9616412abf5d43b7abf87021b847973 to your computer and use it in GitHub Desktop.
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.
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