SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
2.6 KB · 76 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogBody } from "@/components/ui/dialog";4import { Button } from "@/components/ui/button";5import { Input } from "@/components/ui/input";67export interface ConfirmDialogProps {8  open: boolean;9  onOpenChange: (open: boolean) => void;10  title: React.ReactNode;11  description?: React.ReactNode;12  confirmLabel?: string;13  cancelLabel?: string;14  destructive?: boolean;15  /** When set, the user must type this exact string to enable the confirm button. */16  typeToConfirm?: string;17  onConfirm: () => Promise<void> | void;18  children?: React.ReactNode;19}2021export function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel = "Confirm", cancelLabel = "Cancel", destructive, typeToConfirm, onConfirm, children }: ConfirmDialogProps) {22  const [busy, setBusy] = React.useState(false);23  const [typed, setTyped] = React.useState("");24  const canConfirm = !typeToConfirm || typed.trim() === typeToConfirm;2526  const handleOpenChange = (v: boolean) => {27    if (!v) setTyped("");28    onOpenChange(v);29  };3031  return (32    <Dialog open={open} onOpenChange={handleOpenChange}>33      <DialogContent size="sm">34        <DialogHeader>35          <DialogTitle>{title}</DialogTitle>36          {description ? <DialogDescription>{description}</DialogDescription> : null}37        </DialogHeader>38        {children || typeToConfirm ? (39          <DialogBody className="space-y-3">40            {children}41            {typeToConfirm ? (42              <div className="space-y-1.5">43                <p className="text-xs text-fg-muted">44                  Type <span className="font-mono font-medium text-fg">{typeToConfirm}</span> to confirm.45                </p>46                <Input value={typed} onChange={(e) => setTyped(e.target.value)} autoComplete="off" spellCheck={false} aria-label="Confirmation text" />47              </div>48            ) : null}49          </DialogBody>50        ) : null}51        <DialogFooter>52          <Button variant="ghost" onClick={() => handleOpenChange(false)} disabled={busy}>53            {cancelLabel}54          </Button>55          <Button56            variant={destructive ? "danger" : "primary"}57            loading={busy}58            disabled={!canConfirm}59            onClick={async () => {60              setBusy(true);61              try {62                await onConfirm();63                handleOpenChange(false);64              } finally {65                setBusy(false);66              }67            }}68          >69            {confirmLabel}70          </Button>71        </DialogFooter>72      </DialogContent>73    </Dialog>74  );75}76