diff --git a/README.md b/README.md index 34a3ed5..f127f32 100644 --- a/README.md +++ b/README.md @@ -57,4 +57,4 @@ - [좋은 코드리뷰 방법](https://tech.kakao.com/2022/03/17/2022-newkrew-onboarding-codereview/) - [MDN 공식문서-createElement()](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement) - [MDN 공식문서-appendChild()](https://developer.mozilla.org/ko/docs/Web/API/Node/appendChild) -- [DOM 개념,HTML 요소 조작](https://poiemaweb.com/js-dom#3-dom-query--traversing-%EC%9A%94%EC%86%8C%EC%97%90%EC%9D%98-%EC%A0%91%EA%B7%BC) \ No newline at end of file +- [DOM 개념,HTML 요소 조작](https://poiemaweb.com/js-dom#3-dom-query--traversing-%EC%9A%94%EC%86%8C%EC%97%90%EC%9D%98-%EC%A0%91%EA%B7%BC) diff --git a/todo.css b/todo.css new file mode 100644 index 0000000..d7d5707 --- /dev/null +++ b/todo.css @@ -0,0 +1,307 @@ +:root { + --bg-color: #fff5f0; + --text-color: #5d4037; + --container-bg: #ffffff; + --accent-color: #ffab91; + --button-bg: #ffccbc; + --delete-btn-bg: #e65272; + --delete-btn-hover-bg: #c4415b; + --add-btn-bg: #799567; + --add-btn-hover-bg: #5c7348; +} + +/* 다크 테마 - 블루베리 컨셉 */ +body.blueberry-mode { + --bg-color: #1a1c2c; + --text-color: #e0e0ff; + --container-bg: #252a41; + --accent-color: #7986cb; + --button-bg: #3f51b5; + --delete-btn-bg: #a180ad; + --delete-btn-hover-bg: #7b5e8c; +} + +body { + background-color: var(--bg-color); + color: var(--text-color); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 20px; + font-family: Pretendard; + max-width: 500px; + margin: auto; + transition: all 0.4s ease; +} + +.container { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + margin: auto; +} + +#date-container { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} + +title { + font-size: 24px; + font-weight: bold; +} + +#today-date { + font-size: 18px; +} +#today-day { + font-size: 18px; +} +#prev-btn, +#next-btn { + background-color: transparent; + border: none; + font-size: 18px; + cursor: pointer; + color: var(--text-color); +} +#prev-btn:hover, +#next-btn:hover { + color: var(--accent-color); +} + +#add-btn { + background-color: var(--add-btn-bg); + border: none; + padding: 6px 12px; + font-size: 14px; + cursor: pointer; + color: white; + border-radius: 100px; + text-align: center; +} +#add-btn:hover { + background-color: var(--add-btn-hover-bg); +} +#todo-input { + flex: 1; + border: none; + outline: none; + font-size: 14px; + background-color: transparent; + color: var(--text-color); +} +#todo-count { + font-size: 14px; + color: var(--text-color); + margin-left: 10px; + text-align: center; + display: flex; + align-items: center; +} +#todo-form { + display: flex; + flex-direction: row; + background-color: var(--container-bg); + border: none; + padding: 10px; + border-radius: 100px; + gap: 10px; +} + +.input-group { + display: flex; + align-items: center; + background-color: white; + padding: 8px 10px 8px 16px; + border-radius: 30px; /* 둥근 모양 */ + gap: 15px; +} +.todo-list { + display: flex; + flex-direction: column; + justify-content: center; + width: auto; + margin: 20px; + width: 100%; +} +.todo-item { + display: flex; + justify-content: space-between; /* 양 끝 정렬 */ + align-items: center; + padding: 10px 20px; +} +.todo-left { + display: flex; + align-items: center; + gap: 10px; + flex: 1; + margin-right: 10px; +} + +#todo-text { + word-break: break-all; + white-space: normal; + overflow: visible; + text-align: left; + line-height: 1.4; +} + +/* 완료 시 취소선 효과 */ +.todo-item.completed span { + text-decoration: line-through; + color: #aaa; +} + +.delete-btn { + background-color: var(--delete-btn-bg); + color: white; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; +} +.delete-btn:hover { + background-color: var(--delete-btn-hover-bg); +} + +/* 1. 기본 체크박스 숨기기 */ +.checkbox-container input { + display: none; +} + +/* 2. 커스텀 체크박스 외형 */ +.custom-checkbox { + width: 20px; + height: 20px; + border: 2px solid var(--accent-color); + border-radius: 6px; /* 살짝 둥근 사각형 */ + display: inline-block; + position: relative; + cursor: pointer; + transition: all 0.2s ease; + background-color: transparent; + margin: 2px; +} + +/* 3. 체크되었을 때 배경색 변경 */ +.checkbox-container input:checked + .custom-checkbox { + background-color: var(--accent-color); +} + +/* 4. 체크 표시(V) 그리기 (가상 요소 활용) */ +.custom-checkbox::after { + content: ""; + position: absolute; + display: none; + + /* 체크 모양 만들기 */ + left: 7px; + top: 2px; + width: 5px; + height: 10px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +/* 5. 체크되었을 때 V 표시 보이기 */ +.checkbox-container input:checked + .custom-checkbox::after { + display: block; +} + +#theme-btn { + position: fixed; + bottom: 30px; + right: 30px; + width: 60px; + height: 60px; + border-radius: 50%; + background-color: var(--button-bg); + border: none; + font-size: 28px; + cursor: pointer; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; +} + +/* 폰트 */ +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Thin.woff2") + format("woff2"); + font-weight: 100; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-ExtraLight.woff2") + format("woff2"); + font-weight: 200; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Light.woff2") + format("woff2"); + font-weight: 300; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Regular.woff2") + format("woff2"); + font-weight: 400; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Medium.woff2") + format("woff2"); + font-weight: 500; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-SemiBold.woff2") + format("woff2"); + font-weight: 600; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Bold.woff2") + format("woff2"); + font-weight: 700; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-ExtraBold.woff2") + format("woff2"); + font-weight: 800; + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/pretendard@1.0/Pretendard-Black.woff2") + format("woff2"); + font-weight: 900; + font-display: swap; +} diff --git a/todo.html b/todo.html new file mode 100644 index 0000000..1e42af9 --- /dev/null +++ b/todo.html @@ -0,0 +1,40 @@ + + + + + + To-Do List + + + +
+
+

To-Do List

+
+ +

날짜

+

요일

+ +
+
+
+
+ + 남은 할 일 0개 + +
+ +

+ + +
+
+ + + + diff --git a/todo.js b/todo.js new file mode 100644 index 0000000..bcfec99 --- /dev/null +++ b/todo.js @@ -0,0 +1,145 @@ +/* --- 1. 데이터 및 기준 변수 관리 --- */ +let currentViewDate = new Date(); +let todoData = JSON.parse(localStorage.getItem("todoData")) || {}; + +const todoForm = document.getElementById("todo-form"); +const todoInput = document.getElementById("todo-input"); +const todoListElement = document.getElementById("todo-list"); +const countElement = document.getElementById("todo-count"); + +/* --- 2. 유틸리티 함수 (중복 로직 분리) --- */ + +// 현재 보고 있는 날짜를 'YYYY-MM-DD' 형식의 키로 반환하는 함수 +function getCurrentDateKey() { + const year = currentViewDate.getFullYear(); + const month = currentViewDate.getMonth() + 1; + const date = currentViewDate.getDate(); + return `${year}-${month}-${date}`; +} + +// 로컬 스토리지에 현재 데이터 상태 저장 +function saveTodo() { + localStorage.setItem("todoData", JSON.stringify(todoData)); +} + +/* --- 3. 렌더링 함수 (화면 그리기) --- */ + +function renderDate() { + const dayList = [ + "일요일", + "월요일", + "화요일", + "수요일", + "목요일", + "금요일", + "토요일", + ]; + + document.getElementById("today-date").textContent = + `${currentViewDate.getFullYear()}년 ${currentViewDate.getMonth() + 1}월 ${currentViewDate.getDate()}일`; + document.getElementById("today-day").textContent = + dayList[currentViewDate.getDay()]; +} + +function renderTodo() { + const dateKey = getCurrentDateKey(); + const currentTodos = todoData[dateKey] || []; + + // 남은 할 일 개수 계산 (전체 개수를 원하면 .length만 사용) + const remainingTodos = currentTodos.filter((todo) => !todo.completed); + countElement.textContent = `남은 할 일 ${remainingTodos.length}개`; + + todoListElement.innerHTML = ""; + + currentTodos.forEach((todo, index) => { + const li = document.createElement("li"); + li.className = "todo-item"; + if (todo.completed) li.classList.add("completed"); + + li.innerHTML = ` +
+ + +
+ + `; + + li.querySelector(".todo-text").textContent = todo.text; + todoListElement.appendChild(li); + }); +} + +/* --- 4. 기능 함수 (데이터 수정) --- */ + +function addTodo(event) { + event.preventDefault(); + const taskText = todoInput.value.trim(); + if (!taskText) return; + + const dateKey = getCurrentDateKey(); // 함수 실행 시점의 날짜 키 가져오기 + if (!todoData[dateKey]) todoData[dateKey] = []; + + todoData[dateKey].push({ text: taskText, completed: false }); + + saveTodo(); + todoInput.value = ""; + renderTodo(); +} + +function toggleTodo(index) { + const dateKey = getCurrentDateKey(); + todoData[dateKey][index].completed = !todoData[dateKey][index].completed; + saveTodo(); + renderTodo(); +} + +function deleteTodo(index) { + const dateKey = getCurrentDateKey(); + todoData[dateKey].splice(index, 1); + saveTodo(); + renderTodo(); +} + +/* --- 5. 날짜 이동 및 이벤트 연결 --- */ + +function moveDay(offset) { + currentViewDate.setDate(currentViewDate.getDate() + offset); + renderDate(); + renderTodo(); +} + +document + .getElementById("prev-btn") + .addEventListener("click", () => moveDay(-1)); +document.getElementById("next-btn").addEventListener("click", () => moveDay(1)); +todoForm.addEventListener("submit", addTodo); + +/* --- 6. 테마 변경 --- */ +const themeToggle = document.getElementById("theme-btn"); + +function updateThemeUI(theme) { + if (theme === "blueberry") { + document.body.classList.add("blueberry-mode"); + themeToggle.textContent = "🫐"; + } else { + document.body.classList.remove("blueberry-mode"); + themeToggle.textContent = "🍑"; + } +} + +// 초기 테마 설정 +updateThemeUI(localStorage.getItem("theme")); + +themeToggle.addEventListener("click", () => { + const isBlueberry = document.body.classList.toggle("blueberry-mode"); + const theme = isBlueberry ? "blueberry" : "peach"; + localStorage.setItem("theme", theme); + updateThemeUI(theme); +}); + +// 첫 실행 +renderDate(); +renderTodo();