Оверлеи
Поповер
Небольшой блок, всплывающий над интерфейсом по нажатию на кнопку и исчезающий
по клику мимо. Целиком на платформе: popover + popovertarget, без единой
строки скрипта.
Разметка
<button class="inst-btn" type="button" popovertarget="pop-actions">Действия</button>
<div class="inst-popover inst-popover--anchored" id="pop-actions" popover>
<div class="inst-menu" role="menu">
<span class="inst-menu-label">Прогон #4127</span>
<button class="inst-menu-item" type="button" role="menuitem">Перезапустить</button>
<button class="inst-menu-item" type="button" role="menuitem">Скопировать id</button>
</div>
</div>
Использование
<button class="inst-btn" type="button" popovertarget="pop-1">Действия</button>
<div class="inst-popover inst-popover--anchored" id="pop-1" popover>…</div>
| Что | Обязательно | Почему |
|---|---|---|
popover на блоке |
да, у всплывающего | Без него это плавающая поверхность в потоке: ни верхнего слоя, ни закрытия по Escape |
id на блоке |
да | Кнопка ссылается на него по имени |
popovertarget="id" на кнопке |
да | Связка и неявный якорь одновременно |
type="button" на кнопке |
да | Иначе внутри формы она её отправит |
Содержимое поповера своего класса не имеет: чаще всего это меню, но может быть любая разметка.
Вид отделён от механизма. .inst-popover — это поверхность; поведение
верхнего слоя добавляет атрибут popover. Поэтому тот же вид можно взять для
того, что плавает, но поповером не является: списка результатов комбобокса,
подсказки автодополнения. Без атрибута блок просто виден, показом управляет
приложение.
Когда использовать
| Используйте | Возьмите другое |
|---|---|
| Список действий над объектом, вызываемый кнопкой | Выбор одного значения из списка — селект: у него есть значение, а у поповера нет |
| Подробности, которые не нужны постоянно и не блокируют работу | Ответ, без которого нельзя продолжить — модалка: она забирает фокус и гасит фон |
Пояснение, которое обрежется внутри панели с overflow: hidden |
Короткая подпись к контролу — тултип, если он не внутри обрезающего контейнера |
| Длинная форма или сводка сбоку — открывается той же кнопкой | Долгий контекст, который держат открытым — шторка |
Варианты
Разметка
<button class="inst-btn" type="button" popovertarget="pop-anchored">Под кнопкой</button>
<div class="inst-popover inst-popover--anchored" id="pop-anchored" popover>
Встаёт под кнопкой и переворачивается, если снизу не помещается.
</div>
<button class="inst-btn" type="button" popovertarget="pop-centered">По центру</button>
<div class="inst-popover" id="pop-centered" popover>
Без модификатора поповер стоит по центру экрана.
</div>
| Модификатор | Где встаёт | Как меряется ширина |
|---|---|---|
| без модификатора | По центру экрана | max-content в пределах 11–22rem |
inst-popover--anchored |
Под вызвавшей кнопкой | То же |
inst-popover--fill |
Там, куда его поставили | Во всю ширину родителя |
Якорь у --anchored — неявный: кнопка с popovertarget уже является
якорем для своего поповера, поэтому имена якорей заводить не нужно.
Правило спрятано под @supports (position-area: block-end). Где
position-area не поддержан, поповер встаёт по центру экрана: деградирует, а
не ломается. Раскладка задана логическими значениями, поэтому в RTL
зеркалится сама. Если снизу или сбоку не помещается,
position-try-fallbacks переворачивает поповер по нужной оси, а не выпускает
его за край экрана.
inst-popover--fill нужен там, где плашка обязана совпасть по ширине с тем,
под чем стоит: выпадающий список под полем. Список у́же или ши́ре поля читается
как чужой элемент, случайно оказавшийся рядом.
Размеры
| Свойство | Значение |
|---|---|
| минимальная ширина | 11rem |
| максимальная ширина | min(22rem, 100vw - var(--space-8)) — на узком экране поповер не вылезет за край |
| ширина по содержимому | max-content в этих пределах |
с --fill |
100% родителя, без потолка |
Тень здесь при деле: --shadow-popover означает «плавает сверху и сейчас
исчезнет» — то есть описывает поповер целиком. Второй и последний носитель
тени в ките — модалка.
Поведение
Поповер не имеет ни одной строки поведения в ките, потому что всё это уже есть в браузере.
| Что | Кто делает |
|---|---|
Верхний слой — поповер не срежет ни overflow, ни z-index соседа |
Платформа |
Закрытие по Escape |
Платформа |
| Закрытие по клику мимо (лёгкое закрытие) | Платформа |
| Возврат фокуса на вызвавшую кнопку | Платформа |
| Связка кнопки и блока | popovertarget="id" |
| Появление и исчезновение | Кит: переход по opacity и translate |
Переход доигрывает и на закрытии, потому что в правиле объявлены
display и overlay с allow-discrete. Без них поповер исчезал бы мгновенно,
не дождавшись собственной анимации.
Правила
Так Всплывающее — через popover
Верхний слой, Escape, лёгкое закрытие и возврат фокуса приходят от платформы.
Ни одной строки скрипта.
Не так Плавающая плашка на z-index
Число в z-index работает до первого чужого числа побольше, а overflow: hidden у родителя срежет плашку целиком.
Так Плашка под полем — во всю его ширину
inst-popover--fill. Список у́же или ши́ре поля читается как чужой элемент
рядом, а не как его продолжение.
Не так Панель вместо плавающей поверхности
Панель — это место на экране: другой радиус, другая поверхность, тени у неё нет. Приписанная ей тень поповера даёт две разные плашки на одном экране.
Доступность
| Клавиатура | Enter/Space на кнопке открывает, Escape закрывает, фокус возвращается на кнопку. Всё нативное, кит ничего не перехватывает |
| Подложки нет | ::backdrop прозрачен: поповер не блокирует страницу и не притворяется модальным |
| Роль содержимого | Кит роли не выдумывает. Если внутри меню — role="menu" и role="menuitem" ставятся в разметке |
| Поверхность без атрибута | .inst-popover без popover не имеет ни верхнего слоя, ни закрытия по Escape: показ, скрытие и роль — на приложении |
| Уменьшенное движение | Переход схлопывается до 0.01ms, а не выключается: машины состояний, слушающие transitionend, продолжают работать |
| Печать | Поповер на листе не печатается: он по определению временный |
API
| Имя | Значение | Что делает |
|---|---|---|
| класс | ||
inst-popover | — | Плавающая поверхность: рамка, радиус, фон, тень, внутренний отступ |
| модификатор | ||
inst-popover--anchored | — | Привязка к вызвавшей кнопке. Под @supports |
inst-popover--fill | — | Ширина во всю ширину родителя вместо max-content. Для выпадающего списка под полем |
| атрибут | ||
popover | auto · manual | Добавляет к поверхности верхний слой, закрытие по Escape и переход появления |
popovertarget | — | На кнопке. Связка и неявный якорь одновременно |
| токен | ||
--surface-overlay | light-dark(var(--n-0), var(--n-11)) | |
--shadow-popover | 0 4px 12px -2px var(--shadow-color-far), 0 2px 4px -2px var(--shadow-color-near) | |
--border | light-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11)) | |
--hairline | 1px | |
--radius-md | 7px | |
--space-1 | 2px | |
--space-2 | 4px | |
--space-8 | 32px | |
--dur-2 | 140ms | |
--ease-out | cubic-bezier(0.22, 0.61, 0.36, 1) | |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument