GrDialog
Берут, когда окно с шапкой, телом и подвалом.
Обзор
Когда брать
- окно с шапкой, телом и подвалом — типовая раскладка уже собрана: заголовок, кнопка закрытия, ряд действий;
- внутри форма — тело прокручивается независимо, подвал с кнопками остаётся на месте;
- окно открывается разметкой —
v-modelв шаблоне, а не вызовом из кода; - фокус должен встать на конкретный элемент —
initialFocusвместо первого попавшегося контрола.
Когда взять другое
| Нужно | Берите |
|---|---|
| Раскладка нестандартная: во весь экран, без шапки, свой каркас | GrModal |
| Спросить «да/нет» | GrConfirmDialog |
| Запросить одно значение | GrPromptDialog |
| Вызвать окно из кода, без разметки в шаблоне | GrDialogService |
| Панель у края экрана | GrDrawer |
| Содержимое привязано к кнопке, а не к центру экрана | GrPopover |
Playground контролы выводятся из API, а не пишутся руками
Окно откроется поверх страницы с пропами, заданными справа.
0 изменено· состояние — в адресе
<GrDialog title="Rename the report" v-model="value">The report keeps its link and its schedule; only the name changes.</GrDialog>Примеры 4 у каждого — живое демо и его код
Заголовок, тело и действия из коробки
titleрисует заголовок и даёт окну имя, кнопка закрытия приходит вместе с ним, а#footerдержит действия. Фокус, Esc и заблокированная страница — отGrModalпод ним;closedприходит после анимации — безопасный момент сбросить форму.Team9 of 12 seats usedALGWLRMOДлинный текст прокручивается внутри
scroll-behavior="inside"держит заголовок и «Got it» на месте и прокручивает только тело;size="lg"даёт тексту читаемую ширину. По умолчаниюoutsideпрокручивает окно целиком при заблокированной странице — так правильно для формы, которую читают сверху вниз.Version 2.14 is outTen changes since SeptemberСвоя шапка и поля по секциям
Слот
#headerзаменяет заголовок и оставляет кнопку закрытия.header-config,body-configиfooter-configзадают поля и линию одной секции, не трогая остальные: здесь у подвала нет линии, и он ближе к выбору над ним.Invoices · September 2026214 invoices · €96,420.18Без шапки, но с именем
:show-header="false"убирает заголовок и кнопку закрытия — для карточки, которая говорит сама за себя. Имя окну всё равно нужно, иaria-labelвстаёт на место заголовка: окно без имени диктор объявит просто «диалог».Work faster with the keyboard.
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
title | string | undefined | undefined | Заголовок окна. Он же даёт окну доступное имя через `aria-labelledby`. |
size | "sm" | "md" | "lg" | "xl" | "full" | undefined | undefined | Ширина окна — шкала `GrModal`: от `sm` до `xl`, `full` — во весь экран. |
ariaLabel | string | undefined | undefined | Доступное имя окна, когда заголовка нет вовсе: `showHeader: false` без `title` и без слота `#header` оставил бы окно безымянным. |
closeOnBackdrop | boolean | undefined | true | Закрывать по клику на подложку — только по клику, начавшемуся на ней. |
closeOnEsc | boolean | undefined | true | Закрывать по Esc. Закрывается верхний слой стека. |
showHeader | boolean | undefined | true | Показывать шапку. Без неё уходят и заголовок, и кнопка закрытия — имя окну тогда даёт `ariaLabel`. |
showCloseButton | boolean | undefined | true | Кнопка закрытия в шапке. Без неё окно закрывают Esc, подложка и кнопки подвала. |
headerConfig | GrDialogSectionConfig | undefined | undefined | Поля и линия шапки: `paddingX`, `paddingY`, `bordered`. |
footerConfig | GrDialogSectionConfig | undefined | undefined | Поля и линия подвала. Подвал рисуется, только если передан слот `#footer`. |
bodyConfig | GrDialogSectionConfig | undefined | undefined | Поля тела. `bordered` у тела не применяется: своей верхней линии у него нет. |
closeLabel | string | undefined | undefined | A11y-лейбл кнопки закрытия (i18n). |
scrollBehavior | GrModalScrollBehavior | undefined | "outside" | Кто скроллится при длинном содержимом. При `inside` шапка и подвал закреплены, а едет только тело. |
initialFocus | HTMLElement | null | undefined | null | Элемент, получающий фокус при открытии. По умолчанию — панель окна. Элемент **из самого диалога** сюда передавать нельзя: проп, возвращающий наверх то, что рождено внутри поддерева, замыкает рендер в цикл. Фокус на своём содержимом ставится из содержимого — так это сделано в `GrPromptDialog`. |
modelValueобязательныйv-model | boolean | — | Открыто ли окно (`v-model`). Своего состояния у окна нет: открывает и закрывает родитель. |
Слоты
| Slot | Type | Описание |
|---|---|---|
default | any | Тело окна; поля задаёт `bodyConfig`. |
header | { title?: string | undefined; } | Содержимое шапки вместо видимого заголовка; кнопка закрытия остаётся. Доступное имя окну тогда даёт скрытый `title`. |
footer | any | Подвал с действиями окна. Без слота подвала нет. |
События
| Event | Сигнатура | Описание |
|---|---|---|
update:modelValue | (event: "update:modelValue", value: boolean): void | — |
opened | (event: "opened"): void | — |
closed | (event: "closed"): void | — |
Методы
| Methods | Сигнатура | Описание |
|---|---|---|
open | () => void | Попросить родителя открыть окно: шлёт `update:modelValue` с `true`. |
close | () => void | Попросить родителя закрыть окно: шлёт `update:modelValue` с `false`. |
toggle | () => void | Переключить окно через `update:modelValue`. |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrDialogProps, GrDialogEmits, GrDialogHeaderProps } from '@feugene/granularity/components/GrDialog'Файлdist/types/components/GrDialog/GrDialog.vue.d.ts
interface GrDialogProps {
/** Открыто ли окно (`v-model`). Своего состояния у окна нет: открывает и закрывает родитель. */
modelValue: boolean;
/** Заголовок окна. Он же даёт окну доступное имя через `aria-labelledby`. */
title?: string;
/** Закрывать по клику на подложку — только по клику, начавшемуся на ней. */
closeOnBackdrop?: boolean;
/** Закрывать по Esc. Закрывается верхний слой стека. */
closeOnEsc?: boolean;
/**
* Показывать шапку. Без неё уходят и заголовок, и кнопка закрытия — имя окну
* тогда даёт `ariaLabel`.
*/
showHeader?: boolean;
/** Кнопка закрытия в шапке. Без неё окно закрывают Esc, подложка и кнопки подвала. */
showCloseButton?: boolean;
/** Ширина окна — шкала `GrModal`: от `sm` до `xl`, `full` — во весь экран. */
size?: GrDialogSize;
/** Поля и линия шапки: `paddingX`, `paddingY`, `bordered`. */
headerConfig?: GrDialogSectionConfig;
/** Поля и линия подвала. Подвал рисуется, только если передан слот `#footer`. */
footerConfig?: GrDialogSectionConfig;
/** Поля тела. `bordered` у тела не применяется: своей верхней линии у него нет. */
bodyConfig?: GrDialogSectionConfig;
/** A11y-лейбл кнопки закрытия (i18n). */
closeLabel?: string;
/**
* Доступное имя окна, когда заголовка нет вовсе: `showHeader: false` без
* `title` и без слота `#header` оставил бы окно безымянным.
*/
ariaLabel?: string;
/**
* Кто скроллится при длинном содержимом. При `inside` шапка и подвал
* закреплены, а едет только тело.
*/
scrollBehavior?: GrModalScrollBehavior;
/**
* Элемент, получающий фокус при открытии. По умолчанию — панель окна.
* Элемент **из самого диалога** сюда передавать нельзя: проп, возвращающий
* наверх то, что рождено внутри поддерева, замыкает рендер в цикл. Фокус на
* своём содержимом ставится из содержимого — так это сделано в
* `GrPromptDialog`.
*/
initialFocus?: HTMLElement | null;
}interface GrDialogEmits {
/** Окно просит открыться или закрыться: Esc, подложка, кнопка в шапке или методы из `ref`. */
(e: 'update:modelValue', value: boolean): void;
/** Окно открылось и анимация закончилась. */
(e: 'opened'): void;
/** Окно закрылось и анимация закончилась — содержимое можно размонтировать. */
(e: 'closed'): void;
}Файлdist/types/components/GrDialog/GrDialogHeader.vue.d.ts
interface GrDialogHeaderProps {
title?: string;
showCloseButton?: boolean;
config?: GrDialogSectionConfig;
/** A11y-лейбл кнопки закрытия (i18n). */
closeLabel?: string;
}Файлdist/types/components/GrDialog/GrDialogFooter.vue.d.ts
interface GrDialogFooterProps {
config?: GrDialogSectionConfig;
}Файлdist/types/components/GrDialog/dialogShared.d.ts
type GrDialogSize = GrOverlaySize;interface GrDialogSectionConfig {
paddingX?: string;
paddingY?: string;
bordered?: boolean;
}Файлdist/types/components/GrDialog/safelist.d.ts
const grDialogSafelist: string[];Файлdist/types/components/GrDialog/index.d.ts
type GrDialogInstance = ComponentExposed<typeof GrDialogComponent>;Файлdist/types/components/GrDialog/GrDialogCloseButton.vue.d.ts
interface GrDialogCloseButtonEmits {
(e: 'click'): void;
}Доступность
Клавиши
EscTab / Shift+Tab
| Клавиша | Действие |
|---|---|
| Esc | закрыть верхнее окно (если closeOnEsc) |
| Tab / Shift+Tab | цикл внутри фокус-ловушки |
Полный контракт пакета: docs/keyboard.md ↗
Использование из прозы библиотеки
Секции
headerConfig, bodyConfig, footerConfig задают паддинги и рамку каждой
секции (bordered у тела игнорируется: отдельной верхней рамки у него нет).
Подвал рендерится, только если передан слот #footer.
Шапка, подвал и кнопка закрытия — отдельные компоненты того же subpath:
GrDialogHeader, GrDialogFooter, GrDialogCloseButton. Внутри GrDialog они
уже собраны; берут их напрямую, когда раскладку строят на GrModal, а шапку и
подвал хотят те же самые.
Слот #header заменяет содержимое шапки целиком — кнопка закрытия при этом
остаётся на месте. Доступное имя окна в этом случае уходит вниз sr-only
заголовком: пользовательская шапка не обязана содержать DialogTitle, а окно
без имени диктор озвучит как безымянный «диалог».
Скролл длинного содержимого
scrollBehavior:
outside(по умолчанию) — скроллится весь оверлей, окно уезжает вверх вместе со страницей;inside— шапка и подвал закреплены, едет только тело.
<GrDialog v-model="open" title="Настройки профиля" scroll-behavior="inside">
<ProfileForm />
<template #footer>
<GrButton @click="submit">Сохранить</GrButton>
</template>
</GrDialog>Форма на двадцать полей — ровно этот случай: с outside кнопки уезжают за
экран вместе с содержимым, и до «Сохранить» надо доскроллить. Технически шапка
и подвал уходят в layout-слоты GrModal (#header/#footer), которые лежат
вне скроллящегося тела; тело при этом попадает в таб-порядок, чтобы длинный
текст без единого фокусируемого элемента можно было прокрутить с клавиатуры.
Во весь экран — size="full": панель занимает вьюпорт целиком, без полей и
скруглений.
Императивный API
open(), close() и toggle() через ref на компоненте — как у остальных
оверлеев. Диалог управляемый, и методы эмитят update:modelValue: состояние
живёт в v-model родителя, а не внутри (подробнее — GrModal.md).
Фокус и жизненный цикл
initialFocus задаёт элемент, получающий фокус при открытии; по умолчанию это
панель окна. Элемент из самого диалога сюда передавать нельзя — проп,
возвращающий наверх то, что рождено внутри поддерева, замыкает рендер в цикл.
Фокус на собственном содержимом ставится из содержимого; как это делается —
видно в GrPromptDialog.
opened и closed эмитятся после анимации. closed — единственный безопасный
момент, чтобы размонтировать содержимое или сбросить форму: сделать это по
update:modelValue значит оборвать анимацию закрытия на полпути.
Пропы этого примера