# ConfirmProvider

> One app-wide confirm dialog you can await from any handler, so `if (!(await confirm({ title }))) return;` replaces window.confirm with a styled, localised AlertDialog.

Source: https://docs.nasaqui.com/components/confirm-provider

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/confirm-provider.json
```

Mount it once, then call `useConfirm()` anywhere. `confirm(options)` opens an `AlertDialog` and resolves
`true` or `false`, so the code that deletes stays in one handler.

## When to use

- Handlers that need a "yes" first: delete from a menu, leave with unsaved changes, bulk actions.
- When the trigger is not a button you render (a keyboard shortcut, a context menu, a table action).

## When not to use

- One button that confirms before it acts: use `ConfirmButton` from [`AlertDialog`](https://docs.nasaqui.com/components/alert-dialog).
- Undoable actions: act at once and offer Undo in a [`Toast`](https://docs.nasaqui.com/components/toast).
- Dialogs with fields: use [`Dialog`](https://docs.nasaqui.com/components/dialog).

## Import

```tsx
import { ConfirmProvider, useConfirm } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { Button, ConfirmProvider, NasaqProvider, useConfirm } from "@fadymondy/nasaq/web";

declare function deleteProject(id: string): Promise<void>;

function DeleteProject({ id, name }: { id: string; name: string }) {
  const confirm = useConfirm();
  async function onDelete() {
    if (!(await confirm({ title: `Delete ${name}?`, description: "Its tasks and files are deleted too. This can't be undone.", confirmLabel: "Delete" }))) return;
    await deleteProject(id);
  }
  return <Button variant="danger" onClick={onDelete}>Delete</Button>;
}

export function App() {
  return (
    <NasaqProvider>
      <ConfirmProvider>
        <DeleteProject id="p1" name="Billing" />
      </ConfirmProvider>
    </NasaqProvider>
  );
}
```

## Anatomy

```
ConfirmProvider
├─ children
└─ AlertDialog                  data-slot="confirm-dialog"
   ├─ title, description
   └─ Cancel, Confirm           Confirm is danger unless `danger: false`
```

## API

**ConfirmProvider**: `{ children }`. Place it inside `NasaqProvider`.

**useConfirm(): ConfirmFn**: throws when there is no provider above.

**ConfirmOptions**

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `ReactNode` | required | The question. Name what is affected. |
| `description` | `ReactNode` | | What happens, and whether it can be undone. |
| `confirmLabel` | `ReactNode` | "Confirm" | Use the verb: "Delete", "Leave". |
| `cancelLabel` | `ReactNode` | "Cancel" | |
| `danger` | `boolean` | `true` | `false` styles Confirm as primary. |

The promise resolves `false` on Cancel, Escape or a click outside. A new `confirm()` while one is open
resolves the first with `false`.

## Accessibility

- It is an `AlertDialog`: focus moves into it, starts on Cancel, and returns to the trigger on close.
- The title and description name the dialog.

## RTL & i18n

- Default button labels follow the Nasaq locale (English and Arabic). Pass localised `title` and `description`.

## Styling & tokens

- Inherits `AlertDialog` and `Button`. Target `[data-slot="confirm-dialog"]`.

## Do / Don't

- Do write the consequence in `description`.
- Do label Confirm with the verb, not "OK".
- Don't confirm things that can be undone; that teaches people to click through.

## Related

- [`AlertDialog`](https://docs.nasaqui.com/components/alert-dialog)
- [`Dialog`](https://docs.nasaqui.com/components/dialog)
- [`Toast`](https://docs.nasaqui.com/components/toast)

## Lab

https://docs.nasaqui.com/?path=/docs/components-overlays-confirm-provider--docs

## Code

### React

```tsx
import { Button, ConfirmProvider, NasaqProvider, useConfirm } from "@fadymondy/nasaq/web";

declare function deleteProject(id: string): Promise<void>;

function DeleteProject({ id, name }: { id: string; name: string }) {
  const confirm = useConfirm();
  async function onDelete() {
    if (!(await confirm({ title: `Delete ${name}?`, description: "Its tasks and files are deleted too. This can't be undone.", confirmLabel: "Delete" }))) return;
    await deleteProject(id);
  }
  return <Button variant="danger" onClick={onDelete}>Delete</Button>;
}

export function App() {
  return (
    <NasaqProvider>
      <ConfirmProvider>
        <DeleteProject id="p1" name="Billing" />
      </ConfirmProvider>
    </NasaqProvider>
  );
}
```

### shadcn

```tsx
import { Button } from "@/components/ui/button";
import { ConfirmProvider, NasaqProvider, useConfirm } from "@/components/ui/confirm-provider";

