1import { useSyncExternalStore } from "react";
2
3import {
4 completeConfirmDialogClose,
5 readConfirmDialogState,
6 respondToConfirmDialog,
7 subscribeConfirmDialogHost,
8} from "../confirmDialog";
9import {
10 AlertDialog,
11 AlertDialogClose,
12 AlertDialogAction,
13 AlertDialogDescription,
14 AlertDialogFooter,
15 AlertDialogHeader,
16 AlertDialogPopup,
17 AlertDialogTitle,
18} from "./ui/alert-dialog";
19import { Button } from "./ui/button";
20
21type ConfirmationCopy = {
22 readonly title: string;
23 readonly description: string | null;
24};
25
26function resolveConfirmDialogCopy(message: string): ConfirmationCopy {
27 const normalizedMessage = message.trim();
28 const lines = normalizedMessage.split("\n");
29 const questionLineIndex = lines.findIndex((line) => line.trim().endsWith("?"));
30
31 if (questionLineIndex >= 0) {
32 const title = lines[questionLineIndex]!.trim();
33 const description = lines
34 .filter((_, index) => index !== questionLineIndex)
35 .join("\n")
36 .trim();
37 return { title, description: description || null };
38 }
39
40 const questionMarkIndex = normalizedMessage.indexOf("?");
41 if (questionMarkIndex >= 0) {
42 return {
43 title: normalizedMessage.slice(0, questionMarkIndex + 1).trim(),
44 description: normalizedMessage.slice(questionMarkIndex + 1).trim() || null,
45 };
46 }
47
48 return {
49 title: "Confirm action",
50 description: normalizedMessage || "This action requires your confirmation.",
51 };
52}
53
54export function ConfirmDialogHost() {
55 const state = useSyncExternalStore(
56 subscribeConfirmDialogHost,
57 readConfirmDialogState,
58 readConfirmDialogState,
59 );
60
61 const copy = resolveConfirmDialogCopy(state.status === "idle" ? "" : state.message);
62 const confirmVariant = state.status === "idle" ? "default" : state.variant;
63 const focusConfirm = state.status !== "idle" && state.initialFocus === "confirm";
64 const onCancel = () => respondToConfirmDialog(false);
65 const onConfirm = () => respondToConfirmDialog(true);
66
67 return (
68 <AlertDialog
69 open={state.status === "confirming"}
70 onOpenChange={(open) => {
71 if (!open) onCancel();
72 }}
73 onOpenChangeComplete={(open) => {
74 if (!open) completeConfirmDialogClose();
75 }}
76 >
77 <AlertDialogPopup>
78 <AlertDialogHeader>
79 <AlertDialogTitle>{copy.title}</AlertDialogTitle>
80 {copy.description ? (
81 <AlertDialogDescription className="whitespace-pre-line">
82 {copy.description}
83 </AlertDialogDescription>
84 ) : null}
85 </AlertDialogHeader>
86 <AlertDialogFooter>
87 <AlertDialogClose render={<Button variant="outline" />}>Cancel</AlertDialogClose>
88 {focusConfirm && confirmVariant === "default" ? (
89 <AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
90 ) : (
91 <Button variant={confirmVariant} onClick={onConfirm}>
92 Confirm
93 </Button>
94 )}
95 </AlertDialogFooter>
96 </AlertDialogPopup>
97 </AlertDialog>
98 );
99}