GrPromptDialog
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 checked —
rulesandrequiredwork before the confirmation, andfieldErrorshows the server’s refusal; - the text is long —
multilinewithautosizeturns the field into a text area; - the call comes from code —
useDialogService().prompt()returns a Promise with the value.
When to take something else
| Need | Take |
|---|---|
| Ask for consent rather than for a value | GrConfirmDialog |
| There are several fields | GrDialog + GrForm |
| The field lives on the screen itself | GrFormField + GrInput |
| Choose from ready options | GrDialog + 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…
<GrPromptDialog />Install
npm i @feugene/granularityImport
import { GrPromptDialog } from '@feugene/granularity/components/GrPromptDialog'API
Props
| Prop | Type | default | Description |
|---|---|---|---|
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 | The a11y label of the close button (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 | The 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. |
confirmLoading | boolean | undefined | false | The loading state of the Confirm button (an async `onConfirm` in flight). |
confirmDisabled | boolean | undefined | false | Forcibly disables the Confirm button. |
closeOnConfirm | boolean | undefined | true | Whether to close the dialog automatically on a click on Confirm. `true` by default. `false` hands the closing out (`useDialogService` is needed). |
persistent | boolean | undefined | false | A 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. |
rows | number | undefined | undefined | The height of the multiline field in rows. |
rules | GrFormRule | GrFormRule[] | undefined | undefined | The 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" | undefined | undefined | The software keyboard on mobile. |
maxlength | number | undefined | undefined | The limit on the length; with `showCount` a counter is drawn. |
showCount | boolean | undefined | false | — |
requiredErrorText | string | undefined | undefined | The error text for an empty value with `required=true` (i18n). |
inputType | GrInputType | undefined | "text" | The type of the single-line field. It does not apply in the multiline mode. |
multiline | boolean | undefined | false | Multiline input: a `GrTextarea` is drawn instead of a `GrInput`. |
autosize | boolean | undefined | false | Automatic fitting of the height of the multiline field to the content. |
fieldError | string | null | undefined | null | An 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. |
modelValuerequired | boolean | — | — |
valuerequired | string | — | — |
Slots
| Slot | Type | Description |
|---|---|---|
default | any | The content of the dialog instead of the `message` prop. |
error | { error: ResponseErrorInfo | null; } | A breakdown of the error instead of the built-in banner. |
footer | any | The buttons of the dialog instead of the "cancel and confirm" pair. |
Events
| Event | Type | Description |
|---|---|---|
update:modelValue | [value: boolean] | — |
confirm | [value: string] | — |
cancel | [] | — |
update:value | [value: string] | — |
Examples 4
Rename flow with required value
<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
<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
<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
<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 одна — сам контрол, а описание уезжает на него