diff --git a/README.md b/README.md index d1c68b5..9b152e8 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# Todo \ No newline at end of file +GoyoTasks is a clean, minimal, and easy-to-use web-based to-do app that helps you organize your tasks and boost productivity. + +Live Demo: https://goyotasks.netlify.app/ diff --git a/index.html b/index.html index f7ac4e4..73a32c0 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,13 @@ - Todo + + + + Goyo Tasks
diff --git a/package.json b/package.json index caf6289..1bba4b6 100644 --- a/package.json +++ b/package.json @@ -10,18 +10,24 @@ "preview": "vite preview" }, "dependencies": { + "@tailwindcss/vite": "^4.1.18", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-icons": "^5.5.0", + "zustand": "^5.0.10" }, "devDependencies": { "@eslint/js": "^9.21.0", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.23", "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": "^4.1.18", "vite": "^6.2.0" } } diff --git a/src/App.jsx b/src/App.jsx index 5427540..30acc75 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,22 @@ +import { AddTodo } from "./components/AddTodo"; +import { TaskCounter } from "./components/TaskCounter"; +import { TodoList } from "./components/TodoList"; + export const App = () => { return ( -

React Boilerplate

- ) -} +
+
+

+ Goyo Tasks 고요 +

+ +
+
+
+ + +
+
+
+ ); +}; diff --git a/src/assets/goyologo.svg b/src/assets/goyologo.svg new file mode 100644 index 0000000..96f400e --- /dev/null +++ b/src/assets/goyologo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/components/AddTodo.jsx b/src/components/AddTodo.jsx new file mode 100644 index 0000000..14aea84 --- /dev/null +++ b/src/components/AddTodo.jsx @@ -0,0 +1,35 @@ +import { useState } from "react"; +import useTodoStore from "./store/useTodoStore"; + +export const AddTodo = () => { + const [text, setText] = useState(""); + const addTask = useTodoStore((state) => state.addTask); + + const handleSubmit = (e) => { + e.preventDefault(); + addTask(text); + setText(""); + }; + + return ( +
+ setText(e.target.value)} + placeholder="Add a new task..." + className="flex-1 px-4 py-3 bg-white border border-border/50 rounded-lg text-text placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary shadow-sm transition-shadow" + /> + +
+ ); +}; diff --git a/src/components/EmptyState.jsx b/src/components/EmptyState.jsx new file mode 100644 index 0000000..2a9ece4 --- /dev/null +++ b/src/components/EmptyState.jsx @@ -0,0 +1,7 @@ +export const EmptyState = () => { + return ( +
+

No tasks yet. Add one above to get started.

+
+ ); +}; diff --git a/src/components/TaskCounter.jsx b/src/components/TaskCounter.jsx new file mode 100644 index 0000000..b1cff04 --- /dev/null +++ b/src/components/TaskCounter.jsx @@ -0,0 +1,13 @@ +import useTodoStore from "./store/useTodoStore"; + +export const TaskCounter = () => { + const tasks = useTodoStore((state) => state.tasks); + const completedCount = tasks.filter((task) => task.completed).length; + const totalCount = tasks.length; + + return ( +

+ {completedCount} of {totalCount} tasks completed +

+ ); +}; diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx new file mode 100644 index 0000000..e0f418d --- /dev/null +++ b/src/components/TodoItem.jsx @@ -0,0 +1,28 @@ +import { FiTrash2 } from "react-icons/fi"; +import useTodoStore from "./store/useTodoStore"; + +export const TodoItem = ({ task }) => { + const toggleTask = useTodoStore((state) => state.toggleTask); + const removeTask = useTodoStore((state) => state.removeTask); + + return ( +
  • + toggleTask(task.id)} + className="w-4 h-4 accent-primary" + /> + + {task.text} + + +
  • + ); +}; diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx new file mode 100644 index 0000000..4faa911 --- /dev/null +++ b/src/components/TodoList.jsx @@ -0,0 +1,19 @@ +import useTodoStore from "./store/useTodoStore"; +import { TodoItem } from "./TodoItem"; +import { EmptyState } from "./EmptyState"; + +export const TodoList = () => { + const tasks = useTodoStore((state) => state.tasks); + + if (tasks.length === 0) { + return ; + } + + return ( + + ); +}; diff --git a/src/components/store/useTodoStore.js b/src/components/store/useTodoStore.js new file mode 100644 index 0000000..b038a95 --- /dev/null +++ b/src/components/store/useTodoStore.js @@ -0,0 +1,38 @@ +import { create } from "zustand"; + +const useTodoStore = create((set) => ({ + tasks: [], + + addTask: (text) => { + if (!text.trim()) return; + + set((state) => ({ + tasks: [ + { + id: Date.now(), + text, + completed: false, + }, + ...state.tasks, + ], + })); + }, + + toggleTask: (id) => { + set((state) => ({ + tasks: state.tasks.map((task) => + task.id === id + ? { ...task, completed: !task.completed } + : task + ), + })); + }, + + removeTask: (id) => { + set((state) => ({ + tasks: state.tasks.filter((task) => task.id !== id), + })); + }, +})); + +export default useTodoStore; diff --git a/src/index.css b/src/index.css index f7c0aef..bc985a9 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,19 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; +@import "tailwindcss"; + +@theme { + --color-bg: #F8F7F4; + --color-surface: #F1F0EC; + --color-border: #DAD8D2; + --color-text: #1F2328; + --color-text-muted: #6B7075; + --color-primary: #4A5B6A; + --color-primarymuted: #E4E9ED; } + +@layer base { + html, body { + font-family: "DM Sans", system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", sans-serif; + } +} + diff --git a/vite.config.js b/vite.config.js index ba24244..d9e4dfd 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,8 @@ +import tailwindcss from '@tailwindcss/vite' import react from '@vitejs/plugin-react' import { defineConfig } from 'vite' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()] + plugins: [tailwindcss(), react()] })