diff --git a/README.md b/README.md
index d1c68b57..4daf910a 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,10 @@
-# Todo
\ No newline at end of file
+A simple and responsive todo app built with React and Zustand. Using styled-components fopr styling.
+The app allows users to add, complete, and remove tasks, while keeping a clean and accessible UI.
+The app features:
+Add new tasks
+Mark tasks as completed and undo completion
+Remove tasks
+See how many tasks are remaining
+Clear all completed tasks
+Responsive design
+Accessible UI following basic accessibility guidelines
\ No newline at end of file
diff --git a/package.json b/package.json
index caf62893..b40fb595 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,9 @@
},
"dependencies": {
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "styled-components": "^6.3.5",
+ "zustand": "^5.0.10"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
diff --git a/src/App.jsx b/src/App.jsx
index 54275400..c2f7dd07 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,5 +1,61 @@
-export const App = () => {
+import { useTodoStore } from "./store/todoStore";
+import TaskForm from "./components/TaskForm";
+import TaskList from "./components/TaskList";
+import NoTasks from "./components/NoTasks";
+import { GlobalStyles } from "./styles/GlobalStyles";
+import { Container, Header } from "./styles/Layout";
+
+export default function App() {
+ const tasks = useTodoStore((state) => state.tasks);
+ const clear = useTodoStore((state) => state.clearCompleted);
+
+ const openCount = tasks.filter((t) => !t.completed).length;
+ const doneCount = tasks.filter((t) => t.completed).length;
+
return (
-
React Boilerplate
- )
+
+
+
+
+
+
+
+ {tasks.length === 0 ? : }
+
+ {doneCount > 0 && (
+
+
+ Completed — {doneCount}
+
+
+
+
+
+
+ )}
+
+ );
}
diff --git a/src/components/NoTasks.jsx b/src/components/NoTasks.jsx
new file mode 100644
index 00000000..06f65ba9
--- /dev/null
+++ b/src/components/NoTasks.jsx
@@ -0,0 +1,7 @@
+export default function NoTasks() {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Task.jsx b/src/components/Task.jsx
new file mode 100644
index 00000000..81ccee9e
--- /dev/null
+++ b/src/components/Task.jsx
@@ -0,0 +1,26 @@
+import { useTodoStore } from "../store/todoStore";
+import { ItemRow, DeleteButton } from "../styles/TaskStyles";
+
+export default function Task({ task }) {
+ const toggle = useTodoStore((s) => s.toggleTask);
+ const remove = useTodoStore((s) => s.removeTask);
+
+ return (
+
+ toggle(task.id)}
+ />
+
+
+
+ remove(task.id)}>
+ ✕
+
+
+ );
+}
diff --git a/src/components/TaskForm.jsx b/src/components/TaskForm.jsx
new file mode 100644
index 00000000..d22e21b3
--- /dev/null
+++ b/src/components/TaskForm.jsx
@@ -0,0 +1,27 @@
+import { useState } from "react";
+import { useTodoStore } from "../store/todoStore";
+import { Form } from "../styles/TaskStyles";
+
+export default function TaskForm() {
+ const addTask = useTodoStore((state) => state.addTask);
+ const [text, setText] = useState("");
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ addTask(text);
+ setText("");
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/TaskList.jsx b/src/components/TaskList.jsx
new file mode 100644
index 00000000..14c535aa
--- /dev/null
+++ b/src/components/TaskList.jsx
@@ -0,0 +1,20 @@
+import { useTodoStore } from "../store/todoStore";
+import Task from "./Task";
+import { List } from "../styles/TaskStyles";
+
+export default function TaskList({ filter }) {
+ const tasks = useTodoStore((state) => state.tasks);
+
+ const filteredTasks = tasks.filter((t) => {
+ if (filter === "completed") return t.completed;
+ return !t.completed;
+ });
+
+ return (
+
+ {filteredTasks.map((t) => (
+
+ ))}
+
+ );
+}
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
index f7c0aef5..e69de29b 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,3 +0,0 @@
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
-}
diff --git a/src/main.jsx b/src/main.jsx
index 1b8ffe9b..edc6cdba 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,12 +1,9 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App.jsx";
-import { App } from './App.jsx'
-
-import './index.css'
-
-ReactDOM.createRoot(document.getElementById('root')).render(
+ReactDOM.createRoot(document.getElementById("root")).render(
-)
+);
\ No newline at end of file
diff --git a/src/store/todoStore.js b/src/store/todoStore.js
new file mode 100644
index 00000000..4ee5f11d
--- /dev/null
+++ b/src/store/todoStore.js
@@ -0,0 +1,37 @@
+import { create } from "zustand";
+
+export const useTodoStore = create((set, get) => ({
+ tasks: [],
+
+ addTask: (text) => {
+ if (!text.trim()) return;
+
+ const newTask = {
+ id: new Date().getTime(),
+ title: text,
+ completed: false,
+ };
+ set((state) => ({ tasks: [newTask, ...state.tasks] }));
+ },
+
+ toggleTask: (id) => {
+ set((state) => ({
+ tasks: state.tasks.map((t) =>
+ t.id === id ? { ...t, completed: !t.completed } : t
+ ),
+ }));
+ },
+
+ removeTask: (id) => {
+ set((state) => ({
+ tasks: state.tasks.filter((t) => t.id !== id),
+ }));
+ },
+
+ clearCompleted: () => {
+ set((state) => ({
+ tasks: state.tasks.filter((task) => !task.completed),
+ }));
+},
+
+}));
\ No newline at end of file
diff --git a/src/styles/GlobalStyles.js b/src/styles/GlobalStyles.js
new file mode 100644
index 00000000..9cadb0cd
--- /dev/null
+++ b/src/styles/GlobalStyles.js
@@ -0,0 +1,27 @@
+import { createGlobalStyle } from "styled-components";
+
+export const GlobalStyles = createGlobalStyle`
+ :root {
+ --app-font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
+}
+
+
+ html, body, #root {
+ margin: 0;
+ padding: 0;
+ font-family: var(--app-font);
+ background: #fff;
+ color: #000;
+ -webkit-font-smoothing: antialiased;
+ }
+
+ /* Force form controls */
+ button, input, textarea, select {
+ font-family: var(--app-font);
+ font: inherit;
+ }
+
+ *, *::before, *::after {
+ box-sizing: border-box;
+ }
+`;
diff --git a/src/styles/Layout.js b/src/styles/Layout.js
new file mode 100644
index 00000000..c6a9c42d
--- /dev/null
+++ b/src/styles/Layout.js
@@ -0,0 +1,38 @@
+import styled from "styled-components";
+
+export const Container = styled.main`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-height: 100vh;
+ padding: 60px 20px;
+`;
+
+export const Header = styled.header`
+ width: 100%;
+ max-width: 600px;
+ margin-bottom: 40px;
+
+ h1 {
+ font-size: 66px;
+ font-weight: 800;
+ margin: 0;
+ color: #000;
+ letter-spacing: -2px;
+ }
+
+ @media (max-width: 600px) {
+ h1 {
+ font-size: 42px;
+ }
+ }
+
+ p {
+ color: #000;
+ font-size: 14px;
+ margin-top: 5px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ }
+`;
\ No newline at end of file
diff --git a/src/styles/TaskStyles.js b/src/styles/TaskStyles.js
new file mode 100644
index 00000000..cd528953
--- /dev/null
+++ b/src/styles/TaskStyles.js
@@ -0,0 +1,101 @@
+import styled from "styled-components";
+
+export const Form = styled.form`
+ display: flex;
+ width: 100%;
+ max-width: 600px;
+ gap: 12px;
+ margin-bottom: 40px;
+
+ input {
+ font-family: var(--header-font);
+ flex: 1;
+ background: white;
+ border: 2px solid black;
+ border-radius: 12px;
+ padding: 16px;
+ font-size: 18px;
+ outline: none;
+ }
+
+ button {
+ background: black;
+ color: white;
+ border: 2px solid black;
+ border-radius: 12px;
+ padding: 0 25px;
+ font-weight: bold;
+ cursor: pointer;
+
+ &:disabled {
+ background: gray;
+ border-color: gray;
+ cursor: not-allowed;
+ }
+ }
+`;
+
+export const List = styled.ul`
+ width: 100%;
+ max-width: 600px;
+ padding: 0;
+ list-style: none;
+`;
+
+export const ItemRow = styled.li`
+ display: flex;
+ align-items: center;
+ background: white;
+ border: 2px solid black;
+ border-radius: 12px;
+ padding: 15px;
+ margin-bottom: 12px;
+ gap: 15px;
+
+ label {
+ flex: 1;
+ font-size: 18px;
+ font-weight: 400;
+ color: black;
+ cursor: pointer;
+ }
+
+ .checked {
+ color: #888;
+ text-decoration: line-through;
+ }
+
+ input[type="checkbox"] {
+ width: 20px;
+ height: 20px;
+ accent-color: black;
+ }
+`;
+
+export const DeleteButton = styled.button`
+ background: none;
+ border: none;
+ font-size: 20px;
+ color: black;
+ opacity: 0.3;
+ cursor: pointer;
+
+ &:hover {
+ opacity: 1;
+ }
+`;
+
+export const FooterButton = styled.button`
+ background: white;
+ border: 2px solid black;
+ padding: 10px 20px;
+ border-radius: 10px;
+ font-weight: bold;
+ margin-top: 20px;
+ cursor: pointer;
+
+ &:hover {
+ background: black;
+ color: white;
+ }
+`;
\ No newline at end of file