diff --git a/index.html b/index.html index 0c589ec..891a938 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,6 @@
- + diff --git a/package-lock.json b/package-lock.json index 39ae647..c00accb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,11 @@ "version": "0.0.0", "dependencies": { "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-icons": "^5.5.0", + "styled-components": "^6.1.19", + "typescript": "^5.8.3", + "zustand": "^5.0.6" }, "devDependencies": { "@eslint/js": "^9.9.0", @@ -328,6 +332,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/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -1194,13 +1219,13 @@ "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.3.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz", "integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1215,6 +1240,12 @@ "@types/react": "*" } }, + "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/@vitejs/plugin-react": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz", @@ -1519,6 +1550,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.30001666", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz", @@ -1594,11 +1634,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.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -3107,7 +3166,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -3314,10 +3372,10 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/possible-typed-array-names": { "version": "1.0.0", @@ -3329,10 +3387,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -3347,15 +3404,22 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "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", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3428,6 +3492,15 @@ "react": "^18.3.1" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -3660,6 +3733,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", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3703,7 +3782,6 @@ "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==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3817,6 +3895,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-components": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz", + "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", + "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.49", + "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", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -3856,6 +3968,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", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3941,6 +4059,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -4174,6 +4305,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.6.tgz", + "integrity": "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==", + "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 e23e136..7e7bd53 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,11 @@ }, "dependencies": { "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-icons": "^5.5.0", + "styled-components": "^6.1.19", + "typescript": "^5.8.3", + "zustand": "^5.0.6" }, "devDependencies": { "@eslint/js": "^9.9.0", diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index b8b8473..0000000 --- a/src/App.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.jsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) -} - -export default App diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..bbddaec --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,50 @@ +import TodoInsert from "./components/TodoInsert"; +import TodoList from "./components/TodoList"; +import TodoTemplate from "./components/TodoTemplate"; +import useTodosStore from "./stores/todoStore"; +import { useShallow } from "zustand/shallow"; + +function App() { + const [todos, setTodos] = useTodosStore( + useShallow((state) => [state.todos, state.setTodos]) + ); + + const handleAddTodo = (text: string) => { + if (text.trim() === "") { + return alert("할 일을 입력해주세요!"); + } + const newTodo = { + id: Date.now(), + text: text, + checked: false, + }; + setTodos([...todos, newTodo]); + }; + + const handleCheckedTodo = (id: number) => { + setTodos( + todos.map((todo) => + todo.id === id ? { ...todo, checked: !todo.checked } : todo + ) + ); + }; + + const handleDeleteTodo = (id: number) => { + setTodos(todos.filter((todo) => todo.id !== id)); + }; + + return ( + <> + + + + + + ); +} + +export default App; diff --git a/src/components/TodoInsert.styled.ts b/src/components/TodoInsert.styled.ts new file mode 100644 index 0000000..6d5d8e0 --- /dev/null +++ b/src/components/TodoInsert.styled.ts @@ -0,0 +1,40 @@ +import styled from "styled-components"; + +export const TodoInsertWrapper = styled.div` + background-color: #495057; + width: 100px; + max-width: 400px; +`; + +export const InsertForm = styled.form` + display: flex; + background: #495057; +`; + +export const StyledInput = styled.input` + flex: 1; + border: none; + padding: 10px 12px; + border-radius: 4px; + font-size: 1.125rem; + outline: none; + background: transparent; + color: white; + &::placeholder { + color: #ced4da; + } +`; + +export const InsertButton = styled.button` + background: #868e96; + color: white; + padding: 10px 20px; + border: none; + border-radius: 0; + font-size: 1.125rem; + cursor: pointer; + transition: 0.2s ease-in-out; + &:hover { + background: #9aa1a9ff; + } +`; diff --git a/src/components/TodoInsert.tsx b/src/components/TodoInsert.tsx new file mode 100644 index 0000000..662b8e5 --- /dev/null +++ b/src/components/TodoInsert.tsx @@ -0,0 +1,43 @@ +import { useState } from "react"; +import { InsertButton, InsertForm, StyledInput } from "./TodoInsert.styled.ts"; +import { MdAdd } from "react-icons/md"; + +interface TodoInsertProps { + onAddTodo: (text: string) => void; +} + +function TodoInsert({ onAddTodo }: TodoInsertProps) { + const [value, setValue] = useState(""); + + const handleInputChange = (e: React.ChangeEvent) => { + setValue(e.target.value); + }; + + const handleAddTodo = (e: React.FormEvent) => { + e.preventDefault(); + + if (value.trim() === "") { + return alert("할 일을 입력해주세요!"); + } + + onAddTodo(value); + setValue(""); + }; + + return ( + <> + + + + + + + + ); +} + +export default TodoInsert; diff --git a/src/components/TodoList.styled.ts b/src/components/TodoList.styled.ts new file mode 100644 index 0000000..d6e3d2d --- /dev/null +++ b/src/components/TodoList.styled.ts @@ -0,0 +1,9 @@ +import styled from "styled-components"; + +export const TodoListBlock = styled.div` + flex: 1; + padding: 10px 0; + overflow-y: auto; + background: white; + box-sizing: border-box; +`; diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx new file mode 100644 index 0000000..9c81a3b --- /dev/null +++ b/src/components/TodoList.tsx @@ -0,0 +1,31 @@ +import { TodoListBlock } from "./TodoList.styled.ts"; +import TodoListItem from "./TodoListItem.tsx"; + +interface Todo { + id: number; + text: string; + checked: boolean; +} + +interface TodoListProps { + todos: Todo[]; + onCheckedTodo: (id: number) => void; + onDeleteTodo: (id: number) => void; +} + +function TodoList({ todos, onCheckedTodo, onDeleteTodo }: TodoListProps) { + return ( + + {todos.map((todo) => ( + + ))} + + ); +} + +export default TodoList; diff --git a/src/components/TodoListItem.styled.ts b/src/components/TodoListItem.styled.ts new file mode 100644 index 0000000..1acc414 --- /dev/null +++ b/src/components/TodoListItem.styled.ts @@ -0,0 +1,61 @@ +import styled from "styled-components"; + +interface StyleProps { + $checked: boolean; +} + +export const TodoListItemBlock = styled.div` + padding: 15px 20px; + display: flex; + align-items: center; +`; + +export const CheckBox = styled.div` + width: 24px; + height: 24px; + border: 1px solid #ced4da; + font-size: 24px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 20px; + cursor: pointer; + ${(props) => + props.$checked && + ` + border: 1px solid #20c997; + color: #20c997; + `} +`; + +export const Text = styled.div` + flex: 1; + font-size: 1.125rem; + color: #495057; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + ${(props) => + props.$checked && + ` + color: #adb5bd; + text-decoration: line-through; + `} + &:hover { + white-space: normal; + overflow: visible; + text-overflow: clip; + } +`; + +export const Remove = styled.div` + display: flex; + align-items: center; + justify-content: center; + color: #dee2e6; + font-size: 24px; + cursor: pointer; + &:hover { + color: #ff6b6b; + } +`; diff --git a/src/components/TodoListItem.tsx b/src/components/TodoListItem.tsx new file mode 100644 index 0000000..02fbf60 --- /dev/null +++ b/src/components/TodoListItem.tsx @@ -0,0 +1,51 @@ +import { + CheckBox, + TodoListItemBlock, + Remove, + Text, +} from "./TodoListItem.styled.ts"; +import { + MdCheckBoxOutlineBlank, + MdCheckBox, + MdRemoveCircleOutline, +} from "react-icons/md"; + +interface Todo { + id: number; + text: string; + checked: boolean; +} + +interface TodoListItemProps { + todo: Todo; + onCheckedTodo: (id: number) => void; + onDeleteTodo: (id: number) => void; +} + +function TodoListItem({ + todo, + onCheckedTodo, + onDeleteTodo, +}: TodoListItemProps) { + const handleCheckedTodo = () => { + onCheckedTodo(todo.id); + }; + + const handleDeleteTodo = () => { + onDeleteTodo(todo.id); + }; + + return ( + + + {todo.checked ? : } + + {todo.text} + + + + + ); +} + +export default TodoListItem; diff --git a/src/components/TodoTemplate.styled.ts b/src/components/TodoTemplate.styled.ts new file mode 100644 index 0000000..6d6dbd0 --- /dev/null +++ b/src/components/TodoTemplate.styled.ts @@ -0,0 +1,46 @@ +import styled from "styled-components"; + +export const AppContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + width: 100vw; + background-color: #e0e0e0; + padding: 20px; + box-sizing: border-box; +`; + +export const TodoAppWrapper = styled.div` + background-color: white; + border-radius: 8px; + width: 100%; + height: 600px; + max-width: 400px; + overflow: hidden; + display: flex; + flex-direction: column; +`; + +export const HeaderContainer = styled.div` + background-color: #007356; + color: white; + padding: 20px; + text-align: center; + border-radius: 8px 8px 0 0; +`; + +export const TextTitle = styled.h1` + font-size: 1.8em; + margin: 0; + display: flex; + align-items: center; + justify-content: center; +`; + +export const GreedyIcon = styled.img` + width: 50px; + height: auto; + margin-right: 15px; + vertical-align: middle; +`; diff --git a/src/components/TodoTemplate.tsx b/src/components/TodoTemplate.tsx new file mode 100644 index 0000000..4917be5 --- /dev/null +++ b/src/components/TodoTemplate.tsx @@ -0,0 +1,32 @@ +import { + AppContainer, + GreedyIcon, + HeaderContainer, + TextTitle, + TodoAppWrapper, +} from "./TodoTemplate.styled.ts"; + +interface TodoTemplateProps { + children: React.ReactNode; +} + +function TodoTemplate({ children }: TodoTemplateProps) { + return ( + + + + + + 일정관리 + + + {children} + + + ); +} + +export default TodoTemplate; diff --git a/src/main.jsx b/src/main.jsx deleted file mode 100644 index 89f91e5..0000000 --- a/src/main.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import App from './App.jsx' -import './index.css' - -createRoot(document.getElementById('root')).render( - - - , -) diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..bf8512a --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,14 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; + +const rootElement = document.getElementById("root"); +if (!rootElement) throw new Error("rootElement를 찾을 수 없습니다."); + +const root = createRoot(rootElement); +root.render( + + + +); diff --git a/src/stores/todoStore.ts b/src/stores/todoStore.ts new file mode 100644 index 0000000..a29bfce --- /dev/null +++ b/src/stores/todoStore.ts @@ -0,0 +1,20 @@ +import { create } from "zustand"; + +interface Todo { + id: number; + text: string; + checked: boolean; +} + +interface TodoStore { + todos: Todo[]; + setTodos: (newTodos: Todo[]) => void; +} + +const useTodosStore = create((set) => ({ + todos: [], + + setTodos: (newTodos: Todo[]) => set({ todos: newTodos }), +})); + +export default useTodosStore;