@rokku-x/react-hook-dialog 0.0.1
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 +645 -0
- package/dist/components/ModalWindow.d.ts +15 -0
- package/dist/hooks/useHookDialog.d.ts +36 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.esm.js +342 -0
- package/dist/store/dialogStore.d.ts +27 -0
- package/dist/utils/utils.d.ts +1 -0
- package/package.json +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
# react-hook-dialog
|
|
2
|
+
|
|
3
|
+
A powerful and flexible React dialog hook library for confirmation dialogs, alerts, and modals. Built on top of `@rokku-x/react-hook-modal` with a focus on dialog-specific features like action buttons, variants, and customizable styling.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Hook-based API** - Simple and intuitive `useHookDialog()` hook
|
|
8
|
+
- 🎨 **Rich Variants** - 7 button variants (primary, secondary, danger, success, warning, info, neutral)
|
|
9
|
+
- 🧩 **Modular Components** - Composed from reusable Backdrop and DialogWindow components
|
|
10
|
+
- 📝 **Dialog Actions** - Flexible action button system with left/right positioning
|
|
11
|
+
- 💅 **Full Customization** - Injectable className and styles at every level
|
|
12
|
+
- 🧮 **Zustand State** - Centralized state management with Zustand
|
|
13
|
+
- ⌨️ **Rich Configuration** - Default configs with per-call overrides
|
|
14
|
+
- 🎁 **Zero Dependencies** - Only requires React, Zustand, and @rokku-x/react-hook-modal
|
|
15
|
+
- 📱 **TypeScript Support** - Full type safety out of the box
|
|
16
|
+
- ♿ **Backdrop Control** - Configurable backdrop click behavior
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @rokku-x/react-hook-dialog
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
or with yarn:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
yarn add @rokku-x/react-hook-dialog
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### 1. Setup BaseModalRenderer
|
|
33
|
+
|
|
34
|
+
First, add the `BaseModalRenderer` at the root of your application (from `@rokku-x/react-hook-modal`):
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { BaseModalRenderer } from '@rokku-x/react-hook-modal';
|
|
38
|
+
|
|
39
|
+
function App() {
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
<YourComponents />
|
|
43
|
+
<BaseModalRenderer />
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Use useHookDialog Hook
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { useHookDialog } from '@rokku-x/react-hook-dialog';
|
|
53
|
+
|
|
54
|
+
function MyComponent() {
|
|
55
|
+
const [requestDialog] = useHookDialog();
|
|
56
|
+
|
|
57
|
+
const handleConfirm = async () => {
|
|
58
|
+
const result = await requestDialog({
|
|
59
|
+
title: 'Confirm Action',
|
|
60
|
+
content: 'Are you sure you want to proceed?',
|
|
61
|
+
actions: [[
|
|
62
|
+
{ title: 'Cancel', isCancel: true },
|
|
63
|
+
{ title: 'Confirm', variant: 'primary' }
|
|
64
|
+
]]
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log('User chose:', result);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return <button onClick={handleConfirm}>Open Dialog</button>;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Bundle Size
|
|
75
|
+
|
|
76
|
+
- ESM: ~4.06 kB gzipped (13.48 kB raw)
|
|
77
|
+
- CJS: ~3.48 kB gzipped (9.21 kB raw)
|
|
78
|
+
|
|
79
|
+
Measured with Vite build for v0.0.1.
|
|
80
|
+
|
|
81
|
+
## API Reference
|
|
82
|
+
|
|
83
|
+
### useHookDialog
|
|
84
|
+
|
|
85
|
+
Main hook for displaying confirmation dialogs and alerts.
|
|
86
|
+
|
|
87
|
+
#### Parameters
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
useHookDialog(defaultConfig?: UseHookDialogConfig)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Parameter | Type | Description |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `defaultConfig` | `UseHookDialogConfig` | Optional default configuration applied to all dialogs |
|
|
96
|
+
|
|
97
|
+
#### Returns
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
[requestDialog]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Return Value | Type | Description |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `requestDialog` | `(config: ConfirmConfig) => Promise<ValidValue>` | Function to open a dialog and get user response |
|
|
106
|
+
|
|
107
|
+
#### Default Config Options
|
|
108
|
+
|
|
109
|
+
| Property | Type | Default | Description |
|
|
110
|
+
|---|---|---|---|
|
|
111
|
+
| `backdropCancel` | `boolean` | `false` | Allow closing via backdrop click |
|
|
112
|
+
| `rejectOnCancel` | `boolean` | `true` | Reject promise on cancel instead of resolving |
|
|
113
|
+
| `defaultCancelValue` | `ValidValue` | `undefined` | Value to return/reject on cancel |
|
|
114
|
+
| `showCloseButton` | `boolean` | `false` | Show X close button |
|
|
115
|
+
| `classNames` | `DialogClassNames` | `undefined` | Custom CSS classes |
|
|
116
|
+
| `styles` | `DialogStyles` | `undefined` | Custom inline styles |
|
|
117
|
+
| `variantStyles` | `DialogVariantStyles` | `undefined` | Custom variant button styles |
|
|
118
|
+
|
|
119
|
+
### ConfirmConfig
|
|
120
|
+
|
|
121
|
+
Configuration for individual dialog calls.
|
|
122
|
+
|
|
123
|
+
| Property | Type | Description |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `title` | `React.ReactNode` | Dialog title (string or React element) |
|
|
126
|
+
| `content` | `React.ReactNode` | Dialog content (string or React element) |
|
|
127
|
+
| `actions` | `ModalAction[][]` | Array of action button rows |
|
|
128
|
+
| `backdropCancel` | `boolean` | Allow closing via backdrop click |
|
|
129
|
+
| `rejectOnCancel` | `boolean` | Reject promise on cancel |
|
|
130
|
+
| `defaultCancelValue` | `ValidValue` | Value to return/reject on cancel |
|
|
131
|
+
| `showCloseButton` | `boolean` | Show X close button |
|
|
132
|
+
| `classNames` | `DialogClassNames` | Custom CSS classes for elements |
|
|
133
|
+
| `styles` | `DialogStyles` | Custom inline styles for elements |
|
|
134
|
+
| `variantStyles` | `DialogVariantStyles` | Custom variant button styles |
|
|
135
|
+
|
|
136
|
+
### ModalAction
|
|
137
|
+
|
|
138
|
+
Individual action button configuration.
|
|
139
|
+
|
|
140
|
+
| Property | Type | Required | Description |
|
|
141
|
+
|---|---|---|---|
|
|
142
|
+
| `title` | `React.ReactNode` | ✓ | Button label (string or React element) |
|
|
143
|
+
| `value` | `ValidValue` | | Value to return when clicked |
|
|
144
|
+
| `isCancel` | `boolean` | | Treat as cancel button (respects rejectOnCancel) |
|
|
145
|
+
| `isOnLeft` | `boolean` | | Position button on left side of row |
|
|
146
|
+
| `variant` | `ModalVariant` | | Visual style variant |
|
|
147
|
+
| `className` | `string` | | Additional CSS class |
|
|
148
|
+
| `style` | `React.CSSProperties` | | Custom inline styles |
|
|
149
|
+
| `onClick` | `((event, action) => void) \| (() => void)` | | Click handler called before default handling |
|
|
150
|
+
|
|
151
|
+
### ModalVariant
|
|
152
|
+
|
|
153
|
+
Available button variants:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
type ModalVariant = 'primary' | 'secondary' | 'danger' | 'success' | 'warning' | 'info' | 'neutral';
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
| Variant | Color | Text Color |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `primary` | Blue (#2563eb) | White |
|
|
162
|
+
| `secondary` | Gray (#e5e7eb) | Black |
|
|
163
|
+
| `danger` | Red (#dc2626) | White |
|
|
164
|
+
| `success` | Green (#16a34a) | White |
|
|
165
|
+
| `warning` | Amber (#f59e0b) | Black |
|
|
166
|
+
| `info` | Sky (#0ea5e9) | White |
|
|
167
|
+
| `neutral` | Gray (#6b7280) | White |
|
|
168
|
+
|
|
169
|
+
### DialogClassNames
|
|
170
|
+
|
|
171
|
+
Customize CSS classes for all elements:
|
|
172
|
+
|
|
173
|
+
| Property | Type | Description |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| `backdrop` | `string` | Backdrop overlay |
|
|
176
|
+
| `dialog` | `string` | Dialog container |
|
|
177
|
+
| `closeButton` | `string` | Close button |
|
|
178
|
+
| `title` | `string` | Title element |
|
|
179
|
+
| `content` | `string` | Content container |
|
|
180
|
+
| `actions` | `string` | Actions container |
|
|
181
|
+
| `actionsRow` | `string` | Individual action row |
|
|
182
|
+
| `actionButton` | `string` | Action button |
|
|
183
|
+
|
|
184
|
+
### DialogStyles
|
|
185
|
+
|
|
186
|
+
Customize inline styles for all elements:
|
|
187
|
+
|
|
188
|
+
| Property | Type | Description |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| `backdrop` | `React.CSSProperties` | Backdrop overlay styles |
|
|
191
|
+
| `dialog` | `React.CSSProperties` | Dialog container styles |
|
|
192
|
+
| `closeButton` | `React.CSSProperties` | Close button styles |
|
|
193
|
+
| `title` | `React.CSSProperties` | Title element styles |
|
|
194
|
+
| `content` | `React.CSSProperties` | Content container styles |
|
|
195
|
+
| `actions` | `React.CSSProperties` | Actions container styles |
|
|
196
|
+
| `actionsRow` | `React.CSSProperties` | Action row styles |
|
|
197
|
+
| `actionButton` | `React.CSSProperties` | Action button styles |
|
|
198
|
+
|
|
199
|
+
## Components
|
|
200
|
+
|
|
201
|
+
### Backdrop
|
|
202
|
+
|
|
203
|
+
Overlay component that wraps dialog windows.
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
<Backdrop
|
|
207
|
+
onClick={() => handleClose()}
|
|
208
|
+
className="custom-backdrop"
|
|
209
|
+
style={{ backdropFilter: 'blur(5px)' }}
|
|
210
|
+
>
|
|
211
|
+
{children}
|
|
212
|
+
</Backdrop>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### DialogWindow
|
|
216
|
+
|
|
217
|
+
Main dialog container component.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
<DialogWindow
|
|
221
|
+
className="custom-dialog"
|
|
222
|
+
style={{ backgroundColor: '#f5f5f5' }}
|
|
223
|
+
>
|
|
224
|
+
{children}
|
|
225
|
+
</DialogWindow>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Examples
|
|
229
|
+
|
|
230
|
+
### Example 1: Basic Confirmation Dialog
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
import { useHookDialog } from '@rokku-x/react-hook-dialog';
|
|
234
|
+
|
|
235
|
+
function DeleteConfirm() {
|
|
236
|
+
const [requestDialog] = useHookDialog();
|
|
237
|
+
|
|
238
|
+
const handleDelete = async () => {
|
|
239
|
+
const result = await requestDialog({
|
|
240
|
+
title: 'Delete Item?',
|
|
241
|
+
content: 'This action cannot be undone.',
|
|
242
|
+
actions: [[
|
|
243
|
+
{ title: 'Cancel', isCancel: true, variant: 'secondary' },
|
|
244
|
+
{ title: 'Delete', variant: 'danger', value: true }
|
|
245
|
+
]]
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
if (result === true) {
|
|
249
|
+
console.log('Item deleted!');
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return <button onClick={handleDelete}>Delete</button>;
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Example 2: Multiple Action Rows
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
const [requestDialog] = useHookDialog();
|
|
261
|
+
|
|
262
|
+
await requestDialog({
|
|
263
|
+
title: 'Choose Action',
|
|
264
|
+
content: 'What would you like to do?',
|
|
265
|
+
actions: [
|
|
266
|
+
[{ title: 'Back', isOnLeft: true, variant: 'secondary' }],
|
|
267
|
+
[
|
|
268
|
+
{ title: 'Cancel', isCancel: true },
|
|
269
|
+
{ title: 'Save', variant: 'primary' }
|
|
270
|
+
]
|
|
271
|
+
]
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Example 3: Custom Styling
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
const [requestDialog] = useHookDialog({
|
|
279
|
+
styles: {
|
|
280
|
+
dialog: {
|
|
281
|
+
borderRadius: '20px',
|
|
282
|
+
backgroundColor: '#f9fafb'
|
|
283
|
+
},
|
|
284
|
+
actionButton: {
|
|
285
|
+
fontWeight: 'bold'
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
classNames: {
|
|
289
|
+
dialog: 'my-custom-dialog'
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
await requestDialog({
|
|
294
|
+
title: 'Styled Dialog',
|
|
295
|
+
content: 'This dialog has custom styles'
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Example 4: Custom Button Variants
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
const [requestDialog] = useHookDialog({
|
|
303
|
+
variantStyles: {
|
|
304
|
+
primary: {
|
|
305
|
+
backgroundColor: '#7c3aed', // Purple
|
|
306
|
+
color: '#fff'
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await requestDialog({
|
|
312
|
+
title: 'Custom Colors',
|
|
313
|
+
actions: [[
|
|
314
|
+
{ title: 'Confirm', variant: 'primary' }
|
|
315
|
+
]]
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Example 5: Button Click Handlers
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
await requestDialog({
|
|
323
|
+
title: 'Action Dialog',
|
|
324
|
+
actions: [[
|
|
325
|
+
{
|
|
326
|
+
title: 'Log to Console',
|
|
327
|
+
onClick: (e) => console.log('Button clicked!'),
|
|
328
|
+
variant: 'info'
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
title: 'Proceed',
|
|
332
|
+
variant: 'primary'
|
|
333
|
+
}
|
|
334
|
+
]]
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Example 6: Rich Content
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
await requestDialog({
|
|
342
|
+
title: <span style={{ color: 'blue' }}>Custom <strong>Title</strong></span>,
|
|
343
|
+
content: (
|
|
344
|
+
<div>
|
|
345
|
+
<p>This dialog has rich content:</p>
|
|
346
|
+
<ul>
|
|
347
|
+
<li>Item 1</li>
|
|
348
|
+
<li>Item 2</li>
|
|
349
|
+
</ul>
|
|
350
|
+
</div>
|
|
351
|
+
),
|
|
352
|
+
actions: [[
|
|
353
|
+
{ title: 'OK', variant: 'primary' }
|
|
354
|
+
]]
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Example 7: Default Configuration
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
const [requestDialog] = useHookDialog({
|
|
362
|
+
showCloseButton: false,
|
|
363
|
+
backdropCancel: false,
|
|
364
|
+
styles: {
|
|
365
|
+
dialog: { maxWidth: '500px' }
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// All subsequent dialogs will use these defaults
|
|
370
|
+
await requestDialog({
|
|
371
|
+
title: 'Will use defaults',
|
|
372
|
+
content: 'No close button, backdrop click disabled'
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Example 8: Alert Dialog
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
async function showAlert(message: string) {
|
|
380
|
+
const [requestDialog] = useHookDialog();
|
|
381
|
+
|
|
382
|
+
await requestDialog({
|
|
383
|
+
title: 'Alert',
|
|
384
|
+
content: message,
|
|
385
|
+
actions: [[
|
|
386
|
+
{ title: 'OK', variant: 'primary' }
|
|
387
|
+
]]
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
showAlert('Operation completed successfully!');
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Example 9: Multiple Choice with Different Values
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
const [requestDialog] = useHookDialog();
|
|
398
|
+
|
|
399
|
+
const handleSaveOptions = async () => {
|
|
400
|
+
const result = await requestDialog({
|
|
401
|
+
title: 'Save Options',
|
|
402
|
+
content: 'How would you like to save?',
|
|
403
|
+
actions: [[
|
|
404
|
+
{ title: 'Cancel', isCancel: true },
|
|
405
|
+
{ title: 'Save Draft', variant: 'secondary', value: 'draft' },
|
|
406
|
+
{ title: 'Publish', variant: 'primary', value: 'publish' }
|
|
407
|
+
]]
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
if (result === 'draft') {
|
|
411
|
+
console.log('Saving as draft...');
|
|
412
|
+
} else if (result === 'publish') {
|
|
413
|
+
console.log('Publishing...');
|
|
414
|
+
} else {
|
|
415
|
+
console.log('Cancelled');
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Example 10: Numeric Rating Dialog
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
const [requestDialog] = useHookDialog();
|
|
424
|
+
|
|
425
|
+
const handleRating = async () => {
|
|
426
|
+
const rating = await requestDialog({
|
|
427
|
+
title: 'Rate Your Experience',
|
|
428
|
+
content: 'How would you rate our service?',
|
|
429
|
+
actions: [
|
|
430
|
+
[
|
|
431
|
+
{ title: '1 Star', variant: 'danger', value: 1 },
|
|
432
|
+
{ title: '2 Stars', variant: 'warning', value: 2 },
|
|
433
|
+
{ title: '3 Stars', variant: 'neutral', value: 3 }
|
|
434
|
+
],
|
|
435
|
+
[
|
|
436
|
+
{ title: '4 Stars', variant: 'info', value: 4 },
|
|
437
|
+
{ title: '5 Stars', variant: 'success', value: 5 }
|
|
438
|
+
]
|
|
439
|
+
],
|
|
440
|
+
showCloseButton: false,
|
|
441
|
+
backdropCancel: false
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
console.log(`User rated: ${rating} stars`);
|
|
445
|
+
// Send rating to API
|
|
446
|
+
};
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Example 11: Conditional Actions Based on Result
|
|
450
|
+
|
|
451
|
+
```tsx
|
|
452
|
+
const [requestDialog] = useHookDialog();
|
|
453
|
+
|
|
454
|
+
const handleFileOperation = async () => {
|
|
455
|
+
const action = await requestDialog({
|
|
456
|
+
title: 'File Actions',
|
|
457
|
+
content: 'What would you like to do with this file?',
|
|
458
|
+
actions: [[
|
|
459
|
+
{ title: 'Download', variant: 'info', value: 'download' },
|
|
460
|
+
{ title: 'Share', variant: 'primary', value: 'share' },
|
|
461
|
+
{ title: 'Delete', variant: 'danger', value: 'delete', isOnLeft: true }
|
|
462
|
+
]]
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
switch (action) {
|
|
466
|
+
case 'download':
|
|
467
|
+
// Download file logic
|
|
468
|
+
window.location.href = '/api/download/file.pdf';
|
|
469
|
+
break;
|
|
470
|
+
case 'share':
|
|
471
|
+
// Open share dialog
|
|
472
|
+
await requestDialog({
|
|
473
|
+
title: 'Share File',
|
|
474
|
+
content: 'File link copied to clipboard!',
|
|
475
|
+
actions: [[{ title: 'OK', variant: 'primary' }]]
|
|
476
|
+
});
|
|
477
|
+
break;
|
|
478
|
+
case 'delete':
|
|
479
|
+
// Confirm deletion
|
|
480
|
+
const confirm = await requestDialog({
|
|
481
|
+
title: 'Confirm Delete',
|
|
482
|
+
content: 'Are you sure? This cannot be undone.',
|
|
483
|
+
actions: [[
|
|
484
|
+
{ title: 'Cancel', isCancel: true },
|
|
485
|
+
{ title: 'Delete', variant: 'danger', value: true }
|
|
486
|
+
]]
|
|
487
|
+
});
|
|
488
|
+
if (confirm) {
|
|
489
|
+
console.log('File deleted');
|
|
490
|
+
}
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Example 12: Handle Cancel vs Reject
|
|
497
|
+
|
|
498
|
+
```tsx
|
|
499
|
+
const [requestDialog] = useHookDialog({
|
|
500
|
+
rejectOnCancel: true // Reject promise on cancel
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const handleWithErrorHandling = async () => {
|
|
504
|
+
try {
|
|
505
|
+
const result = await requestDialog({
|
|
506
|
+
title: 'Important Action',
|
|
507
|
+
content: 'This requires your confirmation.',
|
|
508
|
+
actions: [[
|
|
509
|
+
{ title: 'Cancel', isCancel: true },
|
|
510
|
+
{ title: 'Proceed', variant: 'primary', value: 'proceed' }
|
|
511
|
+
]]
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
if (result === 'proceed') {
|
|
515
|
+
console.log('User proceeded');
|
|
516
|
+
// Perform action
|
|
517
|
+
}
|
|
518
|
+
} catch (error) {
|
|
519
|
+
console.log('User cancelled or closed dialog');
|
|
520
|
+
// Handle cancellation
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Example 13: Form Submission with Validation
|
|
526
|
+
|
|
527
|
+
```tsx
|
|
528
|
+
const [requestDialog] = useHookDialog();
|
|
529
|
+
|
|
530
|
+
const handleFormSubmit = async (formData: any) => {
|
|
531
|
+
const action = await requestDialog({
|
|
532
|
+
title: 'Review Changes',
|
|
533
|
+
content: (
|
|
534
|
+
<div>
|
|
535
|
+
<p>You are about to submit the following changes:</p>
|
|
536
|
+
<ul>
|
|
537
|
+
<li>Name: {formData.name}</li>
|
|
538
|
+
<li>Email: {formData.email}</li>
|
|
539
|
+
</ul>
|
|
540
|
+
</div>
|
|
541
|
+
),
|
|
542
|
+
actions: [[
|
|
543
|
+
{ title: 'Edit', variant: 'secondary', value: 'edit' },
|
|
544
|
+
{ title: 'Cancel', isCancel: true },
|
|
545
|
+
{ title: 'Submit', variant: 'success', value: 'submit' }
|
|
546
|
+
]]
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
if (action === 'submit') {
|
|
550
|
+
// Submit form
|
|
551
|
+
const response = await fetch('/api/submit', {
|
|
552
|
+
method: 'POST',
|
|
553
|
+
body: JSON.stringify(formData)
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
await requestDialog({
|
|
557
|
+
title: 'Success',
|
|
558
|
+
content: 'Your changes have been saved!',
|
|
559
|
+
actions: [[{ title: 'OK', variant: 'success' }]]
|
|
560
|
+
});
|
|
561
|
+
} else if (action === 'edit') {
|
|
562
|
+
// Return to form
|
|
563
|
+
console.log('User wants to edit');
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Example 14: Boolean Result with Custom Values
|
|
569
|
+
|
|
570
|
+
```tsx
|
|
571
|
+
const [requestDialog] = useHookDialog();
|
|
572
|
+
|
|
573
|
+
const handleLogout = async () => {
|
|
574
|
+
const shouldLogout = await requestDialog({
|
|
575
|
+
title: 'Confirm Logout',
|
|
576
|
+
content: 'Are you sure you want to log out?',
|
|
577
|
+
actions: [[
|
|
578
|
+
{ title: 'Stay Logged In', variant: 'secondary', value: false },
|
|
579
|
+
{ title: 'Log Out', variant: 'danger', value: true }
|
|
580
|
+
]]
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
if (shouldLogout) {
|
|
584
|
+
// Perform logout
|
|
585
|
+
sessionStorage.clear();
|
|
586
|
+
window.location.href = '/login';
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
## Best Practices
|
|
592
|
+
|
|
593
|
+
1. **Mount `BaseModalRenderer` at root level** - Required for modals to render
|
|
594
|
+
2. **Use default configs for consistency** - Set common styles/behaviors once
|
|
595
|
+
3. **Provide meaningful button labels** - Users should know what each button does
|
|
596
|
+
4. **Use appropriate variants** - Use `danger` for destructive actions, `success` for confirmations
|
|
597
|
+
5. **Keep content concise** - Dialogs should be focused and brief
|
|
598
|
+
6. **Handle both resolve and reject** - Account for cancellation scenarios
|
|
599
|
+
7. **Use `isOnLeft` for secondary actions** - Helps with visual hierarchy
|
|
600
|
+
8. **Customize responsibly** - Maintain accessibility and usability standards
|
|
601
|
+
|
|
602
|
+
## Types
|
|
603
|
+
|
|
604
|
+
### ValidValue
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
type ValidValue = string | number | boolean | undefined;
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
The type of value returned from dialog actions.
|
|
611
|
+
|
|
612
|
+
### DialogVariantStyles
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
type DialogVariantStyles = Partial<Record<ModalVariant, React.CSSProperties>>;
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Custom styles for each variant type.
|
|
619
|
+
|
|
620
|
+
## Accessibility
|
|
621
|
+
|
|
622
|
+
- Backdrop close can be enabled with `backdropCancel: true`
|
|
623
|
+
- Close button can be shown with `showCloseButton: true`
|
|
624
|
+
- All buttons are keyboard accessible
|
|
625
|
+
- ARIA labels provided for interactive elements
|
|
626
|
+
- Supports custom ARIA attributes via className injection
|
|
627
|
+
|
|
628
|
+
## Troubleshooting
|
|
629
|
+
|
|
630
|
+
### Dialog not appearing
|
|
631
|
+
- Ensure `BaseModalRenderer` is mounted at root level
|
|
632
|
+
- Check that `useHookDialog()` is called within the component tree
|
|
633
|
+
|
|
634
|
+
### Styles not applying
|
|
635
|
+
- Verify className/style props are passed to `ConfirmConfig`
|
|
636
|
+
- Check CSS specificity - inline styles take precedence
|
|
637
|
+
- Use browser dev tools to inspect applied styles
|
|
638
|
+
|
|
639
|
+
### Promise never resolves
|
|
640
|
+
- Ensure action buttons have appropriate `value` or are configured as cancel buttons
|
|
641
|
+
- Check that action click handlers don't prevent default behavior
|
|
642
|
+
|
|
643
|
+
## License
|
|
644
|
+
|
|
645
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main modal window component that renders the dialog UI.
|
|
3
|
+
*
|
|
4
|
+
* Displays the title, content, action buttons, and optional close button.
|
|
5
|
+
* Action buttons are organized into rows and can be positioned left or right.
|
|
6
|
+
*
|
|
7
|
+
* @param props - Component props
|
|
8
|
+
* @param props.modalWindowId - Unique identifier for this modal instance
|
|
9
|
+
* @param props.handleAction - Callback when an action button is clicked
|
|
10
|
+
* @param props.handleClose - Callback when the dialog should close
|
|
11
|
+
* @param props.config - Dialog configuration options
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export default function ModalWindow({ modalWindowId, handleAction, handleClose, config }: ModalWindowProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for displaying confirmation dialogs and alerts.
|
|
3
|
+
*
|
|
4
|
+
* Returns a function to request a dialog and receive the user's response as a Promise.
|
|
5
|
+
* Dialogs can be customized with titles, content, action buttons, and styling.
|
|
6
|
+
*
|
|
7
|
+
* @param defaultConfig - Optional default configuration applied to all dialogs created by this hook
|
|
8
|
+
* @returns A tuple containing the requestDialog function
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const [requestDialog] = useHookDialog();
|
|
13
|
+
*
|
|
14
|
+
* const result = await requestDialog({
|
|
15
|
+
* title: 'Confirm',
|
|
16
|
+
* content: 'Are you sure?',
|
|
17
|
+
* actions: [[
|
|
18
|
+
* { title: 'Cancel', isCancel: true },
|
|
19
|
+
* { title: 'OK', variant: 'primary', value: true }
|
|
20
|
+
* ]]
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* if (result === true) {
|
|
24
|
+
* console.log('User confirmed');
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example With default config
|
|
29
|
+
* ```tsx
|
|
30
|
+
* const [requestDialog] = useHookDialog({
|
|
31
|
+
* showCloseButton: false,
|
|
32
|
+
* backdropCancel: false
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export default function useHookDialog(defaultConfig?: UseHookDialogConfig): ((config: ConfirmConfig) => Promise<ValidValue>)[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m=require("react"),N=require("react-dom"),f=require("react/jsx-runtime"),R=t=>{let o;const e=new Set,n=(c,M)=>{const a=typeof c=="function"?c(o):c;if(!Object.is(a,o)){const r=o;o=M??(typeof a!="object"||a===null)?a:Object.assign({},o,a),e.forEach(u=>u(o,r))}},l=()=>o,s={setState:n,getState:l,getInitialState:()=>b,subscribe:c=>(e.add(c),()=>e.delete(c))},b=o=t(n,l,s);return s},O=(t=>t?R(t):R),B=t=>t;function T(t,o=B){const e=m.useSyncExternalStore(t.subscribe,m.useCallback(()=>o(t.getState()),[t,o]),m.useCallback(()=>o(t.getInitialState()),[t,o]));return m.useDebugValue(e),e}const I=t=>{const o=O(t),e=n=>T(o,n);return Object.assign(e,o),e},W=(t=>t?I(t):I),x={STACKED:0,CURRENT_ONLY:1,CURRENT_HIDDEN_STACK:2},j=W()((t,o)=>({modalStackMap:new Map,isMounted:!1,renderMode:x.STACKED,modalWindowRefs:void 0,currentModalId:void 0,internalActions:{setModalWindowRefRef:e=>t(n=>({modalWindowRefs:e})),setIsMounted:e=>t({isMounted:e}),setRenderMode:e=>t({renderMode:e})},actions:{pushModal:(e,n,l=!1)=>(e=e??m.useId(),o().isMounted||console.error("BaseModalRenderer must be mounted before using."),o().modalStackMap.get(e)!==void 0?(o().actions.focusModal(e),e):(t(s=>{let b=[n,l];const c=new Map(s.modalStackMap);return c.set(e,b),{modalStackMap:c,currentModalId:e}}),e)),popModal:e=>o().modalStackMap.get(e)?(t(n=>{const l=new Map(n.modalStackMap);l.delete(e);const s=Array.from(l.keys())[l.size-1];return{modalStackMap:l,currentModalId:s}}),!0):!1,getModal:e=>o().modalStackMap.get(e),updateModal:(e,n,l)=>{const s=o().modalStackMap.get(e);return s?(t(b=>{const c=new Map(b.modalStackMap);return s[1]===!0?(console.warn(`Modal with id ${e} is dynamic. Cannot update content.`),{modalStackMap:b.modalStackMap}):(s[0]=n,s[1]=l??s[1],c.set(e,s),{modalStackMap:c})}),!0):!1},focusModal:e=>{const n=o().modalStackMap.get(e);return n?(t(l=>{const s=new Map(l.modalStackMap);return s.delete(e),s.set(e,n),{modalStackMap:s,currentModalId:e}}),!0):!1},getModalOrderIndex:e=>Array.from(o().modalStackMap.keys()).indexOf(e),getModalWindowRef:e=>o().modalWindowRefs?.get(e)}}));function K(){const{actions:t,currentModalId:o,renderMode:e}=j(n=>n);return{...t,currentModalId:o,renderMode:e}}function _(){const{internalActions:t,isMounted:o,modalStackMap:e,modalWindowRefs:n,currentModalId:l,renderMode:s}=j(b=>b);return{...t,isMounted:o,modalStackMap:e,modalWindowRefs:n,currentModalId:l,renderMode:s,store:j}}function z({renderMode:t=x.STACKED,id:o,style:e,className:n,windowClassName:l,windowStyle:s,disableBackgroundScroll:b=!0}){const c=m.useRef(null),M=m.useRef(new Map),{setIsMounted:a,setModalWindowRefRef:r,modalStackMap:u,currentModalId:y,store:k}=_(),g=Array.from(u.values()),d=Array.from(u.keys()),S=o||"base-modal-wrapper";m.useEffect(()=>{if(k.getState().isMounted)throw new Error("Multiple BaseModalRenderer detected. Only one BaseModalRenderer is allowed at a time.");return r(M.current),a(!0),()=>{a(!1),r(void 0)}},[]),m.useEffect(()=>{const p=d[d.length-1];p!==void 0?(c.current?.showModal(),document.body.setAttribute("inert","")):p===void 0&&(c.current?.close(),document.body.removeAttribute("inert"))},[y]);const w=m.useCallback((p,i)=>{p?M.current.set(i,p):M.current.delete(i)},[]),C=()=>{switch(t){case x.STACKED:return g.map(([p,i],h)=>f.jsx("div",{ref:v=>w(v,d[h]),className:`modal-window ${l||""}`,id:d[h],style:{...s||{}},...y!==d[h]?{inert:""}:{},children:typeof p=="function"?p():p},d[h]));case x.CURRENT_ONLY:return f.jsx("div",{id:d[g.length-1],ref:p=>w(p,d[g.length-1]),className:`modal-window ${l||""}`,style:{...s||{}},children:g[g.length-1][1]?null:typeof g[g.length-1][0]=="function"?g[g.length-1][0]():g[g.length-1][0]},d[g.length-1]);case x.CURRENT_HIDDEN_STACK:return g.map(([p,i],h)=>f.jsx("div",{ref:v=>w(v,d[h]),id:d[h],className:`modal-window ${l||""}`,style:{...s||{},visibility:y===d[h]?"visible":"hidden"},...y!==d[h]?{inert:""}:{},children:i?null:typeof p=="function"?p():p},d[h]))}};return d.length===0?null:f.jsxs(f.Fragment,{children:[N.createPortal(f.jsx("style",{children:`${b?`body:has(dialog#${S}[open]){overflow:hidden}body{scrollbar-gutter:stable}`:""}dialog#${S}[open]{width:100vw;height:100vh;max-width:100%;max-height:100%}.modal-wrapper{border:none;padding:0;background:unset}.modal-window{display:block;position:absolute;width:100%;height:100%;backdrop-filter:blur(2px);background-color:rgba(0,0,0,.1)}`}),document.head),N.createPortal(f.jsx("dialog",{ref:c,id:S,className:`modal-wrapper ${n||""}`,style:e,children:C()}),document.body)]})}function P({onClick:t,className:o="",style:e={},children:n}){return f.jsx("div",{className:`hook-modal-backdrop ${o}`,style:{position:"fixed",inset:0,display:"flex",alignItems:"center",justifyContent:"center",zIndex:1e3,...e},onClick:t,children:n})}const V=t=>t.stopPropagation();function q({className:t="",style:o={},children:e}){return f.jsx("div",{className:`hook-modal-window ${t}`,style:{minWidth:360,maxWidth:"90vw",backgroundColor:"#fff",borderRadius:30,boxShadow:"0 10px 40px rgba(0,0,0,0.2)",padding:25,position:"relative",...o},onClick:V,children:e})}const H={primary:{backgroundColor:"#2563eb",color:"#fff"},secondary:{backgroundColor:"#e5e7eb",color:"#111"},danger:{backgroundColor:"#dc2626",color:"#fff"},success:{backgroundColor:"#16a34a",color:"#fff"},warning:{backgroundColor:"#f59e0b",color:"#111"},info:{backgroundColor:"#0ea5e9",color:"#fff"},neutral:{backgroundColor:"#6b7280",color:"#fff"}};function U({modalWindowId:t,handleAction:o,handleClose:e,config:n}){const{actions:l=[],title:s,content:b,backdropCancel:c,showCloseButton:M,classNames:a={},styles:r={},variantStyles:u={}}=n,y=(l.length?l:[[{title:"OK",variant:"primary"}]]).filter(d=>d&&d.length),k={...H,...u},g=()=>{c===!0&&e(t)};return f.jsx(P,{onClick:g,className:a.backdrop||"",style:r.backdrop,children:f.jsxs(q,{className:a.dialog||"",style:r.dialog,children:[M&&f.jsx("button",{type:"button",className:`hook-dialog-close-button ${a.closeButton||""}`,"aria-label":"Close",onClick:()=>e(t),style:{position:"absolute",top:0,right:0,transform:"translate(75%, -75%)",width:32,height:32,background:"none",border:"none",fontSize:21,cursor:"pointer",lineHeight:1,color:"#555",...r.closeButton},children:"×"}),s&&f.jsx("h3",{className:`hook-dialog-title ${a.title||""}`,style:{margin:"0 0 15px",fontSize:20,...r.title},children:s}),b&&f.jsx("div",{className:`hook-dialog-content ${a.content||""}`,style:{marginBottom:15,color:"#555",...r.content},children:b}),f.jsx("div",{className:`hook-dialog-actions ${a.actions||""}`,style:{display:"flex",flexDirection:"column",gap:10,...r.actions},children:y.map((d,S)=>{const w=d.filter(i=>i.isOnLeft),C=d.filter(i=>!i.isOnLeft),p=(i,h)=>{const v=i.variant||"secondary",D=k[v]||k.secondary;return f.jsx("button",{type:"button",className:`hook-dialog-action-button hook-dialog-action-${i.variant||"secondary"} ${a.actionButton||""} ${i.className||""}`,onClick:E=>{i.onClick?.(E,i),o(t,i)},style:{border:"none",borderRadius:15,padding:"10px 18px",fontSize:14,fontWeight:800,cursor:"pointer",...D,...r.actionButton,...i.style||{}},children:i.title},`${i.title}-${h}`)};return f.jsxs("div",{className:`hook-dialog-actions-row ${a.actionsRow||""}`,style:{display:"flex",gap:8,justifyContent:"space-between",marginTop:10,...r.actionsRow},children:[f.jsx("div",{className:"hook-dialog-actions-left",style:{display:"flex",gap:8},children:w.map((i,h)=>p(i,h))}),f.jsx("div",{className:"hook-dialog-actions-right",style:{display:"flex",gap:8},children:C.map((i,h)=>p(i,h))})]},S)})})]})})}const $=t=>{let o;const e=new Set,n=(a,r)=>{const u=typeof a=="function"?a(o):a;if(!Object.is(u,o)){const y=o;o=r??(typeof u!="object"||u===null)?u:Object.assign({},o,u),e.forEach(k=>k(o,y))}},l=()=>o,c={setState:n,getState:l,getInitialState:()=>M,subscribe:a=>(e.add(a),()=>e.delete(a))},M=o=t(n,l,c);return c},L=(t=>t?$(t):$),F=t=>t;function Y(t,o=F){const e=m.useSyncExternalStore(t.subscribe,m.useCallback(()=>o(t.getState()),[t,o]),m.useCallback(()=>o(t.getInitialState()),[t,o]));return m.useDebugValue(e),e}const A=t=>{const o=L(t),e=n=>Y(o,n);return Object.assign(e,o),e},G=(t=>t?A(t):A),J=G((t,o)=>({instances:[],addInstance:e=>t(n=>({instances:[...n.instances,e]})),removeInstance:e=>t(n=>({instances:n.instances.filter(l=>l.id!==e)})),getInstance:e=>o().instances.find(n=>n.id===e)}));function Q(t){const{instances:o,addInstance:e,removeInstance:n,getInstance:l}=J(),s=K(),b=m.useCallback(a=>{const r=l(a);r&&(s.popModal(a),r.config.rejectOnCancel!==!1?r.reject(r.config.defaultCancelValue):r.resolve(r.config.defaultCancelValue),n(a))},[l,n,s]),c=m.useCallback((a,r)=>{const u=l(a);u&&(s.popModal(a),r.isCancel&&u.config.rejectOnCancel!==!1?u.reject(r.value):u.resolve(r.value),n(a))},[l,n,s]);return[a=>new Promise((r,u)=>{const y=Math.random().toString(36).substring(2,6),k={...t,...a,classNames:{...t?.classNames,...a.classNames},styles:{...t?.styles,...a.styles},variantStyles:{...t?.variantStyles,...a.variantStyles}},g={id:y,config:k,resolve:r,reject:u};e(g),g.id=s.pushModal(y,m.createElement(U,{config:k,modalWindowId:y,handleClose:b,handleAction:c}))})]}exports.BaseModalRenderer=z;exports.useHookDialog=Q;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* react-hook-dialog - A powerful and flexible React dialog hook library
|
|
4
|
+
*
|
|
5
|
+
* Provides a hook-based API for displaying confirmation dialogs, alerts, and modals
|
|
6
|
+
* with rich customization options including action buttons, variants, and styling.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { BaseModalRenderer } from '@rokku-x/react-hook-dialog';
|
|
11
|
+
* import { useHookDialog } from '@rokku-x/react-hook-dialog';
|
|
12
|
+
*
|
|
13
|
+
* function App() {
|
|
14
|
+
* return (
|
|
15
|
+
* <>
|
|
16
|
+
* <YourComponents />
|
|
17
|
+
* <BaseModalRenderer />
|
|
18
|
+
* </>
|
|
19
|
+
* );
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* function MyComponent() {
|
|
23
|
+
* const [requestDialog] = useHookDialog();
|
|
24
|
+
*
|
|
25
|
+
* const handleConfirm = async () => {
|
|
26
|
+
* const result = await requestDialog({
|
|
27
|
+
* title: 'Confirm',
|
|
28
|
+
* content: 'Are you sure?',
|
|
29
|
+
* actions: [[
|
|
30
|
+
* { title: 'Cancel', isCancel: true },
|
|
31
|
+
* { title: 'OK', variant: 'primary', value: true }
|
|
32
|
+
* ]]
|
|
33
|
+
* });
|
|
34
|
+
* };
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Re-export of BaseModalRenderer from @rokku-x/react-hook-modal.
|
|
40
|
+
* Must be mounted at the root of your application for dialogs to render.
|
|
41
|
+
*/
|
|
42
|
+
export { BaseModalRenderer } from '@rokku-x/react-hook-modal';
|
|
43
|
+
/**
|
|
44
|
+
* Main hook for displaying confirmation dialogs and alerts.
|
|
45
|
+
* @see {@link useHookDialog}
|
|
46
|
+
*/
|
|
47
|
+
export { default as useHookDialog } from './hooks/useHookDialog';
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import k, { useId as K, useRef as $, useEffect as A, useCallback as I } from "react";
|
|
3
|
+
import { createPortal as E } from "react-dom";
|
|
4
|
+
import { jsxs as R, Fragment as _, jsx as b } from "react/jsx-runtime";
|
|
5
|
+
const j = (t) => {
|
|
6
|
+
let o;
|
|
7
|
+
const e = /* @__PURE__ */ new Set(), n = (c, y) => {
|
|
8
|
+
const a = typeof c == "function" ? c(o) : c;
|
|
9
|
+
if (!Object.is(a, o)) {
|
|
10
|
+
const r = o;
|
|
11
|
+
o = y ?? (typeof a != "object" || a === null) ? a : Object.assign({}, o, a), e.forEach((u) => u(o, r));
|
|
12
|
+
}
|
|
13
|
+
}, l = () => o, s = { setState: n, getState: l, getInitialState: () => p, subscribe: (c) => (e.add(c), () => e.delete(c)) }, p = o = t(n, l, s);
|
|
14
|
+
return s;
|
|
15
|
+
}, z = ((t) => t ? j(t) : j), V = (t) => t;
|
|
16
|
+
function U(t, o = V) {
|
|
17
|
+
const e = k.useSyncExternalStore(
|
|
18
|
+
t.subscribe,
|
|
19
|
+
k.useCallback(() => o(t.getState()), [t, o]),
|
|
20
|
+
k.useCallback(() => o(t.getInitialState()), [t, o])
|
|
21
|
+
);
|
|
22
|
+
return k.useDebugValue(e), e;
|
|
23
|
+
}
|
|
24
|
+
const D = (t) => {
|
|
25
|
+
const o = z(t), e = (n) => U(o, n);
|
|
26
|
+
return Object.assign(e, o), e;
|
|
27
|
+
}, H = ((t) => t ? D(t) : D), C = {
|
|
28
|
+
STACKED: 0,
|
|
29
|
+
CURRENT_ONLY: 1,
|
|
30
|
+
CURRENT_HIDDEN_STACK: 2
|
|
31
|
+
}, x = H()((t, o) => ({
|
|
32
|
+
modalStackMap: /* @__PURE__ */ new Map(),
|
|
33
|
+
isMounted: !1,
|
|
34
|
+
renderMode: C.STACKED,
|
|
35
|
+
modalWindowRefs: void 0,
|
|
36
|
+
currentModalId: void 0,
|
|
37
|
+
internalActions: {
|
|
38
|
+
setModalWindowRefRef: (e) => t((n) => ({ modalWindowRefs: e })),
|
|
39
|
+
setIsMounted: (e) => t({ isMounted: e }),
|
|
40
|
+
setRenderMode: (e) => t({ renderMode: e })
|
|
41
|
+
},
|
|
42
|
+
actions: {
|
|
43
|
+
pushModal: (e, n, l = !1) => (e = e ?? K(), o().isMounted || console.error("BaseModalRenderer must be mounted before using."), o().modalStackMap.get(e) !== void 0 ? (o().actions.focusModal(e), e) : (t((s) => {
|
|
44
|
+
let p = [n, l];
|
|
45
|
+
const c = new Map(s.modalStackMap);
|
|
46
|
+
return c.set(e, p), { modalStackMap: c, currentModalId: e };
|
|
47
|
+
}), e)),
|
|
48
|
+
popModal: (e) => o().modalStackMap.get(e) ? (t((n) => {
|
|
49
|
+
const l = new Map(n.modalStackMap);
|
|
50
|
+
l.delete(e);
|
|
51
|
+
const s = Array.from(l.keys())[l.size - 1];
|
|
52
|
+
return { modalStackMap: l, currentModalId: s };
|
|
53
|
+
}), !0) : !1,
|
|
54
|
+
getModal: (e) => o().modalStackMap.get(e),
|
|
55
|
+
updateModal: (e, n, l) => {
|
|
56
|
+
const s = o().modalStackMap.get(e);
|
|
57
|
+
return s ? (t((p) => {
|
|
58
|
+
const c = new Map(p.modalStackMap);
|
|
59
|
+
return s[1] === !0 ? (console.warn(`Modal with id ${e} is dynamic. Cannot update content.`), { modalStackMap: p.modalStackMap }) : (s[0] = n, s[1] = l ?? s[1], c.set(e, s), { modalStackMap: c });
|
|
60
|
+
}), !0) : !1;
|
|
61
|
+
},
|
|
62
|
+
focusModal: (e) => {
|
|
63
|
+
const n = o().modalStackMap.get(e);
|
|
64
|
+
return n ? (t((l) => {
|
|
65
|
+
const s = new Map(l.modalStackMap);
|
|
66
|
+
return s.delete(e), s.set(e, n), { modalStackMap: s, currentModalId: e };
|
|
67
|
+
}), !0) : !1;
|
|
68
|
+
},
|
|
69
|
+
getModalOrderIndex: (e) => Array.from(o().modalStackMap.keys()).indexOf(e),
|
|
70
|
+
getModalWindowRef: (e) => o().modalWindowRefs?.get(e)
|
|
71
|
+
}
|
|
72
|
+
}));
|
|
73
|
+
function L() {
|
|
74
|
+
const { actions: t, currentModalId: o, renderMode: e } = x((n) => n);
|
|
75
|
+
return { ...t, currentModalId: o, renderMode: e };
|
|
76
|
+
}
|
|
77
|
+
function P() {
|
|
78
|
+
const { internalActions: t, isMounted: o, modalStackMap: e, modalWindowRefs: n, currentModalId: l, renderMode: s } = x((p) => p);
|
|
79
|
+
return { ...t, isMounted: o, modalStackMap: e, modalWindowRefs: n, currentModalId: l, renderMode: s, store: x };
|
|
80
|
+
}
|
|
81
|
+
function se({ renderMode: t = C.STACKED, id: o, style: e, className: n, windowClassName: l, windowStyle: s, disableBackgroundScroll: p = !0 }) {
|
|
82
|
+
const c = $(null), y = $(/* @__PURE__ */ new Map()), { setIsMounted: a, setModalWindowRefRef: r, modalStackMap: u, currentModalId: h, store: M } = P(), g = Array.from(u.values()), d = Array.from(u.keys()), S = o || "base-modal-wrapper";
|
|
83
|
+
A(() => {
|
|
84
|
+
if (M.getState().isMounted) throw new Error("Multiple BaseModalRenderer detected. Only one BaseModalRenderer is allowed at a time.");
|
|
85
|
+
return r(y.current), a(!0), () => {
|
|
86
|
+
a(!1), r(void 0);
|
|
87
|
+
};
|
|
88
|
+
}, []), A(() => {
|
|
89
|
+
const f = d[d.length - 1];
|
|
90
|
+
f !== void 0 ? (c.current?.showModal(), document.body.setAttribute("inert", "")) : f === void 0 && (c.current?.close(), document.body.removeAttribute("inert"));
|
|
91
|
+
}, [h]);
|
|
92
|
+
const w = I((f, i) => {
|
|
93
|
+
f ? y.current.set(i, f) : y.current.delete(i);
|
|
94
|
+
}, []), N = () => {
|
|
95
|
+
switch (t) {
|
|
96
|
+
case C.STACKED:
|
|
97
|
+
return g.map(([f, i], m) => /* @__PURE__ */ b(
|
|
98
|
+
"div",
|
|
99
|
+
{
|
|
100
|
+
ref: (v) => w(v, d[m]),
|
|
101
|
+
className: `modal-window ${l || ""}`,
|
|
102
|
+
id: d[m],
|
|
103
|
+
style: { ...s || {} },
|
|
104
|
+
...h !== d[m] ? { inert: "" } : {},
|
|
105
|
+
children: typeof f == "function" ? f() : f
|
|
106
|
+
},
|
|
107
|
+
d[m]
|
|
108
|
+
));
|
|
109
|
+
case C.CURRENT_ONLY:
|
|
110
|
+
return /* @__PURE__ */ b(
|
|
111
|
+
"div",
|
|
112
|
+
{
|
|
113
|
+
id: d[g.length - 1],
|
|
114
|
+
ref: (f) => w(f, d[g.length - 1]),
|
|
115
|
+
className: `modal-window ${l || ""}`,
|
|
116
|
+
style: { ...s || {} },
|
|
117
|
+
children: g[g.length - 1][1] ? null : typeof g[g.length - 1][0] == "function" ? g[g.length - 1][0]() : g[g.length - 1][0]
|
|
118
|
+
},
|
|
119
|
+
d[g.length - 1]
|
|
120
|
+
);
|
|
121
|
+
case C.CURRENT_HIDDEN_STACK:
|
|
122
|
+
return g.map(([f, i], m) => /* @__PURE__ */ b(
|
|
123
|
+
"div",
|
|
124
|
+
{
|
|
125
|
+
ref: (v) => w(v, d[m]),
|
|
126
|
+
id: d[m],
|
|
127
|
+
className: `modal-window ${l || ""}`,
|
|
128
|
+
style: {
|
|
129
|
+
...s || {},
|
|
130
|
+
visibility: h === d[m] ? "visible" : "hidden"
|
|
131
|
+
},
|
|
132
|
+
...h !== d[m] ? { inert: "" } : {},
|
|
133
|
+
children: i ? null : typeof f == "function" ? f() : f
|
|
134
|
+
},
|
|
135
|
+
d[m]
|
|
136
|
+
));
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
return d.length === 0 ? null : /* @__PURE__ */ R(_, { children: [
|
|
140
|
+
E(/* @__PURE__ */ b("style", { children: `${p ? `body:has(dialog#${S}[open]){overflow:hidden}body{scrollbar-gutter:stable}` : ""}dialog#${S}[open]{width:100vw;height:100vh;max-width:100%;max-height:100%}.modal-wrapper{border:none;padding:0;background:unset}.modal-window{display:block;position:absolute;width:100%;height:100%;backdrop-filter:blur(2px);background-color:rgba(0,0,0,.1)}` }), document.head),
|
|
141
|
+
E(/* @__PURE__ */ b("dialog", { ref: c, id: S, className: `modal-wrapper ${n || ""}`, style: e, children: N() }), document.body)
|
|
142
|
+
] });
|
|
143
|
+
}
|
|
144
|
+
function q({ onClick: t, className: o = "", style: e = {}, children: n }) {
|
|
145
|
+
return /* @__PURE__ */ b(
|
|
146
|
+
"div",
|
|
147
|
+
{
|
|
148
|
+
className: `hook-modal-backdrop ${o}`,
|
|
149
|
+
style: {
|
|
150
|
+
position: "fixed",
|
|
151
|
+
inset: 0,
|
|
152
|
+
display: "flex",
|
|
153
|
+
alignItems: "center",
|
|
154
|
+
justifyContent: "center",
|
|
155
|
+
zIndex: 1e3,
|
|
156
|
+
...e
|
|
157
|
+
},
|
|
158
|
+
onClick: t,
|
|
159
|
+
children: n
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const F = (t) => t.stopPropagation();
|
|
164
|
+
function Y({ className: t = "", style: o = {}, children: e }) {
|
|
165
|
+
return /* @__PURE__ */ b(
|
|
166
|
+
"div",
|
|
167
|
+
{
|
|
168
|
+
className: `hook-modal-window ${t}`,
|
|
169
|
+
style: {
|
|
170
|
+
minWidth: 360,
|
|
171
|
+
maxWidth: "90vw",
|
|
172
|
+
backgroundColor: "#fff",
|
|
173
|
+
borderRadius: 30,
|
|
174
|
+
boxShadow: "0 10px 40px rgba(0,0,0,0.2)",
|
|
175
|
+
padding: 25,
|
|
176
|
+
position: "relative",
|
|
177
|
+
...o
|
|
178
|
+
},
|
|
179
|
+
onClick: F,
|
|
180
|
+
children: e
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const G = {
|
|
185
|
+
primary: { backgroundColor: "#2563eb", color: "#fff" },
|
|
186
|
+
secondary: { backgroundColor: "#e5e7eb", color: "#111" },
|
|
187
|
+
danger: { backgroundColor: "#dc2626", color: "#fff" },
|
|
188
|
+
success: { backgroundColor: "#16a34a", color: "#fff" },
|
|
189
|
+
warning: { backgroundColor: "#f59e0b", color: "#111" },
|
|
190
|
+
info: { backgroundColor: "#0ea5e9", color: "#fff" },
|
|
191
|
+
neutral: { backgroundColor: "#6b7280", color: "#fff" }
|
|
192
|
+
};
|
|
193
|
+
function J({ modalWindowId: t, handleAction: o, handleClose: e, config: n }) {
|
|
194
|
+
const { actions: l = [], title: s, content: p, backdropCancel: c, showCloseButton: y, classNames: a = {}, styles: r = {}, variantStyles: u = {} } = n, h = (l.length ? l : [[{ title: "OK", variant: "primary" }]]).filter((d) => d && d.length), M = { ...G, ...u };
|
|
195
|
+
return /* @__PURE__ */ b(
|
|
196
|
+
q,
|
|
197
|
+
{
|
|
198
|
+
onClick: () => {
|
|
199
|
+
c === !0 && e(t);
|
|
200
|
+
},
|
|
201
|
+
className: a.backdrop || "",
|
|
202
|
+
style: r.backdrop,
|
|
203
|
+
children: /* @__PURE__ */ R(
|
|
204
|
+
Y,
|
|
205
|
+
{
|
|
206
|
+
className: a.dialog || "",
|
|
207
|
+
style: r.dialog,
|
|
208
|
+
children: [
|
|
209
|
+
y && /* @__PURE__ */ b(
|
|
210
|
+
"button",
|
|
211
|
+
{
|
|
212
|
+
type: "button",
|
|
213
|
+
className: `hook-dialog-close-button ${a.closeButton || ""}`,
|
|
214
|
+
"aria-label": "Close",
|
|
215
|
+
onClick: () => e(t),
|
|
216
|
+
style: {
|
|
217
|
+
position: "absolute",
|
|
218
|
+
top: 0,
|
|
219
|
+
right: 0,
|
|
220
|
+
transform: "translate(75%, -75%)",
|
|
221
|
+
width: 32,
|
|
222
|
+
height: 32,
|
|
223
|
+
background: "none",
|
|
224
|
+
border: "none",
|
|
225
|
+
fontSize: 21,
|
|
226
|
+
cursor: "pointer",
|
|
227
|
+
lineHeight: 1,
|
|
228
|
+
color: "#555",
|
|
229
|
+
...r.closeButton
|
|
230
|
+
},
|
|
231
|
+
children: "×"
|
|
232
|
+
}
|
|
233
|
+
),
|
|
234
|
+
s && /* @__PURE__ */ b("h3", { className: `hook-dialog-title ${a.title || ""}`, style: { margin: "0 0 15px", fontSize: 20, ...r.title }, children: s }),
|
|
235
|
+
p && /* @__PURE__ */ b("div", { className: `hook-dialog-content ${a.content || ""}`, style: { marginBottom: 15, color: "#555", ...r.content }, children: p }),
|
|
236
|
+
/* @__PURE__ */ b("div", { className: `hook-dialog-actions ${a.actions || ""}`, style: { display: "flex", flexDirection: "column", gap: 10, ...r.actions }, children: h.map((d, S) => {
|
|
237
|
+
const w = d.filter((i) => i.isOnLeft), N = d.filter((i) => !i.isOnLeft), f = (i, m) => {
|
|
238
|
+
const v = i.variant || "secondary", T = M[v] || M.secondary;
|
|
239
|
+
return /* @__PURE__ */ b(
|
|
240
|
+
"button",
|
|
241
|
+
{
|
|
242
|
+
type: "button",
|
|
243
|
+
className: `hook-dialog-action-button hook-dialog-action-${i.variant || "secondary"} ${a.actionButton || ""} ${i.className || ""}`,
|
|
244
|
+
onClick: (W) => {
|
|
245
|
+
i.onClick?.(W, i), o(t, i);
|
|
246
|
+
},
|
|
247
|
+
style: {
|
|
248
|
+
border: "none",
|
|
249
|
+
borderRadius: 15,
|
|
250
|
+
padding: "10px 18px",
|
|
251
|
+
fontSize: 14,
|
|
252
|
+
fontWeight: 800,
|
|
253
|
+
cursor: "pointer",
|
|
254
|
+
...T,
|
|
255
|
+
...r.actionButton,
|
|
256
|
+
...i.style || {}
|
|
257
|
+
},
|
|
258
|
+
children: i.title
|
|
259
|
+
},
|
|
260
|
+
`${i.title}-${m}`
|
|
261
|
+
);
|
|
262
|
+
};
|
|
263
|
+
return /* @__PURE__ */ R("div", { className: `hook-dialog-actions-row ${a.actionsRow || ""}`, style: { display: "flex", gap: 8, justifyContent: "space-between", marginTop: 10, ...r.actionsRow }, children: [
|
|
264
|
+
/* @__PURE__ */ b("div", { className: "hook-dialog-actions-left", style: { display: "flex", gap: 8 }, children: w.map((i, m) => f(i, m)) }),
|
|
265
|
+
/* @__PURE__ */ b("div", { className: "hook-dialog-actions-right", style: { display: "flex", gap: 8 }, children: N.map((i, m) => f(i, m)) })
|
|
266
|
+
] }, S);
|
|
267
|
+
}) })
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
const O = (t) => {
|
|
275
|
+
let o;
|
|
276
|
+
const e = /* @__PURE__ */ new Set(), n = (a, r) => {
|
|
277
|
+
const u = typeof a == "function" ? a(o) : a;
|
|
278
|
+
if (!Object.is(u, o)) {
|
|
279
|
+
const h = o;
|
|
280
|
+
o = r ?? (typeof u != "object" || u === null) ? u : Object.assign({}, o, u), e.forEach((M) => M(o, h));
|
|
281
|
+
}
|
|
282
|
+
}, l = () => o, c = { setState: n, getState: l, getInitialState: () => y, subscribe: (a) => (e.add(a), () => e.delete(a)) }, y = o = t(n, l, c);
|
|
283
|
+
return c;
|
|
284
|
+
}, Q = ((t) => t ? O(t) : O), X = (t) => t;
|
|
285
|
+
function Z(t, o = X) {
|
|
286
|
+
const e = k.useSyncExternalStore(
|
|
287
|
+
t.subscribe,
|
|
288
|
+
k.useCallback(() => o(t.getState()), [t, o]),
|
|
289
|
+
k.useCallback(() => o(t.getInitialState()), [t, o])
|
|
290
|
+
);
|
|
291
|
+
return k.useDebugValue(e), e;
|
|
292
|
+
}
|
|
293
|
+
const B = (t) => {
|
|
294
|
+
const o = Q(t), e = (n) => Z(o, n);
|
|
295
|
+
return Object.assign(e, o), e;
|
|
296
|
+
}, ee = ((t) => t ? B(t) : B), te = ee((t, o) => ({
|
|
297
|
+
instances: [],
|
|
298
|
+
addInstance: (e) => t((n) => ({
|
|
299
|
+
instances: [...n.instances, e]
|
|
300
|
+
})),
|
|
301
|
+
removeInstance: (e) => t((n) => ({
|
|
302
|
+
instances: n.instances.filter((l) => l.id !== e)
|
|
303
|
+
})),
|
|
304
|
+
getInstance: (e) => o().instances.find((n) => n.id === e)
|
|
305
|
+
}));
|
|
306
|
+
function le(t) {
|
|
307
|
+
const { instances: o, addInstance: e, removeInstance: n, getInstance: l } = te(), s = L(), p = I((a) => {
|
|
308
|
+
const r = l(a);
|
|
309
|
+
r && (s.popModal(a), r.config.rejectOnCancel !== !1 ? r.reject(r.config.defaultCancelValue) : r.resolve(r.config.defaultCancelValue), n(a));
|
|
310
|
+
}, [l, n, s]), c = I((a, r) => {
|
|
311
|
+
const u = l(a);
|
|
312
|
+
u && (s.popModal(a), r.isCancel && u.config.rejectOnCancel !== !1 ? u.reject(r.value) : u.resolve(r.value), n(a));
|
|
313
|
+
}, [l, n, s]);
|
|
314
|
+
return [(a) => new Promise((r, u) => {
|
|
315
|
+
const h = Math.random().toString(36).substring(2, 6), M = {
|
|
316
|
+
...t,
|
|
317
|
+
...a,
|
|
318
|
+
classNames: {
|
|
319
|
+
...t?.classNames,
|
|
320
|
+
...a.classNames
|
|
321
|
+
},
|
|
322
|
+
styles: {
|
|
323
|
+
...t?.styles,
|
|
324
|
+
...a.styles
|
|
325
|
+
},
|
|
326
|
+
variantStyles: {
|
|
327
|
+
...t?.variantStyles,
|
|
328
|
+
...a.variantStyles
|
|
329
|
+
}
|
|
330
|
+
}, g = {
|
|
331
|
+
id: h,
|
|
332
|
+
config: M,
|
|
333
|
+
resolve: r,
|
|
334
|
+
reject: u
|
|
335
|
+
};
|
|
336
|
+
e(g), g.id = s.pushModal(h, k.createElement(J, { config: M, modalWindowId: h, handleClose: p, handleAction: c }));
|
|
337
|
+
})];
|
|
338
|
+
}
|
|
339
|
+
export {
|
|
340
|
+
se as BaseModalRenderer,
|
|
341
|
+
le as useHookDialog
|
|
342
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zustand store interface for managing dialog instances.
|
|
3
|
+
* Maintains a centralized list of active dialog instances.
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
interface DialogStore {
|
|
7
|
+
/** Array of currently active dialog instances */
|
|
8
|
+
instances: ConfirmInstance[];
|
|
9
|
+
/** Add a new dialog instance to the store */
|
|
10
|
+
addInstance: (instance: ConfirmInstance) => void;
|
|
11
|
+
/** Remove a dialog instance from the store by ID */
|
|
12
|
+
removeInstance: (id: string) => void;
|
|
13
|
+
/** Get a dialog instance by ID */
|
|
14
|
+
getInstance: (id: string) => ConfirmInstance | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Zustand store for managing dialog instances.
|
|
18
|
+
* Provides centralized state management for all active dialogs.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const { addInstance, removeInstance, getInstance } = useDialogStore();
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare const useDialogStore: import('zustand').UseBoundStore<import('zustand').StoreApi<DialogStore>>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rokku-x/react-hook-dialog",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"author": "rokku-x",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/rokku-x/react-hook-dialog.git"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.cjs.js",
|
|
10
|
+
"module": "dist/index.esm.js",
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@testing-library/jest-dom": "^6.0.0",
|
|
13
|
+
"@testing-library/react": "^14.0.0",
|
|
14
|
+
"@testing-library/user-event": "^14.5.2",
|
|
15
|
+
"@types/jest": "^30.0.0",
|
|
16
|
+
"@types/react": "^18.0.0",
|
|
17
|
+
"@types/react-dom": "^18.0.0",
|
|
18
|
+
"@vitejs/plugin-react": "^5.0.4",
|
|
19
|
+
"typescript": "^5.0.0",
|
|
20
|
+
"vite": "^7.1.9",
|
|
21
|
+
"vite-plugin-dts": "^4.5.4",
|
|
22
|
+
"vitest": "^1.0.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@rokku-x/react-hook-modal": "^0.7.7",
|
|
26
|
+
"react": "^18.0.0",
|
|
27
|
+
"react-dom": "^18.0.0",
|
|
28
|
+
"zustand": "^4.0.0"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.esm.js",
|
|
34
|
+
"require": "./dist/index.cjs.js"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/rokku-x/react-hook-dialog/issues"
|
|
40
|
+
},
|
|
41
|
+
"description": "A React library",
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md"
|
|
45
|
+
],
|
|
46
|
+
"homepage": "https://github.com/rokku-x/react-hook-dialog#readme",
|
|
47
|
+
"keywords": [
|
|
48
|
+
"react",
|
|
49
|
+
"hook",
|
|
50
|
+
"component",
|
|
51
|
+
"library"
|
|
52
|
+
],
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public",
|
|
56
|
+
"provenance": true
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "vite build",
|
|
60
|
+
"prepare": "npm run build",
|
|
61
|
+
"publish:first": "npm publish --access public --provenance false",
|
|
62
|
+
"dev": "vite",
|
|
63
|
+
"preview": "vite preview",
|
|
64
|
+
"test": "vitest"
|
|
65
|
+
},
|
|
66
|
+
"sideEffects": false,
|
|
67
|
+
"types": "dist/index.d.ts",
|
|
68
|
+
"typesVersions": {
|
|
69
|
+
"*": {
|
|
70
|
+
"*": [
|
|
71
|
+
"dist/*"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|