GrPromptDialog

Пакет: @feugene/granularityядроГруппа: Слои

Берут, когда нужно одно значение.

Когда брать

  • нужно одно значение — имя папки, причина отказа, комментарий к возврату: заводить экран ради одного поля дорого;
  • значение проверяется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

PropTypeпо умолчаниюОписание
titlestring | undefinedundefined
requiredboolean | undefinedtrue
size"sm" | "md" | "lg" | "xl" | "full" | undefinedundefined
placeholderstring | undefinedundefined
descriptionstring | undefinedundefined
labelstring | undefinedundefined
closeOnBackdropboolean | undefinedtrue
closeOnEscboolean | undefinedtrue
showHeaderboolean | undefinedtrue
showCloseButtonboolean | undefinedtrue
headerConfigGrDialogSectionConfig | undefinedundefined
footerConfigGrDialogSectionConfig | undefinedundefined
bodyConfigGrDialogSectionConfig | undefinedundefined
closeLabelstring | undefinedundefinedA11y-лейбл кнопки закрытия (i18n).
buttonSize"xs" | "sm" | "md" | "lg" | undefinedundefined
confirmTextstring | undefinedundefined
cancelTextstring | undefinedundefined
confirmVariantGrButtonVariant | undefined"primary"
confirmTone"primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefined"primary"
errorResponseErrorInfo | null | undefinednullСтруктура ошибки ответа сервера для показа общим блоком в теле диалога (через `GrResponseErrorBanner`). `null` — блок скрыт.
confirmLoadingboolean | undefinedfalseСостояние загрузки кнопки Confirm (async-`onConfirm` in-flight).
confirmDisabledboolean | undefinedfalseПринудительно дизейблит кнопку Confirm.
closeOnConfirmboolean | undefinedtrueЗакрывать ли диалог автоматически по клику Confirm. По умолчанию `true`. `false` — отдаёт управление закрытием наружу (нужно `useDialogService`).
persistentboolean | undefinedfalseЗапрет закрытия «мягкими» способами (Esc, клик по бэкдропу), пока идёт подтверждение или проверка `rules`. Кнопка закрытия и «Отмена» остаются: окно без единого выхода — ловушка.
rowsnumber | undefinedundefinedВысота многострочного поля в строках.
rulesGrFormRule | GrFormRule[] | undefinedundefinedПравила проверки значения — те же, что у `GrForm` (`required`, `type`, `min`/`max`/`len`, `pattern`, свой в т.ч. асинхронный `validator`). Прогоняются на blur (после первого касания) и на подтверждении.
inputmode"search" | "none" | "text" | "email" | "tel" | "url" | "numeric" | "decimal" | undefinedundefinedПрограммная клавиатура на мобильных.
maxlengthnumber | undefinedundefinedОграничение длины; со `showCount` рисуется счётчик.
showCountboolean | undefinedfalse
requiredErrorTextstring | undefinedundefinedТекст ошибки для пустого значения при `required=true` (i18n).
inputTypeGrInputType | undefined"text"Тип однострочного поля. В многострочном режиме не применяется.
multilineboolean | undefinedfalseМногострочный ввод: вместо `GrInput` рисуется `GrTextarea`.
autosizeboolean | undefinedfalseАвтоподбор высоты многострочного поля под содержимое.
fieldErrorstring | null | undefinednullВнешняя ошибка поля ввода (например, серверная валидация). Имеет приоритет над встроенной проверкой. `null`/`undefined` — нет внешней ошибки.
modelValueобязательныйboolean
valueобязательныйstring

Slots

SlotTypeОписание
defaultanyСодержимое диалога вместо пропа `message`.
error{ error: ResponseErrorInfo | null; }Разбор ошибки вместо встроенного баннера.
footeranyКнопки диалога вместо пары «отмена и подтверждение».

Events

EventTypeОписание
update:modelValue[value: boolean]
confirm[value: string]
cancel[]
update:value[value: string]

Примеры 4

Переименование с обязательным значением

Базовый сценарий для GrPromptDialog: controlled value, required validation и сохранение подтверждённого текста.

Q2 North Star

Rename
<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.

Last submitted note: Call finance before noon

Optional Value
<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 и должно сбрасываться на момент повторного открытия.

Persisted value: Acme Corp

Reset Flow
<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: движок валидации в пакете один, а не отдельный у каждого компонента.

Последняя причина:

Multiline Rules
<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

Полный клавиатурный контракт пакета

Документация компонентаВсе компоненты