import { useEffect, useMemo, useRef, useState } from "react"; // Single-file React To‑Do app // Features: add, edit, complete, delete, filter (All/Active/Completed), // clear completed, reorder via drag, and localStorage persistence. // Works out of the box in a Vite React template by replacing App.jsx. const STORAGE_KEY = "todoapp.v1.items"; function uid() { if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID(); return Math.random().toString(36).slice(2) + Date.now().toString(36); } export default function App() { const [items, setItems] = useState(() => { try { const raw = localStorage.getItem(STORAGE_KEY); return raw ? JSON.parse(raw) : []; } catch { return []; } }); const [filter, setFilter] = useState("all"); // all | active | completed const [draft, setDraft] = useState(""); const [editingId, setEditingId] = useState(null); const [editingText, setEditingText] = useState(""); const inputRef = useRef(null); useEffect(() => { localStorage.setItem(STORAGE_KEY, JSON.stringify(items)); }, [items]); const left = items.filter((t) => !t.done).length; const filtered = useMemo(() => { switch (filter) { case "active": return items.filter((t) => !t.done); case "completed": return items.filter((t) => t.done); default: return items; } }, [items, filter]); function addItem() { const text = draft.trim(); if (!text) return; setItems((prev) => [{ id: uid(), text, done: false }, ...prev]); setDraft(""); inputRef.current?.focus(); } function toggle(id) { setItems((prev) => prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t))); } function remove(id) { setItems((prev) => prev.filter((t) => t.id !== id)); } function beginEdit(it) { setEditingId(it.id); setEditingText(it.text); } function commitEdit() { if (!editingId) return; const text = editingText.trim(); if (!text) { // Empty means delete setItems((prev) => prev.filter((t) => t.id !== editingId)); } else { setItems((prev) => prev.map((t) => (t.id === editingId ? { ...t, text } : t))); } setEditingId(null); setEditingText(""); } function clearCompleted() { setItems((prev) => prev.filter((t) => !t.done)); } // Simple drag to reorder const dragSrcId = useRef(null); function onDragStart(e, id) { dragSrcId.current = id; e.dataTransfer.effectAllowed = "move"; } function onDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = "move"; } function onDrop(e, targetId) { e.preventDefault(); const srcId = dragSrcId.current; if (!srcId || srcId === targetId) return; setItems((prev) => { const srcIdx = prev.findIndex((t) => t.id === srcId); const tgtIdx = prev.findIndex((t) => t.id === targetId); if (srcIdx === -1 || tgtIdx === -1) return prev; const next = prev.slice(); const [moved] = next.splice(srcIdx, 1); next.splice(tgtIdx, 0, moved); return next; }); dragSrcId.current = null; } return (

✅ To‑Do

React + Vite • Local first

setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") addItem(); }} />
{[ { id: "all", label: "All" }, { id: "active", label: "Active" }, { id: "completed", label: "Completed" }, ].map(({ id, label }) => ( ))}
{left} left
); } const styles = { page: { minHeight: "100dvh", background: "linear-gradient(180deg,#0f172a 0%, #0b1024 100%)", color: "#e5e7eb", display: "flex", alignItems: "center", justifyContent: "center", padding: 24, boxSizing: "border-box", fontFamily: "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica Neue, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\"", }, card: { width: "min(720px, 100%)", background: "#0b122c", border: "1px solid rgba(255,255,255,0.08)", borderRadius: 16, boxShadow: "0 10px 30px rgba(0,0,0,0.35)", padding: 20, }, header: { display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 12, }, inputRow: { display: "flex", gap: 8, marginTop: 6, }, input: { flex: 1, padding: "12px 14px", borderRadius: 12, border: "1px solid rgba(255,255,255,0.12)", background: "#0a1026", color: "#e5e7eb", outline: "none", }, primaryBtn: { padding: "12px 14px", borderRadius: 12, border: "1px solid rgba(255,255,255,0.12)", background: "#1e40af", color: "white", fontWeight: 600, cursor: "pointer", }, ghostBtn: { padding: "8px 10px", borderRadius: 10, border: "1px solid rgba(255,255,255,0.12)", background: "transparent", color: "#e5e7eb", cursor: "pointer", }, toolbar: { marginTop: 12, marginBottom: 8, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, }, chip: { padding: "8px 12px", borderRadius: 999, border: "1px solid rgba(255,255,255,0.12)", background: "transparent", color: "#e5e7eb", cursor: "pointer", }, chipActive: { background: "#1e293b", }, list: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 8, }, item: { display: "grid", gridTemplateColumns: "28px 1fr auto", alignItems: "center", gap: 10, padding: 12, borderRadius: 12, background: "#0a132f", border: "1px solid rgba(255,255,255,0.06)", }, checkboxLabel: { display: "inline-flex", width: 20, height: 20, alignItems: "center", justifyContent: "center", borderRadius: 6, background: "#0b173a", border: "1px solid rgba(255,255,255,0.12)", }, text: { fontSize: 16, lineHeight: 1.4, }, editInput: { padding: "8px 10px", borderRadius: 8, border: "1px solid rgba(255,255,255,0.12)", background: "#0b122c", color: "#e5e7eb", outline: "none", }, itemBtns: { display: "flex", gap: 6, }, iconBtn: { padding: "6px 8px", borderRadius: 8, border: "1px solid rgba(255,255,255,0.12)", background: "transparent", color: "#e5e7eb", cursor: "pointer", }, footer: { marginTop: 16, opacity: 0.6, fontSize: 14, }, };