GH GambleHub

Модалки та виїжджаючі панелі

1) Коли що використовувати

Modal (діалог з бекдропом) - для критичних рішень і коротких завдань, що вимагають повної уваги: підтвердження дії, правові згоди, небезпечні операції, короткі форми ≤ 1-2 полів. Блокує фон.
Drawer/Sheet (виїжджаюча панель) - для контекстного розширення: деталі об'єкта, редагування атрибутів, вибір зі списку, допоміжна навігація. Фон видно → зберігається контекст.

Правило вибору:
  • Якщо дії потрібна концентрація і підтвердження → Modal.
  • Коли потрібно зберегти контекст і дати «паралельний» огляд → Drawer.

2) Структура і розміри

Modal

Заголовок (обов'язковий) → основний текст → CTA-зона (Primary/Secondary/Destructive).
Розміри: S (480–560 px), M (640–720 px), L (≤ 840 px). На мобілці - повноекранний sheet.

Drawer / Sheet

Напрям: правий край (десктоп, редагування), низ (мобайл, дії), іноді лівий (навігація).
Ширина: 360–480 (S), 480–640 (M), 640–800 (L). На мобілці: 90-100% ширини/висоти.

Висота контенту завжди обмежена, всередині - скролл; заголовок/СТА фіксовані.

3) Копірайтинг і CTA

Заголовок = дія/зміст: «Підтвердити ставку», «Вибір платіжного методу», «Потрібно KYC».
Текст короткий, 1-2 речення. Уникайте розпливчастих формул.
CTA: один Primary, поруч Secondary («Скасування») і, при необхідності, Destructive.

Для ризикованих дій додавайте пояснення в 1 рядок: "Дія незворотна. Ви зможете скасувати протягом 10 сек (якщо доступно)"

4) Поведінка і стани

Відкриття: миттєвий відгук ≤ 100 мс, потім анімація 120-180 мс.
Закриття: швидше відкриття (80-140 мс), повертаємо фокус до тригера.
Busy: 'aria-busy = «true»'на контейнері, кнопка з блокуванням повторів.
Unsaved (брудна форма): при закритті - діалог-попередження («Є незбережені зміни»).
Escape/клік по фону: допустимі для безпечних діалогів; для критичних - тільки явні кнопки.

5) Доступність (A11y)

Контейнер: 'role = «dialog»'і'aria-modal = «true»'( для справжньої модалки).
Заголовок пов'язаний через'aria-labelledby'; опис - «aria-describedby».
Focus trap всередині; первинний фокус - на заголовку або першому інтерактивному елементі.
Повернення фокусу на початковий тригер після закриття.
Ніякого скролла фону: `document. body { overflow: hidden;}'або'inert'на іншому DOM.
Підтримка клавіатури: Tab/Shift + Tab циклічні; Esc закриває (якщо не заборонено сценарно).
Враховуйте'prefers-reduced-motion': відключення/спрощення анімацій.

Шаблон:
html
<div class="backdrop" data-open hidden></div>
<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="d-title" aria-describedby="d-desc" hidden>
<h2 id =" d-title "> Confirm Bid </h2>
<p id =" d-desc "> Sum of 200 ₴ by factor 1. 85</p>
<div class="actions">
<button class =" btn btn--primary "> Confirm </button>
<button class =" btn btn--ghost "> Cancel </button>
</div>
</div>

6) Перформанс і архітектура

Рендер через портал (шар поверх програми) → менше проблем з z-index.
Монтуйте контент ліниво при першому відкритті, демонтуйте після анімації закриття (або переводьте offscreen).
Анімуйте тільки'transform/opacity'; уникайте дорогих blur/тіней з великими розмірами.
Блокуйте скролл фону (scroll-lock), зберігайте поточну позицію, щоб після закриття не «стрибало».
Для великих списків в drawer - використовуйте віртуалізацію.

7) Мобільні патерни

Bottom sheet для швидких дій/підтверджень: жести свайпа вниз для закриття (з порогом).
Sticky-CTA внизу; кнопка закриття - зліва зверху.
Safe-area відступи (notch/gesture areas).
Екранна клавіатура не повинна перекривати CTA; layout - «підйом» контенту або фіксована панель над клавіатурою.

8) Motion-дизайн

Вхід: fade + легкий зсув (modal: за Y, drawer: по осі появи). 120-180 мс.
Вихід: коротше (80-140 мс), easing'cubic-bezier (0. 2,0,0. 2,1)`.
Тло (backdrop): непрозорість 0 → 0. 4–0. 6. Без пульсацій і нескінченних відблисків.
Для `prefers-reduced-motion`: без зсуву, тільки fade.

