diff --git a/README.md b/README.md index 847b26b..3091d79 100644 --- a/README.md +++ b/README.md @@ -1 +1,104 @@ -# js-todo-list-step1 \ No newline at end of file +

+ +

+

JS 투두리스트 스텝1

+

자바스크립트로 구현 하는 투두리스트

+

+ template version + + + + + + +

+ +## 🔥 Projects! + +

+ +

+ +

+ 🖥️ 데모 링크 +

+ +
+ +## 🎯 요구사항 + +- [x] todo list에 todoItem을 키보드로 입력하여 추가하기 +- [x] todo list의 체크박스를 클릭하여 complete 상태로 변경 (li tag 에 completed class 추가, input 태그에 checked 속성 추가) +- [x] todo list의 x버튼을 이용해서 해당 엘리먼트를 삭제 +- [x] todo list를 더블클릭했을 때 input 모드로 변경 (li tag 에 editing class 추가) 단 이때 수정을 완료하지 않은 상태에서 esc키를 누르면 수정되지 않은 채로 다시 view 모드로 복귀 +- [x] todo list의 item갯수를 count한 갯수를 리스트의 하단에 보여주기 +- [x] todo list의 상태값을 확인하여, 해야할 일과, 완료한 일을 클릭하면 해당 상태의 아이템만 보여주기 + +
+ +## 🔔 참고사항 +`TodoItem`을 추가할 시 아래 템플릿을 활용하면 됩니다. +```html + +``` + +
+ +## ⚙️ Before Started + +#### Tip 로컬에서 서버 띄워서 손쉽게 static resources 변경 및 확인하는 방법 + +로컬에서 웹서버를 띄워 html, css, js 등을 실시간으로 손쉽게 테스트해 볼 수 있습니다. 이를 위해서는 우선 npm이 설치되어 있어야 합니다. 구글에 `npm install` 이란 키워드로 각자의 운영체제에 맞게끔 npm을 설치해주세요. 이후 아래의 명령어를 통해 실시간으로 웹페이지를 테스트해볼 수 있습니다. + +``` +npm install -g live-server +``` + +실행은 아래의 커맨드로 할 수 있습니다. + +``` +live-server 폴더명 +``` + +
+ +## 👏 Contributing + +만약 미션 수행 중에 개선사항이 보인다면, 언제든 자유롭게 PR을 보내주세요. + +
+ +## 🐞 Bug Report + +버그를 발견한다면, [Issues](https://github.com/woowacourse/js-todo-list-step1/issues)에 등록해주세요. + +
+ +## 📝 License + +This project is [MIT](https://github.com/woowacourse/js-todo-list-step1/blob/main/LICENSE) licensed. diff --git a/index.html b/index.html new file mode 100644 index 0000000..482723c --- /dev/null +++ b/index.html @@ -0,0 +1,39 @@ + + + + + + 이벤트 - TODOS + + + +
+

TODOS

