From d83030a1e20ae25894235f14deab6cd5d2df670d Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Tue, 13 Jan 2026 16:08:27 +0100 Subject: [PATCH 1/9] added store and components --- package.json | 6 +++++- src/components/Home.jsx | 0 src/stores/useTodoStore.jsx | 7 +++++++ vite.config.js | 13 +++++++++---- 4 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 src/components/Home.jsx create mode 100644 src/stores/useTodoStore.jsx diff --git a/package.json b/package.json index caf62893..d6c4d144 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,12 @@ "preview": "vite preview" }, "dependencies": { + "@mui/material": "^7.3.7", + "@mui/styled-engine-sc": "^7.3.7", "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/components/Home.jsx b/src/components/Home.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/stores/useTodoStore.jsx b/src/stores/useTodoStore.jsx new file mode 100644 index 00000000..1a8988d5 --- /dev/null +++ b/src/stores/useTodoStore.jsx @@ -0,0 +1,7 @@ +import { create } from "zustand"; + +export const useTodoStore = create(() => ({ + todos: { + heading: "Todo", + }, +})); diff --git a/vite.config.js b/vite.config.js index ba242447..b5a3e5d8 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,12 @@ -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vite' +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()] -}) + resolve: { + alias: { + "@mui/styled-engine": "@mui/styled-engine-sc", + }, + }, + plugins: [react()], +}); From 017c3ece3be30bd4aa351b5042b0c78038801eea Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Wed, 14 Jan 2026 16:07:14 +0100 Subject: [PATCH 2/9] added TodoStore --- src/App.jsx | 10 +++++++--- src/components/Home.jsx | 7 +++++++ src/stores/useTodoStore.jsx | 27 ++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 54275400..4d482e80 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,9 @@ +import { Home } from "./components/Home"; + export const App = () => { return ( -

React Boilerplate

- ) -} + <> + + + ); +}; diff --git a/src/components/Home.jsx b/src/components/Home.jsx index e69de29b..ae69cc00 100644 --- a/src/components/Home.jsx +++ b/src/components/Home.jsx @@ -0,0 +1,7 @@ +import { useTodoStore } from "../stores/useTodoStore"; + +export const Home = () => { + const { todo } = useTodoStore(); + + return

{todo.heading}

; +}; diff --git a/src/stores/useTodoStore.jsx b/src/stores/useTodoStore.jsx index 1a8988d5..a68c551e 100644 --- a/src/stores/useTodoStore.jsx +++ b/src/stores/useTodoStore.jsx @@ -1,7 +1,24 @@ import { create } from "zustand"; -export const useTodoStore = create(() => ({ - todos: { - heading: "Todo", - }, -})); +export const useTodoStore = create(set => ({ + todos: [] + addTodo: (text) => set((state) => ({ + todos: [ + ...state.todos, + { id: Date.now(), text, completed: false } + ] + })), + + removeTodo: (id) => + set((state) => ({ + todos: state.todos.filter((t) => t.id !== id) + })), + toggleTodo: (id) => + set((state) => ({ + todos: state.todos.map((t) => + t.id === id ? { ...t, completed: !t.completed} : t + ), + })), + + clearTodos: () => set({ todos: [] }), + })); \ No newline at end of file From 7db4329ea63b1fc1af231f4d53e183d7ba5fb7e1 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Thu, 15 Jan 2026 13:02:42 +0100 Subject: [PATCH 3/9] added all components code --- src/App.jsx | 10 +++++++-- src/components/AddTodoForm.jsx | 24 +++++++++++++++++++++ src/components/Home.jsx | 7 ------- src/components/TodoItem.jsx | 14 +++++++++++++ src/components/TodoList.jsx | 16 ++++++++++++++ src/stores/useTodoStore.jsx | 38 ++++++++++++++++------------------ 6 files changed, 80 insertions(+), 29 deletions(-) create mode 100644 src/components/AddTodoForm.jsx delete mode 100644 src/components/Home.jsx create mode 100644 src/components/TodoItem.jsx create mode 100644 src/components/TodoList.jsx diff --git a/src/App.jsx b/src/App.jsx index 4d482e80..a8a03511 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,9 +1,15 @@ -import { Home } from "./components/Home"; +import { AddTodoForm } from "./components/AddTodoForm.jsx"; +import { TodoList } from "./components/TodoList.jsx"; +import { TodoItem } from "./components/TodoItem.jsx"; export const App = () => { return ( <> - +
+ + + +
); }; diff --git a/src/components/AddTodoForm.jsx b/src/components/AddTodoForm.jsx new file mode 100644 index 00000000..9053e0df --- /dev/null +++ b/src/components/AddTodoForm.jsx @@ -0,0 +1,24 @@ +import { useState } from "react"; +import { useTodoStore } from "../stores/useTodoStore"; + +export const AddTodoForm = () => { + const [text, setText] = useState(""); + const addTodo = useTodoStore((state) => state.addTodo); + + const handleSubmit = (e) => { + e.preventDefault(); + if (!text.trim()) return; + addTodo(text); + setText(""); + }; + return ( +
+ setText(e.target.value)} + placeholder="your new todo" + /> + +
+ ); +}; diff --git a/src/components/Home.jsx b/src/components/Home.jsx deleted file mode 100644 index ae69cc00..00000000 --- a/src/components/Home.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import { useTodoStore } from "../stores/useTodoStore"; - -export const Home = () => { - const { todo } = useTodoStore(); - - return

{todo.heading}

; -}; diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx new file mode 100644 index 00000000..68d184b6 --- /dev/null +++ b/src/components/TodoItem.jsx @@ -0,0 +1,14 @@ +import { useTodoStore } from "../stores/useTodoStore"; +export const TodoItem = ({ id, text, completed }) => { + const removeTodo = useTodoStore((state) => state.removeTodo); + const toggleTodo = useTodoStore((state) => state.toggleTodo); + + return ( +
+

{text}

+

Done: {completed}

+ + +
+ ); +}; diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx new file mode 100644 index 00000000..d31166f3 --- /dev/null +++ b/src/components/TodoList.jsx @@ -0,0 +1,16 @@ +import { useTodoStore } from "../stores/useTodoStore"; +import { TodoItem } from "./todoItem"; +export const TodList = () => { + const todos = useTodoStore((state) => state.todos); + + if (todos.length === 0) return

No todos

; + + return ( + <> + {todos.map((todo) => ( + + ))} + ; + + ); +}; diff --git a/src/stores/useTodoStore.jsx b/src/stores/useTodoStore.jsx index a68c551e..9925a1bd 100644 --- a/src/stores/useTodoStore.jsx +++ b/src/stores/useTodoStore.jsx @@ -1,24 +1,22 @@ import { create } from "zustand"; -export const useTodoStore = create(set => ({ - todos: [] - addTodo: (text) => set((state) => ({ - todos: [ - ...state.todos, - { id: Date.now(), text, completed: false } - ] - })), +export const useTodoStore = create((set) => ({ + todos: [], + addTodo: (text) => + set((state) => ({ + todos: [...state.todos, { id: Date.now(), text, completed: false }], + })), - removeTodo: (id) => - set((state) => ({ - todos: state.todos.filter((t) => t.id !== id) - })), - toggleTodo: (id) => - set((state) => ({ - todos: state.todos.map((t) => - t.id === id ? { ...t, completed: !t.completed} : t - ), - })), + removeTodo: (id) => + set((state) => ({ + todos: state.todos.filter((t) => t.id !== id), + })), + toggleTodo: (id) => + set((state) => ({ + todos: state.todos.map((t) => + t.id === id ? { ...t, completed: !t.completed } : t + ), + })), - clearTodos: () => set({ todos: [] }), - })); \ No newline at end of file + clearTodos: () => set({ todos: [] }), +})); From 639a329ff52573c7e52a5a04cd48896cc88ccd64 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Thu, 15 Jan 2026 15:08:51 +0100 Subject: [PATCH 4/9] added styling --- package.json | 5 ++++- src/App.jsx | 5 ++--- src/components/AddTodoForm.jsx | 11 ++++++++--- src/components/TodoItem.jsx | 24 +++++++++++++++++------- src/components/TodoList.jsx | 4 ++-- 5 files changed, 33 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index d6c4d144..118d144d 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,14 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.7", "@mui/material": "^7.3.7", "@mui/styled-engine-sc": "^7.3.7", "react": "^19.0.0", "react-dom": "^19.0.0", - "styled-components": "^6.3.5", + "styled-components": "^6.3.6", "zustand": "^5.0.10" }, "devDependencies": { diff --git a/src/App.jsx b/src/App.jsx index a8a03511..0c055c17 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,14 +1,13 @@ import { AddTodoForm } from "./components/AddTodoForm.jsx"; import { TodoList } from "./components/TodoList.jsx"; -import { TodoItem } from "./components/TodoItem.jsx"; +import { Container, Paper, Typography, Box } from "@mui/material"; export const App = () => { return ( <>
- - +
); diff --git a/src/components/AddTodoForm.jsx b/src/components/AddTodoForm.jsx index 9053e0df..e2551273 100644 --- a/src/components/AddTodoForm.jsx +++ b/src/components/AddTodoForm.jsx @@ -1,5 +1,6 @@ import { useState } from "react"; import { useTodoStore } from "../stores/useTodoStore"; +import { TextField, Button } from "@mui/material"; export const AddTodoForm = () => { const [text, setText] = useState(""); @@ -12,13 +13,17 @@ export const AddTodoForm = () => { setText(""); }; return ( -
- + setText(e.target.value)} + variant="outlined" placeholder="your new todo" + fullWidth /> - + ); }; diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx index 68d184b6..900c5b03 100644 --- a/src/components/TodoItem.jsx +++ b/src/components/TodoItem.jsx @@ -1,14 +1,24 @@ import { useTodoStore } from "../stores/useTodoStore"; -export const TodoItem = ({ id, text, completed }) => { +import { + List, + ListItem, + ListItemText, + IconButton, + Checkbox, +} from "@mui/material"; +import DeleteIcon from "@mui/icons-material/Delete"; + +export const TodoItem = ({ todo }) => { const removeTodo = useTodoStore((state) => state.removeTodo); const toggleTodo = useTodoStore((state) => state.toggleTodo); return ( -
-

{text}

-

Done: {completed}

- - -
+ + toggleTodo(todo.id)} /> + + removeTodo(todo.id)}> + + + ); }; diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx index d31166f3..d18728bc 100644 --- a/src/components/TodoList.jsx +++ b/src/components/TodoList.jsx @@ -1,6 +1,7 @@ import { useTodoStore } from "../stores/useTodoStore"; import { TodoItem } from "./todoItem"; -export const TodList = () => { + +export const TodoList = () => { const todos = useTodoStore((state) => state.todos); if (todos.length === 0) return

No todos

; @@ -10,7 +11,6 @@ export const TodList = () => { {todos.map((todo) => ( ))} - ; ); }; From 779de151f808290cbc9cd03831ca3ffe88d9d209 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Thu, 15 Jan 2026 22:33:10 +0100 Subject: [PATCH 5/9] added counter + styling --- src/App.jsx | 26 ++++++++++---- src/components/AddTodoForm.jsx | 44 ++++++++++++++++------- src/components/CountTodos.jsx | 43 ++++++++++++++++++++++ src/components/EmptyState.jsx | 39 ++++++++++++++++++++ src/components/TodoItem.jsx | 46 +++++++++++++++++------- src/components/TodoList.jsx | 66 ++++++++++++++++++++++++++++++---- src/index.css | 3 -- src/main.jsx | 20 +++++++---- src/theme.js | 58 ++++++++++++++++++++++++++++++ 9 files changed, 296 insertions(+), 49 deletions(-) create mode 100644 src/components/CountTodos.jsx create mode 100644 src/components/EmptyState.jsx create mode 100644 src/theme.js diff --git a/src/App.jsx b/src/App.jsx index 0c055c17..fd2c9f55 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,14 +1,28 @@ import { AddTodoForm } from "./components/AddTodoForm.jsx"; +import { CountTodos } from "./components/CountTodos.jsx"; import { TodoList } from "./components/TodoList.jsx"; import { Container, Paper, Typography, Box } from "@mui/material"; export const App = () => { return ( - <> -
- - -
- + + + + + Todo App + + + + + + + + ); }; diff --git a/src/components/AddTodoForm.jsx b/src/components/AddTodoForm.jsx index e2551273..b7315a8f 100644 --- a/src/components/AddTodoForm.jsx +++ b/src/components/AddTodoForm.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useTodoStore } from "../stores/useTodoStore"; -import { TextField, Button } from "@mui/material"; +import { TextField, Button, Stack } from "@mui/material"; export const AddTodoForm = () => { const [text, setText] = useState(""); @@ -9,21 +9,39 @@ export const AddTodoForm = () => { const handleSubmit = (e) => { e.preventDefault(); if (!text.trim()) return; - addTodo(text); + addTodo(text.trim()); setText(""); }; + return ( -
- setText(e.target.value)} - variant="outlined" - placeholder="your new todo" - fullWidth - /> - + + + setText(e.target.value)} + label="New todo" + placeholder="Add a new todo" + fullWidth + slotProps={{ + input: { + maxLength: 120, + }, + }} + /> + + + ); }; diff --git a/src/components/CountTodos.jsx b/src/components/CountTodos.jsx new file mode 100644 index 00000000..a93f3225 --- /dev/null +++ b/src/components/CountTodos.jsx @@ -0,0 +1,43 @@ +import { useTodoStore } from "../stores/useTodoStore"; +import { Paper, Typography, Stack } from "@mui/material"; + +const StatPill = ({ label, value }) => ( + + + {label} + + + {value} + + +); + +export const CountTodos = () => { + const total = useTodoStore((s) => s.todos.length); + const completed = useTodoStore( + (s) => s.todos.filter((t) => t.completed).length + ); + const uncompleted = useTodoStore( + (s) => s.todos.filter((t) => !t.completed).length + ); + + return ( + + + + + + ); +}; diff --git a/src/components/EmptyState.jsx b/src/components/EmptyState.jsx new file mode 100644 index 00000000..da53398b --- /dev/null +++ b/src/components/EmptyState.jsx @@ -0,0 +1,39 @@ +import { Box, Typography } from "@mui/material"; + +export const EmptyState = ({ + title = "Nothing here", + description = "Add something to get started.", + icon = "πŸ“", +}) => { + return ( + + + + + {title} + + + + {description} + + + ); +}; diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx index 900c5b03..f1deb423 100644 --- a/src/components/TodoItem.jsx +++ b/src/components/TodoItem.jsx @@ -1,24 +1,44 @@ import { useTodoStore } from "../stores/useTodoStore"; -import { - List, - ListItem, - ListItemText, - IconButton, - Checkbox, -} from "@mui/material"; +import { ListItem, ListItemText, IconButton, Checkbox } from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; export const TodoItem = ({ todo }) => { const removeTodo = useTodoStore((state) => state.removeTodo); const toggleTodo = useTodoStore((state) => state.toggleTodo); + const label = todo.completed ? "Mark as uncompleted" : "Mark as completed"; + return ( - - toggleTodo(todo.id)} /> - - removeTodo(todo.id)}> - - + removeTodo(todo.id)} + aria-label={`Delete task: ${todo.text}`} + > + + + } + > + toggleTodo(todo.id)} + slotProps={{ + input: { + "aria-label": `${label}: ${todo.text}`, + }, + }} + /> + + ); }; diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx index d18728bc..a41a475e 100644 --- a/src/components/TodoList.jsx +++ b/src/components/TodoList.jsx @@ -1,16 +1,68 @@ import { useTodoStore } from "../stores/useTodoStore"; -import { TodoItem } from "./todoItem"; +import { TodoItem } from "./TodoItem"; +import { Typography, List, Divider, Box, Paper } from "@mui/material"; +import { EmptyState } from "./EmptyState"; export const TodoList = () => { const todos = useTodoStore((state) => state.todos); - if (todos.length === 0) return

No todos

; + const uncompletedTodos = todos.filter((t) => !t.completed); + const completedTodos = todos.filter((t) => t.completed); + + // Empty state fΓΆr hela appen + if (todos.length === 0) { + return ( + + + + ); + } return ( - <> - {todos.map((todo) => ( - - ))} - + + + + Uncompleted ({uncompletedTodos.length}) + + + {uncompletedTodos.length === 0 ? ( + + ) : ( + + {uncompletedTodos.map((todo) => ( + + ))} + + )} + + + + + Completed ({completedTodos.length}) + + + {completedTodos.length === 0 ? ( + + ) : ( + + {completedTodos.map((todo) => ( + + ))} + + )} + + ); }; 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..377fbcab 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,12 +1,18 @@ -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 { ThemeProvider } from "@mui/material/styles"; +import { CssBaseline } from "@mui/material"; +import { theme } from "./theme"; -import './index.css' +import "./index.css"; -ReactDOM.createRoot(document.getElementById('root')).render( +ReactDOM.createRoot(document.getElementById("root")).render( - + + + + -) +); diff --git a/src/theme.js b/src/theme.js new file mode 100644 index 00000000..b59eddf7 --- /dev/null +++ b/src/theme.js @@ -0,0 +1,58 @@ +import { createTheme } from "@mui/material/styles"; + +export const theme = createTheme({ + palette: { + mode: "light", + primary: { + main: "#1976d2", + }, + background: { + default: "#f5f5f5", + paper: "#ffffff", + }, + }, + + shape: { + borderRadius: 14, + }, + + typography: { + fontFamily: ["Roboto", "Helvetica", "Arial", "sans-serif"].join(","), + h4: { fontWeight: 700 }, + h6: { fontWeight: 700 }, + }, + + components: { + MuiPaper: { + styleOverrides: { + root: { + borderRadius: 16, + }, + }, + }, + MuiButton: { + defaultProps: { + disableElevation: true, + }, + styleOverrides: { + root: { + borderRadius: 12, + textTransform: "none", + fontWeight: 600, + }, + }, + }, + MuiTextField: { + defaultProps: { + size: "medium", + }, + }, + MuiListItem: { + styleOverrides: { + root: { + borderRadius: 12, + }, + }, + }, + }, +}); From 62f1cf9d58bd7f36db62c2ebf284b44e00c74dac Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Fri, 16 Jan 2026 19:27:19 +0100 Subject: [PATCH 6/9] added counter + styling --- index.html | 9 +++++---- src/App.jsx | 2 +- src/components/AddTodoForm.jsx | 4 ++-- src/components/TodoItem.jsx | 2 +- src/components/TodoList.jsx | 12 ++++++------ 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/index.html b/index.html index f7ac4e42..c9d905dd 100644 --- a/index.html +++ b/index.html @@ -4,13 +4,14 @@ + Todo
- + diff --git a/src/App.jsx b/src/App.jsx index fd2c9f55..b52496d1 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -15,7 +15,7 @@ export const App = () => { - Todo App + Todo diff --git a/src/components/AddTodoForm.jsx b/src/components/AddTodoForm.jsx index b7315a8f..6f2b7a00 100644 --- a/src/components/AddTodoForm.jsx +++ b/src/components/AddTodoForm.jsx @@ -14,7 +14,7 @@ export const AddTodoForm = () => { }; return ( -
+ { type="submit" variant="contained" sx={{ width: { xs: "100%", sm: "auto" } }} - aria-label="Add new task" + aria-label="Add new todo" > Add diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx index f1deb423..bd7ac1e8 100644 --- a/src/components/TodoItem.jsx +++ b/src/components/TodoItem.jsx @@ -15,7 +15,7 @@ export const TodoItem = ({ todo }) => { removeTodo(todo.id)} - aria-label={`Delete task: ${todo.text}`} + aria-label={`Delete todo: ${todo.text}`} > diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx index a41a475e..45dbba04 100644 --- a/src/components/TodoList.jsx +++ b/src/components/TodoList.jsx @@ -15,7 +15,7 @@ export const TodoList = () => { @@ -33,10 +33,10 @@ export const TodoList = () => { ) : ( - + {uncompletedTodos.map((todo) => ( ))} @@ -52,11 +52,11 @@ export const TodoList = () => { {completedTodos.length === 0 ? ( ) : ( - + {completedTodos.map((todo) => ( ))} From 581e642676e1bc4518c5534e90702915fd56510e Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Sun, 18 Jan 2026 10:35:26 +0100 Subject: [PATCH 7/9] added localstorage & complete a all btn --- src/App.jsx | 39 +++++++++++++++++++++++++++++--- src/stores/useTodoStore.jsx | 45 ++++++++++++++++++++++--------------- 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index b52496d1..26d0d6a0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,9 +1,23 @@ import { AddTodoForm } from "./components/AddTodoForm.jsx"; import { CountTodos } from "./components/CountTodos.jsx"; import { TodoList } from "./components/TodoList.jsx"; -import { Container, Paper, Typography, Box } from "@mui/material"; +import { + Container, + Paper, + Typography, + Box, + Button, + Stack, +} from "@mui/material"; +import { useTodoStore } from "./stores/useTodoStore.jsx"; export const App = () => { + const completeAll = useTodoStore((state) => state.completeAll); + const hasTodos = useTodoStore((state) => state.todos.length > 0); + const hasUncompletedTodos = useTodoStore((state) => + state.todos.some((todo) => !todo.completed) + ); + return ( { Todo - - + + + {hasTodos && + (hasUncompletedTodos ? ( + + ) : ( + + All tasks are completed πŸŽ‰ + + ))} + diff --git a/src/stores/useTodoStore.jsx b/src/stores/useTodoStore.jsx index 9925a1bd..96011e79 100644 --- a/src/stores/useTodoStore.jsx +++ b/src/stores/useTodoStore.jsx @@ -1,22 +1,31 @@ import { create } from "zustand"; +import { persist } from "zustand/middleware"; -export const useTodoStore = create((set) => ({ - todos: [], - addTodo: (text) => - set((state) => ({ - todos: [...state.todos, { id: Date.now(), text, completed: false }], - })), +export const useTodoStore = create( + persist((set) => ({ + todos: [], + addTodo: (text) => + set((state) => ({ + todos: [...state.todos, { id: Date.now(), text, completed: false }], + })), - removeTodo: (id) => - set((state) => ({ - todos: state.todos.filter((t) => t.id !== id), - })), - toggleTodo: (id) => - set((state) => ({ - todos: state.todos.map((t) => - t.id === id ? { ...t, completed: !t.completed } : t - ), - })), + removeTodo: (id) => + set((state) => ({ + todos: state.todos.filter((t) => t.id !== id), + })), + toggleTodo: (id) => + set((state) => ({ + todos: state.todos.map((t) => + t.id === id ? { ...t, completed: !t.completed } : t + ), + })), - clearTodos: () => set({ todos: [] }), -})); + clearTodos: () => set({ todos: [] }), + + completeAll: () => + set((state) => ({ + todos: state.todos.map((t) => ({ ...t, completed: true })), + })), + })), + { name: "todo-storage" } +); From b7de2b285785b90883f954dea2c2d9175fad0df1 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Sun, 18 Jan 2026 10:52:15 +0100 Subject: [PATCH 8/9] added README --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d1c68b57..71e02ef9 100644 --- a/README.md +++ b/README.md @@ -1 +1,48 @@ -# Todo \ No newline at end of file +# Todo App + +A responsive and accessible Todo application built with **React**, **Zustand**, and **Material UI**. + +The app allows users to manage tasks efficiently with global state management, persistent storage, and a clean, responsive user interface. + +--- + +## Usage + +1. Enter a task in the input field and click **Add** to create a new todo. +2. Tasks are displayed under **Uncompleted** or **Completed** depending on their status. +3. Use the checkbox to mark a task as completed or uncompleted. +4. Click the trash icon to delete a task. +5. Use the **Complete all** button to mark all uncompleted tasks as completed. +6. Task counters update automatically to show: + - Total tasks + - Completed tasks + - Uncompleted tasks + +Tasks are saved automatically and persist after refreshing the page. + +--- + +## Features + +- Add and remove tasks +- Mark tasks as completed or uncompleted +- Complete all tasks with a single action +- Global state management using Zustand (no prop-drilling) +- Persistent storage using localStorage +- Live task counters (total / completed / uncompleted) +- Responsive layout (mobile β†’ desktop) +- Accessible UI (labels, aria-labels, semantic HTML) +- Empty states for improved user experience +- Custom Material UI theme + +--- + +## Accessibility & Performance + +- Built following accessibility best practices +- Semantic headings and labelled form elements +- aria-labels for interactive components +- Responsive design using Material UI layout components +- Tested with Lighthouse (Accessibility score β‰₯ 95) + +--- From b237e8eb9627c7d5e3d572e0901434ef716518a7 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Sun, 18 Jan 2026 10:55:47 +0100 Subject: [PATCH 9/9] added netlify-link --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 71e02ef9..24c7bc60 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +https://react-zustand-todo.netlify.app/ + # Todo App A responsive and accessible Todo application built with **React**, **Zustand**, and **Material UI**.