@nobertdev/react-confirm-dialog 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,226 @@
1
+ # @nobertdev/react-confirm-dialog
2
+
3
+ A lightweight, fully customizable confirmation dialog hook — `useConfirm()` — that replaces `window.confirm()` with beautiful async modals.
4
+
5
+ **Zero runtime dependencies. ~2KB gzipped. Full TypeScript support.**
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - ✅ Drop-in async replacement for `window.confirm()`
12
+ - 🎨 Four built-in variants: `default`, `danger`, `warning`, `success`
13
+ - 🧩 Fully customizable via props, classNames, styles, or `renderDialog`
14
+ - ⌨️ Keyboard accessible (Escape to cancel, auto-focus confirm)
15
+ - 🖱️ Click outside to dismiss
16
+ - 💨 Smooth enter/exit animations
17
+ - 🔒 Zero runtime dependencies
18
+ - 📦 ~2KB gzipped
19
+ - 🏗️ Tree-shakeable ESM + CJS builds
20
+ - 🔷 Full TypeScript types
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install @nobertdev/react-confirm-dialog
28
+ # or
29
+ yarn add @nobertdev/react-confirm-dialog
30
+ # or
31
+ pnpm add @nobertdev/react-confirm-dialog
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Quick Start
37
+
38
+ **1. Wrap your app with the provider:**
39
+
40
+ ```tsx
41
+ // main.tsx / _app.tsx
42
+ import { ConfirmDialogProvider } from "@nobertdev/react-confirm-dialog";
43
+
44
+ function App() {
45
+ return (
46
+ <ConfirmDialogProvider>
47
+ <YourApp />
48
+ </ConfirmDialogProvider>
49
+ );
50
+ }
51
+ ```
52
+
53
+ **2. Use `useConfirm()` anywhere in your tree:**
54
+
55
+ ```tsx
56
+ import { useConfirm } from "@nobertdev/react-confirm-dialog";
57
+
58
+ function DeleteButton() {
59
+ const confirm = useConfirm();
60
+
61
+ const handleDelete = async () => {
62
+ const ok = await confirm({
63
+ title: "Delete this item?",
64
+ message: "This action cannot be undone.",
65
+ variant: "danger",
66
+ confirmText: "Delete",
67
+ });
68
+
69
+ if (ok) {
70
+ // user clicked Confirm
71
+ await deleteItem();
72
+ }
73
+ };
74
+
75
+ return <button onClick={handleDelete}>Delete</button>;
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## API
82
+
83
+ ### `<ConfirmDialogProvider>`
84
+
85
+ | Prop | Type | Description |
86
+ | ---------------- | ----------------------------------------- | --------------------------------------- |
87
+ | `defaultOptions` | `ConfirmOptions` | Default options merged into every call |
88
+ | `classNames` | `object` | Custom CSS class names for each element |
89
+ | `styles` | `object` | Inline style overrides for each element |
90
+ | `renderDialog` | `(props: RenderDialogProps) => ReactNode` | Fully replace the built-in dialog |
91
+
92
+ ### `useConfirm()`
93
+
94
+ Returns a `confirm` function:
95
+
96
+ ```ts
97
+ const confirm = useConfirm();
98
+
99
+ // Simple string message
100
+ const ok = await confirm("Are you sure?");
101
+
102
+ // Full options object
103
+ const ok = await confirm({
104
+ title: "Permanently delete?",
105
+ message: "This cannot be undone.",
106
+ confirmText: "Yes, delete it",
107
+ cancelText: "Never mind",
108
+ variant: "danger", // "default" | "danger" | "warning" | "success"
109
+ icon: <MyIcon />, // custom React node
110
+ });
111
+ ```
112
+
113
+ Returns `Promise<boolean>` — `true` if confirmed, `false` if cancelled.
114
+
115
+ ### `ConfirmOptions`
116
+
117
+ | Prop | Type | Default | Description |
118
+ | ------------- | ------------------------------------------------- | ----------------- | -------------------- |
119
+ | `title` | `string` | `"Are you sure?"` | Dialog heading |
120
+ | `message` | `string` | — | Body text |
121
+ | `confirmText` | `string` | `"Confirm"` | Confirm button label |
122
+ | `cancelText` | `string` | `"Cancel"` | Cancel button label |
123
+ | `variant` | `"default" \| "danger" \| "warning" \| "success"` | `"default"` | Visual variant |
124
+ | `icon` | `ReactNode` | built-in SVG | Custom icon |
125
+
126
+ ---
127
+
128
+ ## Variants
129
+
130
+ ```tsx
131
+ // Default (dark)
132
+ await confirm({ title: "Proceed?" });
133
+
134
+ // Danger (red) — for destructive actions
135
+ await confirm({ title: "Delete account?", variant: "danger" });
136
+
137
+ // Warning (amber) — for cautionary actions
138
+ await confirm({ title: "Archive project?", variant: "warning" });
139
+
140
+ // Success (green) — for confirmatory actions
141
+ await confirm({ title: "Publish post?", variant: "success" });
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Styling
147
+
148
+ ### Via `classNames`
149
+
150
+ ```tsx
151
+ <ConfirmDialogProvider
152
+ classNames={{
153
+ overlay: "my-overlay",
154
+ dialog: "my-dialog",
155
+ title: "my-title",
156
+ confirmButton: "my-confirm-btn",
157
+ cancelButton: "my-cancel-btn",
158
+ }}
159
+ >
160
+ ```
161
+
162
+ ### Via `styles`
163
+
164
+ ```tsx
165
+ <ConfirmDialogProvider
166
+ styles={{
167
+ dialog: { borderRadius: "4px", fontFamily: "monospace" },
168
+ confirmButton: { background: "hotpink", borderRadius: "2px" },
169
+ }}
170
+ >
171
+ ```
172
+
173
+ ### Via `renderDialog` (fully custom)
174
+
175
+ ```tsx
176
+ <ConfirmDialogProvider
177
+ renderDialog={({ isOpen, options, onConfirm, onCancel }) => (
178
+ <MyCustomModal
179
+ open={isOpen}
180
+ title={options.title}
181
+ onConfirm={onConfirm}
182
+ onCancel={onCancel}
183
+ />
184
+ )}
185
+ >
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Global Defaults
191
+
192
+ ```tsx
193
+ <ConfirmDialogProvider
194
+ defaultOptions={{
195
+ confirmText: "Yes",
196
+ cancelText: "No",
197
+ variant: "danger",
198
+ }}
199
+ >
200
+ ```
201
+
202
+ ---
203
+
204
+ ## SSR / Next.js
205
+
206
+ Works with SSR out of the box. The portal is only created on the client.
207
+
208
+ ---
209
+
210
+ ## TypeScript
211
+
212
+ All types are exported:
213
+
214
+ ```ts
215
+ import type {
216
+ ConfirmOptions,
217
+ ConfirmDialogProviderProps,
218
+ RenderDialogProps,
219
+ } from "@nobertdev/react-confirm-dialog";
220
+ ```
221
+
222
+ ---
223
+
224
+ ## License
225
+
226
+ MIT
@@ -0,0 +1,64 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface ConfirmOptions {
4
+ title?: string;
5
+ message?: string;
6
+ confirmText?: string;
7
+ cancelText?: string;
8
+ variant?: "default" | "danger" | "warning" | "success";
9
+ icon?: React.ReactNode;
10
+ description?: string;
11
+ }
12
+ interface ConfirmDialogProviderProps {
13
+ children: React.ReactNode;
14
+ defaultOptions?: ConfirmOptions;
15
+ classNames?: {
16
+ overlay?: string;
17
+ dialog?: string;
18
+ title?: string;
19
+ message?: string;
20
+ actions?: string;
21
+ confirmButton?: string;
22
+ cancelButton?: string;
23
+ };
24
+ styles?: {
25
+ overlay?: React.CSSProperties;
26
+ dialog?: React.CSSProperties;
27
+ title?: React.CSSProperties;
28
+ message?: React.CSSProperties;
29
+ actions?: React.CSSProperties;
30
+ confirmButton?: React.CSSProperties;
31
+ cancelButton?: React.CSSProperties;
32
+ };
33
+ renderDialog?: (props: RenderDialogProps) => React.ReactNode;
34
+ }
35
+ interface RenderDialogProps {
36
+ isOpen: boolean;
37
+ options: ConfirmOptions;
38
+ onConfirm: () => void;
39
+ onCancel: () => void;
40
+ }
41
+
42
+ /**
43
+ * ConfirmDialogProvider
44
+ *
45
+ * Wrap your app (or a subtree) with this provider to enable useConfirm().
46
+ *
47
+ * @example
48
+ * <ConfirmDialogProvider>
49
+ * <App />
50
+ * </ConfirmDialogProvider>
51
+ */
52
+ declare function ConfirmDialogProvider({ children, defaultOptions, classNames, styles, renderDialog, }: ConfirmDialogProviderProps): react_jsx_runtime.JSX.Element;
53
+
54
+ /**
55
+ * useConfirm — drop-in async replacement for window.confirm()
56
+ *
57
+ * @example
58
+ * const confirm = useConfirm();
59
+ * const ok = await confirm("Are you sure?");
60
+ * const ok = await confirm({ title: "Delete?", variant: "danger" });
61
+ */
62
+ declare function useConfirm(): (options?: ConfirmOptions | string) => Promise<boolean>;
63
+
64
+ export { ConfirmDialogProvider, type ConfirmDialogProviderProps, type ConfirmOptions, type RenderDialogProps, useConfirm };
@@ -0,0 +1,64 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface ConfirmOptions {
4
+ title?: string;
5
+ message?: string;
6
+ confirmText?: string;
7
+ cancelText?: string;
8
+ variant?: "default" | "danger" | "warning" | "success";
9
+ icon?: React.ReactNode;
10
+ description?: string;
11
+ }
12
+ interface ConfirmDialogProviderProps {
13
+ children: React.ReactNode;
14
+ defaultOptions?: ConfirmOptions;
15
+ classNames?: {
16
+ overlay?: string;
17
+ dialog?: string;
18
+ title?: string;
19
+ message?: string;
20
+ actions?: string;
21
+ confirmButton?: string;
22
+ cancelButton?: string;
23
+ };
24
+ styles?: {
25
+ overlay?: React.CSSProperties;
26
+ dialog?: React.CSSProperties;
27
+ title?: React.CSSProperties;
28
+ message?: React.CSSProperties;
29
+ actions?: React.CSSProperties;
30
+ confirmButton?: React.CSSProperties;
31
+ cancelButton?: React.CSSProperties;
32
+ };
33
+ renderDialog?: (props: RenderDialogProps) => React.ReactNode;
34
+ }
35
+ interface RenderDialogProps {
36
+ isOpen: boolean;
37
+ options: ConfirmOptions;
38
+ onConfirm: () => void;
39
+ onCancel: () => void;
40
+ }
41
+
42
+ /**
43
+ * ConfirmDialogProvider
44
+ *
45
+ * Wrap your app (or a subtree) with this provider to enable useConfirm().
46
+ *
47
+ * @example
48
+ * <ConfirmDialogProvider>
49
+ * <App />
50
+ * </ConfirmDialogProvider>
51
+ */
52
+ declare function ConfirmDialogProvider({ children, defaultOptions, classNames, styles, renderDialog, }: ConfirmDialogProviderProps): react_jsx_runtime.JSX.Element;
53
+
54
+ /**
55
+ * useConfirm — drop-in async replacement for window.confirm()
56
+ *
57
+ * @example
58
+ * const confirm = useConfirm();
59
+ * const ok = await confirm("Are you sure?");
60
+ * const ok = await confirm({ title: "Delete?", variant: "danger" });
61
+ */
62
+ declare function useConfirm(): (options?: ConfirmOptions | string) => Promise<boolean>;
63
+
64
+ export { ConfirmDialogProvider, type ConfirmDialogProviderProps, type ConfirmOptions, type RenderDialogProps, useConfirm };
package/dist/index.js ADDED
@@ -0,0 +1,449 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+ var __export = (target, all) => {
24
+ for (var name in all)
25
+ __defProp(target, name, { get: all[name], enumerable: true });
26
+ };
27
+ var __copyProps = (to, from, except, desc) => {
28
+ if (from && typeof from === "object" || typeof from === "function") {
29
+ for (let key of __getOwnPropNames(from))
30
+ if (!__hasOwnProp.call(to, key) && key !== except)
31
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
32
+ }
33
+ return to;
34
+ };
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
+
37
+ // src/index.ts
38
+ var index_exports = {};
39
+ __export(index_exports, {
40
+ ConfirmDialogProvider: () => ConfirmDialogProvider,
41
+ useConfirm: () => useConfirm
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+
45
+ // src/components/ConfirmDialogProvider.tsx
46
+ var import_react3 = require("react");
47
+ var import_react_dom = require("react-dom");
48
+
49
+ // src/components/BuildinDialog.tsx
50
+ var import_react = require("react");
51
+ var import_jsx_runtime = require("react/jsx-runtime");
52
+ var VARIANT_STYLES = {
53
+ default: {
54
+ confirmBg: "#111",
55
+ confirmColor: "#fff",
56
+ confirmHover: "#333",
57
+ accentColor: "#111",
58
+ iconBg: "#f4f4f4"
59
+ },
60
+ danger: {
61
+ confirmBg: "#dc2626",
62
+ confirmColor: "#fff",
63
+ confirmHover: "#b91c1c",
64
+ accentColor: "#dc2626",
65
+ iconBg: "#fef2f2"
66
+ },
67
+ warning: {
68
+ confirmBg: "#d97706",
69
+ confirmColor: "#fff",
70
+ confirmHover: "#b45309",
71
+ accentColor: "#d97706",
72
+ iconBg: "#fffbeb"
73
+ },
74
+ success: {
75
+ confirmBg: "#16a34a",
76
+ confirmColor: "#fff",
77
+ confirmHover: "#15803d",
78
+ accentColor: "#16a34a",
79
+ iconBg: "#f0fdf4"
80
+ }
81
+ };
82
+ var DEFAULT_ICONS = {
83
+ default: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
84
+ "svg",
85
+ {
86
+ width: "22",
87
+ height: "22",
88
+ viewBox: "0 0 24 24",
89
+ fill: "none",
90
+ stroke: "currentColor",
91
+ strokeWidth: "2",
92
+ strokeLinecap: "round",
93
+ strokeLinejoin: "round",
94
+ children: [
95
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
96
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
97
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
98
+ ]
99
+ }
100
+ ),
101
+ danger: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
102
+ "svg",
103
+ {
104
+ width: "22",
105
+ height: "22",
106
+ viewBox: "0 0 24 24",
107
+ fill: "none",
108
+ stroke: "currentColor",
109
+ strokeWidth: "2",
110
+ strokeLinecap: "round",
111
+ strokeLinejoin: "round",
112
+ children: [
113
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2" }),
114
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
116
+ ]
117
+ }
118
+ ),
119
+ warning: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
120
+ "svg",
121
+ {
122
+ width: "22",
123
+ height: "22",
124
+ viewBox: "0 0 24 24",
125
+ fill: "none",
126
+ stroke: "currentColor",
127
+ strokeWidth: "2",
128
+ strokeLinecap: "round",
129
+ strokeLinejoin: "round",
130
+ children: [
131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
134
+ ]
135
+ }
136
+ ),
137
+ success: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
138
+ "svg",
139
+ {
140
+ width: "22",
141
+ height: "22",
142
+ viewBox: "0 0 24 24",
143
+ fill: "none",
144
+ stroke: "currentColor",
145
+ strokeWidth: "2",
146
+ strokeLinecap: "round",
147
+ strokeLinejoin: "round",
148
+ children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "22 4 12 14.01 9 11.01" })
151
+ ]
152
+ }
153
+ )
154
+ };
155
+ var KEYFRAMES = `
156
+ @keyframes rcd-overlay-in {
157
+ from { opacity: 0; }
158
+ to { opacity: 1; }
159
+ }
160
+ @keyframes rcd-dialog-in {
161
+ from { opacity: 0; transform: scale(0.94) translateY(8px); }
162
+ to { opacity: 1; transform: scale(1) translateY(0); }
163
+ }
164
+ @keyframes rcd-overlay-out {
165
+ from { opacity: 1; }
166
+ to { opacity: 0; }
167
+ }
168
+ @keyframes rcd-dialog-out {
169
+ from { opacity: 1; transform: scale(1) translateY(0); }
170
+ to { opacity: 0; transform: scale(0.94) translateY(8px); }
171
+ }
172
+ `;
173
+ function BuiltInDialog({
174
+ state,
175
+ providerProps,
176
+ onConfirm,
177
+ onCancel,
178
+ isClosing
179
+ }) {
180
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s;
181
+ const confirmRef = (0, import_react.useRef)(null);
182
+ const variant = (_a = state.variant) != null ? _a : "default";
183
+ const v = VARIANT_STYLES[variant];
184
+ (0, import_react.useEffect)(() => {
185
+ if (state.isOpen) {
186
+ setTimeout(() => {
187
+ var _a2;
188
+ return (_a2 = confirmRef.current) == null ? void 0 : _a2.focus();
189
+ }, 50);
190
+ }
191
+ }, [state.isOpen]);
192
+ (0, import_react.useEffect)(() => {
193
+ const handleKey = (e) => {
194
+ if (!state.isOpen) return;
195
+ if (e.key === "Escape") onCancel();
196
+ };
197
+ window.addEventListener("keydown", handleKey);
198
+ return () => window.removeEventListener("keydown", handleKey);
199
+ }, [state.isOpen, onCancel]);
200
+ if (!state.isOpen && !isClosing) return null;
201
+ const animationState = isClosing ? "out" : "in";
202
+ const overlayStyle = __spreadValues({
203
+ position: "fixed",
204
+ inset: 0,
205
+ background: "rgba(0,0,0,0.45)",
206
+ backdropFilter: "blur(4px)",
207
+ WebkitBackdropFilter: "blur(4px)",
208
+ display: "flex",
209
+ alignItems: "center",
210
+ justifyContent: "center",
211
+ zIndex: 9999,
212
+ padding: "16px",
213
+ animation: `rcd-overlay-${animationState} 200ms ease forwards`
214
+ }, (_b = providerProps.styles) == null ? void 0 : _b.overlay);
215
+ const dialogStyle = __spreadValues({
216
+ background: "#fff",
217
+ borderRadius: "16px",
218
+ padding: "28px",
219
+ maxWidth: "420px",
220
+ width: "100%",
221
+ boxShadow: "0 24px 64px rgba(0,0,0,0.18), 0 4px 12px rgba(0,0,0,0.08)",
222
+ position: "relative",
223
+ animation: `rcd-dialog-${animationState} 220ms cubic-bezier(0.34,1.56,0.64,1) forwards`
224
+ }, (_c = providerProps.styles) == null ? void 0 : _c.dialog);
225
+ const iconContainerStyle = {
226
+ width: 48,
227
+ height: 48,
228
+ borderRadius: "12px",
229
+ background: v.iconBg,
230
+ color: v.accentColor,
231
+ display: "flex",
232
+ alignItems: "center",
233
+ justifyContent: "center",
234
+ marginBottom: "16px",
235
+ flexShrink: 0
236
+ };
237
+ const titleStyle = __spreadValues({
238
+ margin: "0 0 6px 0",
239
+ fontSize: "17px",
240
+ fontWeight: 700,
241
+ color: "#0f0f0f",
242
+ lineHeight: 1.3,
243
+ letterSpacing: "-0.02em",
244
+ fontFamily: "inherit"
245
+ }, (_d = providerProps.styles) == null ? void 0 : _d.title);
246
+ const messageStyle = __spreadValues({
247
+ margin: "0 0 24px 0",
248
+ fontSize: "14px",
249
+ color: "#6b7280",
250
+ lineHeight: 1.6,
251
+ fontFamily: "inherit"
252
+ }, (_e = providerProps.styles) == null ? void 0 : _e.message);
253
+ const actionsStyle = __spreadValues({
254
+ display: "flex",
255
+ gap: "10px",
256
+ justifyContent: "flex-end"
257
+ }, (_f = providerProps.styles) == null ? void 0 : _f.actions);
258
+ const cancelStyle = __spreadValues({
259
+ padding: "9px 18px",
260
+ borderRadius: "9px",
261
+ border: "1.5px solid #e5e7eb",
262
+ background: "#fff",
263
+ color: "#374151",
264
+ fontSize: "14px",
265
+ fontWeight: 500,
266
+ cursor: "pointer",
267
+ fontFamily: "inherit",
268
+ transition: "all 120ms ease"
269
+ }, (_g = providerProps.styles) == null ? void 0 : _g.cancelButton);
270
+ const confirmStyle = __spreadValues({
271
+ padding: "9px 18px",
272
+ borderRadius: "9px",
273
+ border: "none",
274
+ background: v.confirmBg,
275
+ color: v.confirmColor,
276
+ fontSize: "14px",
277
+ fontWeight: 600,
278
+ cursor: "pointer",
279
+ fontFamily: "inherit",
280
+ transition: "all 120ms ease"
281
+ }, (_h = providerProps.styles) == null ? void 0 : _h.confirmButton);
282
+ const icon = (_i = state.icon) != null ? _i : DEFAULT_ICONS[variant];
283
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
284
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: KEYFRAMES }),
285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
286
+ "div",
287
+ {
288
+ style: overlayStyle,
289
+ className: (_j = providerProps.classNames) == null ? void 0 : _j.overlay,
290
+ role: "dialog",
291
+ "aria-modal": "true",
292
+ "aria-labelledby": "rcd-title",
293
+ "aria-describedby": state.message ? "rcd-message" : void 0,
294
+ onClick: (e) => e.target === e.currentTarget && onCancel(),
295
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: dialogStyle, className: (_k = providerProps.classNames) == null ? void 0 : _k.dialog, children: [
296
+ icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: iconContainerStyle, children: icon }),
297
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
298
+ "h2",
299
+ {
300
+ id: "rcd-title",
301
+ style: titleStyle,
302
+ className: (_l = providerProps.classNames) == null ? void 0 : _l.title,
303
+ children: (_m = state.title) != null ? _m : "Are you sure?"
304
+ }
305
+ ),
306
+ state.message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
307
+ "p",
308
+ {
309
+ id: "rcd-message",
310
+ style: messageStyle,
311
+ className: (_n = providerProps.classNames) == null ? void 0 : _n.message,
312
+ children: state.message
313
+ }
314
+ ),
315
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
316
+ "div",
317
+ {
318
+ style: actionsStyle,
319
+ className: (_o = providerProps.classNames) == null ? void 0 : _o.actions,
320
+ children: [
321
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
322
+ "button",
323
+ {
324
+ style: cancelStyle,
325
+ className: (_p = providerProps.classNames) == null ? void 0 : _p.cancelButton,
326
+ onClick: onCancel,
327
+ onMouseEnter: (e) => {
328
+ e.target.style.background = "#f9fafb";
329
+ },
330
+ onMouseLeave: (e) => {
331
+ var _a2, _b2, _c2, _d2;
332
+ e.target.style.background = (_d2 = (_c2 = (_b2 = (_a2 = providerProps.styles) == null ? void 0 : _a2.cancelButton) == null ? void 0 : _b2.background) == null ? void 0 : _c2.toString()) != null ? _d2 : "#fff";
333
+ },
334
+ children: (_q = state.cancelText) != null ? _q : "Cancel"
335
+ }
336
+ ),
337
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
338
+ "button",
339
+ {
340
+ ref: confirmRef,
341
+ style: confirmStyle,
342
+ className: (_r = providerProps.classNames) == null ? void 0 : _r.confirmButton,
343
+ onClick: onConfirm,
344
+ onMouseEnter: (e) => {
345
+ e.target.style.background = v.confirmHover;
346
+ },
347
+ onMouseLeave: (e) => {
348
+ var _a2, _b2, _c2, _d2;
349
+ e.target.style.background = (_d2 = (_c2 = (_b2 = (_a2 = providerProps.styles) == null ? void 0 : _a2.confirmButton) == null ? void 0 : _b2.background) == null ? void 0 : _c2.toString()) != null ? _d2 : v.confirmBg;
350
+ },
351
+ children: (_s = state.confirmText) != null ? _s : "Confirm"
352
+ }
353
+ )
354
+ ]
355
+ }
356
+ )
357
+ ] })
358
+ }
359
+ )
360
+ ] });
361
+ }
362
+
363
+ // src/hooks/useConfirm.ts
364
+ var import_react2 = require("react");
365
+ var ConfirmDialogContext = (0, import_react2.createContext)(null);
366
+ function useConfirm() {
367
+ const ctx = (0, import_react2.useContext)(ConfirmDialogContext);
368
+ if (!ctx) {
369
+ throw new Error(
370
+ "[@nobertdev/react-confirm-dialog] useConfirm() must be used inside <ConfirmDialogProvider>."
371
+ );
372
+ }
373
+ return ctx.confirm;
374
+ }
375
+
376
+ // src/components/ConfirmDialogProvider.tsx
377
+ var import_jsx_runtime2 = require("react/jsx-runtime");
378
+ var CLOSE_ANIMATION_MS = 180;
379
+ var DEFAULT_STATE = {
380
+ isOpen: false,
381
+ resolve: null,
382
+ title: void 0,
383
+ message: void 0,
384
+ confirmText: void 0,
385
+ cancelText: void 0,
386
+ variant: "default",
387
+ icon: void 0
388
+ };
389
+ function ConfirmDialogProvider({
390
+ children,
391
+ defaultOptions,
392
+ classNames,
393
+ styles,
394
+ renderDialog
395
+ }) {
396
+ const [state, setState] = (0, import_react3.useState)(DEFAULT_STATE);
397
+ const [isClosing, setIsClosing] = (0, import_react3.useState)(false);
398
+ const resolveRef = (0, import_react3.useRef)(null);
399
+ const close = (0, import_react3.useCallback)((result) => {
400
+ setIsClosing(true);
401
+ setTimeout(() => {
402
+ var _a;
403
+ setIsClosing(false);
404
+ setState(DEFAULT_STATE);
405
+ (_a = resolveRef.current) == null ? void 0 : _a.call(resolveRef, result);
406
+ resolveRef.current = null;
407
+ }, CLOSE_ANIMATION_MS);
408
+ }, []);
409
+ const onConfirm = (0, import_react3.useCallback)(() => close(true), [close]);
410
+ const onCancel = (0, import_react3.useCallback)(() => close(false), [close]);
411
+ const confirm = (0, import_react3.useCallback)(
412
+ (options) => {
413
+ return new Promise((resolve) => {
414
+ resolveRef.current = resolve;
415
+ const normalized = typeof options === "string" ? { message: options } : options != null ? options : {};
416
+ setState(__spreadProps(__spreadValues(__spreadValues(__spreadValues({}, DEFAULT_STATE), defaultOptions), normalized), {
417
+ isOpen: true,
418
+ resolve
419
+ }));
420
+ });
421
+ },
422
+ [defaultOptions]
423
+ );
424
+ const contextValue = (0, import_react3.useMemo)(() => ({ confirm }), [confirm]);
425
+ const dialogNode = renderDialog ? renderDialog({
426
+ isOpen: state.isOpen || isClosing,
427
+ options: state,
428
+ onConfirm,
429
+ onCancel
430
+ }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
431
+ BuiltInDialog,
432
+ {
433
+ state,
434
+ providerProps: { classNames, styles, defaultOptions },
435
+ onConfirm,
436
+ onCancel,
437
+ isClosing
438
+ }
439
+ );
440
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(ConfirmDialogContext.Provider, { value: contextValue, children: [
441
+ children,
442
+ typeof document !== "undefined" ? (0, import_react_dom.createPortal)(dialogNode, document.body) : null
443
+ ] });
444
+ }
445
+ // Annotate the CommonJS export names for ESM import in node:
446
+ 0 && (module.exports = {
447
+ ConfirmDialogProvider,
448
+ useConfirm
449
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,424 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+
21
+ // src/components/ConfirmDialogProvider.tsx
22
+ import { useState, useCallback, useRef as useRef2, useMemo } from "react";
23
+ import { createPortal } from "react-dom";
24
+
25
+ // src/components/BuildinDialog.tsx
26
+ import { useEffect, useRef } from "react";
27
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
28
+ var VARIANT_STYLES = {
29
+ default: {
30
+ confirmBg: "#111",
31
+ confirmColor: "#fff",
32
+ confirmHover: "#333",
33
+ accentColor: "#111",
34
+ iconBg: "#f4f4f4"
35
+ },
36
+ danger: {
37
+ confirmBg: "#dc2626",
38
+ confirmColor: "#fff",
39
+ confirmHover: "#b91c1c",
40
+ accentColor: "#dc2626",
41
+ iconBg: "#fef2f2"
42
+ },
43
+ warning: {
44
+ confirmBg: "#d97706",
45
+ confirmColor: "#fff",
46
+ confirmHover: "#b45309",
47
+ accentColor: "#d97706",
48
+ iconBg: "#fffbeb"
49
+ },
50
+ success: {
51
+ confirmBg: "#16a34a",
52
+ confirmColor: "#fff",
53
+ confirmHover: "#15803d",
54
+ accentColor: "#16a34a",
55
+ iconBg: "#f0fdf4"
56
+ }
57
+ };
58
+ var DEFAULT_ICONS = {
59
+ default: /* @__PURE__ */ jsxs(
60
+ "svg",
61
+ {
62
+ width: "22",
63
+ height: "22",
64
+ viewBox: "0 0 24 24",
65
+ fill: "none",
66
+ stroke: "currentColor",
67
+ strokeWidth: "2",
68
+ strokeLinecap: "round",
69
+ strokeLinejoin: "round",
70
+ children: [
71
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
72
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
73
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
74
+ ]
75
+ }
76
+ ),
77
+ danger: /* @__PURE__ */ jsxs(
78
+ "svg",
79
+ {
80
+ width: "22",
81
+ height: "22",
82
+ viewBox: "0 0 24 24",
83
+ fill: "none",
84
+ stroke: "currentColor",
85
+ strokeWidth: "2",
86
+ strokeLinecap: "round",
87
+ strokeLinejoin: "round",
88
+ children: [
89
+ /* @__PURE__ */ jsx("polygon", { points: "7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2" }),
90
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
91
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
92
+ ]
93
+ }
94
+ ),
95
+ warning: /* @__PURE__ */ jsxs(
96
+ "svg",
97
+ {
98
+ width: "22",
99
+ height: "22",
100
+ viewBox: "0 0 24 24",
101
+ fill: "none",
102
+ stroke: "currentColor",
103
+ strokeWidth: "2",
104
+ strokeLinecap: "round",
105
+ strokeLinejoin: "round",
106
+ children: [
107
+ /* @__PURE__ */ jsx("path", { d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
108
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
109
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
110
+ ]
111
+ }
112
+ ),
113
+ success: /* @__PURE__ */ jsxs(
114
+ "svg",
115
+ {
116
+ width: "22",
117
+ height: "22",
118
+ viewBox: "0 0 24 24",
119
+ fill: "none",
120
+ stroke: "currentColor",
121
+ strokeWidth: "2",
122
+ strokeLinecap: "round",
123
+ strokeLinejoin: "round",
124
+ children: [
125
+ /* @__PURE__ */ jsx("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
126
+ /* @__PURE__ */ jsx("polyline", { points: "22 4 12 14.01 9 11.01" })
127
+ ]
128
+ }
129
+ )
130
+ };
131
+ var KEYFRAMES = `
132
+ @keyframes rcd-overlay-in {
133
+ from { opacity: 0; }
134
+ to { opacity: 1; }
135
+ }
136
+ @keyframes rcd-dialog-in {
137
+ from { opacity: 0; transform: scale(0.94) translateY(8px); }
138
+ to { opacity: 1; transform: scale(1) translateY(0); }
139
+ }
140
+ @keyframes rcd-overlay-out {
141
+ from { opacity: 1; }
142
+ to { opacity: 0; }
143
+ }
144
+ @keyframes rcd-dialog-out {
145
+ from { opacity: 1; transform: scale(1) translateY(0); }
146
+ to { opacity: 0; transform: scale(0.94) translateY(8px); }
147
+ }
148
+ `;
149
+ function BuiltInDialog({
150
+ state,
151
+ providerProps,
152
+ onConfirm,
153
+ onCancel,
154
+ isClosing
155
+ }) {
156
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s;
157
+ const confirmRef = useRef(null);
158
+ const variant = (_a = state.variant) != null ? _a : "default";
159
+ const v = VARIANT_STYLES[variant];
160
+ useEffect(() => {
161
+ if (state.isOpen) {
162
+ setTimeout(() => {
163
+ var _a2;
164
+ return (_a2 = confirmRef.current) == null ? void 0 : _a2.focus();
165
+ }, 50);
166
+ }
167
+ }, [state.isOpen]);
168
+ useEffect(() => {
169
+ const handleKey = (e) => {
170
+ if (!state.isOpen) return;
171
+ if (e.key === "Escape") onCancel();
172
+ };
173
+ window.addEventListener("keydown", handleKey);
174
+ return () => window.removeEventListener("keydown", handleKey);
175
+ }, [state.isOpen, onCancel]);
176
+ if (!state.isOpen && !isClosing) return null;
177
+ const animationState = isClosing ? "out" : "in";
178
+ const overlayStyle = __spreadValues({
179
+ position: "fixed",
180
+ inset: 0,
181
+ background: "rgba(0,0,0,0.45)",
182
+ backdropFilter: "blur(4px)",
183
+ WebkitBackdropFilter: "blur(4px)",
184
+ display: "flex",
185
+ alignItems: "center",
186
+ justifyContent: "center",
187
+ zIndex: 9999,
188
+ padding: "16px",
189
+ animation: `rcd-overlay-${animationState} 200ms ease forwards`
190
+ }, (_b = providerProps.styles) == null ? void 0 : _b.overlay);
191
+ const dialogStyle = __spreadValues({
192
+ background: "#fff",
193
+ borderRadius: "16px",
194
+ padding: "28px",
195
+ maxWidth: "420px",
196
+ width: "100%",
197
+ boxShadow: "0 24px 64px rgba(0,0,0,0.18), 0 4px 12px rgba(0,0,0,0.08)",
198
+ position: "relative",
199
+ animation: `rcd-dialog-${animationState} 220ms cubic-bezier(0.34,1.56,0.64,1) forwards`
200
+ }, (_c = providerProps.styles) == null ? void 0 : _c.dialog);
201
+ const iconContainerStyle = {
202
+ width: 48,
203
+ height: 48,
204
+ borderRadius: "12px",
205
+ background: v.iconBg,
206
+ color: v.accentColor,
207
+ display: "flex",
208
+ alignItems: "center",
209
+ justifyContent: "center",
210
+ marginBottom: "16px",
211
+ flexShrink: 0
212
+ };
213
+ const titleStyle = __spreadValues({
214
+ margin: "0 0 6px 0",
215
+ fontSize: "17px",
216
+ fontWeight: 700,
217
+ color: "#0f0f0f",
218
+ lineHeight: 1.3,
219
+ letterSpacing: "-0.02em",
220
+ fontFamily: "inherit"
221
+ }, (_d = providerProps.styles) == null ? void 0 : _d.title);
222
+ const messageStyle = __spreadValues({
223
+ margin: "0 0 24px 0",
224
+ fontSize: "14px",
225
+ color: "#6b7280",
226
+ lineHeight: 1.6,
227
+ fontFamily: "inherit"
228
+ }, (_e = providerProps.styles) == null ? void 0 : _e.message);
229
+ const actionsStyle = __spreadValues({
230
+ display: "flex",
231
+ gap: "10px",
232
+ justifyContent: "flex-end"
233
+ }, (_f = providerProps.styles) == null ? void 0 : _f.actions);
234
+ const cancelStyle = __spreadValues({
235
+ padding: "9px 18px",
236
+ borderRadius: "9px",
237
+ border: "1.5px solid #e5e7eb",
238
+ background: "#fff",
239
+ color: "#374151",
240
+ fontSize: "14px",
241
+ fontWeight: 500,
242
+ cursor: "pointer",
243
+ fontFamily: "inherit",
244
+ transition: "all 120ms ease"
245
+ }, (_g = providerProps.styles) == null ? void 0 : _g.cancelButton);
246
+ const confirmStyle = __spreadValues({
247
+ padding: "9px 18px",
248
+ borderRadius: "9px",
249
+ border: "none",
250
+ background: v.confirmBg,
251
+ color: v.confirmColor,
252
+ fontSize: "14px",
253
+ fontWeight: 600,
254
+ cursor: "pointer",
255
+ fontFamily: "inherit",
256
+ transition: "all 120ms ease"
257
+ }, (_h = providerProps.styles) == null ? void 0 : _h.confirmButton);
258
+ const icon = (_i = state.icon) != null ? _i : DEFAULT_ICONS[variant];
259
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
260
+ /* @__PURE__ */ jsx("style", { children: KEYFRAMES }),
261
+ /* @__PURE__ */ jsx(
262
+ "div",
263
+ {
264
+ style: overlayStyle,
265
+ className: (_j = providerProps.classNames) == null ? void 0 : _j.overlay,
266
+ role: "dialog",
267
+ "aria-modal": "true",
268
+ "aria-labelledby": "rcd-title",
269
+ "aria-describedby": state.message ? "rcd-message" : void 0,
270
+ onClick: (e) => e.target === e.currentTarget && onCancel(),
271
+ children: /* @__PURE__ */ jsxs("div", { style: dialogStyle, className: (_k = providerProps.classNames) == null ? void 0 : _k.dialog, children: [
272
+ icon && /* @__PURE__ */ jsx("div", { style: iconContainerStyle, children: icon }),
273
+ /* @__PURE__ */ jsx(
274
+ "h2",
275
+ {
276
+ id: "rcd-title",
277
+ style: titleStyle,
278
+ className: (_l = providerProps.classNames) == null ? void 0 : _l.title,
279
+ children: (_m = state.title) != null ? _m : "Are you sure?"
280
+ }
281
+ ),
282
+ state.message && /* @__PURE__ */ jsx(
283
+ "p",
284
+ {
285
+ id: "rcd-message",
286
+ style: messageStyle,
287
+ className: (_n = providerProps.classNames) == null ? void 0 : _n.message,
288
+ children: state.message
289
+ }
290
+ ),
291
+ /* @__PURE__ */ jsxs(
292
+ "div",
293
+ {
294
+ style: actionsStyle,
295
+ className: (_o = providerProps.classNames) == null ? void 0 : _o.actions,
296
+ children: [
297
+ /* @__PURE__ */ jsx(
298
+ "button",
299
+ {
300
+ style: cancelStyle,
301
+ className: (_p = providerProps.classNames) == null ? void 0 : _p.cancelButton,
302
+ onClick: onCancel,
303
+ onMouseEnter: (e) => {
304
+ e.target.style.background = "#f9fafb";
305
+ },
306
+ onMouseLeave: (e) => {
307
+ var _a2, _b2, _c2, _d2;
308
+ e.target.style.background = (_d2 = (_c2 = (_b2 = (_a2 = providerProps.styles) == null ? void 0 : _a2.cancelButton) == null ? void 0 : _b2.background) == null ? void 0 : _c2.toString()) != null ? _d2 : "#fff";
309
+ },
310
+ children: (_q = state.cancelText) != null ? _q : "Cancel"
311
+ }
312
+ ),
313
+ /* @__PURE__ */ jsx(
314
+ "button",
315
+ {
316
+ ref: confirmRef,
317
+ style: confirmStyle,
318
+ className: (_r = providerProps.classNames) == null ? void 0 : _r.confirmButton,
319
+ onClick: onConfirm,
320
+ onMouseEnter: (e) => {
321
+ e.target.style.background = v.confirmHover;
322
+ },
323
+ onMouseLeave: (e) => {
324
+ var _a2, _b2, _c2, _d2;
325
+ e.target.style.background = (_d2 = (_c2 = (_b2 = (_a2 = providerProps.styles) == null ? void 0 : _a2.confirmButton) == null ? void 0 : _b2.background) == null ? void 0 : _c2.toString()) != null ? _d2 : v.confirmBg;
326
+ },
327
+ children: (_s = state.confirmText) != null ? _s : "Confirm"
328
+ }
329
+ )
330
+ ]
331
+ }
332
+ )
333
+ ] })
334
+ }
335
+ )
336
+ ] });
337
+ }
338
+
339
+ // src/hooks/useConfirm.ts
340
+ import { createContext, useContext } from "react";
341
+ var ConfirmDialogContext = createContext(null);
342
+ function useConfirm() {
343
+ const ctx = useContext(ConfirmDialogContext);
344
+ if (!ctx) {
345
+ throw new Error(
346
+ "[@nobertdev/react-confirm-dialog] useConfirm() must be used inside <ConfirmDialogProvider>."
347
+ );
348
+ }
349
+ return ctx.confirm;
350
+ }
351
+
352
+ // src/components/ConfirmDialogProvider.tsx
353
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
354
+ var CLOSE_ANIMATION_MS = 180;
355
+ var DEFAULT_STATE = {
356
+ isOpen: false,
357
+ resolve: null,
358
+ title: void 0,
359
+ message: void 0,
360
+ confirmText: void 0,
361
+ cancelText: void 0,
362
+ variant: "default",
363
+ icon: void 0
364
+ };
365
+ function ConfirmDialogProvider({
366
+ children,
367
+ defaultOptions,
368
+ classNames,
369
+ styles,
370
+ renderDialog
371
+ }) {
372
+ const [state, setState] = useState(DEFAULT_STATE);
373
+ const [isClosing, setIsClosing] = useState(false);
374
+ const resolveRef = useRef2(null);
375
+ const close = useCallback((result) => {
376
+ setIsClosing(true);
377
+ setTimeout(() => {
378
+ var _a;
379
+ setIsClosing(false);
380
+ setState(DEFAULT_STATE);
381
+ (_a = resolveRef.current) == null ? void 0 : _a.call(resolveRef, result);
382
+ resolveRef.current = null;
383
+ }, CLOSE_ANIMATION_MS);
384
+ }, []);
385
+ const onConfirm = useCallback(() => close(true), [close]);
386
+ const onCancel = useCallback(() => close(false), [close]);
387
+ const confirm = useCallback(
388
+ (options) => {
389
+ return new Promise((resolve) => {
390
+ resolveRef.current = resolve;
391
+ const normalized = typeof options === "string" ? { message: options } : options != null ? options : {};
392
+ setState(__spreadProps(__spreadValues(__spreadValues(__spreadValues({}, DEFAULT_STATE), defaultOptions), normalized), {
393
+ isOpen: true,
394
+ resolve
395
+ }));
396
+ });
397
+ },
398
+ [defaultOptions]
399
+ );
400
+ const contextValue = useMemo(() => ({ confirm }), [confirm]);
401
+ const dialogNode = renderDialog ? renderDialog({
402
+ isOpen: state.isOpen || isClosing,
403
+ options: state,
404
+ onConfirm,
405
+ onCancel
406
+ }) : /* @__PURE__ */ jsx2(
407
+ BuiltInDialog,
408
+ {
409
+ state,
410
+ providerProps: { classNames, styles, defaultOptions },
411
+ onConfirm,
412
+ onCancel,
413
+ isClosing
414
+ }
415
+ );
416
+ return /* @__PURE__ */ jsxs2(ConfirmDialogContext.Provider, { value: contextValue, children: [
417
+ children,
418
+ typeof document !== "undefined" ? createPortal(dialogNode, document.body) : null
419
+ ] });
420
+ }
421
+ export {
422
+ ConfirmDialogProvider,
423
+ useConfirm
424
+ };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@nobertdev/react-confirm-dialog",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight, fully customizable confirmation dialog hook (useConfirm()) that replaces window.confirm() with beautiful async modals. Zero dependencies.",
5
+ "keywords": [
6
+ "react",
7
+ "confirm",
8
+ "dialog",
9
+ "modal",
10
+ "hook",
11
+ "async",
12
+ "confirm-dialog",
13
+ "useConfirm"
14
+ ],
15
+ "author": "Nobert Langat",
16
+ "license": "MIT",
17
+ "main": "dist/index.js",
18
+ "module": "dist/index.esm.js",
19
+ "types": "dist/index.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean --external react --external react-dom",
26
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch --external react --external react-dom",
27
+ "lint": "tsc --noEmit",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "peerDependencies": {
31
+ "react": ">=17.0.0",
32
+ "react-dom": ">=17.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^18.3.0",
36
+ "@types/react-dom": "^18.3.0",
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.4.0"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/NOBERT167/@nobertdev/react-confirm-dialog.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/NOBERT167/@nobertdev/react-confirm-dialog/issues"
46
+ },
47
+ "homepage": "https://github.com/NOBERT167/@nobertdev/react-confirm-dialog#readme"
48
+ }