@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 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;
@@ -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
+ }