From 0d04c0eca07476188e20a47737a3ebfb1f9fd0bf Mon Sep 17 00:00:00 2001 From: Iris de Gracia Zhang Date: Thu, 15 Jan 2026 10:48:30 +0100 Subject: [PATCH 1/4] Initial commit --- README.md | 11 +++- package.json | 4 +- src/App.jsx | 62 ++++++++++++++++++++-- src/components/NoTasks.jsx | 7 +++ src/components/Task.jsx | 26 ++++++++++ src/components/TaskForm.jsx | 27 ++++++++++ src/components/TaskList.jsx | 20 +++++++ src/index.css | 3 -- src/main.jsx | 13 ++--- src/store/todoStore.js | 36 +++++++++++++ src/styles/GlobalStyles.js | 27 ++++++++++ src/styles/Layout.js | 38 ++++++++++++++ src/styles/TaskStyles.js | 101 ++++++++++++++++++++++++++++++++++++ 13 files changed, 359 insertions(+), 16 deletions(-) create mode 100644 src/components/NoTasks.jsx create mode 100644 src/components/Task.jsx create mode 100644 src/components/TaskForm.jsx create mode 100644 src/components/TaskList.jsx create mode 100644 src/store/todoStore.js create mode 100644 src/styles/GlobalStyles.js create mode 100644 src/styles/Layout.js create mode 100644 src/styles/TaskStyles.js diff --git a/README.md b/README.md index d1c68b57..ccdb8b85 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 (works from mobile to large screens) +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

- ) + + +
+

On My List

+ {tasks.length > 0 &&

{openCount} Tasks Remaining

} +
+ + + + + {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..65dc2d37 --- /dev/null +++ b/src/components/NoTasks.jsx @@ -0,0 +1,7 @@ +export default function NoTasks() { + return ( +
+

No tasks yet.

+
+ ); +} \ 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 ( +
+ + setText(e.target.value)} + placeholder="Add a task..." + /> + +
+ ); +} \ 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..c6b90bd7 --- /dev/null +++ b/src/store/todoStore.js @@ -0,0 +1,36 @@ +import { create } from "zustand"; + +export const useTodoStore = create((set, get) => ({ + tasks: [], + + addTask: (text) => { + if (!text.trim()) return; + const newTask = { + id: Date.now(), + 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: () => { + const tasks = get().tasks; + if (tasks.some((t) => t.completed)) { + set({ tasks: tasks.filter((t) => !t.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 From f328b8612847c0d5d2bcee905b8f1481b45ebf17 Mon Sep 17 00:00:00 2001 From: Iris de Gracia Zhang Date: Fri, 16 Jan 2026 09:40:44 +0100 Subject: [PATCH 2/4] updated todoStore, addtask --- src/store/todoStore.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/store/todoStore.js b/src/store/todoStore.js index c6b90bd7..b5146672 100644 --- a/src/store/todoStore.js +++ b/src/store/todoStore.js @@ -5,8 +5,9 @@ export const useTodoStore = create((set, get) => ({ addTask: (text) => { if (!text.trim()) return; + const newTask = { - id: Date.now(), + id: new Date().getTime(), title: text, completed: false, }; From 75c758bbdc6b4dad84cc4268466e2f4a84509f0c Mon Sep 17 00:00:00 2001 From: Iris de Gracia Zhang Date: Sun, 18 Jan 2026 07:52:07 +0100 Subject: [PATCH 3/4] updated add task --- src/store/todoStore.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/store/todoStore.js b/src/store/todoStore.js index b5146672..4ee5f11d 100644 --- a/src/store/todoStore.js +++ b/src/store/todoStore.js @@ -29,9 +29,9 @@ export const useTodoStore = create((set, get) => ({ }, clearCompleted: () => { - const tasks = get().tasks; - if (tasks.some((t) => t.completed)) { - set({ tasks: tasks.filter((t) => !t.completed) }); - } - }, + set((state) => ({ + tasks: state.tasks.filter((task) => !task.completed), + })); +}, + })); \ No newline at end of file From 19c4daa77bb4a1efd350a5765d19ee50d37e16e1 Mon Sep 17 00:00:00 2001 From: Iris de Gracia Zhang Date: Sun, 18 Jan 2026 09:03:12 +0100 Subject: [PATCH 4/4] Updated readme --- README.md | 2 +- src/components/NoTasks.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ccdb8b85..4daf910a 100644 --- a/README.md +++ b/README.md @@ -6,5 +6,5 @@ Mark tasks as completed and undo completion Remove tasks See how many tasks are remaining Clear all completed tasks -Responsive design (works from mobile to large screens) +Responsive design Accessible UI following basic accessibility guidelines \ No newline at end of file diff --git a/src/components/NoTasks.jsx b/src/components/NoTasks.jsx index 65dc2d37..06f65ba9 100644 --- a/src/components/NoTasks.jsx +++ b/src/components/NoTasks.jsx @@ -1,6 +1,6 @@ export default function NoTasks() { return ( -
+

No tasks yet.

);