Модальное окно
На этой странице
Модальное окно — это элемент popover. Любая кнопка, чей popovertarget совпадает с id окна, открывает его — и нигде никакого скрипта, включая затемнение, закрытие по клику снаружи, Escape и работу с фокусом: всем этим владеет браузер.
Поэтому id обязателен, и поэтому без него компонент бросает ошибку: id — это и есть вся проводка.
Простое окно
Каждое модальное окно на этой странице по-настоящему открывается — попробуйте.
Пересобрать сайт?
fragment(
button({ popovertarget: 'demo-basic' }, 'Открыть окно'),
modal({ id: 'demo-basic', title: 'Пересобрать сайт?' },
'Это запустит sitelo build и заново опубликует dist/.',
),
)С подвалом
Кнопка закрытия — это любая кнопка, указывающая на тот же id с popovertargetaction="hide".
Удалить эту страницу?
fragment(
button({ color: 'danger', popovertarget: 'demo-confirm' }, 'Удалить страницу…'),
modal({
id: 'demo-confirm',
title: 'Удалить эту страницу?',
footer: stack({ direction: 'row', gap: 'sm' },
button({
variant: 'ghost',
color: 'neutral',
popovertarget: 'demo-confirm',
popovertargetaction: 'hide',
}, 'Отмена'),
button({ color: 'danger' }, 'Удалить'),
),
}, 'Это не отменить. Сгенерированный HTML исчезнет при следующей сборке.'),
)Размеры
Маленькое
Среднее
Большое
fragment(
stack({ direction: 'row', gap: 'sm', wrap: true },
button({ variant: 'outline', color: 'neutral', popovertarget: 'demo-sm' }, 'Маленькое'),
button({ variant: 'outline', color: 'neutral', popovertarget: 'demo-md' }, 'Среднее'),
button({ variant: 'outline', color: 'neutral', popovertarget: 'demo-lg' }, 'Большое'),
),
modal({ id: 'demo-sm', size: 'sm', title: 'Маленькое' }, 'size: sm — около 24rem.'),
modal({ id: 'demo-md', title: 'Среднее' }, 'Значение по умолчанию — около 32rem.'),
modal({ id: 'demo-lg', size: 'lg', title: 'Большое' }, 'size: lg — около 48rem.'),
)Формы внутри окна
Новая страница
fragment(
button({ variant: 'soft', popovertarget: 'demo-form' }, 'Новая страница…'),
modal({
id: 'demo-form',
title: 'Новая страница',
footer: stack({ direction: 'row', gap: 'sm' },
button({ variant: 'ghost', color: 'neutral', popovertarget: 'demo-form', popovertargetaction: 'hide' }, 'Отмена'),
button({ type: 'submit' }, 'Создать'),
),
},
stack({ gap: 'md' },
textField({ label: 'Заголовок', name: 'modal-title', placeholder: 'О проекте' }),
selectField({ label: 'Расширение', name: 'modal-ext', options: ['.ht.js', '.ht.ts', '.ht.jsx'] }),
),
),
)Без кнопки закрытия
closable: false убирает × в углу. Escape и клик снаружи всё равно закрывают окно: popover нельзя сделать по-настоящему блокирующим, да и это обычно правильное поведение.
Нажмите Escape
fragment(
button({ variant: 'outline', color: 'neutral', popovertarget: 'demo-bare' }, 'Без кнопки закрытия'),
modal({ id: 'demo-bare', title: 'Нажмите Escape', closable: false },
'Или кликните в любом месте за пределами этого диалога.',
),
)Длинное содержимое
Тело прокручивается; шапка и подвал остаются на месте.
Заметки о выпуске
Изменение 1 — что-то починили.
Изменение 2 — что-то починили.
Изменение 3 — что-то починили.
Изменение 4 — что-то починили.
Изменение 5 — что-то починили.
Изменение 6 — что-то починили.
Изменение 7 — что-то починили.
Изменение 8 — что-то починили.
Изменение 9 — что-то починили.
Изменение 10 — что-то починили.
Изменение 11 — что-то починили.
Изменение 12 — что-то починили.
fragment(
button({ variant: 'outline', color: 'neutral', popovertarget: 'demo-long' }, 'Длинное окно'),
modal({
id: 'demo-long',
title: 'Заметки о выпуске',
footer: button({ popovertarget: 'demo-long', popovertargetaction: 'hide' }, 'Закрыть'),
},
stack({ gap: 'md' },
...Array.from({ length: 12 }, (unused, index) =>
text({ variant: 'small', tone: 'muted' }, 'Изменение ' + (index + 1) + ' — что-то починили.'),
),
),
),
)Прокрутка фона
Страница за открытым окном не прокручивается. Это единственное, что popover API оставляет на вас, и здесь оно сделано на CSS — без скрипта и без всякой инициализации. Передайте lockScroll: false, чтобы фон прокручивался как обычно.
Поддержка браузерами
Popover API есть во всех современных браузерах. В слишком старом, который о нём не знает, окно отрисуется прямо в потоке страницы, а не поверх неё: видимое и рабочее, просто не наложенное сверху. Ничего не пропадает.
Пропсы
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
id | string | — | Обязателен. То, на что указывает popovertarget кнопки-триггера. |
title | Child | — | Заголовок и доступное имя диалога. |
size | 'sm' | 'md' | 'lg' | 'md' | Максимальная ширина. |
footer | Child | — | Нижний ряд на собственной подкрашенной полосе. |
closable | boolean | true | Показывать × в шапке. |
closeLabel | string | 'Close' | Доступное имя этой кнопки. |
lockScroll | boolean | true | Не давать странице за окном прокручиваться, пока оно открыто. |
closeButton({ target }) рисует этот × отдельно — для шапки, которую вы собираете сами.