Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
349a717
first comp
Bianka2112 May 27, 2025
d2ed809
create list section and render added tasks
Bianka2112 May 27, 2025
193a08c
connected all comps to useStore, formatted time stamp
Bianka2112 May 27, 2025
1860c01
added done list, logic to filter
Bianka2112 May 28, 2025
cdcef66
added tailwind npm, taskCount and deleteTask features
Bianka2112 Jun 23, 2025
6c82a23
basic dashboard for counts, error for empty input
Bianka2112 Jun 23, 2025
66c37e7
light styling to input form
Bianka2112 Jun 23, 2025
1b21486
styling for pending and completed task lists
Bianka2112 Jun 23, 2025
c207f38
create tabs for each list, unsure i like it.
Bianka2112 Jun 23, 2025
fb82920
fix pending/incomplete naming for clarity
Bianka2112 Jun 23, 2025
679d49e
fix delete color, awful red. cleanup structure for a11y
Bianka2112 Jun 23, 2025
04730c0
finally a lighthouse pass delete color, still not great
Bianka2112 Jun 23, 2025
e4cd801
fix tailwind import issues, missing configs, chaos reigned
Bianka2112 Jun 24, 2025
5b6c481
stability restored, readme updated
Bianka2112 Jun 24, 2025
23a8c03
tweaks, favicon add
Bianka2112 Jun 24, 2025
c694b99
title icon, tweak color styling
Bianka2112 Jun 24, 2025
0b74a3f
dark toggle mobile fix
Bianka2112 Jun 24, 2025
d5ddeca
ux- spacing, animations, added keyframes
Bianka2112 Jun 24, 2025
f19c61b
add pulse to tabs for clarity, ux
Bianka2112 Jun 24, 2025
215a396
trying to get the animations right, close enough
Bianka2112 Jun 24, 2025
f6f4ef4
clear err on retype, fun quotes feature for empty state
Bianka2112 Jun 24, 2025
f2fccf9
delete fx toast with animation
Bianka2112 Jun 24, 2025
37e072c
fix empty quote on initial load, code tweaks
Bianka2112 Jun 24, 2025
dafdca9
styling tweaks, ta-da
Bianka2112 Jun 24, 2025
4d1efe2
finally fix toast style, now that it works, allegedly
Bianka2112 Jun 24, 2025
bcf743c
improved toast, hopefully. New task pulses pending tab
Bianka2112 Jun 24, 2025
58fce62
media q for spacing on mobile and larger
Bianka2112 Jun 24, 2025
9e8983f
dark mode logo, but dont like it, temp
Bianka2112 Jun 24, 2025
f1d44b7
better tab design and shifting selected line style
Bianka2112 Jun 25, 2025
f3db82e
deployed site, tweaking styling
Bianka2112 Jun 25, 2025
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
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,48 @@
# Todo
# 📝 Doing It... – A Minimalist To-Do App with Zustand + Tailwind

**Doing It...** is a fun and interactive to-do list app built with React, Zustand for global state, and Tailwind CSS for styling. Designed to help you stay focused and inspired while you check off tasks - simple.

---

## 🔗 **Project Access**:

🚀 [Live Demo](https://blr-tootoodo.netlify.app/)

---

## ✨ Features

- ✅ Add, complete, undo, and delete tasks
- 🌑 Toggle **dark/light mode** with full theme styling
- 📆 Timestamp when tasks are created
- 🎯 Task counter for completed items
- 🧠 Zustand global state – no prop drilling
- 🧼 Clean, responsive design with a11y focus
- 📱 Mobile-first responsive layout

---

## 🛠 Tech Stack

React
Vite
Zustand
Tailwind CSS
PostCSS

---

## ♿ Accessibility & Performance

🌱 Fully responsive: from 320px to 1600px+
🏁 Lighthouse score: 100 %
🎨 Color contrast and keyboard navigation friendly

---

## 💡 Future Features (Stretch Goals)

Project categories or tags
Due dates and visual overdue indicators
"Complete All" functionality
Local storage or database persistence
18 changes: 13 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@
<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" />
<meta
name="description"
content="A minimal and responsive to-do app built with Zustand, Tailwind CSS, and React."
/>
<meta name="theme-color" content="#1e293b" />

<link
rel="icon"
type="image/png+xml"
href="./public/checkmark-todo-favicon.png"
/>
<link rel="stylesheet" href="index.css" />
<title>Todo</title>
</head>
<body>
<div id="root"></div>
<script
type="module"
src="./src/main.jsx">
</script>
<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,18 +10,24 @@
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.10",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"zustand": "^5.0.5"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.21",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.1",
"vite": "^6.2.0"
}
}
6 changes: 6 additions & 0 deletions postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
Binary file added public/checkmark-todo-favicon.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/justDoIt.onBlack.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/justDoIt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
Please include your Netlify link here.
Please include your Netlify link here:

