Skip to content

PlConfirmProvider

One dialog, asked for from anywhere under it. await confirm(…) returns the answer, so the branch after a question stays in the handler that asked it.

React
tsx
import { PlConfirmProvider, usePlConfirm } from 'plass-ui';

// once, near the root
<PlConfirmProvider>
  <App />
</PlConfirmProvider>;

// anywhere under it
const { confirm } = usePlConfirm();

if (await confirm({ title: 'Delete this project?', color: 'danger' })) {
  await remove(project);
}
dart
import 'package:plass_ui/plass_ui.dart';

// once, near the root
PlConfirmProvider(child: MyApp());

// anywhere under it
if (await PlConfirmProvider.of(context).confirm(
  const PlConfirmOptions(title: Text('Delete this project?'), color: PlassColor.danger),
)) {
  await remove(project);
}

Props

PropTypeDefaultDescription
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The default size for every question. A single call can override it
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'The family the confirming button takes by default
confirmLabelReactNode'Confirm'The default word on the button that answers yes
cancelLabelReactNode'Cancel'The default word on the button that answers no
acknowledgeLabelReactNode'OK'The default word on an alert's single button
widthnumber | stringHow wide the sheet may get
childrenReactNodeThe tree that can ask questions
PropTypeDefaultDescription
child * WidgetThe application
confirmLabelWidgetText('Confirm')The default word on the button that answers yes
cancelLabelWidgetText('Cancel')The default word on the button that answers no
acknowledgeLabelWidgetText('OK')The default word on an alert's single button
widthdouble?How wide the sheet may get
sizesharedPlassSizePlassSize.mdThe default size for every question. A single call can override it
colorsharedPlassColorPlassColor.primaryThe family the confirming button takes by default

The provider's props are defaults for every question asked under it. A single call can override any of them, see PlConfirmOptions below.

PlConfirmOptions

PropTypeDefaultDescription
titleReactNodeThe question, as the <h2> that names the dialog
descriptionReactNodeA line under it, and the dialog's accessible description. Say what happens
childrenReactNodeAnything more that belongs in the body
confirmLabelReactNodeThe word on the button that answers yes
cancelLabelReactNodeThe word on the button that answers no. Not drawn by alert
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info'The family the confirming button takes. danger for anything that removes something
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl'The size of this one question
initialFocus'confirm' | 'cancel''cancel'Which button holds the focus when it opens. cancel by default, because an Enter key landing on the destructive action defeats the dialog
dismissiblebooleantrueWhether Escape and a click outside answer no. A question that cannot be escaped is a trap
widthnumber | stringHow wide the sheet may get
PropTypeDefaultDescription
titleWidget?The question, as the <h2> that names the dialog
descriptionWidget?A line under it, and the dialog's accessible description. Say what happens
childWidget?Anything more that belongs in the body
confirmLabelWidget?The word on the button that answers yes
cancelLabelWidget?The word on the button that answers no. Not drawn by alert
colorsharedPlassColorThe family the confirming button takes. danger for anything that removes something
sizesharedPlassSizeThe size of this one question
initialFocusPlConfirmFocusPlConfirmFocus.cancelWhich button holds the focus when it opens. cancel by default, because an Enter key landing on the destructive action defeats the dialog
dismissiblebooltrueWhether Escape and a click outside answer no. A question that cannot be escaped is a trap
widthdouble?How wide the sheet may get

PlConfirmProvider.of(context) rather than a hook, the same lookup PlToastProvider offers, and the framework's own shape for this. It asserts outside a provider rather than returning null, for the reason the React build throws.

initialFocus takes a PlConfirmFocus rather than a string. There is no dismissible: false equivalent to worry about: a press outside and the × both report through the same path.

The hook form

The thing a caller has at the moment a question is warranted is a click handler, not a place in the tree. Without this, the same delete button needs a piece of state, a <PlModal> kept mounted beside it, and the work after the answer torn in half across a callback, three edits to add a confirmation to one button, repeated at every button that needs one.

It is PlToastProvider's arrangement for the same reason and with the same trade: one component near the root, and a hook everywhere else.

Examples

alert

One button and no answer. It resolves when the message has been acknowledged, which is what makes it awaitable in the middle of a sequence.

React

initialFocus

Cancel holds the focus by default, and that is the decision worth stating: a confirm dialog exists to make somebody stop, and an Enter key that lands on the destructive action defeats the whole thing.

Move it for a question whose yes is the harmless answer, "Save before closing?", where making somebody reach for the mouse to agree is its own kind of rude.

React

One vocabulary for the application

tsx
<PlConfirmProvider confirmLabel="확인" cancelLabel="취소" acknowledgeLabel="확인">
  <App />
</PlConfirmProvider>

A question that has to be answered

tsx
await confirm({
  title: 'Your changes have not been saved.',
  confirmLabel: 'Discard',
  cancelLabel: 'Go back',
  dismissible: false
});

dismissible is on by default, because Escape is the universal "no" and a question that cannot be escaped is a trap. Turn it off for the one that genuinely has to be answered, and mean it.

Notes

  • Questions asked while one is open are queued, in the order they were asked, and the dialog's content changes rather than the sheet closing and reopening. The alternative is a promise nobody ever resolves, which is a hung button rather than a visible bug.
  • A provider that unmounts with questions outstanding resolves them all with false. A promise that is never settled is a handler that never runs its finally, so a route change would otherwise leave a button spinning for the rest of the session.
  • usePlConfirm throws outside a provider rather than resolving false. A silent false is a delete button that quietly does nothing, which is worse than a missing provider, since that fails on the first press.
  • Escape and a click outside answer no, never yes.

Accessibility

  • It is a real modal dialog: the focus is trapped inside it, the page behind is inert, and the focus returns to whatever opened it.
  • title is the <h2> that names the dialog and description is its accessible description, so a screen reader reads the question and the consequence before either button.
  • The two buttons are named by their labels. Name them for what they do ("Delete", "Discard", "Save") rather than "Yes" and "No", which are unreadable out of context and are exactly what a screen reader reads out of context.

Released under the MIT License