diff --git a/README.md b/README.md deleted file mode 100644 index c258cda..0000000 --- a/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# 1주차 미션: Vanilla-Todo - -# 서론 - -안녕하세요 🙌🏻 20기 프론트엔드 운영진 **이지인**입니다. - -이번 미션은 개발 환경 구축과 스터디 진행 방식에 익숙해지실 수 있도록 간단한 **to-do list** 만들기를 진행합니다. 무작정 첫 스터디부터 React를 다루는 것보다는 왜 React가 필요한지, React가 없으면 무엇이 불편한지 느껴 보고 본격적인 스터디에 들어가는 것이 React를 이해하는 데 더 많은 도움이 될 것이라 생각합니다. - -비교적 가벼운 미션인 만큼 코드를 짜는 데 있어 여러분의 **창의성**을 충분히 발휘해 보시기 바랍니다. 작동하기만 하면 되는 것보다 같은 코드를 짜는 여러가지 방식과 패턴에 대해 고민해 보시고, 본인이 생각한 가장 창의적인 방법으로 코드를 작성해 주세요. 여러분이 미션을 수행하는 과정에서 겪는 고민과 생각의 깊이만큼 스터디에서 더 많은 것을 얻어가실 수 있을 것입니다. - -막히는 부분이 있더라도 우선은 스스로 공부하고 찾아보는 방법을 권고드리지만, 운영진의 도움이 필요하시다면 얼마든지 프론트엔드 카톡방에 편하게 질문을 남겨 주세요! - -# 미션 - -## 미션 목표 - -- VSCode, Prettier를 이용하여 개발 환경을 관리합니다. -- HTML/CSS의 기초를 이해합니다. -- JavaScript를 이용한 DOM 조작을 이해합니다. -- Vanilla Js를 이용한 어플리케이션 상태 관리 방법을 이해합니다. - -## 기한 - -- 2024년 9월 7일 토요일 - -## Key Questions - -- DOM은 무엇인가요? -- 이벤트 흐름 제어(버블링 & 캡처링)이 무엇인가요? -- 클로저와 스코프가 무엇인가요? - -## 필수 요건 - -- [결과 화면](https://vanilla-todo-19th-dh.vercel.app/)의 기능을 구현합니다. (날짜, 요일별 todo 개수) -- 결과 링크의 화면 디자인 그대로 구현해도 좋고, 자신만의 디자인을 적용해도 좋습니다. -- CSS의 Flexbox를 이용하여 레이아웃을 구성합니다. -- JQuery, React, Bootstrap 등 외부 라이브러리를 사용하지 않습니다. -- 함수와 변수의 이름은 lowerCamelCase로 짓습니다. -- 코딩의 단위를 기능별로 나누어 Commit 메세지를 작성합니다. -- Semantic tag를 활용하여 HTML 구조를 완성합니다. - -## 선택 요건 - -- 외부 폰트 Pretendard를 적용합니다. -- 브라우저의 `localStorage` 혹은 `sessionStorage`를 이용하여 다음 번 접속 시에 기존의 투두 데이터를 불러옵니다. -- 이 외에도 추가하고 싶은 기능이 있다면 마음껏 추가하셔도 됩니다. -- 미디어쿼리를 이용해서 반응형을 적용합니다. - -# 링크 및 참고자료 - -- [HTML/CSS 기초](https://heropy.blog/2019/04/24/html-css-starter/) -- [HTML 태그](https://heropy.blog/2019/05/26/html-elements/) -- [FlexBox 가이드](https://heropy.blog/2018/11/24/css-flexible-box/) -- [JS를 통한 DOM 조작](https://velog.io/@bining/javascript-DOM-%EC%A1%B0%EC%9E%91%ED%95%98%EA%B8%B0#append) -- [localStorage, sessionStorage](https://www.daleseo.com/js-web-storage/) -- [git 사용법](https://wayhome25.github.io/git/2017/07/08/git-first-pull-request-story/) -- [좋은 코드리뷰 방법](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) diff --git a/icon/NotCheck.svg b/icon/NotCheck.svg new file mode 100644 index 0000000..9d3d2a2 --- /dev/null +++ b/icon/NotCheck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon/check.svg b/icon/check.svg new file mode 100644 index 0000000..6e79d15 --- /dev/null +++ b/icon/check.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/icon/checkComplete.svg b/icon/checkComplete.svg new file mode 100644 index 0000000..78d0937 --- /dev/null +++ b/icon/checkComplete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html index d241b1b..2e52821 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,23 @@ -
+
✓ To Do
+
+

