@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 +226 -0
- package/dist/index.d.mts +64 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +449 -0
- package/dist/index.mjs +424 -0
- package/package.json +48 -0
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
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|