https://blr-tootoodo.netlify.app/
11 changes: 10 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import AppLayout from "./components/AppLayout"
import Dashboard from "./components/Dashboard"
import { Tabs } from "./components/Tabs"
import { TaskForm } from "./components/TaskForm"

export const App = () => {
return (
<h1>React Boilerplate</h1>
<AppLayout>
<TaskForm />
<Tabs />
<Dashboard />
</AppLayout>
)
}
46 changes: 46 additions & 0 deletions src/components/AppLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect, useState } from "react"

const AppLayout = ({ children }) => {
const [isDark, setIsDark ] = useState(false)

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

const handleToggle = () => {
console.log("Toggling dark mode:", !isDark);
setIsDark(!isDark);
}

return (
<main className="min-h-screen bg-slate-100 dark:bg-slate-900 text-slate-900 dark:text-slate-100 flex items-center justify-center p-4 transition-colors">
<div className="bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 place-self-center w-11/12 max-w-md flex flex-col p-7 min-h-[550px] rounded-xl shadow-md">
<div className='flex items-center justify-between mt-5 mb-4 gap-2'>
<header className="flex items-center gap-2">
<img className="w-12 object-contain block dark:hidden" src="/justDoIt.png" alt="Just Do It logo"/>
<img className="w-12 object-contain hidden dark:block" src="/justDoIt.onBlack.png" alt="Just Do It logo"/>
<h1 className="text-2xl font-bold">Doing it...</h1>
</header>
<button
onClick={handleToggle}
className="text-sm bg-slate-200 dark:bg-slate-700 dark:text-slate-100 px-2 py-1 rounded hover:bg-indigo-300 dark:hover:bg-slate-400"
>
<span aria-hidden="true">
{isDark ? "☀️" : "🌙"}
</span>
<span className="hidden xs:inline">
{isDark ? " Light" : " Dark"}
</span>
</button>
</div>
{children}
</div>
</main>
)
}

export default AppLayout
29 changes: 29 additions & 0 deletions src/components/CompletedTaskList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useTasksStore } from "../stores/useTaskStore"
import { TaskItem } from "./TaskItem"

export const CompletedTaskList = () => {
const tasks = useTasksStore(state => state.tasks)
const completedTasks = tasks.filter(task => task.isCompleted)
const taskCount = useTasksStore(state => state.getCompletedCount())

if (completedTasks.length === 0) {
return (
<div className="flex flex-col items-center justify-center h-48 px-4 text-slate-500 text-sm italic mt-4 mb-2">
<p className="italic text-lg sm:text-xl dark:text-slate-300 opacity-80">
Lets get something ta-done ✨
</p>
</div>
)
}

return (
<section className="mt-6 border-t border-slate-200 pt-4">
<h2 className="text-lg font-semibold mb-2">The Ta-Dones:</h2>
<ul className="space-y-3">
{completedTasks.map(task => (
<TaskItem key={task.id} task={task} />
))}
</ul>
</section>
)
}
21 changes: 21 additions & 0 deletions src/components/Dashboard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useTasksStore } from "../stores/useTaskStore"

