diff --git a/README.md b/README.md index 36daf341..a7dc811b 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,13 @@ # Todo - useContext Project -Replace this readme with your own information about your project. - -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +Todo app where you list your todos, delete when finished. ## Getting Started with the Project ### Dependency Installation & Startup Development Server -Once cloned, navigate to the project's root directory and this project uses npm (Node Package Manager) to manage its dependencies. - -The command below is a combination of installing dependencies, opening up the project on VS Code and it will run a development server on your terminal. +Zustand, useContext ```bash npm i && code . && npm run dev @@ -24,11 +20,12 @@ npm i && code . && npm run dev ### The Problem -Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? + ### View it live -Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. +Netlify: https://mytodos-erika.netlify.app/ +https://typescript--mytodos-erika.netlify.app/ ## Instructions diff --git a/index.html b/index.html index 09bcfb14..e6bf95c2 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,6 @@
- + diff --git a/package-lock.json b/package-lock.json index 5c1b936d..f7f1b8a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.18.0" + "react-router-dom": "^6.18.0", + "styled-components": "^6.1.13", + "zustand": "^5.0.1" }, "devDependencies": { "@types/react": "^18.2.15", @@ -20,6 +22,7 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", + "typescript": "^5.7.2", "vite": "^4.4.5" } }, @@ -346,6 +349,27 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.18.20", "cpu": [ @@ -578,12 +602,12 @@ }, "node_modules/@types/prop-types": { "version": "15.7.10", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.2.37", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -601,7 +625,13 @@ }, "node_modules/@types/scheduler": { "version": "0.16.6", - "dev": true, + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", "license": "MIT" }, "node_modules/@ungap/structured-clone": { @@ -866,6 +896,15 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001561", "dev": true, @@ -934,9 +973,30 @@ "node": ">= 8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/csstype": { - "version": "3.1.2", - "dev": true, + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, "node_modules/debug": { @@ -2255,7 +2315,6 @@ }, "node_modules/nanoid": { "version": "3.3.7", - "dev": true, "funding": [ { "type": "github", @@ -2472,12 +2531,12 @@ }, "node_modules/picocolors": { "version": "1.0.0", - "dev": true, "license": "ISC" }, "node_modules/postcss": { - "version": "8.4.31", - "dev": true, + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -2494,14 +2553,20 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -2800,6 +2865,12 @@ "node": ">= 0.4" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "dev": true, @@ -2833,8 +2904,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "dev": true, + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2923,6 +2995,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-components": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", + "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "5.5.0", "dev": true, @@ -2958,6 +3064,12 @@ "node": ">=4" } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -3041,6 +3153,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "dev": true, @@ -3253,6 +3379,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz", + "integrity": "sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index a23290e8..faf28f8b 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.18.0" + "react-router-dom": "^6.18.0", + "styled-components": "^6.1.13", + "zustand": "^5.0.1" }, "devDependencies": { "@types/react": "^18.2.15", @@ -22,6 +24,7 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", + "typescript": "^5.7.2", "vite": "^4.4.5" } } diff --git a/pull_request_template.md b/pull_request_template.md index 39a2aa32..f19e239f 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,6 +1,6 @@ ## Netlify link -Add your Netlify link here. -PS. Don't forget to add it in your readme as well. +https://mytodos-erika.netlify.app/ +https://typescript--mytodos-erika.netlify.app/ ## Collaborators -Add any collaborators here. +- diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index 496ab1b1..00000000 --- a/src/App.jsx +++ /dev/null @@ -1,3 +0,0 @@ -export const App = () => { - return
Find me in App.jsx!
; -}; diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 00000000..057d1c47 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { useTheme } from "./context/ThemeContext"; +import { GlobalStyle } from "./styles/GlobalStyle"; +import Header from "./components/Header"; +import TodoList from "./components/TodoList"; +import AddTodoPopup from "./components/AddTodoPopup"; +import AddTodoButton from "./components/AddTodoButton"; + + + + +export const App: React.FC = () => { + const { isDarkMode } = useTheme(); + const [isPopupOpen, setIsPopupOpen] = React.useState(false); + + + + return ( + <> + +
+
setIsPopupOpen(true)} /> + + {isPopupOpen && setIsPopupOpen(false)} />} + setIsPopupOpen(true)} /> +
+ + ) +}; + diff --git a/src/assets/Detective-check-footprint 1.png b/src/assets/Detective-check-footprint 1.png new file mode 100644 index 00000000..4da3bf72 Binary files /dev/null and b/src/assets/Detective-check-footprint 1.png differ diff --git a/src/assets/Detective-check-footprint-dark.png b/src/assets/Detective-check-footprint-dark.png new file mode 100644 index 00000000..873e58f6 Binary files /dev/null and b/src/assets/Detective-check-footprint-dark.png differ diff --git a/src/components/AddTodoButton.tsx b/src/components/AddTodoButton.tsx new file mode 100644 index 00000000..53fd3733 --- /dev/null +++ b/src/components/AddTodoButton.tsx @@ -0,0 +1,35 @@ +import styled from "styled-components"; + +const FloatingButton = styled.button` + position: fixed; + bottom: 20px; + right: 20px; + width: 60px; + height: 60px; + background-color: #6c63ff; /* Lila färg */ + color: #fff; + border: none; + border-radius: 50%; + font-size: 24px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2); + cursor: pointer; + transition: background-color 0.3s ease; + + &:hover { + background-color: #5a54e3; /* Lite mörkare lila vid hover */ + } +`; + + +interface AddTodoButtonProps { + onClick: () => void; +} + +const AddTodoButton: React.FC = ({ onClick }) => { + return +; +}; + +export default AddTodoButton; diff --git a/src/components/AddTodoPopup.tsx b/src/components/AddTodoPopup.tsx new file mode 100644 index 00000000..35904fbf --- /dev/null +++ b/src/components/AddTodoPopup.tsx @@ -0,0 +1,118 @@ +import { useState } from "react"; +import useTodoStore from "../store/todoStore"; +import styled from "styled-components"; + + +const Overlay = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; +`; + +const PopupContainer = styled.div` + background-color: ${({ theme }) => (theme.isDarkMode ? "#2b2b2b" : "#fff")}; + padding: 20px; + width: 400px; + border-radius: 12px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); + text-align: center; +`; + +const Title = styled.h2` + margin: 0; + margin-bottom: 20px; + font-size: 18px; + color: ${({ theme }) => (theme.isDarkMode ? "#fff" : "#333")}; +`; + +const Input = styled.input` + width: 100%; + padding: 10px; + margin-bottom: 20px; + border: 1px solid #6c63ff; + border-radius: 6px; + font-size: 16px; + outline: none; + color: ${({ theme }) => (theme.isDarkMode ? "#fff" : "#333")}; + background-color: ${({ theme }) => + theme.isDarkMode ? "#444" : "#f9f9f9"}; + + &::placeholder { + color: #b2b2b2; + } +`; + +const ButtonGroup = styled.div` + display: flex; + justify-content: space-between; +`; + +const CancelButton = styled.button` + background: none; + border: 2px solid #6c63ff; + color: #6c63ff; + border-radius: 6px; + padding: 10px 20px; + font-size: 14px; + cursor: pointer; + + &:hover { + background-color: rgba(108, 99, 255, 0.1); + } +`; + +const ApplyButton = styled.button` + background-color: #6c63ff; + border: none; + color: #fff; + border-radius: 6px; + padding: 10px 20px; + font-size: 14px; + cursor: pointer; + + &:hover { + background-color: #5a54e3; + } +`; + + +interface AddTodoPopupProps { + onClose: () => void; +} + +const AddTodoPopup: React.FC = ({ onClose }) => { + const [text, setText] = useState(""); + const addTodo = useTodoStore((state) => state.addTodo); + + const handleSubmit = () => { + if (text.trim().length === 0) return; + addTodo(text); + setText(""); + onClose(); + }; + + return ( + + +

