GrPromptDialog

Package: @feugene/granularitycoreGroup: overlays

Requests a short text input from the user in a dialog.

Machine-translated from the Russian original, not yet reviewed. Read the original

When to take it

  • a single value is needed — the name of a folder, the reason for a refusal, a comment on a return: setting up a screen for the sake of one field is expensive;
  • the value is checkedrules and required work before the confirmation, and fieldError shows the server’s refusal;
  • the text is longmultiline with autosize turns the field into a text area;
  • the call comes from codeuseDialogService().prompt() returns a Promise with the value.

When to take something else

NeedTake
Ask for consent rather than for a valueGrConfirmDialog
There are several fieldsGrDialog + GrForm
The field lives on the screen itselfGrFormField + GrInput
Choose from ready optionsGrDialog + GrSelect

The field

multiline switches GrInput to GrTextarea (rows, autosize) — the reason for a refusal or a comment must not be assembled through a slot. For a single-line field there are inputType (email, password, number, …) and inputmode. maxlength with showCount draws a counter; it is already linked to the field through aria-describedby.

The field is not given an id: GrFormField generates it, and the field reads it from the context. A literal id used to break exactly what it was written for — two open dialogs (an ordinary one and one through the service) gave a duplicate DOM id, and <label for> led to someone else’s input.

Checking the value

required (true by default) is a quick synchronous “not empty” check. It blocks the button and does not go into the asynchronous engine.

Everything else is the rules prop with the same rules as in GrForm: type, min/max/len, pattern, a validator of your own, an asynchronous one included. There is no third special case of validation in the package — it is the same runFieldRules, and the messages are resolved by the same createGrFormMessageResolver (both are public).

<GrPromptDialog
  v-model="open"
  v-model:value="email"
  :rules="{ type: 'email', message: 'A work address is required' }"
/>

The rules are run on blur (after the first touch) and on the confirmation. While a check is running, the button shows loading; the answer of a stale run is discarded — an asynchronous rule may come back after the value has already changed.

An error arrives from the outside as well: fieldError (the server-side validation of the field) is stronger than the built-in checks, and error draws a general banner in the body of the window.

The focus and the keyboard

On opening, the focus goes into the field rather than onto the panel of the window: the dialog exists precisely for the sake of the input. That is done on the side of the content rather than with the initialFocus prop of GrModal — the element is born inside the subtree of the dialog, and returning it upwards through a prop closes the render into a loop.

Enter in a single-line field confirms. In the multiline mode Enter remains a line break, and the confirmation is done with the button.

Playground 28

Loading…

Code
<GrPromptDialog />

Install

npm i @feugene/granularity

Import

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

API

Props

PropTypedefaultDescription
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 | undefinedundefinedThe a11y label of the close button (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 | undefinednullThe structure of a server response error for showing as a common block in the body of the dialog (through `GrResponseErrorBanner`). `null` — the block is hidden.
confirmLoadingboolean | undefinedfalseThe loading state of the Confirm button (an async `onConfirm` in flight).
confirmDisabledboolean | undefinedfalseForcibly disables the Confirm button.
closeOnConfirmboolean | undefinedtrueWhether to close the dialog automatically on a click on Confirm. `true` by default. `false` hands the closing out (`useDialogService` is needed).
persistentboolean | undefinedfalseA ban on closing by the "soft" ways (Esc, a click on the backdrop) while a confirmation or a check of the `rules` is in progress. The close button and "Cancel" stay: a window without a single exit is a trap.
rowsnumber | undefinedundefinedThe height of the multiline field in rows.
rulesGrFormRule | GrFormRule[] | undefinedundefinedThe rules for checking the value — the same as in `GrForm` (`required`, `type`, `min`/`max`/`len`, `pattern`, a `validator` of your own including an asynchronous one). They are run on blur (after the first touch) and on confirmation.
inputmode"search" | "none" | "text" | "email" | "tel" | "url" | "numeric" | "decimal" | undefinedundefinedThe software keyboard on mobile.
maxlengthnumber | undefinedundefinedThe limit on the length; with `showCount` a counter is drawn.
showCountboolean | undefinedfalse
requiredErrorTextstring | undefinedundefinedThe error text for an empty value with `required=true` (i18n).
inputTypeGrInputType | undefined"text"The type of the single-line field. It does not apply in the multiline mode.
multilineboolean | undefinedfalseMultiline input: a `GrTextarea` is drawn instead of a `GrInput`.
autosizeboolean | undefinedfalseAutomatic fitting of the height of the multiline field to the content.
fieldErrorstring | null | undefinednullAn external error of the input field (server-side validation, for instance). It takes priority over the built-in check. `null`/`undefined` — there is no external error.
modelValuerequiredboolean
valuerequiredstring

Slots

SlotTypeDescription
defaultanyThe content of the dialog instead of the `message` prop.
error{ error: ResponseErrorInfo | null; }A breakdown of the error instead of the built-in banner.
footeranyThe buttons of the dialog instead of the "cancel and confirm" pair.

Events

EventTypeDescription
update:modelValue[value: boolean]
confirm[value: string]
cancel[]
update:value[value: string]

Examples 4

Rename flow with required value

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>

Optional input mode

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>

External source-of-truth reset

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 input with shared validation rules

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

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>

Accessibility

APG pattern
| GrTooltip | Esc — скрыть (мгновенно, минуя closeDelay); показывается по фокусу триггера, не только по наведению. Со слотом остановка Tab одна — сам контрол, а описание уезжает на него

Full keyboard contract of the package

Component documentationAll components