const Dashboard = () => {
const total = useTasksStore(state => state.tasks.length)
const completed = useTasksStore(state => state.getCompletedCount())
const pending = useTasksStore(state => state.getPendingCount())

return (
<section className="p-4 my-4 bg-gray-100 dark:bg-slate-700 rounded shadow-md">
<h2 className="text-xl font-bold mb-2">📋 Your Task Overview</h2>
<p>Total tasks: {total}</p>
<p>✅ Completed: {completed}</p>
<p>🕒 Remaining: {pending}</p>

{total === 0 && <p className="italic mt-2">Start by adding your first task!</p>}
{total > 0 && pending === 0 && <p className="mt-2 text-green-600 font-medium">You're all caught up! 🎉</p>}
</section>
)
}

export default Dashboard
58 changes: 58 additions & 0 deletions src/components/Tabs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useState } from "react"

import { useTasksStore } from "../stores/useTaskStore"
import { CompletedTaskList } from "./CompletedTaskList"
import { TaskList } from "./TaskList"

export const Tabs = () => {
const [activeTab, setActiveTab] = useState("todo")

const pendingCount = useTasksStore(state => state.getPendingCount())
const completedCount = useTasksStore(state => state.getCompletedCount())
const pulseTab = useTasksStore(state => state.pulseTab)


return (
<div className="w-full mt-6 max-w-sm mx-auto">
<div className="relative flex mr-1 mb-6 border-b border-slate-300 dark:border-slate-600 ">
<button
onClick={() => setActiveTab("todo")}
className={`w-1/2 px-4 py-2 text-sm font-medium rounded-md transition
${activeTab === "todo"
? "bg-slate-700 text-white"
: "bg-slate-200 text-slate-700 hover:bg-indigo-300"}
${pulseTab === "todo"
? "animate-pulse-once"
: ""}`}
>
Pending Tasks ({pendingCount})
</button>
<button
onClick={() => setActiveTab("completed")}
className={`w-1/2 px-4 py-2 text-sm font-medium rounded-md transition
${activeTab === "completed"
? "bg-slate-700 text-white "
: "bg-slate-200 text-slate-700 hover:bg-indigo-300"}
${pulseTab === "completed"
? "animate-pulse-once"
: ""}`}
>
Completed Tasks ({completedCount})
</button>
<span
className={`absolute bottom-0 left-0 h-1 w-1/2 bg-indigo-500 rounded transition-all duration-300 ease-out transform
${activeTab === "completed" ? "translate-x-full" : "translate-x-0"}`}
/>
</div>
<div
className={`min-h-[80px] xs:min-h-[250px] transition-opacity duration-500 ease-out
${activeTab === "todo"
? "opacity-100"
: "opacity-50"}`}
>
{activeTab === "todo" && <TaskList />}
{activeTab === "completed" && <CompletedTaskList />}
</div>
</div>
)
}
42 changes: 42 additions & 0 deletions src/components/TaskForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useState } from "react"
import { useTasksStore } from "../stores/useTaskStore"

export const TaskForm = () => {
const [taskMsg, setTaskMsg] = useState("")
const [error, setError] = useState()
const createTask = useTasksStore(state => state.createTask)
const triggerPulse = useTasksStore(state => state.triggerPulseTab)

const handleSubmit = e => {
e.preventDefault()
if (taskMsg.trim() === "") {
setError("Cannot add an empty task.")
return
}
createTask(taskMsg)
triggerPulse("todo")
setTaskMsg("")
setError("")
}

return (
<form onSubmit={handleSubmit} className="space-y-2">
<label htmlFor="taskInput" className="sr-only">Add new task</label>
<input
id="taskInput"
type="text"
value={taskMsg}
onChange={e => {
setTaskMsg(e.target.value)
if (error) setError("")
}}
placeholder="Next on the list..."
className="w-full p-2 border rounded resize-none dark:text-slate-600"
/>
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
<button type="submit" className="px-4 py-2 bg-slate-600 text-white rounded hover:bg-indigo-400 disabled:bg-gray-300">
Add Task
</button>
</form>
)
}
Loading