Add a new todo

+ setText(e.target.value)} + /> + + Add + Close + +
+
+ ); +}; + +export default AddTodoPopup; diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 00000000..81e5007c --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import { useTheme } from "../context/ThemeContext"; +import styled from "styled-components"; + +const HeaderContainer = styled.header` + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + background-color: ${({ theme }: { theme: { isDarkMode: boolean } }) => + theme.isDarkMode ? "#121212" : "#ffffff"}; + border-bottom: 1px solid + ${({ theme }: { theme: { isDarkMode: boolean } }) => + theme.isDarkMode ? "#444" : "#ddd"}; + color: ${({ theme }: { theme: { isDarkMode: boolean } }) => + theme.isDarkMode ? "#ddd" : "#444"}; +`; + +const Title = styled.h1` + font-size: 24px; + font-weight: bold; +`; + +const Button = styled.button` + background: #6c63ff; + border: none; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + background-color: ${({ theme }: { theme: { isDarkMode: boolean } }) => + theme.isDarkMode ? "#6c63ff" : "#ece8ff"}; + } +`; + + + +interface HeaderProps { + theme: { isDarkMode: boolean }; + togglePopup: () => void; +} + +const Header: React.FC = ({ theme, togglePopup }) => { + const { isDarkMode, toggleTheme } = useTheme(); + + return ( + + TODO LIST +
+ +
+
+ ); +}; + +export default Header; diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx new file mode 100644 index 00000000..b5bc0c1f --- /dev/null +++ b/src/components/TodoItem.tsx @@ -0,0 +1,61 @@ +import useTodoStore from "../store/todoStore"; +import styled from "styled-components"; + + +const TodoContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 0; + border-bottom: 1px solid ${({ theme }) => (theme.isDarkMode ? "#444" : "#ddd")}; +`; + +const Checkbox = styled.input.attrs({ type: "checkbox" })` + width: 20px; + height: 20px; + accent-color: #6c63ff; /* Gör checkboxen lila när markerad */ + cursor: pointer; +`; + +const TodoText = styled.span<{ completed: boolean }>` + flex: 1; + margin-left: 10px; + text-decoration: ${({ completed }) => (completed ? "line-through" : "none")}; + color: ${({ theme, completed }) => + completed + ? theme.isDarkMode + ? "#888" + : "#aaa" + : theme.isDarkMode + ? "#fff" + : "#333"}; +`; + + +interface Todo { + id: number; + text: string; + completed: boolean; +} + +interface TodoItemProps { + todo: Todo; +} + + +const TodoItem: React.FC = ({ todo }) => { + const { toggleTodo, deleteTodo } = useTodoStore(); + + return ( + + toggleTodo(todo.id)} + /> + {todo.text} + + + ); +}; + +export default TodoItem; \ No newline at end of file diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx new file mode 100644 index 00000000..b0a7a11a --- /dev/null +++ b/src/components/TodoList.tsx @@ -0,0 +1,34 @@ +import useTodoStore from "../store/todoStore"; +import TodoItem from "./TodoItem"; +import styled from "styled-components"; +import empty from "../assets/Detective-check-footprint 1.png" + + +const ListContainer = styled.div` + max-width: 600px; + margin: 20px auto; + padding: 20px; + display: flex; + flex-direction: column; +`; + +const Image = styled.img ` + margin: 0 auto; +`; + + + +const TodoList: React.FC = () => { + const todos = useTodoStore((state) => state.todos); + + return ( + + {todos.length === 0 && No todo's yet!} + {todos.map((todo) => ( + + ))} + + ); + }; + + export default TodoList; \ No newline at end of file diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx new file mode 100644 index 00000000..d43560a6 --- /dev/null +++ b/src/context/ThemeContext.tsx @@ -0,0 +1,52 @@ +import { createContext, useState, useContext, ReactNode } from "react"; + + +interface ThemeContextType { + isDarkMode: boolean; + toggleTheme: () => void; +}; + +interface ThemeProviderProps { + children: ReactNode; +} + +const ThemeContext = createContext(undefined); + +export const ThemeProvider: React.FC = ({ children }) => { + const [isDarkMode, setIsDarkMode] = useState(false); + + const toggleTheme = () => { + setIsDarkMode((prev) => !prev); + }; + + return ( + + {children} + + ); +}; + +// Hook too use themecontext or give an error +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error("useTheme måste användas inom ThemeProvider"); + } + return context; +}; + +/* const ThemeContext = createContext(); + +export const ThemeProvider = ({ children }) => { + const [isDarkMode, setIsDarkMode] = useState(false); + const toggleTheme = () => setIsDarkMode(!isDarkMode); + + + return ( + + {children} + + ); +}; + +export const useTheme = () => useContext(ThemeContext); */ diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 00000000..6dc871aa --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,3 @@ + + +declare module '*.png'; \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx deleted file mode 100644 index 51294f39..00000000 --- a/src/main.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import { App } from "./App.jsx"; -import "./index.css"; - -ReactDOM.createRoot(document.getElementById("root")).render( - - - -); diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 00000000..56e94124 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { ThemeProvider } from "./context/ThemeContext.js"; +import { App } from "./App.js"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + + + +); diff --git a/src/store/todoStore.tsx b/src/store/todoStore.tsx new file mode 100644 index 00000000..007baa04 --- /dev/null +++ b/src/store/todoStore.tsx @@ -0,0 +1,61 @@ +import { create } from "zustand"; + +/* +CODE BEFORE TYPESCRIPT + +const useTodoStore = create((set) => ({ + todos: [], + addTodo: (text) => + set((state) => ({ + todos: [...state.todos, { id: Date.now(), text, completed: false }], + })), + toggleTodo: (id) => + set((state) => ({ + todos: state.todos.map((todo) => + todo.id === id ? { ...todo, completed: !todo.completed } : todo + ), + })), + deleteTodo: (id) => + set((state) => ({ + todos: state.todos.filter((todo) => todo.id !== id), + })), + })); + + */ + + + // into typescript + interface Todo { + id: number; + text: string; + completed: boolean; + } + + interface TodoStore { + todos: Todo[]; + addTodo: (text: string) => void; + toggleTodo: (id: number) => void; + deleteTodo: (id: number) => void; + } + + + const useTodoStore = create((set) => ({ + todos: [], + addTodo: (text) => + set((state) => ({ + todos: [...state.todos, { id: Date.now(), text, completed: false }], + })), + toggleTodo: (id) => + set((state) => ({ + todos: state.todos.map((todo) => + todo.id === id ? { ...todo, completed: !todo.completed } : todo + ), + })), + deleteTodo: (id) => + set((state) => ({ + todos: state.todos.filter((todo) => todo.id !== id), + })), + })); + + +export default useTodoStore; \ No newline at end of file diff --git a/src/styled.d.ts b/src/styled.d.ts new file mode 100644 index 00000000..67631f46 --- /dev/null +++ b/src/styled.d.ts @@ -0,0 +1,7 @@ +import "styled-components"; + +declare module "styled-components" { + export interface DefaultTheme { + isDarkMode: boolean; + } +} diff --git a/src/styles/GlobalStyle.tsx b/src/styles/GlobalStyle.tsx new file mode 100644 index 00000000..a8459d1f --- /dev/null +++ b/src/styles/GlobalStyle.tsx @@ -0,0 +1,39 @@ +import { createGlobalStyle } from "styled-components"; + +export const GlobalStyle = createGlobalStyle` + body { + font-family: Arial, sans-serif; + background-color: ${({ theme }) => { + console.log("Theme in GlobalStyle:", theme); + return theme.isDarkMode ? '#252525' : '#ffffff'; + }}; + color: ${({ theme }) => (theme.isDarkMode ? '#ffffff' : '#121212')}; + margin: 0; + padding: 0; + transition: background-color 0.3s ease, color 0.3s ease; + } + + h1, h2, h3, h4, h5, h6 { + margin: 0; + } + + button { + cursor: pointer; + } + + input { + font-size: 16px; + padding: 8px; + margin: 5px 0; + border-radius: 4px; + border: 1px solid ${({ theme }) => (theme.isDarkMode ? '#444' : '#ccc')}; + background-color: ${({ theme }) => (theme.isDarkMode ? '#333' : '#fff')}; + color: ${({ theme }) => (theme.isDarkMode ? '#fff' : '#333')}; + outline: none; + transition: background-color 0.3s, color 0.3s; + } + + input:focus { + border-color: ${({ theme }) => (theme.isDarkMode ? '#888' : '#007bff')}; + } +`; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..ee4897e7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + + /* Visit https://aka.ms/tsconfig to read more about this file */ + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "node", + "allowJs": true, + "checkJs": true, + "jsx": "react-jsx", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file