Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# Todo
# Todo

## 🚀 Live Demo

https://task-by-task.netlify.app/
52 changes: 38 additions & 14 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Todo</title>
</head>
<body>
<div id="root"></div>
<script
type="module"
src="./src/main.jsx">
</script>
</body>
</html>

<head>
<meta charset="UTF-8" />
<link
rel="icon"
type="image/svg+xml"
href="./todolist.png"
/>
<link
rel="preconnect"
href="https://fonts.googleapis.com"
>
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin
>
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet"
>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>To-do List</title>
</head>

<body>
<div id="root"></div>
<script
type="module"
src="./src/main.jsx"
>
</script>
</body>

</html>
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.18",
"date-fns": "^4.1.0",
"framer-motion": "^12.26.2",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-datepicker": "^9.1.0",
"react-dom": "^19.0.0",
"tailwindcss": "^4.1.18",
"zustand": "^5.0.10"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
Expand Down
Binary file added public/darkmode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/todolist.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 42 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
import { useState, useEffect } from "react"
import { Heading } from "./components/Heading"
import { TodoList } from "./components/TodoList"
import { AddTodoToggle } from "./components/AddTodoToggle"
import { TodoForm } from "./components/TodoForm"
import { useTodoStore } from "./stores/todoStore"

export const App = () => {
const [isAdding, setIsAdding] = useState(false)
const darkMode = useTodoStore((state) => state.darkMode)

useEffect(() => {
if (darkMode) {
document.documentElement.classList.add("dark")
} else {
document.documentElement.classList.remove("dark")
}
}, [darkMode])

return (
<h1>React Boilerplate</h1>
<main className="min-h-screen grid-bg flex justify-center py-10">
<section className="
w-full max-w-md
bg-white dark:bg-gray-800
dark:text-white
dark:shadow-black/30
dark:border
dark:border-gray-700
rounded-2xl shadow-md
flex flex-col">
<Heading />

<div className="flex-1 px-4">
{isAdding && (
<TodoForm onClose={() => setIsAdding(false)} />
)}

<TodoList isAdding={isAdding} />
</div>

<AddTodoToggle onAdd={() => setIsAdding(true)} />
</section>
</main>
)
}
}
39 changes: 39 additions & 0 deletions src/components/AddTodoToggle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export const AddTodoToggle = ({ onAdd }) => {
return (
<div className="p-4 mb-4">
<button
onClick={onAdd}
type="button"
aria-label="Add a to-do"
className="
relative z-10
bg-white dark:bg-gray-900 rounded-xl shadow p-4 w-full
hover:bg-gray-50 transition cursor-pointer border border-gray-300
"
>
<div className="flex items-center gap-3">
<div
className="
w-7 h-7
rounded-full
bg-blue-700
dark:bg-lime-500
text-white
flex items-center justify-center
text-lg font-semibold
leading-none
"
>
<span className="mb-2 text-2xl">
+
</span>
</div>

<div className="text-blue-700 dark:text-lime-500 text-lg font-medium">
Add a to-do
</div>
</div>
</button>
</div>
)
}
72 changes: 72 additions & 0 deletions src/components/Heading.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { format } from "date-fns"
import { useTodoStore } from "../stores/todoStore"

export const Heading = () => {
const todos = useTodoStore((state) => state.todos)
const completeAllTodos = useTodoStore((state) => state.completeAllTodos)
const darkMode = useTodoStore((state) => state.darkMode)
const toggleDarkMode = useTodoStore((state) => state.toggleDarkMode)

const totalTodos = todos.length
const completed = todos.filter((todo) => todo.completed).length
const uncompleted = totalTodos - completed

const today = format(new Date(), "EEE d MMMM")

return (
<header className="header-bg text-white p-6 rounded-t-xl mb-1 xl:min-h-50">
<div className="flex justify-between items-start gap-4">

<div>
<h1 className="text-3xl md:text-4xl font-semibold mt-18 xl:mt-24 mb-2">
To-do list
</h1>
<p className="md:text-xl text-blue-50 ml-1">{today}</p>
</div>

<div className="text-right space-y-1">
<button
onClick={toggleDarkMode}
aria-label="Toggle dark mode"
className="ml auto mb-1 p-2 rounded-full hover:scale-105 transition">
<img
src="/darkmode.png"
alt="Dark mode toggle icon"
className={`w-8 h-8 transition ${darkMode ? "opacity-100" : "opacity-70"}`}
/>
</button>
<p className="text-sm">Total: {totalTodos}</p>
<p className="text-sm text-green-200 dark:text-green-400">
Completed: {completed}
</p>
<p className="text-sm text-yellow-200 dark:text-yellow-400">
Uncompleted: {uncompleted}
</p>

{totalTodos > 0 && (
<button
onClick={completeAllTodos}
disabled={uncompleted === 0}
className="
mt-2 xl:mt-10
px-3 py-1.5
text-sm
font-medium
rounded-md
border border-white/30
text-white
hover:bg-white/10
cursor-pointer
transition
disabled:opacity-40
disabled:cursor-not-allowed
"
>
Complete all
</button>
)}
</div>
</div>
</header>
)
}
99 changes: 99 additions & 0 deletions src/components/TodoForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useState } from "react"
import DatePicker from "react-datepicker"
import "react-datepicker/dist/react-datepicker.css"
import { useTodoStore } from "../stores/todoStore"

export const TodoForm = ({ onClose }) => {
const addTodo = useTodoStore((state) => state.addTodo)

const [text, setText] = useState("")
const [category, setCategory] = useState("General")
const [dueDate, setDueDate] = useState(null)


const handleSubmit = (e) => {
e.preventDefault()
if (!text.trim()) return
addTodo(text, category, dueDate || null)
onClose()
}

return (
<form
onSubmit={handleSubmit}
className="
bg-white
dark:bg-blue-900
p-4 mt-4
rounded-xl
shadow space-y-3
border border-gray-300
"
>
<textarea
value={text}
rows={1}
placeholder="What do you need to do?"
onChange={(e) => setText(e.target.value)}
className="w-full border rounded px-3 py-2 wrap-break-word resize-none"
/>

<div className="flex flex-col sm:flex-row gap-2">
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
className="
flex-1 border
rounded px-2 py-2
dark:bg-blue-900
dark:text-gray-100
dark:border-gray-100
scheme-light
dark:scheme-dark
"
>
<option>General</option>
<option>Work</option>
<option>Home</option>
<option>Personal</option>
<option>Family</option>
</select>

<div className="flex-1">
<DatePicker
selected={dueDate}
onChange={(date) => setDueDate(date)}
dateFormat="yyyy-MM-dd"
placeholderText="åååå-mm-dd"
className="
w-full
border
rounded
px-2 py-2
bg-white text-gray-900
dark:bg-blue-900 dark:text-gray-100 dark:border-gray-100
focus:outline-none
focus:ring-2 focus:ring-blue-500
"
/>
</div>
</div>

<div className="flex justify-end gap-2">
<button
type="button"
onClick={onClose}
className="text-gray-500 dark:text-gray-400 cursor-pointer px-4 py-1 rounded hover:bg-gray-100 transition"
>
Cancel
</button>
<button
type="submit"
className="bg-blue-500 text-white px-4 py-1 rounded cursor-pointer hover:bg-blue-600 transition"
>
Save
</button>
</div>
</form>
)
}
Loading