GrDialog

  • ядро
  • Слои
  • API · 14 props
  • ⌨ клавиши

Берут, когда окно с шапкой, телом и подвалом.

import { GrDialog } from '@feugene/granularity/components/GrDialog'

Обзор

Когда брать

  • окно с шапкой, телом и подвалом — типовая раскладка уже собрана: заголовок, кнопка закрытия, ряд действий;
  • внутри форма — тело прокручивается независимо, подвал с кнопками остаётся на месте;
  • окно открывается разметкой — v-model в шаблоне, а не вызовом из кода;
  • фокус должен встать на конкретный элемент — initialFocus вместо первого попавшегося контрола.

Когда взять другое

НужноБерите
Раскладка нестандартная: во весь экран, без шапки, свой каркасGrModal
Спросить «да/нет»GrConfirmDialog
Запросить одно значениеGrPromptDialog
Вызвать окно из кода, без разметки в шаблонеGrDialogService
Панель у края экранаGrDrawer
Содержимое привязано к кнопке, а не к центру экранаGrPopover

Playground контролы выводятся из API, а не пишутся руками

Сцена

Окно откроется поверх страницы с пропами, заданными справа.

0 изменено· состояние — в адресе

Примеры 4 у каждого — живое демо и его код

  • Заголовок, тело и действия из коробки

    title рисует заголовок и даёт окну имя, кнопка закрытия приходит вместе с ним, а #footer держит действия. Фокус, Esc и заблокированная страница — от GrModal под ним; closed приходит после анимации — безопасный момент сбросить форму.

    Team9 of 12 seats used
    ALGWLRMO
  • Длинный текст прокручивается внутри

    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

PropTypeпо умолчаниюОписание
titlestring | undefinedundefinedЗаголовок окна. Он же даёт окну доступное имя через `aria-labelledby`.
size"sm" | "md" | "lg" | "xl" | "full" | undefinedundefinedШирина окна — шкала `GrModal`: от `sm` до `xl`, `full` — во весь экран.
ariaLabelstring | undefinedundefinedДоступное имя окна, когда заголовка нет вовсе: `showHeader: false` без `title` и без слота `#header` оставил бы окно безымянным.
closeOnBackdropboolean | undefinedtrueЗакрывать по клику на подложку — только по клику, начавшемуся на ней.
closeOnEscboolean | undefinedtrueЗакрывать по Esc. Закрывается верхний слой стека.
showHeaderboolean | undefinedtrueПоказывать шапку. Без неё уходят и заголовок, и кнопка закрытия — имя окну тогда даёт `ariaLabel`.
showCloseButtonboolean | undefinedtrueКнопка закрытия в шапке. Без неё окно закрывают Esc, подложка и кнопки подвала.
headerConfigGrDialogSectionConfig | undefinedundefinedПоля и линия шапки: `paddingX`, `paddingY`, `bordered`.
footerConfigGrDialogSectionConfig | undefinedundefinedПоля и линия подвала. Подвал рисуется, только если передан слот `#footer`.
bodyConfigGrDialogSectionConfig | undefinedundefinedПоля тела. `bordered` у тела не применяется: своей верхней линии у него нет.
closeLabelstring | undefinedundefinedA11y-лейбл кнопки закрытия (i18n).
scrollBehaviorGrModalScrollBehavior | undefined"outside"Кто скроллится при длинном содержимом. При `inside` шапка и подвал закреплены, а едет только тело.
initialFocusHTMLElement | null | undefinednullЭлемент, получающий фокус при открытии. По умолчанию — панель окна. Элемент **из самого диалога** сюда передавать нельзя: проп, возвращающий наверх то, что рождено внутри поддерева, замыкает рендер в цикл. Фокус на своём содержимом ставится из содержимого — так это сделано в `GrPromptDialog`.
modelValueобязательныйv-modelboolean—Открыто ли окно (`v-model`). Своего состояния у окна нет: открывает и закрывает родитель.

Слоты

SlotTypeОписание
defaultanyТело окна; поля задаёт `bodyConfig`.
header{ title?: string | undefined; }Содержимое шапки вместо видимого заголовка; кнопка закрытия остаётся. Доступное имя окну тогда даёт скрытый `title`.
footeranyПодвал с действиями окна. Без слота подвала нет.

События

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

GrDialogPropsinterface
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;
}
GrDialogEmitsinterface
interface GrDialogEmits {
    /** Окно просит открыться или закрыться: Esc, подложка, кнопка в шапке или методы из `ref`. */
    (e: 'update:modelValue', value: boolean): void;
    /** Окно открылось и анимация закончилась. */
    (e: 'opened'): void;
    /** Окно закрылось и анимация закончилась — содержимое можно размонтировать. */
    (e: 'closed'): void;
}

Файлdist/types/components/GrDialog/GrDialogHeader.vue.d.ts

GrDialogHeaderPropsinterface
interface GrDialogHeaderProps {
    title?: string;
    showCloseButton?: boolean;
    config?: GrDialogSectionConfig;
    /** A11y-лейбл кнопки закрытия (i18n). */
    closeLabel?: string;
}

Файлdist/types/components/GrDialog/GrDialogFooter.vue.d.ts

GrDialogFooterPropsinterface
interface GrDialogFooterProps {
    config?: GrDialogSectionConfig;
}

Файлdist/types/components/GrDialog/dialogShared.d.ts

GrDialogSizetype
type GrDialogSize = GrOverlaySize;
GrDialogSectionConfiginterface
interface GrDialogSectionConfig {
    paddingX?: string;
    paddingY?: string;
    bordered?: boolean;
}

Файлdist/types/components/GrDialog/safelist.d.ts

grDialogSafelistconst
const grDialogSafelist: string[];

Файлdist/types/components/GrDialog/index.d.ts

GrDialogInstancetype
type GrDialogInstance = ComponentExposed<typeof GrDialogComponent>;

Файлdist/types/components/GrDialog/GrDialogCloseButton.vue.d.ts

GrDialogCloseButtonEmitsinterface
interface GrDialogCloseButtonEmits {
    (e: 'click'): void;
}

Доступность

Клавиши

EscTab / Shift+Tab

КлавишаДействие
Escзакрыть верхнее окно (если closeOnEsc)
Tab / Shift+Tabцикл внутри фокус-ловушки

Использование из прозы библиотеки

Секции

headerConfig, bodyConfig, footerConfig задают паддинги и рамку каждой секции (bordered у тела игнорируется: отдельной верхней рамки у него нет). Подвал рендерится, только если передан слот #footer.

Шапка, подвал и кнопка закрытия — отдельные компоненты того же subpath: GrDialogHeader, GrDialogFooter, GrDialogCloseButton. Внутри GrDialog они уже собраны; берут их напрямую, когда раскладку строят на GrModal, а шапку и подвал хотят те же самые.

Слот #header заменяет содержимое шапки целиком — кнопка закрытия при этом остаётся на месте. Доступное имя окна в этом случае уходит вниз sr-only заголовком: пользовательская шапка не обязана содержать DialogTitle, а окно без имени диктор озвучит как безымянный «диалог».

Скролл длинного содержимого

scrollBehavior:

  • outside (по умолчанию) — скроллится весь оверлей, окно уезжает вверх вместе со страницей;
  • inside — шапка и подвал закреплены, едет только тело.
vue
<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 значит оборвать анимацию закрытия на полпути.