diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..ab905aa
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "singleQuote": true,
+ "semi": true,
+ "trailingComma": "all",
+ "printWidth": 100,
+ "tabWidth": 3
+}
diff --git a/README.md b/README.md
index 820935e..0817e84 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# 2주차 과제: React Todo
+
# 서론
안녕하세요 🙌🏻 22기 프론트엔드 운영진 **권동욱**입니다.
@@ -25,7 +26,7 @@
- VSCode, Prettier를 이용하여 개발환경을 관리합니다.
- React의 기초를 이해합니다.
- React를 통한 어플리케이션 상태 관리 방법을 이해합니다.
-- React Hooks에 대한 기초를 이해합니다.
+- React Hooks에 대한 기초를 이해합니다.
- Vite를 통한 React 프로젝트 개발환경 구축을 익힙니다.
- Styled-Components를 통한 CSS-in-JS 및 CSS Preprocessor의 사용법을 익힙니다.
@@ -40,14 +41,15 @@
- React 컴포넌트 생명주기에 대해서 설명해주세요.
## 필수 요건
+
- 1주차 미션의 결과물을 그대로 React로 구현합니다. (‼️ todo / done 개수 잊지 마세요 ‼️)
- Styled-Component를 사용합니다.
-- React Hooks만을 사용해 상태를 관리합니다.(전역 상태관리 라이브러리 사용 XX)
+- React Hooks만을 사용해 상태를 관리합니다.(전역 상태관리 라이브러리 사용 XX)
- Vite를 활용하여 React 프로젝트 환경 구축을 진행합니다
## 선택 요건
-- 기존 Todo-list에 여러분들이 추가하고 싶은 기능과 디자인을 자유롭게 추가해보세요.
+- 기존 Todo-list에 여러분들이 추가하고 싶은 기능과 디자인을 자유롭게 추가해보세요.
- TypeScript를 활용하여 프로젝트를 진행해보세요.
## 로컬 실행방법
@@ -64,6 +66,6 @@
- [useState, useEffect hooks](https://velog.io/@velopert/react-hooks#1-usestate)
- [styled-component](https://styled-components.com/docs/basics#getting-started)
- [create react app (CRA) 지원종료 공식문서](https://react.dev/blog/2025/02/14/sunsetting-create-react-app)
-- [create react app 지원종료관련 okky 커뮤니티 게시글](https://okky.kr/articles/1527414)
-- [cra 대신에 vite로 React 프로젝트 시작하기](https://www.daleseo.com/vite-react/)
+- [create react app 지원종료관련 okky 커뮤니티 게시글](https://okky.kr/articles/1527414)
+- [cra 대신에 vite로 React 프로젝트 시작하기](https://www.daleseo.com/vite-react/)
- [Vite 실무 적용기 - 설명 + 프로젝트 설정](https://blog.hectodata.co.kr/bonjour-vite/)
diff --git a/demoscript.js b/demoscript.js
new file mode 100644
index 0000000..70a8e3c
--- /dev/null
+++ b/demoscript.js
@@ -0,0 +1,233 @@
+document.addEventListener('DOMContentLoaded', () => {
+ //menu tab
+ const menuTab = document.getElementById('menuTab');
+ const openBtn = document.querySelector('.menu');
+ const closeBtn = document.getElementById('closeMenu');
+
+ //open and close menu drawer
+ const openDrawer = () => {
+ menuTab.classList.add('active');
+ };
+ const closeDrawer = () => {
+ menuTab.classList.remove('active');
+ };
+
+ openBtn.addEventListener('click', openDrawer);
+ closeBtn.addEventListener('click', closeDrawer);
+
+ //close menu when clicked outside of menu tab
+ document.addEventListener('click', (e) => {
+ if (
+ menuTab.classList.contains('active') &&
+ !menuTab.contains(e.target) &&
+ !openBtn.contains(e.target)
+ ) {
+ closeDrawer();
+ }
+ });
+
+ //close menu when user presses esc
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape' && menuTab.classList.contains('active')) closeDrawer();
+ });
+
+ //define date manipulating buttons
+ const today = document.querySelector('.today');
+ const prevBtn = document.getElementById('yesterday');
+ const nextBtn = document.getElementById('tomorrow');
+
+ //get date
+ let current = new Date();
+ const fmt = { year: 'numeric', month: 'long', day: 'numeric' };
+
+ //Date manipulation
+ function render() {
+ today.textContent = current.toLocaleDateString(undefined, fmt);
+ }
+ render();
+
+ //define next and last week buttons in menu
+ const nWeek = document.querySelector('.nWeek');
+ const lWeek = document.querySelector('.lWeek');
+
+ //피드백 주신 대로 changWeek함수를 만들고 +7/-7을 인자로 남겨 처리했습니다
+ function changeDays(offSetDays) {
+ current.setDate(current.getDate() + offSetDays);
+ render();
+ loadList(current);
+ ifNoEvents();
+ }
+ nWeek.addEventListener('click', () => {
+ changeDays(7);
+ });
+ lWeek.addEventListener('click', () => {
+ changeDays(-7);
+ });
+
+ prevBtn.addEventListener('click', () => {
+ changeDays(-1);
+ });
+
+ nextBtn.addEventListener('click', () => {
+ changeDays(1);
+ });
+
+ //when the date is clicked on, change current date to today
+ today.addEventListener('click', () => {
+ current = new Date();
+ render();
+ loadList(current);
+ });
+
+ const dateKey = (d) => new Date(d).toLocaleDateString('en-CA');
+ // "YYYY-MM-DD" in local time
+ const storageKey = (d) => `${dateKey(d)}`;
+
+ const numEvent = document.querySelector('.numEvent');
+
+ //saves the events for a date
+ function saveList(date) {
+ const dateData = {
+ dateTodoEl: todoEl.innerHTML,
+ dateTodos: todos,
+ };
+ sessionStorage.setItem(storageKey(date), JSON.stringify(dateData));
+ }
+
+ //loads the events for a date
+ function loadList(date) {
+ const raw = sessionStorage.getItem(storageKey(date) || '');
+ if (raw) {
+ const parsed = JSON.parse(raw);
+ todoEl.innerHTML = parsed.dateTodoEl;
+ todos = parsed.dateTodos;
+ } else {
+ todos = [];
+ todoEl.innerHTML = '';
+ }
+ getNumEvent();
+ }
+
+ const input = document.querySelector('.input');
+ const add = document.querySelector('.register');
+ const todoEl = document.querySelector('.todoEl');
+ const clearAll = document.querySelector('.clearAll');
+
+ let todos = [];
+
+ //event listener that is restored once loaded from storage
+ todoEl.addEventListener('click', (e) => {
+ //선택된 li
+ const li = e.target.closest('li');
+ //선택된 li의 index number을 idx라 명명
+ const id = li.dataset.id;
+ const idx = todos.findIndex((t) => t.id === id);
+
+ //delete button
+ if (e.target && e.target.classList.contains('delEvent')) {
+ todos.splice(idx, 1);
+ }
+
+ //done button
+ if (e.target && e.target.classList.contains('doneEvent')) {
+ todos[idx].done = !todos[idx].done;
+ console.log('done');
+ }
+ //pin and unpin events
+ if (e.target && e.target.classList.contains('pinEvent')) {
+ todos[idx].pinned = !todos[idx].pinned;
+ }
+
+ renderTodos();
+ saveList(current);
+ ifNoEvents();
+ getNumEvent();
+ });
+
+ //add event using enter and button click
+ add.addEventListener('click', () => {
+ if (input.value !== '') {
+ addToList(input.value);
+ input.value = '';
+ saveList(current);
+ }
+ });
+ input.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter' && input.value !== '') {
+ addToList(input.value);
+ input.value = '';
+ saveList(current);
+ }
+ });
+
+ function addToList(text) {
+ const newTodo = {
+ id: crypto.randomUUID(),
+ done: false,
+ pinned: false,
+ text: text,
+ };
+ todos.push(newTodo);
+ saveList();
+ renderTodos();
+ }
+
+ function renderTodos() {
+ const sorted = [...todos].sort((a, b) => b.pinned - a.pinned);
+ todoEl.innerHTML = '';
+
+ for (const t of sorted) {
+ const li = document.createElement('li');
+ li.dataset.id = t.id;
+ li.className = t.pinned ? 'pinned' : '';
+ li.innerHTML = `
+
+ ${t.text}
+
+
+ `;
+ todoEl.appendChild(li);
+ }
+ getNumEvent();
+ }
+
+ //clear all events for a date
+ function clearALlEvents(date) {
+ todos = [];
+ renderTodos();
+ saveList(current);
+ ifNoEvents();
+ }
+ clearAll.addEventListener('click', () => {
+ clearALlEvents(current);
+ getNumEvent();
+ });
+
+ function ifNoEvents() {
+ if (todos.length === 0) {
+ sessionStorage.removeItem(storageKey(current));
+ }
+ }
+
+ //get the number of Events
+ function getNumEvent() {
+ numEvent.textContent = 'To-do: ' + todoEl.children.length;
+ }
+
+ //calendar manipulation
+ const menuContent = document.getElementById('menuContent');
+ const datePickerEl = document.createElement('input');
+ datePickerEl.type = 'date';
+ datePickerEl.className = 'menuDatePicker'; // make it obvious
+ menuContent.appendChild(datePickerEl);
+ datePickerEl.addEventListener('change', () => {
+ if (!datePickerEl.value) return;
+ const [y, m, d] = datePickerEl.value.split('-').map(Number);
+ saveList(current);
+ current = new Date(y, m - 1, d);
+ render();
+ loadList(current);
+ });
+});
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..daf38b2
--- /dev/null
+++ b/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+ react-todo 백승선
+
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..be09eb4
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1233 @@
+{
+ "name": "assignment2",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "assignment2",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "styled-components": "^6.1.19"
+ },
+ "devDependencies": {
+ "prettier": "^3.6.2",
+ "vite": "^7.1.6"
+ }
+ },
+ "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.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz",
+ "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz",
+ "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz",
+ "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz",
+ "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz",
+ "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz",
+ "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz",
+ "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz",
+ "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz",
+ "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz",
+ "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz",
+ "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz",
+ "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz",
+ "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz",
+ "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz",
+ "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz",
+ "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz",
+ "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz",
+ "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz",
+ "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz",
+ "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz",
+ "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz",
+ "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz",
+ "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz",
+ "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz",
+ "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz",
+ "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz",
+ "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz",
+ "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz",
+ "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz",
+ "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz",
+ "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz",
+ "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz",
+ "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz",
+ "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz",
+ "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz",
+ "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz",
+ "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz",
+ "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz",
+ "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz",
+ "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz",
+ "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz",
+ "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz",
+ "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz",
+ "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz",
+ "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz",
+ "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz",
+ "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": 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/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/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==",
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz",
+ "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.10",
+ "@esbuild/android-arm": "0.25.10",
+ "@esbuild/android-arm64": "0.25.10",
+ "@esbuild/android-x64": "0.25.10",
+ "@esbuild/darwin-arm64": "0.25.10",
+ "@esbuild/darwin-x64": "0.25.10",
+ "@esbuild/freebsd-arm64": "0.25.10",
+ "@esbuild/freebsd-x64": "0.25.10",
+ "@esbuild/linux-arm": "0.25.10",
+ "@esbuild/linux-arm64": "0.25.10",
+ "@esbuild/linux-ia32": "0.25.10",
+ "@esbuild/linux-loong64": "0.25.10",
+ "@esbuild/linux-mips64el": "0.25.10",
+ "@esbuild/linux-ppc64": "0.25.10",
+ "@esbuild/linux-riscv64": "0.25.10",
+ "@esbuild/linux-s390x": "0.25.10",
+ "@esbuild/linux-x64": "0.25.10",
+ "@esbuild/netbsd-arm64": "0.25.10",
+ "@esbuild/netbsd-x64": "0.25.10",
+ "@esbuild/openbsd-arm64": "0.25.10",
+ "@esbuild/openbsd-x64": "0.25.10",
+ "@esbuild/openharmony-arm64": "0.25.10",
+ "@esbuild/sunos-x64": "0.25.10",
+ "@esbuild/win32-arm64": "0.25.10",
+ "@esbuild/win32-ia32": "0.25.10",
+ "@esbuild/win32-x64": "0.25.10"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "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/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "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/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
+ "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
+ "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.1"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.50.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz",
+ "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.50.2",
+ "@rollup/rollup-android-arm64": "4.50.2",
+ "@rollup/rollup-darwin-arm64": "4.50.2",
+ "@rollup/rollup-darwin-x64": "4.50.2",
+ "@rollup/rollup-freebsd-arm64": "4.50.2",
+ "@rollup/rollup-freebsd-x64": "4.50.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.50.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.50.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.50.2",
+ "@rollup/rollup-linux-arm64-musl": "4.50.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.50.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.50.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.50.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.50.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.50.2",
+ "@rollup/rollup-linux-x64-gnu": "4.50.2",
+ "@rollup/rollup-linux-x64-musl": "4.50.2",
+ "@rollup/rollup-openharmony-arm64": "4.50.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.50.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.50.2",
+ "@rollup/rollup-win32-x64-msvc": "4.50.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "license": "MIT"
+ },
+ "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/source-map-js": {
+ "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"
+ }
+ },
+ "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/styled-components/node_modules/postcss": {
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "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/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "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/vite": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz",
+ "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..c8a45c6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "assignment2",
+ "version": "1.0.0",
+ "description": "안녕하세요 🙌🏻 22기 프론트엔드 운영진 **권동욱**입니다.",
+ "main": "index.js",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "styled-components": "^6.1.19"
+ },
+ "devDependencies": {
+ "prettier": "^3.6.2",
+ "vite": "^7.1.6"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/baekseungsun/react-todo-22nd.git"
+ },
+ "bugs": {
+ "url": "https://github.com/baekseungsun/react-todo-22nd/issues"
+ },
+ "homepage": "https://github.com/baekseungsun/react-todo-22nd#readme"
+}
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 0000000..a2843e5
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,464 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import styled, { createGlobalStyle } from 'styled-components';
+
+const GlobalStyle = createGlobalStyle`
+ * { box-sizing: border-box; }
+ body { margin: 0; font-family: Arial, sans-serif; background: #e2e8f0; color:#0f172a;}
+ button { cursor: pointer; }
+`;
+
+//Styled components 이전 css파일에서 그대로 옮겨서 작성
+const Header = styled.header`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #0f172a;
+ color: white;
+ padding: 14px 16px;
+ position: relative;
+`;
+const Title = styled.h1`
+ margin: 0;
+ font-size: 35px;
+ font-weight: 600;
+`;
+const MenuBtn = styled.button`
+ position: absolute;
+ left: 16px;
+ background: none;
+ border: none;
+ padding: 8px;
+`;
+const MenuIcon = styled.div`
+ width: 24px;
+ height: 2px;
+ background: white;
+ border-radius: 2px;
+ position: relative;
+ &::before,
+ &::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ width: 24px;
+ height: 2px;
+ background: white;
+ border-radius: 2px;
+ }
+ &::before {
+ top: -10px;
+ }
+ &::after {
+ top: 10px;
+ }
+`;
+
+const Drawer = styled.aside`
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 300px;
+ background: #e2e8f0;
+ transform: translateX(${(p) => (p.open ? '0' : '-100%')});
+ transition: transform 0.25s ease;
+ z-index: 10;
+`;
+const DrawerInner = styled.nav`
+ display: flex;
+ height: 100%;
+ flex-direction: column;
+ gap: 8px;
+ font-size: 15px;
+ position: relative;
+ padding: 20px;
+`;
+const CloseBtn = styled.button`
+ position: absolute;
+ left: 20px;
+ top: 15px;
+ font-size: 30px;
+ width: 28px;
+ height: 28px;
+ background: none;
+ border: none;
+`;
+const DrawerOptions = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 110px;
+ margin-top: 80px;
+`;
+const WeekBtn = styled.button`
+ width: 120px;
+ height: 30px;
+ font-size: 16px;
+ border-radius: 10px;
+`;
+
+const DateRow = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 20px;
+ margin: 30px;
+`;
+const DateBtn = styled.button`
+ font-size: 20px;
+ background: none;
+ border: none;
+`;
+const TodayBtn = styled.button`
+ font-size: 20px;
+ background: none;
+ border: none;
+ color: #0f172a;
+`;
+
+const InputRow = styled.div`
+ display: flex;
+ justify-content: center;
+ gap: 20px;
+ margin: 40px auto;
+ background: #fff;
+ border-radius: 20px;
+ padding-left: 10px;
+ width: 400px;
+ height: 50px;
+ align-items: center;
+`;
+const TextInput = styled.input`
+ flex: 1;
+ font-size: 23px;
+ border: none;
+ outline: none;
+ background: none;
+ color: #0f172a;
+`;
+const AddBtn = styled.button`
+ font-size: 15px;
+ width: 60px;
+ height: 25px;
+ border-radius: 13px;
+ border: 2px solid black;
+ background: #0f172a;
+ color: white;
+ margin-right: 6px;
+`;
+
+const ListWrap = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ height: 425px;
+ width: 600px;
+ margin: 0 auto;
+ background: whitesmoke;
+ border: 6px solid #0f172a;
+ border-radius: 20px;
+`;
+const TopRow = styled.div`
+ display: flex;
+`;
+const ClearBtn = styled.button`
+ color: #0f172a;
+ font-size: 20px;
+ border-radius: 20px;
+ width: 120px;
+ margin: 5px 0 0 10px;
+ background: #e2e8f0;
+`;
+const NumToDos = styled.div`
+ line-height: 1.5;
+ color: #0f172a;
+ font-size: 18px;
+ border-radius: 20px;
+ width: 120px;
+ margin-top: 5px;
+ margin-left: auto;
+ margin-right: 10px;
+ background: #e2e8f0;
+ border: 2px solid #0f172a;
+ padding-left: 20px;
+`;
+
+const UL = styled.ul`
+ list-style: none;
+ padding: 0;
+ margin: 5px;
+ max-height: 350px;
+ overflow: auto;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+ font-size: 26px;
+`;
+
+const LI = styled.li`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 5px;
+ border: 3px solid #0f172a;
+ border-radius: 15px;
+ width: 550px;
+ background: #e2e8f0;
+`;
+const Txt = styled.span`
+ margin-left: 10px;
+ margin-right: auto;
+ font-size: 20px;
+`;
+const SmallBtn = styled.button`
+ border-radius: 20px;
+ font-size: 15px;
+ padding: 4px 10px;
+ border: ${(p) => (p.outline ? '1px solid black' : 'none')};
+ background: ${(p) => (p.solid ? '#0f172a' : 'none')};
+ color: ${(p) => (p.solid ? 'white' : '#0f172a')};
+`;
+const DoneBtn = styled(SmallBtn)`
+ background: ${(p) => (p.active ? 'grey' : '#0f172a')};
+ color: ${(p) => (p.active ? 'black' : 'white')};
+`;
+
+const PinBtn = styled(SmallBtn)`
+ background: ${(p) => (p.active ? 'crimson' : 'none')};
+ color: ${(p) => (p.active ? 'white' : '#0f172a')};
+`;
+
+
+
+//Functions
+const toDateKey = (d) => new Date(d).toLocaleDateString('en-CA'); // YYYY-MM-DD in local time
+
+export default function App() {
+ // 날짜와 메뉴 상태
+ const [current, setCurrent] = useState(() => new Date());
+ const [open, setOpen] = useState(false);
+
+ // 날짜별 todos 저장용
+ const [byDate, setByDate] = useState(() => {
+ return {};
+ });
+
+ // 현재 날짜의 todos
+ const dateKey = toDateKey(current);
+ const todos = byDate[dateKey] ?? [];
+
+ // pin기준 정렬
+ const sorted = useMemo(
+ () => [...todos].sort((a, b) => Number(b.pinned) - Number(a.pinned)),
+ [todos],
+ );
+ const total = todos.length;
+
+ // 첫 로드 시
+ useEffect(() => {
+ const raw = sessionStorage.getItem(dateKey);
+ if (raw) {
+ try {
+ const arr = JSON.parse(raw);
+ setByDate((prev) => ({...prev, [dateKey]: arr}));
+ } catch {
+ /* ignore */
+ }
+ } else {
+ setByDate((prev) => ({...prev, [dateKey]: []}));
+ }
+ }, []);
+
+ // 날짜 바뀜에 따라 롣드
+ useEffect(() => {
+ const raw = sessionStorage.getItem(dateKey);
+ if (raw) {
+ try {
+ const arr = JSON.parse(raw);
+ setByDate((prev) => ({...prev, [dateKey]: arr}));
+ } catch {
+ setByDate((prev) => ({...prev, [dateKey]: []}));
+ }
+ } else {
+ setByDate((prev) => ({...prev, [dateKey]: []}));
+ }
+ }, [dateKey]);
+
+ // 현재 날짜 todos 저장, 비어 있다면 해당 날짜 key 삭제
+ const persist = useCallback(
+ (nextList) => {
+ if (!nextList || nextList.length === 0) {
+ sessionStorage.removeItem(dateKey);
+ } else {
+ sessionStorage.setItem(dateKey, JSON.stringify(nextList));
+ }
+ },
+ [dateKey],
+ );
+
+ //투두 추가
+ const [text, setText] = useState('');
+ const addTodo = useCallback(() => {
+ const v = text.trim();
+ if (!v) return;
+ const next = [
+ ...todos,
+ { id: crypto.randomUUID?.() || String(Date.now()), text: v, done: false, pinned: false },
+ ];
+ setByDate((prev) => ({ ...prev, [dateKey]: next }));
+ persist(next);
+ setText('');
+ }, [text, todos, dateKey, persist]);
+
+ //제거
+ const deleteTodo = useCallback(
+ (id) => {
+ const next = todos.filter((t) => t.id !== id);
+ setByDate((prev) => ({ ...prev, [dateKey]: next }));
+ persist(next);
+ },
+ [todos, dateKey, persist],
+ );
+
+
+ //투두 done
+ const toggleDone = useCallback(
+ (id) => {
+ const next = todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t));
+ setByDate((prev) => ({ ...prev, [dateKey]: next }));
+ persist(next);
+ },
+ [todos, dateKey, persist],
+ );
+
+ //투두 pin
+ const togglePin = useCallback(
+ (id) => {
+ const next = todos.map((t) => (t.id === id ? { ...t, pinned: !t.pinned } : t));
+ setByDate((prev) => ({ ...prev, [dateKey]: next }));
+ persist(next);
+ },
+ [todos, dateKey, persist],
+ );
+
+ //전체삭제
+ const clearAll = useCallback(() => {
+ setByDate((prev) => ({ ...prev, [dateKey]: [] }));
+ sessionStorage.removeItem(dateKey);
+ }, [dateKey]);
+
+
+
+ // 날짜 조작
+ const changeDays = useCallback((offset) => {
+ setCurrent((prev) => {
+ const d = new Date(prev);
+ d.setDate(d.getDate() + offset);
+ return d;
+ });
+ }, []);
+ const goToday = useCallback(() => setCurrent(new Date()), []);
+ const onPickDate = useCallback((e) => {
+ if (!e.target.value) return;
+ const [y, m, d] = e.target.value.split('-').map(Number);
+ setCurrent(new Date(y, m - 1, d));
+ }, []);
+
+ // 메뉴탭
+ const drawerRef = useRef(null);
+ const menuBtnRef = useRef(null);
+ useEffect(() => {
+ const onClick = (e) => {
+ //닫혀 있다면 아무 액션 없이 return
+ if (!open) return;
+ if (drawerRef.current?.contains(e.target)) return;
+ if (menuBtnRef.current?.contains(e.target)) return;
+ setOpen(false);
+ };
+ const onKey = (e) => {
+ if (open && e.key === 'Escape') setOpen(false);
+ };
+ document.addEventListener('click', onClick);
+ document.addEventListener('keydown', onKey);
+ return () => {
+ document.removeEventListener('click', onClick);
+ document.removeEventListener('keydown', onKey);
+ };
+ }, [open]);
+
+ const fmt = useMemo(() => ({ year: 'numeric', month: 'long', day: 'numeric' }), []);
+ const dateLabel = useMemo(() => current.toLocaleDateString(undefined, fmt), [current, fmt]);
+
+ return (
+ <>
+
+
+
+ setOpen(true)}>
+
+
+ To Do
+
+
+
+
+ setOpen(false)}>x
+
+
+ changeDays(-7)}>Last Week
+ changeDays(7)}>Next Week
+
+
+
+
+
+
+ changeDays(-1)}>◀
+ {dateLabel}
+ changeDays(1)}>▶
+
+
+
+ setText(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') addTodo();
+ }}
+ />
+ Add
+
+
+
+
+ Clear all
+ To-do: {total}
+
+
+
+ {sorted.map((t) => (
+ -
+ toggleDone(t.id)}>
+ {t.done ? 'Undone' : 'Done'}
+
+ {t.text}
+ togglePin(t.id)}>
+ {t.pinned ? 'Unpin' : 'Pin'}
+
+ deleteTodo(t.id)}>
+ Delete
+
+
+ ))}
+
+
+ >
+ );
+}
+
+
+
+
+
diff --git a/src/main.jsx b/src/main.jsx
new file mode 100644
index 0000000..1b79458
--- /dev/null
+++ b/src/main.jsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+ ,
+);
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..6f78dc4
--- /dev/null
+++ b/style.css
@@ -0,0 +1,281 @@
+/*reset*/
+body {
+ margin: 0;
+ font-family: arial, sans-serif;
+ background: #e2e8f0;
+}
+
+/* Header layout with flexbox */
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #0f172a;
+ color: white;
+ padding: 14px 16px;
+}
+
+.menu {
+ position: absolute;
+ left: 16px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 8px;
+}
+
+.menuIcon,
+.menuIcon::before,
+.menuIcon::after {
+ display: block;
+ width: 24px;
+ height: 2px;
+ background: white;
+ border-radius: 2px;
+ content: '';
+ position: relative;
+}
+
+.menuIcon::before,
+.menuIcon::after {
+ position: absolute;
+ left: 0;
+}
+
+.menuIcon::before {
+ top: -10px;
+}
+.menuIcon::after {
+ top: 10px;
+}
+
+.menuTab {
+ position: fixed;
+ background: #e2e8f0;
+ width: 300px;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ transform: translateX(-100%);
+ transition: transform 0.25s ease;
+}
+
+.menuTab.active {
+ transform: translateX(0);
+}
+
+.menuContent {
+ display: flex;
+ height: 100%;
+ flex-direction: column;
+ gap: 8px;
+ font-size: 15px;
+}
+
+.options {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin-top: 80px;
+ align-items: center;
+ gap: 110px;
+ padding: 20px;
+}
+
+.nWeek,
+.lWeek {
+ width: 120px;
+ height: 30px;
+ font-size: 16px;
+ border-radius: 10px;
+}
+
+.menuDatePicker {
+ margin-top: 80px;
+ margin-left: 71px;
+ font-size: 16px;
+ width: 160px;
+ height: 30px;
+ border-radius: 10px;
+}
+
+#closeMenu {
+ position: absolute;
+ font-size: 30px;
+ width: 22px;
+ margin-top: 15px;
+ margin-left: 20px;
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+/* Centered title */
+.title {
+ margin: 0;
+ font-size: 35px;
+ font-weight: 600;
+}
+
+.dateSelector {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ margin: 30px;
+ gap: 20px;
+}
+
+.date {
+ font-size: 20px;
+ background: none;
+ border: none;
+}
+
+.today {
+ display: flex;
+ color: #0f172a;
+ font-size: 20px;
+ background: none;
+ border: none;
+}
+
+.inputContainer {
+ display: flex;
+ justify-content: center;
+ gap: 20px;
+ margin: 40px auto;
+ background: whitesmoke;
+ border-radius: 20px;
+ padding-left: 10px;
+ width: 400px;
+ height: 50px;
+}
+
+.input {
+ font-size: 23px;
+ border: none;
+ outline: none;
+ background: none;
+ color: #0f172a;
+}
+
+.register {
+ font-size: 15px;
+ width: 60px;
+ height: 25px;
+ border-radius: 13px;
+ border: 2px solid black;
+ cursor: pointer;
+ background: #0f172a;
+ color: white;
+ margin-right: 1px;
+ margin-top: auto;
+ margin-bottom: auto;
+}
+
+.list {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ height: 425px;
+ width: 600px;
+ margin: 0 auto;
+ background: whitesmoke;
+ border: 6px solid #0f172a;
+ border-radius: 20px;
+}
+
+.todoEl {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+ font-size: 26px;
+ padding: 0;
+ margin: 5px;
+ max-height: 350px;
+ overflow-y: auto;
+}
+
+.topRowList {
+ display: flex;
+}
+
+.clearAll {
+ color: #0f172a;
+ font-size: 20px;
+ border-radius: 20px;
+ width: 120px;
+ margin-top: 5px;
+ margin-left: 10px;
+ background: #e2e8f0;
+}
+
+.numEvent {
+ line-height: 1.5;
+ color: #0f172a;
+ font-size: 18px;
+ border-radius: 20px;
+ width: 120px;
+ margin-top: 5px;
+ margin-left: auto;
+ margin-right: 10px;
+ background: #e2e8f0;
+ border: 2px solid #0f172a;
+ padding-left: 20px;
+}
+.todoEl li {
+ line-height: 1.5;
+ display: flex;
+ padding: 10px 5px;
+ border: 3px solid #0f172a;
+ border-radius: 15px;
+ height: 30px;
+ width: 550px;
+ background: #e2e8f0;
+}
+
+.delEvent {
+ background: #0f172a;
+ color: white;
+ border: none;
+ font-size: 16px;
+ border-radius: 20px;
+}
+
+.text {
+ margin-left: 10px;
+ margin-right: auto;
+ font-size: 20px;
+}
+
+.pinEvent {
+ background: none;
+ color: white;
+ border: black solid 1px;
+ font-size: 15px;
+ border-radius: 20px;
+ margin-right: 8px;
+}
+
+.doneEvent {
+ background: #0f172a;
+ color: white;
+ border: none;
+ font-size: 15px;
+ border-radius: 20px;
+}
+
+.markDone {
+ text-decoration: line-through;
+ text-decoration-color: red;
+ color: grey;
+}
+
+.markPin {
+ background: red;
+}
+
+body.invert {
+ filter: invert(1);
+}