9) Управління закриттям

Негайне закриття тільки при безпечних операціях.
При помилці - залишаємося в діалозі, показуємо причину і Retry.
При фоновому виконанні - закрити діалог і показати тост «Виконуємо в фоні»..., плюс розділ «Історія».

10) Типові сценарії iGaming

10. 1 Підтвердження ставки (Modal)

Вміст: подія, коефіцієнт, сума, потенційний виграш, термін дії коефіцієнта.
Кнопки: «Підтвердити» (primary), «Скасування».
Патерн затримки> 3 с: текст «Очікуємо підтвердження»...; при зміні коефіцієнта - чесний апдейт.

10. 2 Кешаут (Modal/Sheet)

Показ поточної суми кешауту і таймер вікна.
Підтвердження + можливий Undo (якщо регламент дозволяє).

10. 3 Вибір платіжного методу (Drawer)

Список методів з комісіями/ЕТА; вибір → міні-форми.
Збереження методу за замовчуванням; повернення без втрати введених даних.

10. 4 KYC (Drawer → Modal)

Drawer для завантаження документів/підказок.
Modal при спробі закрити з незавершеним завантаженням: попередження про незбережених.

10. 5 Ліміти відповідальної гри (Modal)

Радіо «День/Тиждень/Місяць», поле суми, рядок «Набере чинності через»....

11) Анти-патерни

Вкладені модалки (modal поверх modal). Використовуйте одне діалогове вікно або послідовність кроків.
Модалка для звичайного перегляду контенту (краще drawer/сторінка).
Прихований хрестик або закриття тільки по «мікрозоні».
Ризиковану дію → дозвіл закрити «по фону».
Довжелезна форма в модалці (→ винесіть в окремий екран/панель).
Відсутність повернення фокусу до тригера.

12) Токени дизайн-системи (приклад)

json
{
"dialog": {
"radius": 12,
"shadow": "var(--elev-4)",
"sizes": { "s": 520, "m": 680, "l": 840 },
"backdropOpacity": 0. 5,
"padding": "20 24",
"gap": 16
},
"drawer": {
"width": { "s": 360, "m": 480, "l": 640 },
"edge": "right",
"radius": 12,
"shadow": "var(--elev-4)"
},
"motion": {
"inMs": 160,
"outMs": 120,
"ease": "cubic-bezier(0. 2,0,0. 2,1)",
"reduce": true
},
"a11y": {
"useAriaModal": true,
"focusTrap": true,
"returnFocus": true
}
}
CSS-пресети (концепт):
css
.backdrop[data-open]{position:fixed; inset:0; background:rgba(0,0,0,.5); backdrop-filter:saturate(80%); opacity:1; transition:opacity. 16s}
.dialog,[data-drawer]{position:fixed; background:var(--bg-elevated); border-radius:12px; box-shadow:var(--elev-4);}
.dialog{inset:auto; left:50%;top:50%;transform:translate(-50%,-50%); max-width:840px; width:min(100% - 32px, var(--dialog-w,680px));}
[data-drawer="right"]{top:0; right:0; height:100%;width:var(--drawer-w,480px); transform:translateX(0)}
.dialog[hidden],.backdrop[hidden]{display:none}

13) Сніпети поведінки

Focus trap + повернення фокусу:
js const openBtn = document. getElementById('open');
const dlg = document. querySelector('.dialog');
let prevFocus;

