GrPromptDialog
Берут, когда нужно одно значение.
Когда брать
- нужно одно значение — имя папки, причина отказа, комментарий к возврату: заводить экран ради одного поля дорого;
- значение проверяется —
rulesиrequiredработают до подтверждения,fieldErrorпоказывает отказ сервера; - текст длинный —
multilineсautosizeпревращает поле в область ввода; - вызов из кода —
useDialogService().prompt()возвращает Promise со значением.
Когда взять другое
| Нужно | Берите |
|---|---|
| Спросить согласие, а не значение | GrConfirmDialog |
| Полей несколько | GrDialog + GrForm |
| Поле живёт на самом экране | GrFormField + GrInput |
| Выбрать из готовых вариантов | GrDialog + GrSelect |
Поле
multiline переключает GrInput на GrTextarea (rows, autosize) —
причина отказа или комментарий не должны собираться через слот. Для
однострочного поля есть inputType (email, password, number, …) и
inputmode. maxlength со showCount рисует счётчик; он уже связан с полем
через aria-describedby.
id полю не задаётся: его генерирует GrFormField, а поле читает из контекста.
Литеральный id ломал ровно то, ради чего он был написан — два открытых
диалога (обычный и через сервис) давали дубликат DOM-id, и <label for> уводил
на чужой инпут.
Проверка значения
required (по умолчанию true) — быстрая синхронная проверка «непусто». Она
блокирует кнопку и не ходит в асинхронный движок.
Всё остальное — проп rules с теми же правилами, что у GrForm: type,
min/max/len, pattern, свой в том числе асинхронный validator.
Третьего частного случая валидации в пакете нет — это тот же
runFieldRules, и сообщения резолвит тот же
createGrFormMessageResolver (оба публичны).
<GrPromptDialog
v-model="open"
v-model:value="email"
:rules="{ type: 'email', message: 'Нужен рабочий адрес' }"
/>
Правила прогоняются на blur (после первого касания) и на подтверждении. Пока проверка идёт, кнопка показывает загрузку; ответ устаревшего прогона отбрасывается — асинхронное правило может вернуться уже после того, как значение сменилось.
Ошибка приходит и снаружи: fieldError (серверная валидация поля) сильнее
встроенных проверок, error рисует общий баннер в теле окна.
Фокус и клавиатура
При открытии фокус уходит в поле, а не на панель окна: диалог существует
ровно ради ввода. Сделано на стороне содержимого, а не пропом initialFocus
у GrModal, — элемент рождается внутри поддерева диалога, и возврат его же
пропом наверх замыкает рендер в цикл.
Enter в однострочном поле подтверждает. В многострочном режиме Enter
остаётся переводом строки, и подтверждают кнопкой.
Playground 28
Загружается…
<GrPromptDialog />Установка
npm i @feugene/granularityИмпорт
import { GrPromptDialog } from '@feugene/granularity/components/GrPromptDialog'API
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
title | string | undefined | undefined | — |
required | boolean | undefined | true | — |
size | "sm" | "md" | "lg" | "xl" | "full" | undefined | undefined | — |
placeholder | string | undefined | undefined | — |
description | string | undefined | undefined | — |
label | string | undefined | undefined | — |
closeOnBackdrop | boolean | undefined | true | — |
closeOnEsc | boolean | undefined | true | — |
showHeader | boolean | undefined | true | — |
showCloseButton | boolean | undefined | true | — |
headerConfig | GrDialogSectionConfig | undefined | undefined | — |
footerConfig | GrDialogSectionConfig | undefined | undefined | — |
bodyConfig | GrDialogSectionConfig | undefined | undefined | — |
closeLabel | string | undefined | undefined | A11y-лейбл кнопки закрытия (i18n). |
buttonSize | "xs" | "sm" | "md" | "lg" | undefined | undefined | — |
confirmText | string | undefined | undefined | — |
cancelText | string | undefined | undefined | — |
confirmVariant | GrButtonVariant | undefined | "primary" | — |
confirmTone | "primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefined | "primary" | — |
error | ResponseErrorInfo | null | undefined | null | Структура ошибки ответа сервера для показа общим блоком в теле диалога (через `GrResponseErrorBanner`). `null` — блок скрыт. |
confirmLoading | boolean | undefined | false | Состояние загрузки кнопки Confirm (async-`onConfirm` in-flight). |
confirmDisabled | boolean | undefined | false | Принудительно дизейблит кнопку Confirm. |
closeOnConfirm | boolean | undefined | true | Закрывать ли диалог автоматически по клику Confirm. По умолчанию `true`. `false` — отдаёт управление закрытием наружу (нужно `useDialogService`). |
persistent | boolean | undefined | false | Запрет закрытия «мягкими» способами (Esc, клик по бэкдропу), пока идёт подтверждение или проверка `rules`. Кнопка закрытия и «Отмена» остаются: окно без единого выхода — ловушка. |
rows | number | undefined | undefined | Высота многострочного поля в строках. |
rules | GrFormRule | GrFormRule[] | undefined | undefined | Правила проверки значения — те же, что у `GrForm` (`required`, `type`, `min`/`max`/`len`, `pattern`, свой в т.ч. асинхронный `validator`). Прогоняются на blur (после первого касания) и на подтверждении. |
inputmode | "search" | "none" | "text" | "email" | "tel" | "url" | "numeric" | "decimal" | undefined | undefined | Программная клавиатура на мобильных. |
maxlength | number | undefined | undefined | Ограничение длины; со `showCount` рисуется счётчик. |
showCount | boolean | undefined | false | — |
requiredErrorText | string | undefined | undefined | Текст ошибки для пустого значения при `required=true` (i18n). |
inputType | GrInputType | undefined | "text" | Тип однострочного поля. В многострочном режиме не применяется. |
multiline | boolean | undefined | false | Многострочный ввод: вместо `GrInput` рисуется `GrTextarea`. |
autosize | boolean | undefined | false | Автоподбор высоты многострочного поля под содержимое. |
fieldError | string | null | undefined | null | Внешняя ошибка поля ввода (например, серверная валидация). Имеет приоритет над встроенной проверкой. `null`/`undefined` — нет внешней ошибки. |
modelValueобязательный | boolean | — | — |
valueобязательный | string | — | — |
Slots
| Slot | Type | Описание |
|---|---|---|
default | any | Содержимое диалога вместо пропа `message`. |
error | { error: ResponseErrorInfo | null; } | Разбор ошибки вместо встроенного баннера. |
footer | any | Кнопки диалога вместо пары «отмена и подтверждение». |
Events
| Event | Type | Описание |
|---|---|---|
update:modelValue | [value: boolean] | — |
confirm | [value: string] | — |
cancel | [] | — |
update:value | [value: string] | — |
Примеры 4
Переименование с обязательным значением
Базовый сценарий для GrPromptDialog: controlled value, required validation и сохранение подтверждённого текста.
<script setup lang="ts">
import { ref } from 'vue'
import { GrBadge, GrButton, GrPromptDialog } from '@feugene/granularity'
const open = ref(false)
const value = ref('Q2 North Star')
const savedValue = ref(value.value)
</script>
<template>
<div class="grid gap-3">
<div class="flex items-center gap-3">
<GrButton class="justify-self-start" @click="open = true">
Rename objective
</GrButton>
<GrBadge size="sm" tone="neutral">
{{ savedValue }}
</GrBadge>
</div>
<GrPromptDialog
v-model="open"
v-model:value="value"
title="Rename objective"
label="Objective title"
confirm-text="Save"
@confirm="savedValue = $event"
/>
</div>
</template>Необязательный ввод
Показываем required=false, placeholder и compact footer для необязательных handoff notes.
<script setup lang="ts">
import { ref } from 'vue'
import { GrButton, GrPromptDialog } from '@feugene/granularity'
const open = ref(false)
const note = ref('Call finance before noon')
const lastSubmitted = ref(note.value)
</script>
<template>
<div class="grid gap-3">
<GrButton variant="outline" class="justify-self-start" @click="open = true">
Open optional prompt
</GrButton>
<div class="text-xs text-[var(--gr-muted-fg)]">
Last submitted note: <span class="font-medium text-[var(--gr-fg)]">{{ lastSubmitted || '—' }}</span>
</div>
<GrPromptDialog
v-model="open"
v-model:value="note"
title="Leave handoff note"
label="Optional note"
placeholder="Add context for the next shift"
confirm-text="Attach"
:required="false"
button-size="sm"
@confirm="lastSubmitted = $event"
/>
</div>
</template>Сброс к значению из внешнего источника
Изолируем кейс, когда значение приходит из внешнего store и должно сбрасываться на момент повторного открытия.
<script setup lang="ts">
import { ref } from 'vue'
import { GrButton, GrPromptDialog } from '@feugene/granularity'
const open = ref(false)
const value = ref('Acme Corp')
const savedCompany = ref(value.value)
function openDialog() {
value.value = savedCompany.value
open.value = true
}
</script>
<template>
<div class="grid gap-3">
<GrButton class="justify-self-start" @click="openDialog">
Edit billing company
</GrButton>
<div class="text-xs text-[var(--gr-muted-fg)]">
Persisted value: <span class="font-medium text-[var(--gr-fg)]">{{ savedCompany }}</span>
</div>
<GrPromptDialog
v-model="open"
v-model:value="value"
title="Billing company"
label="Legal entity"
description="Reset incoming value on open if the source of truth lives outside the dialog."
confirm-text="Update"
cancel-text="Keep current"
@confirm="savedCompany = $event"
/>
</div>
</template>Многострочный ввод и общие правила проверки
multiline даёт GrTextarea вместо однострочного поля, а rules — те же правила, что у GrForm: движок валидации в пакете один, а не отдельный у каждого компонента.
<script setup lang="ts">
import { ref } from 'vue'
import type { GrFormRule } from '@feugene/granularity'
import { GrBadge, GrButton, GrPromptDialog } from '@feugene/granularity'
const open = ref(false)
const reason = ref('')
const lastSubmitted = ref('')
// Те же правила, что и у `GrForm`: движок один на пакет.
const rules: GrFormRule[] = [
{ min: 15, message: 'Опишите причину подробнее — минимум 15 символов' },
{
validator: (value) => {
const text = String(value).trim().toLowerCase()
return text === 'нет' || text === 'не хочу'
? 'Такая причина не пройдёт проверку у согласующего'
: true
},
},
]
</script>
<template>
<div class="grid gap-3">
<GrButton variant="outline" class="justify-self-start" @click="open = true">
Отклонить заявку
</GrButton>
<div class="text-xs text-[var(--gr-muted-fg)]">
Последняя причина:
<GrBadge class="ml-1">
{{ lastSubmitted || '—' }}
</GrBadge>
</div>
<GrPromptDialog
v-model="open"
v-model:value="reason"
title="Причина отказа"
label="Причина"
placeholder="Что именно не так с заявкой"
confirm-text="Отклонить"
confirm-tone="danger"
multiline
:rows="4"
autosize
:maxlength="300"
show-count
:rules="rules"
@confirm="lastSubmitted = $event"
/>
</div>
</template>Доступность
- Паттерн APG
| GrTooltip | Esc — скрыть (мгновенно, минуя closeDelay); показывается по фокусу триггера, не только по наведению. Со слотом остановка Tab одна — сам контрол, а описание уезжает на него- Клавиши
Enterв однострочном поле — подтвердить; в многострочном (multiline) остаётся переводом строки. Фокус при открытии — в поле, а не на панели.persistent— как уGrConfirmDialog