Оверлеи
Одиннадцать компонентов кладут содержимое поверх страницы. Выбор между ними — выбор про фокус, а не про вид окна.
Что решает
Положить что-то поверх страницы просто. Трудны — и именно об этом всякая ошибка в оверлеях — три вопроса следом: работает ли фон, куда уходит клавиатура и что происходит по Escape.
Библиотека отвечает на все три в одном месте, общим стеком слоёв, — поэтому ответы одинаковы для любого оверлея на странице. Это важнее всего тогда, когда оверлеев два.
Анатомия
Выбирают по тому, что за содержимое, а не по тому, как оно должно выглядеть. Условие для каждого записано в библиотеке:
- Своя раскладка поверх страницы —
GrModal. Основа, на которой построено всё модальное; берут, когда готовые формы не подходят. - Окно с шапкой, телом и подвалом —
GrDialog. Форма, нужная в девяти случаях из десяти. - Действие необратимо —
GrConfirmDialog. Отдельный компонент, а не диалог с красной кнопкой: вопрос и опасность — его предмет. - Нужно одно значение —
GrPromptDialog. Целая форма ради одного поля — это форма, которую теперь надо верстать. - Панель приходит от края —
GrDrawer. Модальная или нет; смотрите ниже, потому что этот флаг меняет клавиатуру. - Своё содержимое у якоря —
GrPopover. Привязан к триггеру, а не к центру экрана. - Меню со своими пунктами —
GrDropdown. Список действий рядом с тем, над чем они совершаются. - Действия над строкой или узлом дерева —
GrContextMenu. Приносит их к указателю, вместо того чтобы гнать человека к панели инструментов.
Две оси под этим: модальный или нет (продолжает ли работать фон) и у якоря или по центру (это про один элемент или про страницу).
Пример
Так и не так
Так — Позволить стеку вернуть фокус
Закрытие возвращает фокус на триггер, и делает это сам стек слоёв. Переставлять
фокус руками через nextTick или таймаут — значит соревноваться с ним, и на
втором оверлее победитель определяется случайностью.
Так не надо — Открывать оверлей из оверлея без нужды
На двух слоях «назад» перестаёт что-либо значить: Escape закрывает верхний, а человек ждал, что уйдёт всё. Если второй слой — подтверждение, это тот случай, ради которого он и сделан; всё прочее — мастер, переодетый в стопку окон.
Так — Немодальная панель, когда страница и есть предмет
:modal="false" снимает ловушку фокуса: Tab уводит на страницу — ровно за этим
и открывают панель фильтров или инспектор. Оверлей здесь помощь, а не стена.
Так не надо — Запирать фокус там, где нет решения
Модальная панель поверх таблицы означает, что до таблицы с клавиатуры не добраться, пока панель открыта. Если человек должен смотреть на оба — ловушка и есть ошибка, а не раскладка.
Так — Опасный вопрос — своим компонентом
GrConfirmDialog существует потому, что «удалить» и «сохранить» не должны
выглядеть одним диалогом разного цвета. Компонент несёт сам вопрос, формулировку
последствий и выделение безопасного ответа.
Так не надо — Подтверждать всё подряд
Подтверждение обратимого действия приучает закрывать подтверждения не глядя — и тогда закрывается то, которое имело значение. Отмена лучше подтверждения везде, где отмена возможна.
Доступность
Оверлеи — место, где клавиатуру чаще всего доделывают наполовину, а половина паттерна хуже его отсутствия: скринридер объявит роль, которой поведение не соответствует.
Escape закрывает верхний слой, а не все. При двух открытых оверлеях одно нажатие не должно очищать экран: человек отменяет один шаг.
Ловушка фокуса — свойство модальности. GrModal и всё на нём — GrDialog,
GrConfirmDialog, GrPromptDialog — циклят Tab внутри. Панель с
:modal="false" намеренно этого не делает, и в этом весь смысл флага.
Возврат не необязателен. Клавиатуру нельзя оставлять в начале документа после закрытия: человек где-то был, и работа продолжается именно там.
Контракты ниже приходят из библиотеки и тестируются там. Чего не решит ни один компонент — сколько слоёв заслуживает экран, и честный ответ обычно «один».
Компоненты паттерна
Список строится из frontmatter, а не из ссылок в тексте: переименование слага здесь не теряется.
GrModalСлоиGrDialogСлоиGrConfirmDialogСлоиGrPromptDialogСлоиGrDrawerСлоиGrPopoverСлоиGrDropdownСлоиGrContextMenuСлои
Клавиатура
Контракты приходят из библиотеки. Паттерн их не пересказывает — он на них ссылается.
GrModalКонтракт компонентаGrDialogКонтракт компонентаGrConfirmDialogКонтракт компонентаGrPromptDialogКонтракт компонентаGrDrawerКонтракт компонентаGrDropdownКонтракт компонентаGrContextMenuКонтракт компонента