Вивчай
Домашнє завдання #12 ·
балівintermediate

Модальне вікно та dropdown меню

У цьому завданні ти створиш два популярні UI-компоненти, які зустрічаються на кожному сайті. Працюватимеш з DOM-маніпуляціями, обробкою подій та CSS-класами для анімацій.


Файлова структура

modal-dropdown/
├── index.html
├── style.css
└── script.js

Частина 1: Модальне вікно (Modal)

Створи кнопку, яка відкриває модальне вікно з напівпрозорим фоном (overlay).

Вимоги

1. HTML-структура:

<button class="btn-open-modal">Відкрити модалку</button>

<div class="modal-overlay hidden">
  <div class="modal">
    <button class="modal-close">&times;</button>
    <h2>Заголовок модалки</h2>
    <p>Тут може бути будь-який контент...</p>
  </div>
</div>

2. Відкриття модалки:

  • Клік на кнопку .btn-open-modal — прибирає клас hidden з .modal-overlay
  • При відкритті додай клас no-scroll до document.body, щоб заблокувати прокрутку фону
// Приблизна логіка
openBtn.addEventListener('click', () => {
  overlay.classList.remove('hidden');
  document.body.classList.add('no-scroll');
});

3. Закриття модалки (три способи):

  • Клік на кнопку закриття (&times;)
  • Клік на overlay (темний фон за модалкою)
  • Натискання клавіші Escape
// Закриття по Escape
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    closeModal();
  }
});

// Клік на overlay (але НЕ на саму модалку)
overlay.addEventListener('click', (e) => {
  if (e.target === overlay) {
    closeModal();
  }
});

4. Анімація появи:

  • Overlay: плавна поява фону через opacity та transition
  • Модалка: поява з ефектом transform: scale(0.8) -> scale(1) або translateY(-20px) -> translateY(0)
  • Використай CSS-класи та transition, не JS-анімацію
/* Приклад CSS */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  opacity: 1;
  transition: opacity 0.3s ease;
}

.modal-overlay.hidden {
  opacity: 0;
  pointer-events: none;
}

.modal {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  max-width: 500px;
  width: 90%;
  transform: scale(1);
  transition: transform 0.3s ease;
}

.modal-overlay.hidden .modal {
  transform: scale(0.8);
}

.no-scroll {
  overflow: hidden;
}

Частина 2: Dropdown меню

Створи кілька dropdown-кнопок, кожна з яких розкриває власне меню.

Вимоги

1. HTML-структура (3 dropdown):

<div class="dropdown">
  <button class="dropdown-toggle">Файл &#9662;</button>
  <ul class="dropdown-menu">
    <li><a href="#">Новий</a></li>
    <li><a href="#">Відкрити</a></li>
    <li><a href="#">Зберегти</a></li>
  </ul>
</div>

<div class="dropdown">
  <button class="dropdown-toggle">Редагувати &#9662;</button>
  <ul class="dropdown-menu">
    <li><a href="#">Копіювати</a></li>
    <li><a href="#">Вставити</a></li>
    <li><a href="#">Вирізати</a></li>
  </ul>
</div>

<!-- Ще один dropdown -->

2. Відкриття/закриття:

  • Клік на кнопку .dropdown-toggle — toggle класу open на батьківському .dropdown
  • Якщо один dropdown вже відкритий, при відкритті іншого попередній закривається
// Закрити всі dropdown, крім поточного
function closeAllDropdowns(except) {
  document.querySelectorAll('.dropdown').forEach(dd => {
    if (dd !== except) {
      dd.classList.remove('open');
    }
  });
}

3. Закриття при кліку зовні:

document.addEventListener('click', (e) => {
  if (!e.target.closest('.dropdown')) {
    closeAllDropdowns();
  }
});

4. Клавіатурна навігація:

  • Enter або Space на кнопці — відкрити/закрити dropdown
  • Escape — закрити відкритий dropdown

5. CSS для dropdown:

.dropdown {
  position: relative;
  display: inline-block;
}

.dropdown-menu {
  position: absolute;
  top: 100%;
  left: 0;
  background: white;
  border: 1px solid #ddd;
  border-radius: 8px;
  list-style: none;
  padding: 0.5rem 0;
  min-width: 160px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  opacity: 0;
  transform: translateY(-10px);
  pointer-events: none;
  transition: opacity 0.2s, transform 0.2s;
}

.dropdown.open .dropdown-menu {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

Частина 3 (бонус): Confirm-діалог

Створи варіант модалки — діалог підтвердження з двома кнопками.

function confirmDialog(message) {
  return new Promise((resolve) => {
    // Створити модалку з текстом message
    // Кнопки "Так" та "Ні"
    // "Так" -> resolve(true)
    // "Ні"  -> resolve(false)
  });
}

// Використання:
const result = await confirmDialog('Ти впевнений?');
if (result) {
  console.log('Користувач підтвердив');
} else {
  console.log('Користувач скасував');
}

Вимоги:

  • Функція повертає Promise
  • Модалка створюється динамічно через createElement
  • Після натискання кнопки модалка видаляється з DOM
  • Той самий стиль та анімації, що й у основній модалці

Підказки

  • e.target === overlay перевіряє, що клік був саме на overlay, а не на дочірній елемент
  • e.target.closest('.dropdown') знаходить найближчий батьківський .dropdown (або null, якщо кліку не на dropdown)
  • pointer-events: none робить елемент "невидимим" для кліків, навіть якщо він видимий на екрані
  • Для блокування прокрутки: overflow: hidden на body
  • transition працює тільки між конкретними значеннями (не з display: none)

Критерії оцінки

КритерійБали
Модалка відкривається/закривається (кнопка, overlay, Escape)25
Блокування прокрутки та CSS-анімація модалки15
Dropdown toggle (відкриття/закриття по кліку)20
Тільки один dropdown відкритий одночасно10
Закриття dropdown при кліку зовні15
Клавіатурна навігація (Enter, Escape)15
Бонус: Confirm-діалог з Promise+15