├── README.md ├── assets ├── css │ └── style.css ├── images │ └── calender-image.svg ├── js │ └── script.js └── sounds │ └── task-complete.mp3 ├── favicon.svg ├── index.html ├── index.txt ├── readme-images └── desktop.png └── style-guide.md /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![GitHub repo size](https://img.shields.io/github/repo-size/codewithsadee/taskbuddy) 4 | ![GitHub stars](https://img.shields.io/github/stars/codewithsadee/taskbuddy?style=social) 5 | ![GitHub forks](https://img.shields.io/github/forks/codewithsadee/taskbuddy?style=social) 6 | [![Twitter Follow](https://img.shields.io/twitter/follow/codewithsadee_?style=social)](https://twitter.com/intent/follow?screen_name=codewithsadee_) 7 | [![YouTube Video Views](https://img.shields.io/youtube/views/BWmL66b9bOo?style=social)](https://youtu.be/BWmL66b9bOo) 8 | 9 |
10 |
11 | 12 |

Taskbuddy - Todo app

13 | 14 | Taskbuddy is a fully responsive todo app,
Responsive for all devices, build using HTML, CSS, and JavaScript. 15 | 16 | ➥ Live Demo 17 | 18 |
19 | 20 |
21 | 22 | ### Demo Screeshots 23 | 24 | ![Taskbuddy Desktop Demo](./readme-images/desktop.png "Desktop Demo") 25 | 26 | ### Prerequisites 27 | 28 | Before you begin, ensure you have met the following requirements: 29 | 30 | * [Git](https://git-scm.com/downloads "Download Git") must be installed on your operating system. 31 | 32 | ### Run Locally 33 | 34 | To run **Taskbuddy** locally, run this command on your git bash: 35 | 36 | Linux and macOS: 37 | 38 | ```bash 39 | sudo git clone https://github.com/codewithsadee/taskbuddy.git 40 | ``` 41 | 42 | Windows: 43 | 44 | ```bash 45 | git clone https://github.com/codewithsadee/taskbuddy.git 46 | ``` 47 | 48 | ### Contact 49 | 50 | If you want to contact with me you can reach me at [Twitter](https://www.twitter.com/codewithsadee). 51 | 52 | ### License 53 | 54 | This project is **free to use** and does not contains any license. 55 | -------------------------------------------------------------------------------- /assets/css/style.css: -------------------------------------------------------------------------------- 1 | /*-----------------------------------*\ 2 | #style.css 3 | \*-----------------------------------*/ 4 | 5 | /** 6 | * copyright 2022 codewithsadee 7 | */ 8 | 9 | 10 | 11 | 12 | 13 | /*-----------------------------------*\ 14 | #CUSTOM PROPERTY 15 | \*-----------------------------------*/ 16 | 17 | :root { 18 | 19 | /** 20 | * colors 21 | */ 22 | 23 | --granite-gray: hsl(210, 1%, 37%); 24 | --eerie-black: hsl(240, 2%, 11%); 25 | --slimy-green: hsl(91, 100%, 30%); 26 | --fuzzy-wuzzy: hsl(0, 42%, 57%); 27 | --davys-gray: hsl(220, 2%, 28%); 28 | --cultured-1: hsl(228, 17%, 94%); 29 | --cultured-2: hsl(0, 0%, 96%); 30 | --dim-gray: hsl(240, 1%, 41%); 31 | --white_80: hsla(0, 0%, 100%, 0.8); 32 | --white_70: hsla(0, 0%, 100%, 0.7); 33 | --white_40: hsla(0, 0%, 100%, 0.4); 34 | --white_10: hsla(0, 0%, 100%, 0.1); 35 | --black_05: hsla(0, 0%, 0%, 0.05); 36 | --black_50: hsla(0, 0%, 0%, 0.5); 37 | --black_25: hsla(0, 0%, 0%, 0.25); 38 | --white-2: hsl(0, 0%, 98%); 39 | --white-1: hsl(0, 0%, 100%); 40 | --black: hsl(0, 0%, 0%); 41 | 42 | /** 43 | * theme colors 44 | */ 45 | 46 | --hue: 227; 47 | --theme-bg: linear-gradient(to bottom, 48 | hsl(var(--hue, 227), 40%, 50%), 49 | hsl(var(--hue, 227), 40%, 60%)); 50 | --theme-color: hsl(var(--hue, 227), 42%, 57%); 51 | 52 | /** 53 | * typography 54 | */ 55 | 56 | --ff-roboto: "Roboto", sans-serif; 57 | 58 | --fs-1: 2.6rem; 59 | --fs-2: 2.2rem; 60 | --fs-3: 1.8rem; 61 | --fs-4: 1.4rem; 62 | --fs-5: 1.2rem; 63 | 64 | --fw-500: 500; 65 | 66 | /** 67 | * radius 68 | */ 69 | 70 | --radius-4: 4px; 71 | --radius-5: 5px; 72 | --radius-10: 10px; 73 | 74 | /** 75 | * shadow 76 | */ 77 | 78 | --shadow-1: 0 0 5px hsla(0, 0%, 0%, 0.05); 79 | --shadow-2: 0 0 10px hsla(0, 0%, 0%, 0.05); 80 | 81 | /** 82 | * transition 83 | */ 84 | 85 | --transition: 0.15s ease; 86 | --cubic-out: cubic-bezier(0.05, 0.65, 0.25, 1); 87 | 88 | } 89 | 90 | 91 | 92 | 93 | 94 | /*-----------------------------------*\ 95 | #RESET 96 | \*-----------------------------------*/ 97 | 98 | *, 99 | *::before, 100 | *::after { 101 | margin: 0; 102 | padding: 0; 103 | box-sizing: border-box; 104 | } 105 | 106 | li { list-style: none; } 107 | 108 | a { 109 | text-decoration: none; 110 | color: inherit; 111 | } 112 | 113 | a, 114 | img, 115 | span, 116 | input, 117 | label, 118 | button, 119 | strong, 120 | ion-icon { display: block; } 121 | 122 | img { height: auto; } 123 | 124 | input, 125 | button { 126 | background: none; 127 | border: none; 128 | font: inherit; 129 | } 130 | 131 | input { width: 100%; } 132 | 133 | button { cursor: pointer; } 134 | 135 | strong { font-weight: normal; } 136 | 137 | ion-icon { pointer-events: none; } 138 | 139 | html { 140 | font-family: var(--ff-roboto); 141 | font-size: 10px; 142 | scroll-behavior: smooth; 143 | } 144 | 145 | body { 146 | background-image: var(--theme-bg); 147 | color: var(--eerie-black); 148 | font-size: 1.6rem; 149 | min-height: 100vh; 150 | padding-block: 30px; 151 | display: grid; 152 | grid-template-columns: 1fr; 153 | overflow: hidden; 154 | opacity: 0; 155 | } 156 | 157 | body.loaded { 158 | opacity: 1; 159 | transition: 0.3s ease; 160 | } 161 | 162 | :focus-visible { outline-offset: 4px; } 163 | 164 | :focus-visible:active { outline-offset: 2px; } 165 | 166 | 167 | 168 | 169 | 170 | /*-----------------------------------*\ 171 | #REUSED STYLE 172 | \*-----------------------------------*/ 173 | 174 | .container { padding-inline: 20px; } 175 | 176 | .container, 177 | .main, 178 | .app-box { height: 100%; } 179 | 180 | .h1, 181 | .h2 { font-weight: var(--fw-500); } 182 | 183 | .h1 { font-size: var(--fs-1); } 184 | 185 | .h2 { font-size: var(--fs-3); } 186 | 187 | 188 | 189 | 190 | 191 | /*-----------------------------------*\ 192 | #HEADER 193 | \*-----------------------------------*/ 194 | 195 | .app-box { 196 | position: relative; 197 | z-index: 1; 198 | } 199 | 200 | .header { 201 | display: flex; 202 | justify-content: space-between; 203 | align-items: flex-start; 204 | } 205 | 206 | .header-title { 207 | color: var(--white-1); 208 | margin-block-end: 5px; 209 | } 210 | 211 | .header-time { 212 | color: var(--white-1); 213 | font-size: var(--fs-5); 214 | } 215 | 216 | .dropdown-wrapper { position: relative; } 217 | 218 | .menu-btn { 219 | color: var(--white-1); 220 | font-size: 16px; 221 | padding: 6px; 222 | border-radius: var(--radius-5); 223 | } 224 | 225 | .menu-btn:is(:hover, :focus) { background-color: var(--white_40); } 226 | 227 | .dropdown-container { 228 | position: absolute; 229 | top: calc(100% + 5px); 230 | right: 0; 231 | width: 200px; 232 | overflow: hidden; 233 | z-index: 3; 234 | visibility: hidden; 235 | } 236 | 237 | .dropdown-container.active { visibility: visible; } 238 | 239 | .dropdown-list { 240 | background-color: var(--white-1); 241 | border-radius: var(--radius-5); 242 | transform: translateY(-50%); 243 | box-shadow: var(--shadow-2); 244 | transition: none; 245 | } 246 | 247 | .dropdown-container.active .dropdown-list { 248 | transform: translateY(0); 249 | transition: 0.5s var(--cubic-out); 250 | transition-delay: 0.25s; 251 | } 252 | 253 | .dropdown-item { padding: 15px; } 254 | 255 | .item-title { 256 | color: var(--black); 257 | font-size: var(--fs-4); 258 | margin-block-end: 15px; 259 | } 260 | 261 | .theme-btn-group { 262 | display: grid; 263 | grid-template-columns: repeat(4, 1fr); 264 | gap: 12px; 265 | } 266 | 267 | .theme-btn { 268 | background-image: linear-gradient(to bottom, 269 | hsl(var(--hue, 227), 40%, 50%), 270 | hsl(var(--hue, 227), 40%, 60%)); 271 | width: 100%; 272 | aspect-ratio: 1 / 1; 273 | outline: 2px solid transparent; 274 | outline-offset: 2px; 275 | } 276 | 277 | .theme-btn:is(:hover, :focus) { outline-color: var(--cultured-1); } 278 | 279 | .theme-btn.active { outline-color: var(--slimy-green); } 280 | 281 | .theme-btn:active { transform: scale(0.98); } 282 | 283 | .dropdown-link { 284 | width: 100%; 285 | display: flex; 286 | align-items: center; 287 | gap: 15px; 288 | padding: 12px 15px; 289 | } 290 | 291 | .dropdown-link:is(:hover, :focus) { background-color: var(--black_05); } 292 | 293 | .dropdown-link:active { --black_05: hsla(0, 0%, 0%, 0.1); } 294 | 295 | .dropdown-link ion-icon { 296 | color: var(--dim-gray); 297 | font-size: 20px; 298 | } 299 | 300 | .dropdown-link .span { 301 | color: var(--eerie-black); 302 | font-size: var(--fs-4); 303 | } 304 | 305 | 306 | 307 | 308 | 309 | /*-----------------------------------*\ 310 | #MAIN 311 | \*-----------------------------------*/ 312 | 313 | .title-wrapper { 314 | position: absolute; 315 | top: 50%; 316 | left: 50%; 317 | transform: translate(-50%, -50%); 318 | min-width: max-content; 319 | user-select: none; 320 | z-index: -1; 321 | } 322 | 323 | .title-wrapper.hide { opacity: 0; } 324 | 325 | .title-wrapper .img { 326 | width: 90px; 327 | margin-inline: auto; 328 | margin-block-end: 10px; 329 | } 330 | 331 | .title-wrapper .h2 { 332 | color: var(--white-1); 333 | text-align: center; 334 | } 335 | 336 | .task-list { 337 | margin-block: 20px; 338 | max-height: calc(100vh - 200px); 339 | padding-inline-end: 4px; 340 | margin-inline-end: -4px; 341 | overflow-y: auto; 342 | } 343 | 344 | .task-list::-webkit-scrollbar { width: 2px; } 345 | 346 | .task-list::-webkit-scrollbar-thumb { 347 | background-color: transparent; 348 | border-radius: 5px; 349 | } 350 | 351 | .task-list:is(:hover, :focus-within)::-webkit-scrollbar-thumb { 352 | background-color: hsla(0, 0%, 0%, 0.5); 353 | } 354 | 355 | .task-item:not(:last-child) { margin-block-end: 2px; } 356 | 357 | .task-item { 358 | background-color: var(--cultured-1); 359 | display: flex; 360 | justify-content: flex-start; 361 | align-items: flex-start; 362 | gap: 10px; 363 | padding: 14px 18px; 364 | border-radius: var(--radius-4); 365 | user-select: none; 366 | } 367 | 368 | .task-item:is(:hover, :focus-within) { background-color: var(--cultured-2); } 369 | 370 | .task-item:active { transform: scale(0.99); } 371 | 372 | .task-item .check-icon { 373 | --icon-color: var(--granite-gray); 374 | --icon-opacity: 0; 375 | 376 | position: relative; 377 | width: 20px; 378 | height: 20px; 379 | border: 2px solid var(--granite-gray); 380 | border-radius: 50%; 381 | } 382 | 383 | .task-item .item-icon:is(:hover, :focus) .check-icon { --icon-opacity: 1; } 384 | 385 | .task-item .item-icon:active .check-icon { 386 | --icon-color: var(--cultured-1); 387 | background-color: var(--granite-gray); 388 | } 389 | 390 | .task-item .check-icon::before, 391 | .task-item .check-icon::after { 392 | content: ""; 393 | position: absolute; 394 | width: 9px; 395 | height: 2px; 396 | border-radius: 5px; 397 | background-color: var(--icon-color); 398 | opacity: var(--icon-opacity); 399 | } 400 | 401 | .task-item .check-icon::before { 402 | width: 6px; 403 | transform: rotate(45deg); 404 | top: 8px; 405 | left: 2px; 406 | } 407 | 408 | .task-item .check-icon::after { 409 | transform: rotate(-45deg); 410 | top: 7px; 411 | left: 5px; 412 | } 413 | 414 | .task-item.complete .check-icon { 415 | background-color: var(--theme-color); 416 | border-color: var(--theme-color); 417 | --icon-color: var(--cultured-1); 418 | --icon-opacity: 1; 419 | animation: scale-up 0.2s ease forwards; 420 | } 421 | 422 | @keyframes scale-up { 423 | 0% { transform: scale(0.5); } 424 | 100% { transform: scale(1); } 425 | } 426 | 427 | .task-item .item-text { 428 | font-size: var(--fs-4); 429 | padding-block-start: 2px; 430 | } 431 | 432 | .task-item.complete .item-text { 433 | color: var(--granite-gray); 434 | text-decoration: line-through; 435 | } 436 | 437 | .task-item .item-action-btn { 438 | margin-inline-start: auto; 439 | color: var(--dim-gray); 440 | opacity: 0; 441 | } 442 | 443 | .task-item:is(:hover, :focus-within) .item-action-btn { opacity: 1; } 444 | 445 | .task-item .item-action-btn:is(:hover, :focus) { color: var(--fuzzy-wuzzy); } 446 | 447 | .task-item .item-action-btn:is(:hover, :focus) ion-icon { --ionicon-stroke-width: 50px; } 448 | 449 | .input-wrapper { 450 | position: absolute; 451 | bottom: 0; 452 | left: 0; 453 | right: 0; 454 | } 455 | 456 | .input-field { 457 | background-color: var(--white_70); 458 | color: var(--eerie-black); 459 | font-size: var(--fs-4); 460 | padding: 14px; 461 | padding-inline-start: 50px; 462 | box-shadow: var(--shadow-1); 463 | border-radius: var(--radius-4); 464 | } 465 | 466 | .input-field::placeholder { 467 | color: var(--dim-gray); 468 | opacity: 0; 469 | } 470 | 471 | .input-field:focus::placeholder { opacity: 1; } 472 | 473 | .input-field:is(:hover, :focus) { 474 | background-color: var(--white_80); 475 | outline: none; 476 | } 477 | 478 | .input-text, 479 | .input-icon { 480 | position: absolute; 481 | top: 50%; 482 | transform: translateY(-50%); 483 | } 484 | 485 | .input-text { 486 | left: 50px; 487 | font-size: var(--fs-4); 488 | pointer-events: none; 489 | } 490 | 491 | .input-icon { 492 | left: 14px; 493 | font-size: 26px; 494 | } 495 | 496 | .input-field:is(:not(:placeholder-shown), :focus) ~ .input-text { opacity: 0; } 497 | 498 | .input-icon .circle-icon { 499 | color: var(--davys-gray); 500 | --ionicon-stroke-width: 40px; 501 | } 502 | 503 | .input-field:focus ~ .input-icon .add-icon, 504 | .input-field ~ .input-icon .circle-icon { display: none; } 505 | 506 | .input-field ~ .input-icon .add-icon, 507 | .input-field:focus ~ .input-icon .circle-icon { display: block; } 508 | 509 | 510 | 511 | 512 | 513 | /*-----------------------------------*\ 514 | #MODAL 515 | \*-----------------------------------*/ 516 | 517 | .app-info { 518 | position: fixed; 519 | top: 0; 520 | left: 0; 521 | width: 100%; 522 | height: 100%; 523 | background-color: var(--black_50); 524 | display: flex; 525 | justify-content: center; 526 | align-items: center; 527 | z-index: 4; 528 | opacity: 0; 529 | visibility: hidden; 530 | transition: var(--transition); 531 | } 532 | 533 | .app-info.active { 534 | opacity: 1; 535 | visibility: visible; 536 | } 537 | 538 | .modal { 539 | background-color: var(--cultured-2); 540 | padding: 20px; 541 | padding-block-start: 30px; 542 | border-radius: var(--radius-5); 543 | max-width: 280px; 544 | width: 100%; 545 | transform: scale(1.1); 546 | transform-origin: bottom; 547 | transition: 0.15s var(--cubic-out); 548 | } 549 | 550 | .app-info.active .modal { 551 | transform: scale(1); 552 | transition-duration: 0.4s; 553 | } 554 | 555 | .modal-title { 556 | font-size: var(--fs-2); 557 | font-weight: var(--fw-500); 558 | } 559 | 560 | .modal-list { margin-block: 10px 30px; } 561 | 562 | .modal-item { font-size: var(--fs-4); } 563 | 564 | .modal-item:not(:last-child) { margin-block-end: 5px; } 565 | 566 | .modal-item > * { display: inline-block; } 567 | 568 | .modal-item .span { color: var(--dim-gray); } 569 | 570 | .modal-item a.span { color: var(--theme-color); } 571 | 572 | .modal-btn { 573 | width: 100%; 574 | min-height: 30px; 575 | font-size: var(--fs-4); 576 | background-color: var(--white-1); 577 | color: var(--black); 578 | border-radius: var(--radius-4); 579 | outline: 1px solid var(--cultured-1); 580 | box-shadow: 0 1.5px 1px var(--black_25); 581 | } 582 | 583 | .modal-btn:is(:hover, :focus) { background-color: var(--white-2); } 584 | 585 | .modal-btn:active { 586 | color: var(--dim-gray); 587 | box-shadow: none; 588 | } 589 | 590 | 591 | 592 | 593 | 594 | /*-----------------------------------*\ 595 | #MEDIA QUERIES 596 | \*-----------------------------------*/ 597 | 598 | /** 599 | * responsive for larger than 550px screen 600 | */ 601 | 602 | @media (min-width: 550px) { 603 | 604 | /** 605 | * CUSTOM PROPERTY 606 | */ 607 | 608 | :root { 609 | 610 | /** 611 | * typography 612 | */ 613 | 614 | --fs-1: 3rem; 615 | --fs-3: 2.2rem; 616 | --fs-5: 1.4rem; 617 | 618 | } 619 | 620 | 621 | 622 | /** 623 | * HEADER 624 | */ 625 | 626 | .dropdown-container { width: 280px; } 627 | 628 | .theme-btn-group { grid-template-columns: repeat(5, 1fr); } 629 | 630 | 631 | 632 | /** 633 | * MAIN 634 | */ 635 | 636 | .title-wrapper .img { width: 100px; } 637 | 638 | } 639 | 640 | 641 | 642 | 643 | 644 | /** 645 | * responsive for larger than 768px screen 646 | */ 647 | 648 | @media (min-width: 768px) { 649 | 650 | /** 651 | * REUSED STYLE 652 | */ 653 | 654 | .container { 655 | max-width: 650px; 656 | width: 100%; 657 | margin-inline: auto; 658 | } 659 | 660 | 661 | 662 | /** 663 | * HEADER 664 | */ 665 | 666 | .app-box { 667 | background-color: var(--white_10); 668 | padding: 25px; 669 | border-radius: var(--radius-10); 670 | } 671 | 672 | 673 | 674 | /** 675 | * MAIN 676 | */ 677 | 678 | .task-list { max-height: calc(100vh - 250px); } 679 | 680 | .input-wrapper { 681 | bottom: 25px; 682 | left: 25px; 683 | right: 25px; 684 | } 685 | 686 | } -------------------------------------------------------------------------------- /assets/images/calender-image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /assets/js/script.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | 5 | // select all DOM elements 6 | 7 | const headerTime = document.querySelector("[data-header-time]"); 8 | const menuTogglers = document.querySelectorAll("[data-menu-toggler]"); 9 | const menu = document.querySelector("[data-menu]"); 10 | const themeBtns = document.querySelectorAll("[data-theme-btn]"); 11 | const modalTogglers = document.querySelectorAll("[data-modal-toggler]"); 12 | const welcomeNote = document.querySelector("[data-welcome-note]"); 13 | const taskList = document.querySelector("[data-task-list]"); 14 | const taskInput = document.querySelector("[data-task-input]"); 15 | const modal = document.querySelector("[data-info-modal]"); 16 | let taskItem = {}; 17 | let taskRemover = {}; 18 | 19 | // store current date from build-in date object 20 | const date = new Date(); 21 | 22 | // import task complete sound 23 | const taskCompleteSound = new Audio("./assets/sounds/task-complete.mp3"); 24 | 25 | 26 | 27 | /** 28 | * convert weekday number to weekday name 29 | * totalParameter: 1; 30 | * parameterValue: 0-6; 31 | */ 32 | 33 | const getWeekDayName = function (dayNumber) { 34 | switch (dayNumber) { 35 | case 0: 36 | return "Sunday"; 37 | case 1: 38 | return "Monday"; 39 | case 2: 40 | return "Tuesday"; 41 | case 3: 42 | return "Wednesday"; 43 | case 4: 44 | return "Thursday"; 45 | case 5: 46 | return "Friday"; 47 | case 6: 48 | return "Satureday"; 49 | default: 50 | return "Not a valid day"; 51 | } 52 | } 53 | 54 | 55 | 56 | /** 57 | * convert month number to month name 58 | * totalParameter: 1; 59 | * parameterValue: 0-11; 60 | */ 61 | 62 | const getMonthName = function (monthNumber) { 63 | switch (monthNumber) { 64 | case 0: 65 | return "Jan"; 66 | case 1: 67 | return "Feb"; 68 | case 2: 69 | return "Mar"; 70 | case 3: 71 | return "Apr"; 72 | case 4: 73 | return "May"; 74 | case 5: 75 | return "Jun"; 76 | case 6: 77 | return "Jul"; 78 | case 7: 79 | return "Aug"; 80 | case 8: 81 | return "Sep"; 82 | case 9: 83 | return "Oct"; 84 | case 10: 85 | return "Nov"; 86 | case 11: 87 | return "Dec"; 88 | default: 89 | return "Not a valid month"; 90 | } 91 | } 92 | 93 | 94 | 95 | // store weekday name, month name & month-of-day number 96 | const weekDayName = getWeekDayName(date.getDay()); 97 | const monthName = getMonthName(date.getMonth()); 98 | const monthOfDay = date.getDate(); 99 | 100 | // update headerTime date 101 | headerTime.textContent = `${weekDayName}, ${monthName} ${monthOfDay}`; 102 | 103 | 104 | 105 | /** 106 | * toggle active class on element 107 | * totalParameter: 1; 108 | * parameterValue: elementNode; 109 | */ 110 | 111 | const elemToggler = function (elem) { elem.classList.toggle("active"); } 112 | 113 | 114 | 115 | /** 116 | * toggle active class on multiple elements 117 | * totalParameter: 2; 118 | * parameterValue: elementNode, any; 119 | */ 120 | 121 | const addEventOnMultiElem = function (elems, event) { 122 | for (let i = 0; i < elems.length; i++) { 123 | elems[i].addEventListener("click", event); 124 | } 125 | } 126 | 127 | 128 | 129 | /** 130 | * create taskItem elementNode and return it 131 | * totalParameter: 1; 132 | * parameterValue: any; 133 | */ 134 | 135 | const taskItemNode = function (taskText) { 136 | 137 | const createTaskItem = document.createElement("li"); 138 | createTaskItem.classList.add("task-item"); 139 | createTaskItem.setAttribute("data-task-item", ""); 140 | 141 | createTaskItem.innerHTML = ` 142 | 143 | 146 | 147 |

${taskText}

148 | 149 | 152 | 153 | `; 154 | 155 | return createTaskItem; 156 | 157 | } 158 | 159 | 160 | 161 | /** 162 | * task input validation 163 | * totalParameter: 1; 164 | * parameterValue: any 165 | */ 166 | 167 | const taskInputValidation = function (taskIsValid) { 168 | if (taskIsValid) { 169 | 170 | /** 171 | * if there is an existing task 172 | * then the new task will be added before it 173 | */ 174 | if (taskList.childElementCount > 0) { 175 | taskList.insertBefore(taskItemNode(taskInput.value), taskItem[0]); 176 | } else { 177 | taskList.appendChild(taskItemNode(taskInput.value)); 178 | } 179 | 180 | // after adding task on taskList, input field should be empty 181 | taskInput.value = ""; 182 | 183 | // hide the welcome note 184 | welcomeNote.classList.add("hide"); 185 | 186 | // update taskItem DOM selection 187 | taskItem = document.querySelectorAll("[data-task-item]"); 188 | taskRemover = document.querySelectorAll("[data-task-remove]"); 189 | 190 | } else { 191 | // if user pass any falsy value like(0, "", undefined, null, NaN) 192 | console.log("Please write something!"); 193 | } 194 | } 195 | 196 | 197 | 198 | /** 199 | * if there is an existing task, 200 | * the welcome note will be hidden 201 | */ 202 | 203 | const removeWelcomeNote = function () { 204 | if (taskList.childElementCount > 0) { 205 | welcomeNote.classList.add("hide"); 206 | } else { 207 | welcomeNote.classList.remove("hide"); 208 | } 209 | } 210 | 211 | 212 | 213 | /** 214 | * removeTask when click on delete button or check button 215 | */ 216 | 217 | const removeTask = function () { 218 | 219 | // select clicked taskItem 220 | const parentElement = this.parentElement; 221 | 222 | /** 223 | * if the task is completed, the taskItem would be remove after 250ms 224 | * if deleted than taskItem remove instant 225 | */ 226 | if (this.dataset.taskRemove === "complete") { 227 | 228 | parentElement.classList.add("complete"); // add "complete" class on taskItem 229 | taskCompleteSound.play(); // play taskCompleteSound 230 | 231 | setTimeout(function () { 232 | parentElement.remove(); // remove taskItem 233 | removeWelcomeNote(); // remove welcome note 234 | }, 250); 235 | 236 | } else { 237 | parentElement.remove(); // remove taskItem 238 | removeWelcomeNote(); // remove welcom note 239 | } 240 | 241 | } 242 | 243 | 244 | 245 | /** 246 | * addTask function 247 | */ 248 | 249 | const addTask = function () { 250 | 251 | // check the task inpu validation 252 | taskInputValidation(taskInput.value); 253 | 254 | // addEventListere to all taskItem checkbox and delete button 255 | addEventOnMultiElem(taskRemover, removeTask); 256 | 257 | } 258 | 259 | 260 | 261 | /** 262 | * add keypress listener on taskInput 263 | */ 264 | 265 | taskInput.addEventListener("keypress", function (e) { 266 | 267 | // addTask if user press 'Enter' 268 | switch (e.key) { 269 | case "Enter": 270 | addTask(); 271 | break; 272 | } 273 | 274 | }); 275 | 276 | 277 | 278 | // toggle active class on menu when click on menuBtn or dropdownLink 279 | const toggleMenu = function () { elemToggler(menu); } 280 | addEventOnMultiElem(menuTogglers, toggleMenu); 281 | 282 | // toggle active class on modal when click on dropdownLink or modal Ok button 283 | const toggleModal = function () { elemToggler(modal); } 284 | addEventOnMultiElem(modalTogglers, toggleModal); 285 | 286 | 287 | 288 | /** 289 | * add "loaded" class on body when website is fully loaded 290 | */ 291 | 292 | window.addEventListener("load", function () { 293 | document.body.classList.add("loaded"); 294 | }); 295 | 296 | 297 | 298 | /** 299 | * change body background when click on any themeBtn 300 | */ 301 | 302 | const themeChanger = function () { 303 | // store hue value from clicked themeBtn 304 | const hueValue = this.dataset.hue; 305 | 306 | // create css custom property on root and set value from hueValue 307 | document.documentElement.style.setProperty("--hue", hueValue); 308 | 309 | // remove "active" class from all themeBtns 310 | for (let i = 0; i < themeBtns.length; i++) { 311 | if (themeBtns[i].classList.contains("active")) { 312 | themeBtns[i].classList.remove("active"); 313 | } 314 | } 315 | 316 | // add "active" class on clicked themeBtn 317 | this.classList.add("active"); 318 | } 319 | 320 | // add event on all themeBtns 321 | addEventOnMultiElem(themeBtns, themeChanger); -------------------------------------------------------------------------------- /assets/sounds/task-complete.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewithsadee/taskbuddy/cf10679d5e8877c9dc220ad992475641aefeff63/assets/sounds/task-complete.mp3 -------------------------------------------------------------------------------- /favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Taskbuddy 9 | 10 | 13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 |
35 |
36 | 37 |
38 | 39 | 42 | 43 |
44 | 45 |
46 |

My Day

47 | 48 |

Saturday, July 2

49 |
50 | 51 | 95 | 96 |
97 | 98 | 99 | 100 | 101 | 102 | 105 | 106 |
107 | calender image 108 | 109 |

110 | Wondering where
111 | your tasks are? 112 |

113 |
114 | 115 |
    116 | 117 |
    118 | 119 | 121 | 122 | Add a task 123 | 124 |
    125 | 126 | 127 | 128 |
    129 | 130 |
    131 | 132 |
    133 | 134 | 135 | 136 | 137 | 138 | 141 | 142 |
    143 | 178 |
    179 | 180 |
    181 |
    182 | 183 | 184 | 185 | 186 | 187 | 190 | 191 | 192 | 195 | 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /index.txt: -------------------------------------------------------------------------------- 1 | Taskbuddy 2 | 3 | My Day 4 | Saturday, July 2 5 | 6 | aria-label = Toggle menu 7 | 8 | 9 | Theme 10 | 11 | 12 | App info 13 | 14 | alt = calender image 15 | 16 | Wondering where 17 | your tasks are? 18 | 19 | Build a todo app ✅ 20 | 21 | aria-label = Remove task 22 | 23 | 24 | title = Add a task in 'My Day' 25 | 26 | aria-label = Add task 27 | 28 | placeholder = Try typing 'Jogging 20min at morning' 29 | 30 | Add a task 31 | 32 | 33 | 34 | 35 | 36 | App info 37 | 38 | Version: 39 | 1.0.0 40 | 41 | Publish: 42 | 06 July 2022 43 | 44 | Last Update: 45 | 06 July 2022 46 | 47 | Created by: 48 | codewithsadee 49 | 50 | Ok -------------------------------------------------------------------------------- /readme-images/desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codewithsadee/taskbuddy/cf10679d5e8877c9dc220ad992475641aefeff63/readme-images/desktop.png -------------------------------------------------------------------------------- /style-guide.md: -------------------------------------------------------------------------------- 1 | # Essential Stuff 2 | 3 | ## Html import links 4 | 5 | Google font 6 | 7 | ``` html 8 | 9 | 10 | 11 | ``` 12 | 13 | Ionicon 14 | 15 | ``` html 16 | 17 | 18 | ``` 19 | 20 | --- 21 | 22 | ## Colors 23 | 24 | ``` css 25 | --granite-gray: hsl(210, 1%, 37%); 26 | --eerie-black: hsl(240, 2%, 11%); 27 | --slimy-green: hsl(91, 100%, 30%); 28 | --fuzzy-wuzzy: hsl(0, 42%, 57%); 29 | --davys-gray: hsl(220, 2%, 28%); 30 | --cultured-1: hsl(228, 17%, 94%); 31 | --cultured-2: hsl(0, 0%, 96%); 32 | --dim-gray: hsl(240, 1%, 41%); 33 | --white_80: hsla(0, 0%, 100%, 0.8); 34 | --white_70: hsla(0, 0%, 100%, 0.7); 35 | --white_40: hsla(0, 0%, 100%, 0.4); 36 | --white_10: hsla(0, 0%, 100%, 0.1); 37 | --black_05: hsla(0, 0%, 0%, 0.05); 38 | --black_50: hsla(0, 0%, 0%, 0.5); 39 | --black_25: hsla(0, 0%, 0%, 0.25); 40 | --white-2: hsl(0, 0%, 98%); 41 | --white-1: hsl(0, 0%, 100%); 42 | --black: hsl(0, 0%, 0%); 43 | ``` 44 | 45 | ## Theme colors 46 | 47 | ``` css 48 | --hue: 227; 49 | --theme-bg: linear-gradient(to bottom, 50 | hsl(var(--hue, 227), 40%, 50%), 51 | hsl(var(--hue, 227), 40%, 60%)); 52 | --theme-color: hsl(var(--hue, 227), 42%, 57%); 53 | ``` 54 | 55 | ## Typography 56 | 57 | ``` css 58 | --ff-roboto: "Roboto", sans-serif; 59 | 60 | --fs-1: 2.6rem; 61 | --fs-2: 2.2rem; 62 | --fs-3: 1.8rem; 63 | --fs-4: 1.4rem; 64 | --fs-5: 1.2rem; 65 | 66 | --fw-500: 500; 67 | ``` 68 | 69 | ## Radius 70 | 71 | ``` css 72 | --radius-4: 4px; 73 | --radius-5: 5px; 74 | --radius-10: 10px; 75 | ``` 76 | 77 | ## Shadow 78 | 79 | ``` css 80 | --shadow-1: 0 0 5px hsla(0, 0%, 0%, 0.05); 81 | --shadow-2: 0 0 10px hsla(0, 0%, 0%, 0.05); 82 | ``` 83 | 84 | ## Transition 85 | 86 | ``` css 87 | --transition: 0.15s ease; 88 | --cubic-out: cubic-bezier(0.05, 0.65, 0.25, 1); 89 | ``` 90 | --------------------------------------------------------------------------------