Primitives
Confirm dialog
center, split, banner, sheet; danger, warning, primary; type a word or press and hold for the irreversible ones. Focus starts on Cancel.
confirm-dialog.tsx
"use client";
import { Button, ConfirmDialog, type ConfirmTone, type ConfirmVariant, toast } from "@waveinno/ui";
import { Ban, Banknote, Rocket, Trash2 } from "lucide-react";
import * as React from "react";
const CONFIRMS: {
key: string;
label: string;
variant: ConfirmVariant;
tone: ConfirmTone;
title: string;
description: string;
confirmLabel: string;
items?: string[];
confirmWord?: string;
hold?: boolean | number;
icon?: React.ReactNode;
done: string;
}[] = [
{
key: "center",
label: "center · type to confirm",
variant: "center",
tone: "danger",
title: "Delete 12 sessions?",
description: "Their receipts are cancelled and drivers are not charged. This cannot be undone.",
confirmLabel: "Delete 12 sessions",
items: ["DHA-GA 12-3456 · Gate 1 · 08:42", "DHA-KHA 45-1020 · Gate 2 · 09:05", "DHA-THA 11-7781 · Gate 1 · 09:31", "DHA-GA 90-1144", "DHA-KHA 23-0091"],
confirmWord: "DELETE",
done: "12 sessions deleted",
},
{
key: "split",
label: "split · warning",
variant: "split",
tone: "warning",
title: "Suspend Gate 2?",
description: "Cars cannot leave through it until it is resumed. Attendants are told at once.",
confirmLabel: "Suspend gate",
icon: <Ban />,
done: "Gate 2 suspended",
},
{
key: "banner",
label: "banner · press and hold",
variant: "banner",
tone: "danger",
title: "Close Banani Tower for good?",
description: "The site, its 160 bays and its monthly passes are archived. Reports keep its history.",
confirmLabel: "Hold to close the site",
hold: true,
icon: <Trash2 />,
done: "Banani Tower closed",
},
{
key: "sheet",
label: "sheet · primary",
variant: "sheet",
tone: "primary",
title: "Send ৳1,24,500 to your bank?",
description: "It reaches the account ending 4410 by tomorrow, 11 am.",
confirmLabel: "Send payout",
icon: <Banknote />,
done: "Payout sent",
},
{
key: "publish",
label: "center · primary",
variant: "center",
tone: "primary",
title: "Publish the new rates?",
description: "Drivers see them from 1 November. Monthly passes keep their price.",
confirmLabel: "Publish rates",
icon: <Rocket />,
done: "Rates published",
},
{
key: "split-hold",
label: "split · hold, danger",
variant: "split",
tone: "danger",
title: "Void every open session?",
description: "128 cars inside lose their tickets; attendants must issue new ones.",
confirmLabel: "Hold to void all",
hold: 1600,
done: "128 sessions voided",
},
];
export default function ConfirmDemo() {
const [open, setOpen] = React.useState<string | null>(null);
return (
<div className="grid gap-2.5 sm:grid-cols-2 lg:grid-cols-3">
{CONFIRMS.map((c) => (
<React.Fragment key={c.key}>
<Button variant="secondary" onClick={() => setOpen(c.key)} className="justify-start">
<span className="size-2 rounded-full" style={{ background: c.tone === "danger" ? "var(--danger)" : c.tone === "warning" ? "var(--warning)" : "var(--primary)" }} />
{c.label}
</Button>
<ConfirmDialog
open={open === c.key}
onOpenChange={(o) => setOpen(o ? c.key : null)}
variant={c.variant}
tone={c.tone}
title={c.title}
description={c.description}
confirmLabel={c.confirmLabel}
items={c.items}
confirmWord={c.confirmWord}
hold={c.hold}
icon={c.icon}
onConfirm={async () => {
await new Promise((r) => setTimeout(r, 600));
toast.success(c.done);
}}
/>
</React.Fragment>
))}
</div>
);
}Import#
Everything comes from the package root. The example above uses:
your-component.tsx
import { Button, ConfirmDialog, type ConfirmTone, type ConfirmVariant, toast } from "@waveinno/ui";Props#
Read from the source of this version, so they match the code exactly.
<ConfirmDialog>
src/components/ui/confirm-dialog.tsx| Prop | Type | Default | Description |
|---|---|---|---|
openrequired | boolean | — | |
onOpenChangerequired | (open: boolean) => void | — | |
titlerequired | React.ReactNode | — | |
description | React.ReactNode | — | |
items | string[] | — | What is affected: names shown in a list (first `previewCount`, then "+ n more"). |
previewCount | number | 3 | |
confirmWord | string | — | Require typing this word (e.g. "DELETE") before the button unlocks. |
hold | boolean | number | — | Press and hold the button to confirm; a number sets the hold in ms (default 1200). |
confirmLabelrequired | React.ReactNode | — | |
cancelLabel | React.ReactNode | "Cancel" | |
tone | "danger" | "warning" | "primary" | "danger" | |
variant | "center" | "split" | "banner" | "sheet" | "center" | |
icon | React.ReactNode | — | |
onConfirmrequired | () => void | Promise<void> | — | Runs on confirm; the button spins until it settles, the dialog closes on success. |
labels | { typeToConfirm?: (word: string) => string; more?: (n: number) => string; hold?: string } | — |
<Button>
src/components/ui/button.tsx| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | |
loading | boolean | — |
Also takes ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants>.
Install#
If the project does not have the package yet, add it, then follow the setup for Next.js or Vite once.
$ npm install @waveinno/ui