declare function deleteProject(id: string): Promise<void>;

function DeleteProject({ id, name }: { id: string; name: string }) {
  const confirm = useConfirm();
  async function onDelete() {
    if (!(await confirm({ title: `Delete ${name}?`, description: "Its tasks and files are deleted too. This can't be undone.", confirmLabel: "Delete" }))) return;
    await deleteProject(id);
  }
  return <Button variant="danger" onClick={onDelete}>Delete</Button>;
}

export function App() {
  return (
    <NasaqProvider>
      <ConfirmProvider>
        <DeleteProject id="p1" name="Billing" />
      </ConfirmProvider>
    </NasaqProvider>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqButton, NqConfirmProvider, useConfirm } from "@fadymondy/nasaq/vue";
import { defineComponent, h } from "vue";

async function deleteProject(_id: string) {}

const DeleteProject = defineComponent({
  props: { id: { type: String, required: true }, name: { type: String, required: true } },
  setup(props) {
    const confirm = useConfirm();
    async function onDelete() {
      if (!(await confirm({ title: `Delete ${props.name}?`, description: "Its tasks and files are deleted too. This can't be undone.", confirmLabel: "Delete" }))) return;
      await deleteProject(props.id);
    }
    return () => h(NqButton, { variant: "danger", onClick: onDelete }, () => "Delete");
  },
});
</script>

<template>
  <NqConfirmProvider>
    <DeleteProject id="p1" name="Billing" />
  </NqConfirmProvider>
</template>
```

### Blade

```blade
<x-nq::confirm-provider />
<x-nq::button variant="danger" x-data x-on:click="if (await $confirm({ title: 'Delete Billing?', description: 'Its tasks and files are deleted too. This cannot be undone.', confirmLabel: 'Delete' })) $dispatch('deleted', { id: 'p1' })">Delete</x-nq::button>
```

### HTML + Alpine

```html
<div data-slot="confirm-provider" x-data="nqConfirmProvider()" x-id="['nq-confirm']" x-on:nq:confirm.window="ask($event.detail)" class="contents">
    <template x-teleport="body">
        <div data-slot="alert-dialog-portal">
            <div data-slot="alert-dialog-backdrop" x-nq-presence="open" class="fixed inset-0 z-50 bg-nq-fg/15 dark:bg-nq-bg/60 transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0"></div>
            <div data-slot="confirm-dialog" x-bind="popup" x-nq-presence="open" x-trap.noscroll="open"
                class="fixed inset-0 z-50 m-auto grid h-fit w-[calc(100%-2rem)] max-w-md gap-4 rounded-floating border border-border bg-popover p-6 text-popover-foreground outline-none max-h-[calc(100dvh-2rem)] overflow-y-auto transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
                <div data-slot="alert-dialog-header" class="flex flex-col gap-1.5 text-start"><h2 data-slot="alert-dialog-title" :id="$id('nq-confirm', 'title')" class="text-h3 text-foreground" x-text="options.title"></h2>
                    <p data-slot="alert-dialog-description" x-show="options.description" :id="$id('nq-confirm', 'description')" class="text-body-sm text-muted-foreground" x-text="options.description"></p></div>
                <div data-slot="alert-dialog-footer" class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"><button data-slot="alert-dialog-cancel"
     type="button"                         autofocus="autofocus" x-on:click="settle(false)" x-text="options.cancelLabel ?? $nq.t(&#039;Cancel&#039;, &#039;إلغاء&#039;)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Cancel</button>
                    <button data-slot="button"
     type="button"                         x-show="options.danger !== false" x-on:click="settle(true)" x-text="options.confirmLabel ?? $nq.t(&#039;Confirm&#039;, &#039;تأكيد&#039;)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-destructive text-destructive-foreground hover:bg-[color-mix(in_oklab,var(--nq-danger-solid)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        Confirm</button>
                    <button data-slot="button"
     type="button"                         x-show="options.danger === false" x-on:click="settle(true)" x-text="options.confirmLabel ?? $nq.t(&#039;Confirm&#039;, &#039;تأكيد&#039;)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        Confirm</button></div>
            </div>
        </div>
    </template>
</div>
<button data-slot="button"
     type="button"                         x-data="" x-on:click="if (await $confirm({ title: &#039;Delete Billing?&#039;, description: &#039;Its tasks and files are deleted too. This cannot be undone.&#039;, confirmLabel: &#039;Delete&#039; })) $dispatch(&#039;deleted&#039;, { id: &#039;p1&#039; })" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-destructive text-destructive-foreground hover:bg-[color-mix(in_oklab,var(--nq-danger-solid)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        Delete</button>
```