+ +
+ + +
+ 0 + +
+
+
+ + + diff --git a/src/css/style.css b/src/css/style.css new file mode 100644 index 0000000..7dcbc6d --- /dev/null +++ b/src/css/style.css @@ -0,0 +1,334 @@ +html, +body { + margin: 0; + padding: 10px; +} + +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + font-weight: inherit; + color: inherit; + -webkit-appearance: none; + appearance: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #f5f5f5; + color: #4d4d4d; + min-width: 230px; + max-width: 550px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 300; +} + +:focus { + outline: 0; +} + +.hidden { + display: none; +} + +.todoapp { + background: #fff; + margin: 130px 0 40px 0; + position: relative; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), + 0 25px 50px 0 rgba(0, 0, 0, 0.1); +} + +.todoapp input::-webkit-input-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp h1 { + position: absolute; + top: -125px; + width: 100%; + font-size: 60px; + text-align: center; + color: dimgray; + font-weight: 100; + font-family: Helvetica Neue, Helvetica, Arial, sans-serif; +} + +.new-todo, +.edit { + position: relative; + margin: 0; + width: 100%; + font-size: 24px; + font-family: inherit; + font-weight: inherit; + line-height: 1.4em; + border: 0; + color: inherit; + padding: 6px; + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.new-todo { + padding: 16px 16px 16px 60px; + border: none; + background: rgba(0, 0, 0, 0.003); + box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); +} + +main { + position: relative; + z-index: 2; + border-top: 1px solid #e6e6e6; +} + +.toggle-all { + width: 1px; + height: 1px; + border: none; + opacity: 0; + position: absolute; + right: 100%; + bottom: 100%; +} + +.toggle-all + label { + width: 60px; + height: 34px; + font-size: 0; + position: absolute; + top: -52px; + left: -13px; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); +} + +.toggle-all + label:before { + content: '❯'; + font-size: 22px; + color: #e6e6e6; + padding: 10px 27px 10px 27px; +} + +.toggle-all:checked + label:before { + color: #737373; +} + +.todo-list { + margin: 0; + padding: 0; + list-style: none; +} + +.todo-list li { + position: relative; + font-size: 24px; + border-bottom: 1px solid #ededed; +} + +.todo-list li:last-child { + border-bottom: none; +} + +.todo-list li.editing { + border-bottom: none; + padding: 0; +} + +.todo-list li.editing .edit { + display: block; + width: calc(100% - 43px); + padding: 12px 16px; + margin: 0 0 0 43px; +} + +.todo-list li.editing .view { + display: none; +} + +.todo-list li .toggle { + text-align: center; + width: 40px; + height: auto; + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; + border: none; + -webkit-appearance: none; + appearance: none; +} + +.todo-list li .toggle { + opacity: 0; +} + +.todo-list li .toggle + label { + background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E'); + background-repeat: no-repeat; + background-position: center left; +} + +.todo-list li .toggle:checked + label { + background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E'); +} + +.todo-list li label { + word-break: break-all; + padding: 15px 15px 15px 60px; + display: block; + line-height: 1.2; + transition: color 0.4s; +} + +.todo-list li.completed label { + color: #d9d9d9; + text-decoration: line-through; +} + +.todo-list li .destroy { + display: none; + position: absolute; + top: 0; + right: 10px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 30px; + color: #cc9a9a; + margin-bottom: 11px; + transition: color 0.2s ease-out; + cursor: pointer; +} + +.todo-list li .destroy:hover { + color: #af5b5e; +} + +.todo-list li .destroy:after { + content: '×'; +} + +.todo-list li:hover .destroy { + display: block; +} + +.todo-list li .edit { + display: none; +} + +.todo-list li.editing:last-child { + margin-bottom: -1px; +} + +.count-container { + color: #777; + padding: 10px 15px; + height: 20px; + text-align: center; + border-top: 1px solid #e6e6e6; +} + +.count-container:before { + content: ''; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 50px; + overflow: hidden; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), + 0 8px 0 -3px #f6f6f6, + 0 9px 1px -3px rgba(0, 0, 0, 0.2), + 0 16px 0 -6px #f6f6f6, + 0 17px 2px -6px rgba(0, 0, 0, 0.2); +} + +.todo-count { + float: left; + text-align: left; +} + +.todo-count strong { + font-weight: 300; +} + +.filters { + margin: 0; + padding: 0; + list-style: none; + position: absolute; + right: 0; + left: 0; +} + +.filters li { + display: inline; +} + +.filters li a { + color: inherit; + margin: 3px; + padding: 3px 7px; + text-decoration: none; + border: 1px solid transparent; + border-radius: 3px; +} + +.filters li a:hover { + border-color: rgba(175, 47, 47, 0.1); +} + +.filters li a.selected { + border-color: rgba(175, 47, 47, 0.2); +} + +.clear-completed, html .clear-completed:active { + float: right; + position: relative; + line-height: 20px; + text-decoration: none; + cursor: pointer; +} + +.clear-completed:hover { + text-decoration: underline; +} + +.info { + margin: 65px auto 0; + color: #bfbfbf; + font-size: 10px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-align: center; +} + +.info p { + line-height: 1; +} + +.info a { + color: inherit; + text-decoration: none; + font-weight: 400; +} + +.info a:hover { + text-decoration: underline; +} diff --git a/src/images/check_list.png b/src/images/check_list.png new file mode 100644 index 0000000..dc86070 Binary files /dev/null and b/src/images/check_list.png differ diff --git a/src/images/sample.gif b/src/images/sample.gif new file mode 100644 index 0000000..f36c914 Binary files /dev/null and b/src/images/sample.gif differ diff --git a/src/script/todolist.js b/src/script/todolist.js new file mode 100644 index 0000000..28a8a8d --- /dev/null +++ b/src/script/todolist.js @@ -0,0 +1,110 @@ +const $todoInput = document.getElementById("new-todo-title"); +const $todoList = document.getElementById("todo-list"); +const $count = document.querySelector("strong") +$todoInput.addEventListener('keyup', addTodoItem); +$todoList.addEventListener('keyup', cancelEditItem); +$todoList.addEventListener('click', todoComplete); +$todoList.addEventListener('click', todoDelete); +$todoList.addEventListener('dblclick', todoEdit); + +function addTodoItem(event) { + if (event.key === 'Enter' && event.target.value !== "") { + const addedItem = getTodoItem($todoInput); + $todoList.insertAdjacentHTML("beforeend", addedItem); + $todoInput.value = null; // input clear + } + updateTodoItemsCount($todoList.childElementCount); +} + +function todoComplete(event) { + const todoItem = event.target.closest("li") + + if (event.target.type === 'checkbox' && + !event.target.classList.contains("checked")) { + todoItem.classList.toggle("completed"); + todoItem.querySelector(".toggle").classList.add("checked") + } else if (event.target.type === 'checkbox' && + event.target.classList.contains("checked")) { + todoItem.classList.toggle("completed"); + todoItem.querySelector(".toggle").classList.remove("checked") + } +} + +function updateTodoItemsCount(count) { + $count.innerText = count; +} + +function todoDelete(event) { + if (event.target.tagName === 'BUTTON') { + event.target.closest("li").remove(); + } + updateTodoItemsCount($todoList.childElementCount); +} + +function todoEdit(event) { + const todoItem = event.target.closest("li"); + todoItem.classList.add("editing"); + todoItem.querySelector(".edit").addEventListener('keyup', completeEdit) +} + +function completeEdit(event) { + const todoItem = event.target.closest("li"); + if (event.key === 'Enter') { + todoItem.querySelector(".label").textContent = event.target.value; + todoItem.classList.remove("editing"); + } +} + +function cancelEditItem(event) { + if (event.target.className === 'edit' && event.key === 'Escape') { + event.target.closest("li").classList.remove("editing"); + } +} + +function getTodoItem(itemTitle) { + return `
  • +
    + + + +
    + + +
  • ` +} + +const $allSelected = document.querySelector(".selected"); +const $active = document.querySelector(".active"); +const $completed = document.querySelector(".completed"); + +$allSelected.addEventListener('click', showAll) +$active.addEventListener('click', showTodo) +$completed.addEventListener('click', showCompleted) + +function showAll() { + Array.from($todoList.children).forEach( + element => element.style.display = "block") + updateTodoItemsCount($todoList.childElementCount); +} + +function showTodo() { + const completed = Array.from($todoList.children).filter( + element => element.classList.contains("completed")); + const notCompleted = Array.from($todoList.children).filter( + element => !element.classList.contains("completed")); + + notCompleted.forEach(element => element.style.display = 'block') + completed.forEach(element => element.style.display = 'none') + updateTodoItemsCount(notCompleted.length) +} + +function showCompleted() { + const completed = Array.from($todoList.children).filter( + element => element.classList.contains("completed")); + const notCompleted = Array.from($todoList.children).filter( + element => !element.classList.contains("completed")); + + completed.forEach(element => element.style.display = 'block') + notCompleted.forEach(element => element.style.display = 'none') + updateTodoItemsCount(completed.length) +}