function openDialog() {
prevFocus = document. activeElement;
dlg. hidden = false; document. body. style. overflow = 'hidden';
const focusable = dlg. querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
(focusable[0]        dlg). focus();
function onKey(e){
if(e. key==='Escape') return closeDialog();
if(e. key!=='Tab') return;
const first = focusable[0], last = focusable[focusable. length-1];
if(e. shiftKey && document. activeElement===first){ e. preventDefault(); last. focus(); }
else if(!e.shiftKey && document. activeElement===last){ e. preventDefault(); first. focus(); }
}
dlg. addEventListener('keydown', onKey);
dlg. dataset. off = ()=> dlg. removeEventListener('keydown', onKey);
}
function closeDialog() {
dlg. dataset. off && dlg. dataset. off();
dlg. hidden = true; document. body. style. overflow = '';
prevFocus && prevFocus. focus();
}
Sheet з жестом закриття (мобайл, спрощено):
js let startY=0, delta=0;
const sheet = document. querySelector('.sheet');
sheet. addEventListener('touchstart', e => startY = e. touches[0].clientY);
sheet. addEventListener('touchmove', e => {
delta = Math. max(0, e. touches[0].clientY - startY);
sheet. style. transform = `translateY(${delta}px)`;
});
sheet. addEventListener('touchend', () => {
if (delta > 120) sheet. classList. remove('open'); else sheet. style. transform = '';
delta = 0;
});

14) Метрики та експерименти

Open Rate/Completion Rate за модалками: скільки відкрили і завершили дію.
Time-to-Decision: від відкриття до кліка по Primary.
Dismiss Rate і причини (закриття по Esc/фону vs «Скасування»).
Error/Retry Rate в busy-сценаріях.
A/B: modal vs drawer, текст CTA, порядок полів, «підтвердження» vs «undo».

15) QA-чек-лист

Доступність

  • 'role = «dialog»','aria-modal = «true»', правильні'aria-labelledby/-describedby'.
  • Focus trap працює; фокус повертається до тригера.
  • Esc закриває (якщо дозволено); Tab циклічний.
  • Контраст ≥ AA; не тільки колір передає сенс.

Поведінка

  • TTFF ≤ 100 мс; анімації in 120-180 мс/out 80-140 мс.
  • Scroll-lock фону без «стрибка» сторінки.
  • Unsaved-guard при брудній формі.
  • Busy-стан, коректні Retry/помилки.

Інтерфейс

  • Ясний заголовок і один Primary-CTA.
  • Хрестик/кнопка «Закрити» доступні.
  • Розміри адаптивні; на мобілці - sheet з жестом.

Перформанс

  • Портали/z-index коректні; без «просвічування».
  • Лінива ініціалізація; анімуються тільки transform/opacity.

16) Документація в дизайн-системі

Компоненти: `Modal`, `Drawer/Sheet`, `ConfirmDialog`, `UnsavedGuard`.
Токени: розміри, відступи, тіні, анімації, backdrop, focus-ring.
Гайди: «Коли modal vs drawer», шаблони копірайтингу, ризиковані дії (confirm/undo), scroll-lock і portals, reduce-motion.
Do/Don't-галерея: nested modals (don't), довгі форми в модалці (don't), sheet для розширення контексту (do).

Коротке резюме

Модалка - для рішень під повною увагою, drawer - для розширення контексту без розриву потоку. Тримайте структуру простою, CTA - однозначним, а взаємодії - передбачуваними і доступними. Поважайте перформанс, блокуйте фон і повертайте фокус. У сценаріях iGaming це безпосередньо впливає на довіру: підтвердження ставок, кешаут, вибір платіжного методу і KYC повинні бути чесними, швидкими і безпечними.

Contact

Зв’яжіться з нами

Звертайтеся з будь-яких питань або за підтримкою.Ми завжди готові допомогти!

Telegram
@Gamble_GC
Розпочати інтеграцію

Email — обов’язковий. Telegram або WhatsApp — за бажанням.

Ваше ім’я необов’язково
Email необов’язково
Тема необов’язково
Повідомлення необов’язково
Telegram необов’язково
@
Якщо ви вкажете Telegram — ми відповімо й там, додатково до Email.
WhatsApp необов’язково
Формат: +код країни та номер (наприклад, +380XXXXXXXXX).

Натискаючи кнопку, ви погоджуєтесь на обробку даних.