TO DO LIST

+

9월 6일 금요일

+ + +
+
+ + + +
+
+ + + +
diff --git a/script.js b/script.js index 355dcc2..08f6c1c 100644 --- a/script.js +++ b/script.js @@ -1 +1,119 @@ -//😍CEOS 20기 프론트엔드 파이팅😍 +const addBtn = document.querySelector('.addBtn'); +const TodoInput = document.querySelector('.TodoInput'); +const todoList = document.querySelector('.todoList'); +const todoForm = document.getElementById('todoForm'); + +let todos = []; + +// localStorage에서 todos 불러오기 +function loadTodos() { + const savedTodos = localStorage.getItem('todos'); + if (savedTodos) { + todos = JSON.parse(savedTodos); + todos.forEach(todo => createTodoElement(todo.text, todo.completed)); + } +} + +// localStorage에 todos 저장 +function saveTodos() { + localStorage.setItem('todos', JSON.stringify(todos)); +} + +function addTodo(e) { + e.preventDefault(); + + const Todo = TodoInput.value.trim(); + + if (Todo) { + createTodoElement(Todo, false); + todos.push({ text: Todo, completed: false }); + saveTodos(); + TodoInput.value = ''; + } else { + alert('To Do를 입력하세요'); + } +} + +todoForm.addEventListener('submit', addTodo); + +// 투두 추가 함수 +function createTodoElement(Todo, isCompleted) { + const listItem = document.createElement('li'); + listItem.classList.add('animate-slide-down'); + + if (isCompleted) { + listItem.classList.add('completed'); + } + + // 완료 토글 아이콘 + const toggleIcon = document.createElement('img'); + toggleIcon.src = isCompleted ? './icon/checkComplete.svg' : 'https://raw.githubusercontent.com/ryu-won/vanilla-todo-20th/40e5a4dcd0113eadd85034cc953aa3fa97de4527/icon/NotCheck.svg'; + toggleIcon.alt = isCompleted ? 'Toggle Complete' : 'Toggle unComplete'; + toggleIcon.classList.add('toggle-icon'); + + // 투두 텍스트 + const todoText = document.createElement('span'); + todoText.textContent = Todo; + if (isCompleted) { + todoText.classList.add('completed-text'); + } + + toggleIcon.addEventListener('click', () => { + listItem.classList.toggle('completed'); + todoText.classList.toggle('completed-text'); + const isNowCompleted = listItem.classList.contains('completed'); + if (isNowCompleted) { + toggleIcon.src = './icon/checkComplete.svg'; + toggleIcon.alt = 'Toggle Complete'; + } else { + toggleIcon.src = 'https://raw.githubusercontent.com/ryu-won/vanilla-todo-20th/40e5a4dcd0113eadd85034cc953aa3fa97de4527/icon/NotCheck.svg'; + toggleIcon.alt = 'Toggle unComplete'; + } + // localStorage 업데이트 + const index = todos.findIndex(item => item.text === Todo); + if (index !== -1) { + todos[index].completed = isNowCompleted; + saveTodos(); + } + }); + + // 삭제 버튼 + const deleteBtn = document.createElement('button'); + deleteBtn.textContent = 'Del'; + deleteBtn.classList.add('delete-btn'); + deleteBtn.addEventListener('click', () => { + listItem.classList.add('animate-fade-out'); + setTimeout(() => { + todoList.removeChild(listItem); + // localStorage에서 삭제 + todos = todos.filter(item => item.text !== Todo); + saveTodos(); + }, 300); + }); + + listItem.appendChild(toggleIcon); + listItem.appendChild(todoText); + listItem.appendChild(deleteBtn); + todoList.appendChild(listItem); +} + +// 날짜 표시 함수 +function formatDateKorean(date) { + const days = ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일']; + const months = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']; + + const month = months[date.getMonth()]; + const day = date.getDate(); + const dayOfWeek = days[date.getDay()]; + + return `${month} ${day}일 ${dayOfWeek}`; +} + +// 페이지 로드 시 실행 +document.addEventListener('DOMContentLoaded', () => { + const todayDateElement = document.getElementById('todayDate'); + const today = new Date(); + todayDateElement.textContent = formatDateKorean(today); + + loadTodos(); // localStorage에서 todos 불러오기 +}); \ No newline at end of file diff --git a/style.css b/style.css index 599136a..737d460 100644 --- a/style.css +++ b/style.css @@ -1 +1,187 @@ -/* 본인의 디자인 감각을 최대한 발휘해주세요! */ +@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.8/dist/web/static/pretendard.css"); + +html{ + font-family: "Pretendard"; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + li { + list-style: none; + display: flex; + align-items: center; + } + + /*헤더*/ + header{ + padding-left: 45px; + height: 10vh; + display: flex; + align-items: center; + background-color: #788bff; + color: white; + font-size: 2rem; + box-shadow: 0 0 100px 100px #d6ddff; + border-bottom: 1px solid #7e8dea; + +} +/**/ + +/*투두 시작*/ + .todoContainer { + background-color: #f1f3ff; + width: 50%; + height: 90vh; + margin:0 auto; + border-radius: 5px; + padding: 30px; + box-shadow: inset 0 0 20px #d6ddff;; +} + +.todoContainer > .title { + font-size: 23px; + font-weight: 700; + color: #788bff; +} + +.todoContainer > #todayDate { + margin-top: 5px; + font-size: 13px; + color:#8790ca; +} + +.WriteForm{ + width: 100%; + border-radius: 10px; + margin-top: 20px; + display: flex; + flex-direction: column; + background-color: #d6ddff; +} + +.check_icon { + width: 20px; + height: 20px; + margin: 0 15px 0 0px; +} + +.writeTodoForm { + width: 100%; + height: 52px; + display: flex; + flex-direction: row; + align-items: center; + padding: 0 15px 0 15px; +} + +.writeTodoForm > .TodoInput { + width: 100%; + height: 50px; + outline: none; + border: none; + color: #788bff; + background-color: transparent; + font-weight: 700; + font-size: 0.8rem; +} + +.writeTodoForm > .TodoInput::placeholder{ + color: #788bff; +} + +.writeTodoForm > .addBtn, .delete-btn { + cursor: pointer; + background-color: #788bff; + width: 36px; + height: 22px; + border-radius: 10px; + font-size: 0.75rem; + color: white; + padding: 2px; + border: none; + transition: background-color .3s, color .3s; +} + +.writeTodoForm > .addBtn:hover, .delete-btn:hover{ + background-color: white; + color: #788bff; +} + +.todoList{ + width:100%; +} + +.todoList li { + width: 100%; + border-radius: 10px; + margin-top: 20px; + display: flex; + flex-direction: row; + align-items: center; + background-color: #d6ddff; + height: 50px; + padding: 0 15px 0 15px; + color: #788bff; + font-weight: 700; + font-size: 0.8rem; +} + +.todoList .completed-text { + text-decoration: line-through; + color: #7d808e; +} + +.todoList .toggle-icon, .delete-icon { + width: 20px; + height: 20px; + cursor: pointer; +} + +.todoList .toggle-icon { + margin-right: 10px; +} + +.todoList .delete-icon { + margin-left: auto; +} + +.todoList span { + flex-grow: 1; +} + +/* 할일 추가 애니메이션*/ +@keyframes slideDownFadeIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + .animate-slide-down { + animation: slideDownFadeIn 0.2s ease-out forwards; + } + /* */ + + /* 할일 삭제 애니메이션 */ + @keyframes fadeOutScaleDown { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.9); + transform: translateY(+10px); + } + } + .animate-fade-out { + animation: fadeOutScaleDown 0.2s ease-out forwards; + } + /* */ \ No newline at end of file