GrPopover
Берут, когда своё содержимое у якоря.
Обзор
Когда брать
- своё содержимое у якоря — фильтр, форма, палитра, карточка предпросмотра: слой и позиционирование берёт примитив, разметку и клавиатуру внутри пишете вы;
- подтверждение у самой кнопки —
trigger="manual"плюсmodal, чтобы фон не уводил фокус с формы подтверждения; - контекстное меню — момент открытия решает потребитель,
roleпереключается наmenu; - свой оверлейный компонент — это примитив, поверх которого в пакете собраны меню, палитры и Popconfirm; второй модальный слой заводить не надо.
Когда взять другое
| Нужно | Берите |
|---|---|
| Меню с готовыми пунктами, группами и разделителями | GrDropdownMenu |
| Слой и клавиатура меню, пункты свои | GrDropdown |
| Текстовая подсказка к контролу, без интерактива внутри | GrTooltip |
| Окно по центру со своей шапкой и подвалом | GrDialog |
| Панель у края экрана | GrDrawer |
| Да/нет по опасному действию | GrConfirmDialog |
Playground контролы выводятся из API, а не пишутся руками
Загружается…
0 изменено· состояние — в адресе
<GrPopover open />Примеры 3 у каждого — живое демо и его код
Метка, добавленная прямо у задачи
Панель —
dialogи при открытии забирает фокус: поле, до которого не дотянуться клавиатурой, — не форма.closeприходит в слот содержимого, поэтому панель закрывается по «Добавить», а не на каждый клик внутри, а настоящая<form>делает рабочим Enter.#1093 · Invoice shows the wrong VAT numberOpened by Contoso Ltd · 2 hours ago billingКарточка исполнителя при наведении
Карточка при наведении отвечает «кто это» без клика. Отвечать ей нечего, поэтому фокус она не забирает —
auto-focus="false", — аrole="none"не даёт объявить «диалог» поверх справки. Задержки открытия и закрытия прощают курсор, который просто проходил мимо.Вопрос, на который не ответить кликом мимо
modalделает остальную страницу инертной, аclose-on-click-outside="false"оставляет ответ кнопкам — клик мимо панели не должен считаться ни «да», ни «нет». Панель остаётся у своей кнопки: вопрос про это действие, — аlabelled-byназывает её видимым заголовком.Release notes · OctoberInvoices now carry the purchase order number, and payouts can go to a second account…
Draft · edited 3 minutes ago
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
placement | Placement | undefined | "bottom-start" | Сторона и выравнивание панели у триггера; при нехватке места она переворачивается. По умолчанию `bottom-start`. |
disabled | boolean | undefined | false | Панель не открывается ни кликом, ни наведением; триггер из `triggerProps` получает `disabled`. |
openv-model | boolean | undefined | undefined | Открыт ли поповер. Без этого пропа компонент ведёт состояние сам (uncontrolled), с ним — слушайте `update:open`. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | Поле и кегль панели по шкале контролов. Не задан — из `GrConfigProvider`, иначе `md`. |
ariaLabel | string | undefined | undefined | Доступное имя панели. Обязательно для `role="dialog"` без видимого заголовка. |
block | boolean | undefined | false | Обёртка триггера занимает всю ширину родителя вместо того, чтобы обжимать содержимое. Нужно форм-контролам: обёртка `inline-block` схлопывается по содержимому, и `w-full` у самого триггера начинает резолвиться относительно неё, а не относительно поля. Контрол, объявивший себя во всю ширину, рисуется по содержимому — рядом с полем ввода это читается как сбитая вёрстка. Имя то же, что у `GrButton` и `GrSegmented`: в ядре это устоявшееся слово для «во всю ширину». |
padding | GrPopoverPadding | undefined | "default" | Поле панели. `none` — содержимое рисует своё (меню, список опций). |
anchor | GrFloatingAnchorRect | null | undefined | undefined | Якорь-прямоугольник в координатах вьюпорта вместо обёртки слота `#trigger`: панель встаёт у точки курсора или у строки списка. Нужен контекстному меню, у которого триггера-элемента нет вовсе. `triggerProps` в этом режиме некому потребить, и своих ARIA-атрибутов поповер никуда не вешает: `aria-haspopup` невалиден вне интерактивного элемента, а `aria-expanded` без хозяина — шум. Связь с содержимым страницы объявляет тот, кто открывает. |
closeOnEsc | boolean | undefined | true | Закрывать по `Esc`. Esc закрывает верхний слой стека: поповер в модалке закроет себя, а не её. По умолчанию `true`. |
trigger | "click" | "manual" | "hover" | undefined | "click" | Чем открывается панель. `manual` — только программно (через `v-model:open`): нужно контекстному меню и подтверждениям, где момент открытия решает потребитель. `hover` — по наведению, с задержками из `openDelay` и `closeDelay`; клик и клавиатура в этом режиме продолжают работать. |
offsetPx | number | undefined | 8 | Зазор между триггером и панелью, px. |
labelledBy | string | undefined | undefined | `id` видимого заголовка внутри панели — альтернатива `ariaLabel`. |
teleportTo | string | HTMLElement | undefined | undefined | Точечное переопределение точки монтирования. По умолчанию — общий портал оверлеев (`#gr-portal` либо `portalTarget` из `GrConfigProvider`). |
contentClass | string | undefined | undefined | Свои классы панели. Поле так не снять — для этого `padding="none"`. |
modal | boolean | undefined | false | Модальный режим: фон уходит в `inert`, Tab ходит по кругу внутри панели, скролл страницы блокируется, а слой встаёт в стек модальным — как окно. Нужен поповеру с формой или подтверждением внутри: без изоляции фона пользователь уводит фокус на страницу, к которой поповер и относится. В этом режиме фокус переносится в панель независимо от `autoFocus`: фон недоступен, и слой без фокуса внутри стал бы клавиатурной ловушкой. |
openDelay | number | undefined | 120 | Задержка открытия по наведению, мс. Обе задержки нужны, и каждая по своей причине: без `openDelay` панель выпрыгивает на любое пересечение курсором, без `closeDelay` её не удержать при переходе с триггера на панель — между ними зазор `offsetPx`. |
closeDelay | number | undefined | 160 | Задержка закрытия после ухода курсора, мс. |
closeOnContentClick | boolean | undefined | false | Закрывать по клику внутри панели — удобно для меню, вредно для формы. |
role | GrPopoverRole | undefined | "dialog" | Роль панели. Меняется теми, кто строит поверх примитива своё меню/список. |
closeOnClickOutside | boolean | undefined | true | Закрывать по клику вне панели. Слои, открытые из самой панели (подменю, селект), внешними не считаются. По умолчанию `true`. |
autoFocus | boolean | undefined | true | Переносить фокус на панель при открытии. Именно на панель, а не на первый контрол внутри: сфокусировать поле ввода за пользователя — решение содержимого, а не оболочки. |
matchWidth | boolean | "min" | undefined | false | Брать ширину у триггера: `true` — точно его ширина, `'min'` — не уже его, дальше по содержимому. Панель у поля или у широкой кнопки, оказавшаяся уже своего триггера, читается как ошибка вёрстки. С потолком `--gr-popover-max-width` сочетается, а не спорит: ширину задаёт триггер, потолок её ограничивает — «шириной с триггер, но не шире читаемого». Поэтому это отдельный проп, а не значение одного перечисления вместе с потолком: оси независимы, и сочетание осмысленно. |
Слоты
| Slot | Type | Описание |
|---|---|---|
trigger |
| Триггер панели. `triggerProps` обязаны попасть на сам интерактивный элемент, а не на обёртку вокруг него: `aria-expanded` и `aria-controls` читаются с того узла, который получает фокус. |
content | { close: () => void; } | Содержимое панели. |
События
| Event | Сигнатура | Описание |
|---|---|---|
update:open | (event: "update:open", value: boolean): void | — |
Методы
| Methods | Сигнатура | Описание |
|---|---|---|
close | () => void | Закрыть панель; фокус возвращается на триггер, если был внутри. |
toggle | () => void | Открыть закрытую панель или закрыть открытую. |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrPopoverProps, GrPopoverEmits, GrPopoverConfigurableProps } from '@feugene/granularity/components/GrPopover'Файлdist/types/components/GrPopover/GrPopover.vue.d.ts
interface GrPopoverProps {
/**
* Открыт ли поповер. Без этого пропа компонент ведёт состояние сам
* (uncontrolled), с ним — слушайте `update:open`.
*/
open?: boolean;
/**
* Сторона и выравнивание панели у триггера; при нехватке места она переворачивается.
* По умолчанию `bottom-start`.
*/
placement?: UseFloatingPlacement;
/** Зазор между триггером и панелью, px. */
offsetPx?: number;
/** Поле и кегль панели по шкале контролов. Не задан — из `GrConfigProvider`, иначе `md`. */
size?: GrPopoverSize;
/** Роль панели. Меняется теми, кто строит поверх примитива своё меню/список. */
role?: GrPopoverRole;
/**
* Модальный режим: фон уходит в `inert`, Tab ходит по кругу внутри панели,
* скролл страницы блокируется, а слой встаёт в стек модальным — как окно.
* Нужен поповеру с формой или подтверждением внутри: без изоляции фона
* пользователь уводит фокус на страницу, к которой поповер и относится.
*
* В этом режиме фокус переносится в панель независимо от `autoFocus`: фон
* недоступен, и слой без фокуса внутри стал бы клавиатурной ловушкой.
*/
modal?: boolean;
/** Доступное имя панели. Обязательно для `role="dialog"` без видимого заголовка. */
ariaLabel?: string;
/** `id` видимого заголовка внутри панели — альтернатива `ariaLabel`. */
labelledBy?: string;
/**
* Чем открывается панель.
*
* `manual` — только программно (через `v-model:open`): нужно контекстному меню
* и подтверждениям, где момент открытия решает потребитель. `hover` — по
* наведению, с задержками из `openDelay` и `closeDelay`; клик и клавиатура в
* этом режиме продолжают работать.
*/
trigger?: 'click' | 'manual' | 'hover';
/**
* Задержка открытия по наведению, мс.
*
* Обе задержки нужны, и каждая по своей причине: без `openDelay` панель
* выпрыгивает на любое пересечение курсором, без `closeDelay` её не удержать
* при переходе с триггера на панель — между ними зазор `offsetPx`.
*/
openDelay?: number;
/** Задержка закрытия после ухода курсора, мс. */
closeDelay?: number;
/**
* Закрывать по `Esc`. Esc закрывает верхний слой стека: поповер в модалке закроет себя, а не её.
* По умолчанию `true`.
*/
closeOnEsc?: boolean;
/**
* Закрывать по клику вне панели. Слои, открытые из самой панели (подменю, селект),
* внешними не считаются. По умолчанию `true`.
*/
closeOnClickOutside?: boolean;
/** Закрывать по клику внутри панели — удобно для меню, вредно для формы. */
closeOnContentClick?: boolean;
/**
* Переносить фокус на панель при открытии. Именно на панель, а не на первый
* контрол внутри: сфокусировать поле ввода за пользователя — решение
* содержимого, а не оболочки.
*/
autoFocus?: boolean;
/**
* Точечное переопределение точки монтирования. По умолчанию — общий портал
* оверлеев (`#gr-portal` либо `portalTarget` из `GrConfigProvider`).
*/
teleportTo?: string | HTMLElement;
/** Свои классы панели. Поле так не снять — для этого `padding="none"`. */
contentClass?: string;
/** Панель не открывается ни кликом, ни наведением; триггер из `triggerProps` получает `disabled`. */
disabled?: boolean;
/**
* Якорь-прямоугольник в координатах вьюпорта вместо обёртки слота `#trigger`:
* панель встаёт у точки курсора или у строки списка. Нужен контекстному меню,
* у которого триггера-элемента нет вовсе.
*
* `triggerProps` в этом режиме некому потребить, и своих ARIA-атрибутов
* поповер никуда не вешает: `aria-haspopup` невалиден вне интерактивного
* элемента, а `aria-expanded` без хозяина — шум. Связь с содержимым страницы
* объявляет тот, кто открывает.
*/
anchor?: GrFloatingAnchorRect | null;
/** Поле панели. `none` — содержимое рисует своё (меню, список опций). */
padding?: GrPopoverPadding;
/**
* Брать ширину у триггера: `true` — точно его ширина, `'min'` — не уже его,
* дальше по содержимому. Панель у поля или у широкой кнопки, оказавшаяся уже
* своего триггера, читается как ошибка вёрстки.
*
* С потолком `--gr-popover-max-width` сочетается, а не спорит: ширину задаёт
* триггер, потолок её ограничивает — «шириной с триггер, но не шире
* читаемого». Поэтому это отдельный проп, а не значение одного перечисления
* вместе с потолком: оси независимы, и сочетание осмысленно.
*/
matchWidth?: boolean | 'min';
/**
* Обёртка триггера занимает всю ширину родителя вместо того, чтобы обжимать
* содержимое.
*
* Нужно форм-контролам: обёртка `inline-block` схлопывается по содержимому, и
* `w-full` у самого триггера начинает резолвиться относительно неё, а не
* относительно поля. Контрол, объявивший себя во всю ширину, рисуется по
* содержимому — рядом с полем ввода это читается как сбитая вёрстка.
*
* Имя то же, что у `GrButton` и `GrSegmented`: в ядре это устоявшееся слово
* для «во всю ширину».
*/
block?: boolean;
}interface GrPopoverEmits {
/** Панель открылась или закрылась — триггером, `Esc`, кликом вне или из кода (`v-model:open`). */
(e: 'update:open', value: boolean): void;
}Файлdist/types/components/GrPopover/config.d.ts
const grPopoverConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrPopover">;Файлdist/types/components/GrPopover/defaults.d.ts
Пропы GrPopover, настраиваемые глобально через componentDefaults.
Только оформление: open и обработчики принадлежат конкретному экземпляру и
через конфиг задаваться не должны.
interface GrPopoverConfigurableProps {
size: GrComponentSize;
}Файлdist/types/components/GrPopover/grPopoverStyles.d.ts
type GrPopoverSize = GrComponentSize;Роль панели.
dialog — произвольный интерактивный контент (значение по умолчанию).
Остальные нужны компонентам, которые строятся поверх этого примитива:
меню (GrMenu, GrContextMenu), списки, палитры. Роль задаёт потребитель,
потому что клавиатурный паттерн принадлежит ему, а не поповеру.
type GrPopoverRole = 'dialog' | 'menu' | 'listbox' | 'grid' | 'group' | 'none';Поле панели. none — поле рисует содержимое (меню, список опций).
type GrPopoverPadding = 'default' | 'none';Файлdist/types/components/GrPopover/safelist.d.ts
const grPopoverSafelist: string[];Файлdist/types/components/GrPopover/index.d.ts
type GrPopoverInstance = ComponentExposed<typeof GrPopoverComponent>;Использование из прозы библиотеки
Почему не GrDropdown
Роль универсального поповера какое-то время исполнял GrDropdown, но он про
меню: жёстко объявляет role="menu", aria-haspopup="menu" и водит фокус по
пунктам. Для формы, подтверждения или палитры это неверная семантика — диктор
объявит меню там, где меню нет.
Здесь наоборот: role задаётся пропом (dialog по умолчанию, плюс menu,
listbox, grid, group и none), потому что клавиатурный паттерн внутри
панели принадлежит содержимому. На этом примитиве собираются меню, контекстные
меню, палитры и Popconfirm.
Триггер
Слот #trigger получает triggerProps — их нужно навесить на реальный
фокусируемый элемент, а не на обёртку:
<GrPopover>
<template #trigger="{ triggerProps }">
<GrButton v-bind="triggerProps">Открыть</GrButton>
</template>
</GrPopover>aria-expanded и aria-controls обязаны жить на самом интерактивном элементе:
на div вокруг него они немы. Клик при этом слушает обёртка — поэтому триггером
может быть что угодно, лишь бы ARIA уехала на кнопку.
trigger="manual" отключает открытие по клику: момент открытия решает
потребитель через v-model:open. Это режим контекстного меню и подтверждений.
Ловушки фокуса по умолчанию нет
И это не упущение. Немодальный слой не блокирует страницу, поэтому Tab обязан
уводить фокус наружу — иначе пользователь заперт в панели на работающей
странице.
modal включает второй режим: фон уходит в inert, Tab ходит по кругу внутри
панели, скролл страницы блокируется, а слой встаёт в стек модальным — как окно.
Нужен поповеру с формой или подтверждением: без изоляции фона пользователь
уводит фокус на ту самую страницу, к которой поповер и относится. В этом режиме
фокус переносится в панель независимо от autoFocus — фон недоступен, и слой без
фокуса внутри стал бы клавиатурной ловушкой.
Модальность приходит той же сборкой, что у окна, drawer’а и просмотрщика, так
что второй реализации модального слоя в пакете нет. Стек, Esc и возврат фокуса
на триггер — ../overlays.md.
autoFocus фокусирует панель, а не поле внутри
Панель фокусируемая (tabindex="-1"), и при открытии фокус переносится именно на
неё. Сфокусировать первый контрол за пользователя — решение содержимого, а не
оболочки: в фильтре это уместно, в подтверждении удаления — нет.
Доступное имя
Для role="dialog" имя обязательно: либо ariaLabel, либо labelledBy с id
видимого заголовка внутри панели. Панель без имени диктор объявит как «диалог» и
ничего больше.
Закрытие
closeOnEsc и closeOnClickOutside включены по умолчанию.
closeOnContentClick — нет: он удобен меню, где клик по пункту и означает
выбор, и вреден форме, где каждый клик по полю закрывал бы панель.
Императивно — open(), close(), toggle() через ref компонента. Этот набор в
пакете считается эталонным для оверлеев и закреплён гейтом
overlayImperativeApi.
Слой и размер
Панель лежит на той же высоте, что GrDropdown (--gr-z-dropdown), и это
осознанно: оба — якорные немодальные оверлеи одного класса, и на разных высотах
они начали бы перекрывать друг друга в зависимости от порядка открытия. Шкала
слоёв — ../z-index.md.
placement и offsetPx задают сторону и зазор; итоговая сторона может
отличаться от заданной — панель переворачивается, когда места не хватает, и
transform-origin анимации переворачивается вместе с ней.
size задаёт внутренние отступы и кегль по контрольной шкале — см.
../sizes.md.
Триггером считается элемент с triggerProps
triggerProps несут и ARIA, и клик. Биндить их надо на сам интерактивный
элемент, а не на обёртку вокруг него:
<GrPopover>
<template #trigger="{ triggerProps }">
<GrButton v-bind="triggerProps">
Открыть
</GrButton>
</template>
</GrPopover>Причина не в аккуратности, а в поведении. Слот #trigger может содержать не
только триггер: кнопку рядом, ссылку в карточке, крестик на чипе. Клик,
живущий на обёртке, ловил бы их все и открывал панель мимо намерения
пользователя. Живущий в triggerProps — открывает только с того элемента,
которому его отдали.
Слот, оставленный без v-bind="triggerProps", по-прежнему открывается кликом
по обёртке: так работало раньше, и это не отняли. Но такой триггер остаётся без
клавиатуры и без aria-haspopup/aria-expanded — панель для скринридера не
объявлена, и с Tab до неё не добраться. В dev-сборке компонент предупреждает о
таком триггере в консоли.
Чем открывается
trigger — click (по умолчанию), manual или hover.
manual открывает только программно, через v-model:open: так работают
контекстное меню и подтверждения, где момент открытия решает потребитель.
hover открывает по наведению с задержками openDelay и closeDelay. Нужны
обе, и каждая по своей причине: без первой панель выпрыгивает на любое
пересечение курсором, без второй её не удержать при переходе с триггера на
панель — между ними зазор offsetPx. Наведение слушает и панель, поэтому курсор,
переехавший на неё, панель не гасит.
Клик в режиме наведения продолжает работать. С клавиатуры и с тачскрина наведения не бывает, и панель, открываемая только курсором, для них не существует вовсе.
Обёртка триггера
Обёртка вокруг слота #trigger по умолчанию inline-block: она обжимает содержимое,
и панель встаёт у края самой кнопки, а не у края колонки. Для кнопки это верно.
Форм-контролу — нет. Контрол объявляет себя w-full, как GrInput и GrSelect, но
w-full резолвится относительно обёртки, а она уже обжалась по содержимому. Итог:
контрол во всю ширину рисуется по содержимому, а рядом с полем ввода это читается как
сбитая вёрстка. Замер: GrColorPicker в GrFormField шириной 384px рисовался на 113px.
Проп block растягивает обёртку на всю ширину родителя:
<GrPopover block>Имя то же, что у GrButton и GrSegmented, — в ядре это устоявшееся слово для «во всю
ширину». matchWidth при этом берёт ширину именно у обёртки, поэтому с block панель
пойдёт по ширине поля, а не по ширине содержимого триггера.
Ширина
Две независимые оси и один предел, который не настраивается.
Потолок содержимого — хук --gr-popover-max-width, по умолчанию 22rem. Это
читаемая ширина колонки текста, и для формы или карточки она верна. Содержимому
шире прозы — тулбару, палитре, сетке — потолок снимают:
<GrPopover content-class="[--gr-popover-max-width:100vw]">Хуком, а не пропом, намеренно: значение остаётся значением, его можно менять по
брейкпоинту (md:[--gr-popover-max-width:100vw]) и по теме, и оно не спорит по
специфичности с классом панели.
100vw, а не none: min(none, …) — невалидный CSS, и запись через 100vw
заодно оставляет на месте второй предел.
Через
contentClass, а не инлайновым стилем. Панель телепортируется в портал (#gr-portalвbody), поэтому обёртка триггера ей не предок:<GrPopover style="--gr-popover-max-width: 100vw">ляжет на обёртку, до панели не дойдёт и молча ничего не сделает.contentClass— единственный путь на саму панель. Глобально хук работает как обычно: в теме или на:rootон наследуется в портал вместе со всем остальным.
Ширина от триггера — проп matchWidth. true — точно ширина триггера,
'min' — не уже его, дальше по содержимому. Панель у поля или у широкой кнопки,
оказавшаяся уже своего триггера, читается как ошибка вёрстки.
<GrPopover match-width> <!-- ровно ширина триггера -->
<GrPopover match-width="min"> <!-- не уже триггера -->Оси сочетаются, а не спорят, но разрешаются по-разному, и это следствие CSS, а не решение компонента:
| Сочетание | Что получится | Почему |
|---|---|---|
matchWidth + потолок | «шириной с триггер, но не шире читаемого» | width от триггера, max-width его срезает |
matchWidth="min" + потолок | шире потолка, если триггер шире | min-width в CSS сильнее max-width: пол выигрывает у потолка |
Второе — не изъян, а смысл режима: min означает «не уже триггера», то есть пол, а
пол по определению главнее потолка. Нужен именно потолок — берите matchWidth
без min.
Поэтому это два независимых рычага, а не одно перечисление: сочетания осмысленны и разрешаются предсказуемо.
Не шире вьюпорта — не настраивается. calc(100vw - 1rem) стоит вторым
операндом min() и снаружи не отключается ничем, включая
--gr-popover-max-width: 100vw: позиционирование смещает панель в пределах
экрана, но не сужает её, и без этого предела панель у края уезжала бы за него.
Высота
Устроена как ширина — min() из хука и предела, который не настраивается, — но
второй операнд здесь не константа, а замер.
Потолок содержимого — хук --gr-popover-max-height, по умолчанию 100vh,
то есть мнения нет. Задают его, когда панель обязана быть ниже доступного
места:
<GrPopover content-class="[--gr-popover-max-height:20rem]">Через contentClass и по той же причине, что у ширины: панель телепортирована,
инлайновый стиль ляжет на обёртку и до неё не дойдёт.
Не выше, чем есть места, — не настраивается. Второй операнд — переменная
--gr-floating-available-height, которую пишет слой: расстояние до края
вьюпорта на той стороне, куда панель встала. Она пересчитывается вместе с
позицией, в том числе на скролле, и снаружи не отключается.
Статичной эта величина быть не может, и в этом всё отличие от ширины: 100vw
известен заранее, а сколько места под триггером — зависит от того, где триггер
стоит. flip перевернёт панель на свободную сторону, shift подвинет вдоль
края, но сжать её не может ни тот, ни другой: панель выше вьюпорта после
обоих остаётся выше вьюпорта, и её низ уходит за экран без всякой возможности
туда добраться.
Панель скроллится. Потолок приезжает вместе с overflow-y: auto, потому что
потолок без скролла не ограничивает, а обрезает. Пока потолок не упёрся, полосы
прокрутки нет — для коротких панелей не меняется ничего.
Это касается и всего, что стоит на GrPopover: длинное меню GrDropdown или
GrContextMenu у нижнего края экрана теперь сжимается со скроллом, а не
съезжает.
Пропы этого примера