@rokku-x/react-hook-dialog 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -41
- package/dist/README.md +123 -41
- package/dist/components/ModalWindow.cjs.js +1 -0
- package/dist/components/ModalWindow.esm.js +6 -0
- package/dist/constants/theme.cjs.js +1 -0
- package/dist/constants/theme.esm.js +1 -0
- package/dist/hooks/useHookDialog.cjs.js +1 -0
- package/dist/hooks/useHookDialog.d.ts +2 -7
- package/dist/hooks/useHookDialog.esm.js +1 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -192
- package/dist/store/dialogStore.cjs.js +1 -0
- package/dist/store/dialogStore.d.ts +11 -1
- package/dist/store/dialogStore.esm.js +1 -0
- package/dist/types/index.d.ts +22 -0
- package/dist/utils/utils.cjs.js +1 -0
- package/dist/utils/utils.esm.js +1 -0
- package/package.json +3 -33
package/README.md
CHANGED
|
@@ -72,13 +72,6 @@ function MyComponent() {
|
|
|
72
72
|
}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
## Bundle Size
|
|
76
|
-
|
|
77
|
-
- ESM: ~4.06 kB gzipped (13.48 kB raw)
|
|
78
|
-
- CJS: ~3.48 kB gzipped (9.21 kB raw)
|
|
79
|
-
|
|
80
|
-
Measured with Vite build for v0.0.1.
|
|
81
|
-
|
|
82
75
|
## API Reference
|
|
83
76
|
|
|
84
77
|
### useHookDialog
|
|
@@ -103,7 +96,57 @@ useHookDialog(defaultConfig?: UseHookDialogConfig)
|
|
|
103
96
|
|
|
104
97
|
| Return Value | Type | Description |
|
|
105
98
|
|---|---|---|
|
|
106
|
-
| `requestDialog` | `(config: ConfirmConfig) =>
|
|
99
|
+
| `requestDialog` | `(config: ConfirmConfig) => RequestDialogReturnType<ValidValue>` | Function to open a dialog and get user response. `RequestDialogReturnType<T>` is defined as `Promise<T> & { id: string; context: DialogInstanceContext }` which means the native Promise resolves with `T` while the *returned* value exposes `id` and `context` for programmatic control. |
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Convenience alias shown in the library
|
|
103
|
+
type RequestDialogReturnType<T> = Promise<T> & { id: string; context: DialogInstanceContext };
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Dialog Context & Force Functions ✅
|
|
107
|
+
|
|
108
|
+
The Promise returned from `requestDialog(...)` is augmented with:
|
|
109
|
+
|
|
110
|
+
- `id: string` — unique identifier for the dialog instance
|
|
111
|
+
- `context: DialogInstanceContext` — helper methods to programmatically control the dialog instance
|
|
112
|
+
|
|
113
|
+
You can access the context either directly from the returned Promise (`promise.context`) or via the second value returned from the hook (`getContext(id)`).
|
|
114
|
+
|
|
115
|
+
Dialog context methods:
|
|
116
|
+
|
|
117
|
+
- `forceCancel(forceReject?: boolean)` — close the dialog as a cancellation. If `forceReject` is `true` the promise will be rejected even if the dialog `rejectOnCancel` config is `false`. Otherwise the usual `rejectOnCancel` behavior applies.
|
|
118
|
+
- `forceAction(action: ModalAction)` — programmatically trigger a specific action (resolve/reject according to the action and dialog config).
|
|
119
|
+
- `forceDefault()` — triggers the dialog's default action (first action marked with `isFocused`). Throws if no default action is defined.
|
|
120
|
+
|
|
121
|
+
Example usage:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
const [requestDialog, getContext] = useHookDialog();
|
|
125
|
+
|
|
126
|
+
// open a dialog and get the augmented promise
|
|
127
|
+
const p = requestDialog({
|
|
128
|
+
title: 'Confirm',
|
|
129
|
+
content: 'Proceed?',
|
|
130
|
+
actions: [[
|
|
131
|
+
{ title: 'Cancel', isCancel: true },
|
|
132
|
+
{ title: 'OK', value: true, isFocused: true }
|
|
133
|
+
]]
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
console.log('dialog id:', p.id);
|
|
137
|
+
|
|
138
|
+
// force the default action (same as clicking the focused action)
|
|
139
|
+
p.context.forceDefault();
|
|
140
|
+
|
|
141
|
+
// or cancel programmatically (forces reject by default)
|
|
142
|
+
p.context.forceCancel();
|
|
143
|
+
|
|
144
|
+
// you can also use the helper returned from the hook
|
|
145
|
+
const ctx = getContext(p.id);
|
|
146
|
+
ctx.forceAction({ title: 'OK', value: true });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> Note: `forceDefault()` will throw if no action is marked with `isFocused`. Use `forceAction(...)` to explicitly specify which action to run.
|
|
107
150
|
|
|
108
151
|
#### Default Config Options
|
|
109
152
|
|
|
@@ -276,6 +319,8 @@ function DeleteConfirm() {
|
|
|
276
319
|
}
|
|
277
320
|
```
|
|
278
321
|
|
|
322
|
+

|
|
323
|
+
|
|
279
324
|
### Example 2: Multiple Action Rows
|
|
280
325
|
|
|
281
326
|
```tsx
|
|
@@ -294,6 +339,8 @@ await requestDialog({
|
|
|
294
339
|
});
|
|
295
340
|
```
|
|
296
341
|
|
|
342
|
+

|
|
343
|
+
|
|
297
344
|
### Example 3: Custom Styling
|
|
298
345
|
|
|
299
346
|
```tsx
|
|
@@ -318,6 +365,8 @@ await requestDialog({
|
|
|
318
365
|
});
|
|
319
366
|
```
|
|
320
367
|
|
|
368
|
+

|
|
369
|
+
|
|
321
370
|
### Example 4: Custom Button Variants
|
|
322
371
|
|
|
323
372
|
```tsx
|
|
@@ -338,6 +387,8 @@ await requestDialog({
|
|
|
338
387
|
});
|
|
339
388
|
```
|
|
340
389
|
|
|
390
|
+

|
|
391
|
+
|
|
341
392
|
### Example 5: Button Click Handlers
|
|
342
393
|
|
|
343
394
|
```tsx
|
|
@@ -357,6 +408,8 @@ await requestDialog({
|
|
|
357
408
|
});
|
|
358
409
|
```
|
|
359
410
|
|
|
411
|
+

|
|
412
|
+
|
|
360
413
|
### Example 6: Rich Content
|
|
361
414
|
|
|
362
415
|
```tsx
|
|
@@ -377,6 +430,8 @@ await requestDialog({
|
|
|
377
430
|
});
|
|
378
431
|
```
|
|
379
432
|
|
|
433
|
+

|
|
434
|
+
|
|
380
435
|
### Example 7: Default Configuration
|
|
381
436
|
|
|
382
437
|
```tsx
|
|
@@ -395,6 +450,8 @@ await requestDialog({
|
|
|
395
450
|
});
|
|
396
451
|
```
|
|
397
452
|
|
|
453
|
+

|
|
454
|
+
|
|
398
455
|
### Example 8: Alert Dialog
|
|
399
456
|
|
|
400
457
|
```tsx
|
|
@@ -413,6 +470,8 @@ async function showAlert(message: string) {
|
|
|
413
470
|
showAlert('Operation completed successfully!');
|
|
414
471
|
```
|
|
415
472
|
|
|
473
|
+

|
|
474
|
+
|
|
416
475
|
### Example 9: Multiple Choice with Different Values
|
|
417
476
|
|
|
418
477
|
```tsx
|
|
@@ -439,6 +498,8 @@ const handleSaveOptions = async () => {
|
|
|
439
498
|
};
|
|
440
499
|
```
|
|
441
500
|
|
|
501
|
+

|
|
502
|
+
|
|
442
503
|
### Example 10: Numeric Rating Dialog
|
|
443
504
|
|
|
444
505
|
```tsx
|
|
@@ -468,6 +529,8 @@ const handleRating = async () => {
|
|
|
468
529
|
};
|
|
469
530
|
```
|
|
470
531
|
|
|
532
|
+

|
|
533
|
+
|
|
471
534
|
### Example 11: Conditional Actions Based on Result
|
|
472
535
|
|
|
473
536
|
```tsx
|
|
@@ -515,6 +578,8 @@ const handleFileOperation = async () => {
|
|
|
515
578
|
};
|
|
516
579
|
```
|
|
517
580
|
|
|
581
|
+

|
|
582
|
+
|
|
518
583
|
### Example 12: Handle Cancel vs Reject
|
|
519
584
|
|
|
520
585
|
```tsx
|
|
@@ -544,6 +609,8 @@ const handleWithErrorHandling = async () => {
|
|
|
544
609
|
};
|
|
545
610
|
```
|
|
546
611
|
|
|
612
|
+

|
|
613
|
+
|
|
547
614
|
### Example 13: Form Submission with Validation
|
|
548
615
|
|
|
549
616
|
```tsx
|
|
@@ -587,6 +654,33 @@ const handleFormSubmit = async (formData: any) => {
|
|
|
587
654
|
};
|
|
588
655
|
```
|
|
589
656
|
|
|
657
|
+

|
|
658
|
+
|
|
659
|
+
### Example 14: Boolean Result with Custom Values
|
|
660
|
+
|
|
661
|
+
```tsx
|
|
662
|
+
const [requestDialog] = useHookDialog();
|
|
663
|
+
|
|
664
|
+
const handleLogout = async () => {
|
|
665
|
+
const shouldLogout = await requestDialog({
|
|
666
|
+
title: 'Confirm Logout',
|
|
667
|
+
content: 'Are you sure you want to log out?',
|
|
668
|
+
actions: [[
|
|
669
|
+
{ title: 'Stay Logged In', variant: 'secondary', value: false },
|
|
670
|
+
{ title: 'Log Out', variant: 'danger', value: true }
|
|
671
|
+
]]
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
if (shouldLogout) {
|
|
675
|
+
// Perform logout
|
|
676
|
+
sessionStorage.clear();
|
|
677
|
+
window.location.href = '/login';
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+

|
|
683
|
+
|
|
590
684
|
### Example 15: Form Dialog Returning Values (isReturnSubmit)
|
|
591
685
|
|
|
592
686
|
```tsx
|
|
@@ -623,40 +717,9 @@ async function openProfileDialog() {
|
|
|
623
717
|
}
|
|
624
718
|
```
|
|
625
719
|
|
|
626
|
-
|
|
627
|
-
### Example 14: Boolean Result with Custom Values
|
|
628
|
-
|
|
629
|
-
```tsx
|
|
630
|
-
const [requestDialog] = useHookDialog();
|
|
631
|
-
|
|
632
|
-
const handleLogout = async () => {
|
|
633
|
-
const shouldLogout = await requestDialog({
|
|
634
|
-
title: 'Confirm Logout',
|
|
635
|
-
content: 'Are you sure you want to log out?',
|
|
636
|
-
actions: [[
|
|
637
|
-
{ title: 'Stay Logged In', variant: 'secondary', value: false },
|
|
638
|
-
{ title: 'Log Out', variant: 'danger', value: true }
|
|
639
|
-
]]
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
if (shouldLogout) {
|
|
643
|
-
// Perform logout
|
|
644
|
-
sessionStorage.clear();
|
|
645
|
-
window.location.href = '/login';
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
```
|
|
720
|
+

|
|
649
721
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
1. **Mount `BaseModalRenderer` at root level** - Required for modals to render
|
|
653
|
-
2. **Use default configs for consistency** - Set common styles/behaviors once
|
|
654
|
-
3. **Provide meaningful button labels** - Users should know what each button does
|
|
655
|
-
4. **Use appropriate variants** - Use `danger` for destructive actions, `success` for confirmations
|
|
656
|
-
5. **Keep content concise** - Dialogs should be focused and brief
|
|
657
|
-
6. **Handle both resolve and reject** - Account for cancellation scenarios
|
|
658
|
-
7. **Use `isOnLeft` for secondary actions** - Helps with visual hierarchy
|
|
659
|
-
8. **Customize responsibly** - Maintain accessibility and usability standards
|
|
722
|
+
> Note: `isReturnSubmit` overrides `noActionReturn` and returns the serialized form values as the action `value`. `isSubmit` still triggers `requestSubmit()` to allow native validation flows.
|
|
660
723
|
|
|
661
724
|
## Types
|
|
662
725
|
|
|
@@ -684,6 +747,17 @@ Custom styles for each variant type.
|
|
|
684
747
|
- ARIA labels provided for interactive elements
|
|
685
748
|
- Supports custom ARIA attributes via className injection
|
|
686
749
|
|
|
750
|
+
## Best Practices
|
|
751
|
+
|
|
752
|
+
1. **Mount `BaseModalRenderer` at root level** - Required for modals to render
|
|
753
|
+
2. **Use default configs for consistency** - Set common styles/behaviors once
|
|
754
|
+
3. **Provide meaningful button labels** - Users should know what each button does
|
|
755
|
+
4. **Use appropriate variants** - Use `danger` for destructive actions, `success` for confirmations
|
|
756
|
+
5. **Keep content concise** - Dialogs should be focused and brief
|
|
757
|
+
6. **Handle both resolve and reject** - Account for cancellation scenarios
|
|
758
|
+
7. **Use `isOnLeft` for secondary actions** - Helps with visual hierarchy
|
|
759
|
+
8. **Customize responsibly** - Maintain accessibility and usability standards
|
|
760
|
+
|
|
687
761
|
## Troubleshooting
|
|
688
762
|
|
|
689
763
|
### Dialog not appearing
|
|
@@ -699,6 +773,14 @@ Custom styles for each variant type.
|
|
|
699
773
|
- Ensure action buttons have appropriate `value` or are configured as cancel buttons
|
|
700
774
|
- Check that action click handlers don't prevent default behavior
|
|
701
775
|
|
|
776
|
+
|
|
777
|
+
## Bundle Size
|
|
778
|
+
|
|
779
|
+
- ESM: ~4.06 kB gzipped (13.48 kB raw)
|
|
780
|
+
- CJS: ~3.48 kB gzipped (9.21 kB raw)
|
|
781
|
+
|
|
782
|
+
Measured with Vite build for v0.0.1.
|
|
783
|
+
|
|
702
784
|
## License
|
|
703
785
|
|
|
704
786
|
MIT
|
package/dist/README.md
CHANGED
|
@@ -72,13 +72,6 @@ function MyComponent() {
|
|
|
72
72
|
}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
## Bundle Size
|
|
76
|
-
|
|
77
|
-
- ESM: ~4.06 kB gzipped (13.48 kB raw)
|
|
78
|
-
- CJS: ~3.48 kB gzipped (9.21 kB raw)
|
|
79
|
-
|
|
80
|
-
Measured with Vite build for v0.0.1.
|
|
81
|
-
|
|
82
75
|
## API Reference
|
|
83
76
|
|
|
84
77
|
### useHookDialog
|
|
@@ -103,7 +96,57 @@ useHookDialog(defaultConfig?: UseHookDialogConfig)
|
|
|
103
96
|
|
|
104
97
|
| Return Value | Type | Description |
|
|
105
98
|
|---|---|---|
|
|
106
|
-
| `requestDialog` | `(config: ConfirmConfig) =>
|
|
99
|
+
| `requestDialog` | `(config: ConfirmConfig) => RequestDialogReturnType<ValidValue>` | Function to open a dialog and get user response. `RequestDialogReturnType<T>` is defined as `Promise<T> & { id: string; context: DialogInstanceContext }` which means the native Promise resolves with `T` while the *returned* value exposes `id` and `context` for programmatic control. |
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Convenience alias shown in the library
|
|
103
|
+
type RequestDialogReturnType<T> = Promise<T> & { id: string; context: DialogInstanceContext };
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Dialog Context & Force Functions ✅
|
|
107
|
+
|
|
108
|
+
The Promise returned from `requestDialog(...)` is augmented with:
|
|
109
|
+
|
|
110
|
+
- `id: string` — unique identifier for the dialog instance
|
|
111
|
+
- `context: DialogInstanceContext` — helper methods to programmatically control the dialog instance
|
|
112
|
+
|
|
113
|
+
You can access the context either directly from the returned Promise (`promise.context`) or via the second value returned from the hook (`getContext(id)`).
|
|
114
|
+
|
|
115
|
+
Dialog context methods:
|
|
116
|
+
|
|
117
|
+
- `forceCancel(forceReject?: boolean)` — close the dialog as a cancellation. If `forceReject` is `true` the promise will be rejected even if the dialog `rejectOnCancel` config is `false`. Otherwise the usual `rejectOnCancel` behavior applies.
|
|
118
|
+
- `forceAction(action: ModalAction)` — programmatically trigger a specific action (resolve/reject according to the action and dialog config).
|
|
119
|
+
- `forceDefault()` — triggers the dialog's default action (first action marked with `isFocused`). Throws if no default action is defined.
|
|
120
|
+
|
|
121
|
+
Example usage:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
const [requestDialog, getContext] = useHookDialog();
|
|
125
|
+
|
|
126
|
+
// open a dialog and get the augmented promise
|
|
127
|
+
const p = requestDialog({
|
|
128
|
+
title: 'Confirm',
|
|
129
|
+
content: 'Proceed?',
|
|
130
|
+
actions: [[
|
|
131
|
+
{ title: 'Cancel', isCancel: true },
|
|
132
|
+
{ title: 'OK', value: true, isFocused: true }
|
|
133
|
+
]]
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
console.log('dialog id:', p.id);
|
|
137
|
+
|
|
138
|
+
// force the default action (same as clicking the focused action)
|
|
139
|
+
p.context.forceDefault();
|
|
140
|
+
|
|
141
|
+
// or cancel programmatically (forces reject by default)
|
|
142
|
+
p.context.forceCancel();
|
|
143
|
+
|
|
144
|
+
// you can also use the helper returned from the hook
|
|
145
|
+
const ctx = getContext(p.id);
|
|
146
|
+
ctx.forceAction({ title: 'OK', value: true });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> Note: `forceDefault()` will throw if no action is marked with `isFocused`. Use `forceAction(...)` to explicitly specify which action to run.
|
|
107
150
|
|
|
108
151
|
#### Default Config Options
|
|
109
152
|
|
|
@@ -276,6 +319,8 @@ function DeleteConfirm() {
|
|
|
276
319
|
}
|
|
277
320
|
```
|
|
278
321
|
|
|
322
|
+

|
|
323
|
+
|
|
279
324
|
### Example 2: Multiple Action Rows
|
|
280
325
|
|
|
281
326
|
```tsx
|
|
@@ -294,6 +339,8 @@ await requestDialog({
|
|
|
294
339
|
});
|
|
295
340
|
```
|
|
296
341
|
|
|
342
|
+

|
|
343
|
+
|
|
297
344
|
### Example 3: Custom Styling
|
|
298
345
|
|
|
299
346
|
```tsx
|
|
@@ -318,6 +365,8 @@ await requestDialog({
|
|
|
318
365
|
});
|
|
319
366
|
```
|
|
320
367
|
|
|
368
|
+

|
|
369
|
+
|
|
321
370
|
### Example 4: Custom Button Variants
|
|
322
371
|
|
|
323
372
|
```tsx
|
|
@@ -338,6 +387,8 @@ await requestDialog({
|
|
|
338
387
|
});
|
|
339
388
|
```
|
|
340
389
|
|
|
390
|
+

|
|
391
|
+
|
|
341
392
|
### Example 5: Button Click Handlers
|
|
342
393
|
|
|
343
394
|
```tsx
|
|
@@ -357,6 +408,8 @@ await requestDialog({
|
|
|
357
408
|
});
|
|
358
409
|
```
|
|
359
410
|
|
|
411
|
+

|
|
412
|
+
|
|
360
413
|
### Example 6: Rich Content
|
|
361
414
|
|
|
362
415
|
```tsx
|
|
@@ -377,6 +430,8 @@ await requestDialog({
|
|
|
377
430
|
});
|
|
378
431
|
```
|
|
379
432
|
|
|
433
|
+

|
|
434
|
+
|
|
380
435
|
### Example 7: Default Configuration
|
|
381
436
|
|
|
382
437
|
```tsx
|
|
@@ -395,6 +450,8 @@ await requestDialog({
|
|
|
395
450
|
});
|
|
396
451
|
```
|
|
397
452
|
|
|
453
|
+

|
|
454
|
+
|
|
398
455
|
### Example 8: Alert Dialog
|
|
399
456
|
|
|
400
457
|
```tsx
|
|
@@ -413,6 +470,8 @@ async function showAlert(message: string) {
|
|
|
413
470
|
showAlert('Operation completed successfully!');
|
|
414
471
|
```
|
|
415
472
|
|
|
473
|
+

|
|
474
|
+
|
|
416
475
|
### Example 9: Multiple Choice with Different Values
|
|
417
476
|
|
|
418
477
|
```tsx
|
|
@@ -439,6 +498,8 @@ const handleSaveOptions = async () => {
|
|
|
439
498
|
};
|
|
440
499
|
```
|
|
441
500
|
|
|
501
|
+

|
|
502
|
+
|
|
442
503
|
### Example 10: Numeric Rating Dialog
|
|
443
504
|
|
|
444
505
|
```tsx
|
|
@@ -468,6 +529,8 @@ const handleRating = async () => {
|
|
|
468
529
|
};
|
|
469
530
|
```
|
|
470
531
|
|
|
532
|
+

|
|
533
|
+
|
|
471
534
|
### Example 11: Conditional Actions Based on Result
|
|
472
535
|
|
|
473
536
|
```tsx
|
|
@@ -515,6 +578,8 @@ const handleFileOperation = async () => {
|
|
|
515
578
|
};
|
|
516
579
|
```
|
|
517
580
|
|
|
581
|
+

|
|
582
|
+
|
|
518
583
|
### Example 12: Handle Cancel vs Reject
|
|
519
584
|
|
|
520
585
|
```tsx
|
|
@@ -544,6 +609,8 @@ const handleWithErrorHandling = async () => {
|
|
|
544
609
|
};
|
|
545
610
|
```
|
|
546
611
|
|
|
612
|
+

|
|
613
|
+
|
|
547
614
|
### Example 13: Form Submission with Validation
|
|
548
615
|
|
|
549
616
|
```tsx
|
|
@@ -587,6 +654,33 @@ const handleFormSubmit = async (formData: any) => {
|
|
|
587
654
|
};
|
|
588
655
|
```
|
|
589
656
|
|
|
657
|
+

|
|
658
|
+
|
|
659
|
+
### Example 14: Boolean Result with Custom Values
|
|
660
|
+
|
|
661
|
+
```tsx
|
|
662
|
+
const [requestDialog] = useHookDialog();
|
|
663
|
+
|
|
664
|
+
const handleLogout = async () => {
|
|
665
|
+
const shouldLogout = await requestDialog({
|
|
666
|
+
title: 'Confirm Logout',
|
|
667
|
+
content: 'Are you sure you want to log out?',
|
|
668
|
+
actions: [[
|
|
669
|
+
{ title: 'Stay Logged In', variant: 'secondary', value: false },
|
|
670
|
+
{ title: 'Log Out', variant: 'danger', value: true }
|
|
671
|
+
]]
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
if (shouldLogout) {
|
|
675
|
+
// Perform logout
|
|
676
|
+
sessionStorage.clear();
|
|
677
|
+
window.location.href = '/login';
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+

|
|
683
|
+
|
|
590
684
|
### Example 15: Form Dialog Returning Values (isReturnSubmit)
|
|
591
685
|
|
|
592
686
|
```tsx
|
|
@@ -623,40 +717,9 @@ async function openProfileDialog() {
|
|
|
623
717
|
}
|
|
624
718
|
```
|
|
625
719
|
|
|
626
|
-
|
|
627
|
-
### Example 14: Boolean Result with Custom Values
|
|
628
|
-
|
|
629
|
-
```tsx
|
|
630
|
-
const [requestDialog] = useHookDialog();
|
|
631
|
-
|
|
632
|
-
const handleLogout = async () => {
|
|
633
|
-
const shouldLogout = await requestDialog({
|
|
634
|
-
title: 'Confirm Logout',
|
|
635
|
-
content: 'Are you sure you want to log out?',
|
|
636
|
-
actions: [[
|
|
637
|
-
{ title: 'Stay Logged In', variant: 'secondary', value: false },
|
|
638
|
-
{ title: 'Log Out', variant: 'danger', value: true }
|
|
639
|
-
]]
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
if (shouldLogout) {
|
|
643
|
-
// Perform logout
|
|
644
|
-
sessionStorage.clear();
|
|
645
|
-
window.location.href = '/login';
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
```
|
|
720
|
+

|
|
649
721
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
1. **Mount `BaseModalRenderer` at root level** - Required for modals to render
|
|
653
|
-
2. **Use default configs for consistency** - Set common styles/behaviors once
|
|
654
|
-
3. **Provide meaningful button labels** - Users should know what each button does
|
|
655
|
-
4. **Use appropriate variants** - Use `danger` for destructive actions, `success` for confirmations
|
|
656
|
-
5. **Keep content concise** - Dialogs should be focused and brief
|
|
657
|
-
6. **Handle both resolve and reject** - Account for cancellation scenarios
|
|
658
|
-
7. **Use `isOnLeft` for secondary actions** - Helps with visual hierarchy
|
|
659
|
-
8. **Customize responsibly** - Maintain accessibility and usability standards
|
|
722
|
+
> Note: `isReturnSubmit` overrides `noActionReturn` and returns the serialized form values as the action `value`. `isSubmit` still triggers `requestSubmit()` to allow native validation flows.
|
|
660
723
|
|
|
661
724
|
## Types
|
|
662
725
|
|
|
@@ -684,6 +747,17 @@ Custom styles for each variant type.
|
|
|
684
747
|
- ARIA labels provided for interactive elements
|
|
685
748
|
- Supports custom ARIA attributes via className injection
|
|
686
749
|
|
|
750
|
+
## Best Practices
|
|
751
|
+
|
|
752
|
+
1. **Mount `BaseModalRenderer` at root level** - Required for modals to render
|
|
753
|
+
2. **Use default configs for consistency** - Set common styles/behaviors once
|
|
754
|
+
3. **Provide meaningful button labels** - Users should know what each button does
|
|
755
|
+
4. **Use appropriate variants** - Use `danger` for destructive actions, `success` for confirmations
|
|
756
|
+
5. **Keep content concise** - Dialogs should be focused and brief
|
|
757
|
+
6. **Handle both resolve and reject** - Account for cancellation scenarios
|
|
758
|
+
7. **Use `isOnLeft` for secondary actions** - Helps with visual hierarchy
|
|
759
|
+
8. **Customize responsibly** - Maintain accessibility and usability standards
|
|
760
|
+
|
|
687
761
|
## Troubleshooting
|
|
688
762
|
|
|
689
763
|
### Dialog not appearing
|
|
@@ -699,6 +773,14 @@ Custom styles for each variant type.
|
|
|
699
773
|
- Ensure action buttons have appropriate `value` or are configured as cancel buttons
|
|
700
774
|
- Check that action click handlers don't prevent default behavior
|
|
701
775
|
|
|
776
|
+
|
|
777
|
+
## Bundle Size
|
|
778
|
+
|
|
779
|
+
- ESM: ~4.06 kB gzipped (13.48 kB raw)
|
|
780
|
+
- CJS: ~3.48 kB gzipped (9.21 kB raw)
|
|
781
|
+
|
|
782
|
+
Measured with Vite build for v0.0.1.
|
|
783
|
+
|
|
702
784
|
## License
|
|
703
785
|
|
|
704
786
|
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("react/jsx-runtime"),t=require("../constants/theme.cjs.js"),o=require("../utils/utils.cjs.js"),n=require("@rokku-x/react-hook-modal"),i=require("react"),a=n.ModalWindow;exports.default=function({modalWindowId:s,handleAction:l,handleClose:r,config:c}){const{actions:u=[],title:d,backdropCancel:f,showCloseButton:m,classNames:p={},styles:h={},variantStyles:g={}}=c;let{content:y}=c;const b=(u.length?u:[[{title:"OK",variant:"primary"}]]).filter(e=>e&&e.length),x={...t.baseVariantStyles,...g},k=i.useRef(null),v=i.useRef(null),j=i.useRef(null);return i.useEffect(()=>{const e=k.current;if(!e)return;const t=document.activeElement;if(v.current)v.current.focus();else{const t=e.querySelector("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])");t?t.focus():(e.setAttribute("tabindex","-1"),e.focus())}const o=t=>{if("Escape"===t.key)t.stopPropagation(),r(s);else if("Tab"===t.key){const o=Array.from(e.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])")).filter(Boolean);if(0===o.length)return void t.preventDefault();const n=o[0],i=o[o.length-1];t.shiftKey?document.activeElement===n&&(t.preventDefault(),i.focus()):document.activeElement===i&&(t.preventDefault(),n.focus())}};return e.addEventListener("keydown",o),()=>{e.removeEventListener("keydown",o),t&&t.focus&&t.focus()}},[r,s]),o.IsForm(y)&&(y=i.cloneElement(y,{ref:j})),e.jsx(n.ModalBackdrop,{onClick:()=>f&&r(s),className:p.backdrop||"",style:h.backdrop,children:e.jsxs(a,{ref:k,role:"dialog","aria-modal":"true","aria-labelledby":d?`${s}-title`:void 0,className:p.dialog||"",style:h.dialog,children:[m&&e.jsx("button",{type:"button",className:`hook-dialog-close-button ${p.closeButton||""}`,"aria-label":"Close",onClick:()=>r(s),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",...h.closeButton},children:"×"}),d&&e.jsx("h3",{id:`${s}-title`,className:`hook-dialog-title ${p.title||""}`,style:{margin:"0 0 15px",fontSize:20,...h.title},children:d}),y&&e.jsx("div",{className:`hook-dialog-content ${p.content||""}`,style:{marginBottom:15,color:"#555",...h.content},children:y}),e.jsx("div",{className:`hook-dialog-actions ${p.actions||""}`,style:{display:"flex",flexDirection:"column",gap:10,paddingTop:15,...h.actions},children:b.map((t,n)=>{const i=t.filter(e=>e.isOnLeft),a=t.filter(e=>!e.isOnLeft),r=(t,n)=>{const i=x[t.variant||"secondary"]||x.secondary;return e.jsx("button",{ref:e=>{t.isFocused&&e&&(v.current=e)},"data-action-focused":t.isFocused?"true":void 0,className:`hook-dialog-action-button hook-dialog-action-${t.variant||"secondary"} ${p.actionButton||""} ${t.className||""}`,onClick:e=>{try{t.onClick?.(e,t)}catch{}return t.isSubmit&&c.isReturnSubmit&&j.current?l(s,t,o.FormDataToObject(new FormData(j.current))):(t.isSubmit&&j.current?.requestSubmit(),t.noActionReturn?e.stopPropagation():void l(s,t))},style:{border:"none",borderRadius:15,padding:"10px 18px",fontSize:14,fontWeight:800,cursor:"pointer",...i,...h.actionButton,...t.style||{}},children:t.title},`${t.title}-${n}`)};return e.jsxs("div",{className:`hook-dialog-actions-row ${p.actionsRow||""}`,style:{display:"flex",gap:8,justifyContent:"space-between",...h.actionsRow},children:[e.jsx("div",{className:"hook-dialog-actions-left",style:{display:"flex",gap:8},children:i.map((e,t)=>r(e,t))}),e.jsx("div",{className:"hook-dialog-actions-right",style:{display:"flex",gap:8},children:a.map((e,t)=>r(e,t))})]},n)})})]})})};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{jsx as t,jsxs as e}from"react/jsx-runtime";import{baseVariantStyles as o}from"../constants/theme.esm.js";import{IsForm as n,FormDataToObject as i}from"../utils/utils.esm.js";import{ModalBackdrop as a,ModalWindow as l}from"@rokku-x/react-hook-modal";import r,{useRef as s,useEffect as c}from"react";const d=l;function u({modalWindowId:l,handleAction:u,handleClose:m,config:f}){const{actions:p=[],title:h,backdropCancel:g,showCloseButton:y,classNames:b={},styles:k={},variantStyles:v={}}=f;let{content:x}=f;const N=(p.length?p:[[{title:"OK",variant:"primary"}]]).filter(t=>t&&t.length),$={...o,...v},w=s(null),S=s(null),C=s(null);return c(()=>{const t=w.current;if(!t)return;const e=document.activeElement;if(S.current)S.current.focus();else{const e=t.querySelector("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])");e?e.focus():(t.setAttribute("tabindex","-1"),t.focus())}const o=e=>{if("Escape"===e.key)e.stopPropagation(),m(l);else if("Tab"===e.key){const o=Array.from(t.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])")).filter(Boolean);if(0===o.length)return void e.preventDefault();const n=o[0],i=o[o.length-1];e.shiftKey?document.activeElement===n&&(e.preventDefault(),i.focus()):document.activeElement===i&&(e.preventDefault(),n.focus())}};return t.addEventListener("keydown",o),()=>{t.removeEventListener("keydown",o),e&&e.focus&&e.focus()}},[m,l]),n(x)&&(x=r.cloneElement(x,{ref:C})),/* @__PURE__ */t(a,{onClick:()=>g&&m(l),className:b.backdrop||"",style:k.backdrop,children:/* @__PURE__ */e(d,{ref:w,role:"dialog","aria-modal":"true","aria-labelledby":h?`${l}-title`:void 0,className:b.dialog||"",style:k.dialog,children:[y&&/* @__PURE__ */t("button",{type:"button",className:`hook-dialog-close-button ${b.closeButton||""}`,"aria-label":"Close",onClick:()=>m(l),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",...k.closeButton},children:"×"}),h&&/* @__PURE__ */t("h3",{id:`${l}-title`,className:`hook-dialog-title ${b.title||""}`,style:{margin:"0 0 15px",fontSize:20,...k.title},children:h}),x&&/* @__PURE__ */t("div",{className:`hook-dialog-content ${b.content||""}`,style:{marginBottom:15,color:"#555",...k.content},children:x}),
|
|
2
|
+
/* @__PURE__ */t("div",{className:`hook-dialog-actions ${b.actions||""}`,style:{display:"flex",flexDirection:"column",gap:10,paddingTop:15,...k.actions},children:N.map((o,n)=>{const a=o.filter(t=>t.isOnLeft),r=o.filter(t=>!t.isOnLeft),s=(e,o)=>{const n=$[e.variant||"secondary"]||$.secondary;/* @__PURE__ */
|
|
3
|
+
return t("button",{ref:t=>{e.isFocused&&t&&(S.current=t)},"data-action-focused":e.isFocused?"true":void 0,className:`hook-dialog-action-button hook-dialog-action-${e.variant||"secondary"} ${b.actionButton||""} ${e.className||""}`,onClick:t=>{try{e.onClick?.(t,e)}catch{}return e.isSubmit&&f.isReturnSubmit&&C.current?u(l,e,i(new FormData(C.current))):(e.isSubmit&&C.current?.requestSubmit(),e.noActionReturn?t.stopPropagation():void u(l,e))},style:{border:"none",borderRadius:15,padding:"10px 18px",fontSize:14,fontWeight:800,cursor:"pointer",...n,...k.actionButton,...e.style||{}},children:e.title},`${e.title}-${o}`)};/* @__PURE__ */
|
|
4
|
+
return e("div",{className:`hook-dialog-actions-row ${b.actionsRow||""}`,style:{display:"flex",gap:8,justifyContent:"space-between",...k.actionsRow},children:[
|
|
5
|
+
/* @__PURE__ */t("div",{className:"hook-dialog-actions-left",style:{display:"flex",gap:8},children:a.map((t,e)=>s(t,e))}),
|
|
6
|
+
/* @__PURE__ */t("div",{className:"hook-dialog-actions-right",style:{display:"flex",gap:8},children:r.map((t,e)=>s(t,e))})]},n)})})]})})}export{u as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});exports.baseVariantStyles={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"}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o={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"}};export{o as baseVariantStyles};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("@rokku-x/react-hook-modal"),s=require("../store/dialogStore.cjs.js");exports.default=function(o){const{addInstance:t,handleAction:a,handleClose:n,getContext:l}=s.useDialogStore();return e.useBaseModal(),[function(e){const s=Math.random().toString(36).substring(2,6);(e.actions?.flat().filter(e=>e.isFocused)||[]).length>1&&console.warn(`useHookDialog: Multiple actions are marked as isFocused in dialog "${s}". Only one action should be focused.`);const a=e.actions?.some(e=>e.some(e=>e.isCancel)),n=e.backdropCancel??o?.backdropCancel??!a,r={...o,...e,classNames:{...o?.classNames,...e.classNames},styles:{...o?.styles,...e.styles},variantStyles:{...o?.variantStyles,...e.variantStyles},backdropCancel:n},c=new Promise((e,o)=>{t({id:s,config:r,resolve:e,reject:o})});return Object.defineProperties(c,{context:{get:()=>l(s),enumerable:!0,configurable:!1},id:{get:()=>s,enumerable:!0,configurable:!1}}),c},l]};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UseHookDialogConfig, ValidValue, UseHookDialogReturnType } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* Hook for displaying confirmation dialogs and alerts.
|
|
4
4
|
*
|
|
@@ -34,9 +34,4 @@ import { FormDataObject, ConfirmConfig, UseHookDialogConfig, ValidValue } from '
|
|
|
34
34
|
* });
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
|
-
export default function useHookDialog<T = ValidValue>(defaultConfig?: UseHookDialogConfig):
|
|
38
|
-
<U = FormDataObject>(config: ConfirmConfig & {
|
|
39
|
-
isReturnSubmit: true;
|
|
40
|
-
}): Promise<U>;
|
|
41
|
-
<U = T>(config: ConfirmConfig): Promise<U>;
|
|
42
|
-
}[];
|
|
37
|
+
export default function useHookDialog<T = ValidValue>(defaultConfig?: UseHookDialogConfig): UseHookDialogReturnType<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useBaseModal as e}from"@rokku-x/react-hook-modal";import{useDialogStore as o}from"../store/dialogStore.esm.js";function s(s){const{addInstance:t,handleAction:a,handleClose:n,getContext:r}=o();return e(),[function(e){const o=Math.random().toString(36).substring(2,6);(e.actions?.flat().filter(e=>e.isFocused)||[]).length>1&&console.warn(`useHookDialog: Multiple actions are marked as isFocused in dialog "${o}". Only one action should be focused.`);const a=e.actions?.some(e=>e.some(e=>e.isCancel)),n=e.backdropCancel??s?.backdropCancel??!a,l={...s,...e,classNames:{...s?.classNames,...e.classNames},styles:{...s?.styles,...e.styles},variantStyles:{...s?.variantStyles,...e.variantStyles},backdropCancel:n},c=new Promise((e,s)=>{t({id:o,config:l,resolve:e,reject:s})});return Object.defineProperties(c,{context:{get:()=>r(o),enumerable:!0,configurable:!1},id:{get:()=>o,enumerable:!0,configurable:!1}}),c},r]}export{s as default};
|
package/dist/index.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@rokku-x/react-hook-modal"),o=require("./hooks/useHookDialog.cjs.js");Object.defineProperty(exports,"BaseModalRenderer",{enumerable:!0,get:()=>e.BaseModalRenderer}),exports.useHookDialog=o.default;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,192 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { ModalBackdrop as A, ModalWindow as j, useBaseModal as F } from "@rokku-x/react-hook-modal";
|
|
3
|
-
import { BaseModalRenderer as X } from "@rokku-x/react-hook-modal";
|
|
4
|
-
import R, { useRef as E, useEffect as O, useCallback as M } from "react";
|
|
5
|
-
import { jsx as p, jsxs as w } from "react/jsx-runtime";
|
|
6
|
-
import { create as L } from "zustand";
|
|
7
|
-
const V = {
|
|
8
|
-
primary: { backgroundColor: "#2563eb", color: "#fff" },
|
|
9
|
-
secondary: { backgroundColor: "#e5e7eb", color: "#111" },
|
|
10
|
-
danger: { backgroundColor: "#dc2626", color: "#fff" },
|
|
11
|
-
success: { backgroundColor: "#16a34a", color: "#fff" },
|
|
12
|
-
warning: { backgroundColor: "#f59e0b", color: "#111" },
|
|
13
|
-
info: { backgroundColor: "#0ea5e9", color: "#fff" },
|
|
14
|
-
neutral: { backgroundColor: "#6b7280", color: "#fff" }
|
|
15
|
-
};
|
|
16
|
-
function q(t) {
|
|
17
|
-
const u = {};
|
|
18
|
-
return t.forEach((s, n) => {
|
|
19
|
-
if (n in u) {
|
|
20
|
-
const a = u[n];
|
|
21
|
-
Array.isArray(a) ? a.push(s) : u[n] = [a, s];
|
|
22
|
-
} else
|
|
23
|
-
u[n] = s;
|
|
24
|
-
}), u;
|
|
25
|
-
}
|
|
26
|
-
const z = (t) => R.isValidElement(t) && (t.type === "form" || typeof t.type == "string" && t.type.toLowerCase() === "form"), I = j;
|
|
27
|
-
function K({ modalWindowId: t, handleAction: u, handleClose: s, config: n }) {
|
|
28
|
-
const { actions: a = [], title: d, backdropCancel: S, showCloseButton: N, classNames: f = {}, styles: o = {}, variantStyles: r = {} } = n;
|
|
29
|
-
let { content: l } = n;
|
|
30
|
-
const y = (a.length ? a : [[{ title: "OK", variant: "primary" }]]).filter((i) => i && i.length), b = { ...V, ...r }, k = () => S && s(t), $ = E(null), B = E(null), x = E(null);
|
|
31
|
-
return O(() => {
|
|
32
|
-
const i = $.current;
|
|
33
|
-
if (!i) return;
|
|
34
|
-
const h = document.activeElement;
|
|
35
|
-
if (B.current)
|
|
36
|
-
B.current.focus();
|
|
37
|
-
else {
|
|
38
|
-
const c = i.querySelector("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])");
|
|
39
|
-
c ? c.focus() : (i.setAttribute("tabindex", "-1"), i.focus());
|
|
40
|
-
}
|
|
41
|
-
const C = (c) => {
|
|
42
|
-
if (c.key === "Escape")
|
|
43
|
-
c.stopPropagation(), s(t);
|
|
44
|
-
else if (c.key === "Tab") {
|
|
45
|
-
const g = Array.from(i.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])")).filter(Boolean);
|
|
46
|
-
if (g.length === 0) {
|
|
47
|
-
c.preventDefault();
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const e = g[0], m = g[g.length - 1];
|
|
51
|
-
c.shiftKey ? document.activeElement === e && (c.preventDefault(), m.focus()) : document.activeElement === m && (c.preventDefault(), e.focus());
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
return i.addEventListener("keydown", C), () => {
|
|
55
|
-
i.removeEventListener("keydown", C), h && h.focus && h.focus();
|
|
56
|
-
};
|
|
57
|
-
}, [s, t]), z(l) && (l = R.cloneElement(l, { ref: x })), /* @__PURE__ */ p(
|
|
58
|
-
A,
|
|
59
|
-
{
|
|
60
|
-
onClick: k,
|
|
61
|
-
className: f.backdrop || "",
|
|
62
|
-
style: o.backdrop,
|
|
63
|
-
children: /* @__PURE__ */ w(
|
|
64
|
-
I,
|
|
65
|
-
{
|
|
66
|
-
ref: $,
|
|
67
|
-
role: "dialog",
|
|
68
|
-
"aria-modal": "true",
|
|
69
|
-
"aria-labelledby": d ? `${t}-title` : void 0,
|
|
70
|
-
className: f.dialog || "",
|
|
71
|
-
style: o.dialog,
|
|
72
|
-
children: [
|
|
73
|
-
N && /* @__PURE__ */ p(
|
|
74
|
-
"button",
|
|
75
|
-
{
|
|
76
|
-
type: "button",
|
|
77
|
-
className: `hook-dialog-close-button ${f.closeButton || ""}`,
|
|
78
|
-
"aria-label": "Close",
|
|
79
|
-
onClick: () => s(t),
|
|
80
|
-
style: {
|
|
81
|
-
position: "absolute",
|
|
82
|
-
top: 0,
|
|
83
|
-
right: 0,
|
|
84
|
-
transform: "translate(75%, -75%)",
|
|
85
|
-
width: 32,
|
|
86
|
-
height: 32,
|
|
87
|
-
background: "none",
|
|
88
|
-
border: "none",
|
|
89
|
-
fontSize: 21,
|
|
90
|
-
cursor: "pointer",
|
|
91
|
-
lineHeight: 1,
|
|
92
|
-
color: "#555",
|
|
93
|
-
...o.closeButton
|
|
94
|
-
},
|
|
95
|
-
children: "×"
|
|
96
|
-
}
|
|
97
|
-
),
|
|
98
|
-
d && /* @__PURE__ */ p("h3", { id: `${t}-title`, className: `hook-dialog-title ${f.title || ""}`, style: { margin: "0 0 15px", fontSize: 20, ...o.title }, children: d }),
|
|
99
|
-
l && /* @__PURE__ */ p("div", { className: `hook-dialog-content ${f.content || ""}`, style: { marginBottom: 15, color: "#555", ...o.content }, children: l }),
|
|
100
|
-
/* @__PURE__ */ p("div", { className: `hook-dialog-actions ${f.actions || ""}`, style: { display: "flex", flexDirection: "column", gap: 10, paddingTop: 15, ...o.actions }, children: y.map((i, h) => {
|
|
101
|
-
const C = i.filter((e) => e.isOnLeft), c = i.filter((e) => !e.isOnLeft), g = (e, m) => {
|
|
102
|
-
const D = b[e.variant || "secondary"] || b.secondary;
|
|
103
|
-
return /* @__PURE__ */ p(
|
|
104
|
-
"button",
|
|
105
|
-
{
|
|
106
|
-
ref: (v) => {
|
|
107
|
-
e.isFocused && v && (B.current = v);
|
|
108
|
-
},
|
|
109
|
-
"data-action-focused": e.isFocused ? "true" : void 0,
|
|
110
|
-
className: `hook-dialog-action-button hook-dialog-action-${e.variant || "secondary"} ${f.actionButton || ""} ${e.className || ""}`,
|
|
111
|
-
onClick: (v) => {
|
|
112
|
-
if (e.onClick?.(v, e), e.isSubmit && n.isReturnSubmit && x.current) return u(t, e, q(new FormData(x.current)));
|
|
113
|
-
if (e.isSubmit && x.current?.requestSubmit(), e.noActionReturn) return v.stopPropagation();
|
|
114
|
-
u(t, e);
|
|
115
|
-
},
|
|
116
|
-
style: {
|
|
117
|
-
border: "none",
|
|
118
|
-
borderRadius: 15,
|
|
119
|
-
padding: "10px 18px",
|
|
120
|
-
fontSize: 14,
|
|
121
|
-
fontWeight: 800,
|
|
122
|
-
cursor: "pointer",
|
|
123
|
-
...D,
|
|
124
|
-
...o.actionButton,
|
|
125
|
-
...e.style || {}
|
|
126
|
-
},
|
|
127
|
-
children: e.title
|
|
128
|
-
},
|
|
129
|
-
`${e.title}-${m}`
|
|
130
|
-
);
|
|
131
|
-
};
|
|
132
|
-
return /* @__PURE__ */ w("div", { className: `hook-dialog-actions-row ${f.actionsRow || ""}`, style: { display: "flex", gap: 8, justifyContent: "space-between", ...o.actionsRow }, children: [
|
|
133
|
-
/* @__PURE__ */ p("div", { className: "hook-dialog-actions-left", style: { display: "flex", gap: 8 }, children: C.map((e, m) => g(e, m)) }),
|
|
134
|
-
/* @__PURE__ */ p("div", { className: "hook-dialog-actions-right", style: { display: "flex", gap: 8 }, children: c.map((e, m) => g(e, m)) })
|
|
135
|
-
] }, h);
|
|
136
|
-
}) })
|
|
137
|
-
]
|
|
138
|
-
}
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
const P = L((t, u) => ({
|
|
144
|
-
instances: [],
|
|
145
|
-
addInstance: (s) => t((n) => ({
|
|
146
|
-
instances: [...n.instances, s]
|
|
147
|
-
})),
|
|
148
|
-
removeInstance: (s) => t((n) => ({
|
|
149
|
-
instances: n.instances.filter((a) => a.id !== s)
|
|
150
|
-
})),
|
|
151
|
-
getInstance: (s) => u().instances.find((n) => n.id === s)
|
|
152
|
-
}));
|
|
153
|
-
function Q(t) {
|
|
154
|
-
const { instances: u, addInstance: s, removeInstance: n, getInstance: a } = P(), d = F(), S = M((o) => {
|
|
155
|
-
const r = a(o);
|
|
156
|
-
r && (d.popModal(o), r.config.rejectOnCancel !== !1 ? r.reject(r.config.defaultCancelValue) : r.resolve(r.config.defaultCancelValue), n(o));
|
|
157
|
-
}, [a, n, d]), N = M((o, r) => {
|
|
158
|
-
const l = a(o);
|
|
159
|
-
l && (d.popModal(o), r.isCancel && l.config.rejectOnCancel !== !1 ? l.reject(r.value) : l.resolve(r.value), n(o));
|
|
160
|
-
}, [a, n, d]);
|
|
161
|
-
function f(o) {
|
|
162
|
-
return new Promise((r, l) => {
|
|
163
|
-
const y = Math.random().toString(36).substring(2, 6), b = {
|
|
164
|
-
...t,
|
|
165
|
-
...o,
|
|
166
|
-
classNames: {
|
|
167
|
-
...t?.classNames,
|
|
168
|
-
...o.classNames
|
|
169
|
-
},
|
|
170
|
-
styles: {
|
|
171
|
-
...t?.styles,
|
|
172
|
-
...o.styles
|
|
173
|
-
},
|
|
174
|
-
variantStyles: {
|
|
175
|
-
...t?.variantStyles,
|
|
176
|
-
...o.variantStyles
|
|
177
|
-
}
|
|
178
|
-
}, k = {
|
|
179
|
-
id: y,
|
|
180
|
-
config: b,
|
|
181
|
-
resolve: r,
|
|
182
|
-
reject: l
|
|
183
|
-
};
|
|
184
|
-
s(k), k.id = d.pushModal(y, R.createElement(K, { config: b, modalWindowId: y, handleClose: S, handleAction: N }));
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
return [f];
|
|
188
|
-
}
|
|
189
|
-
export {
|
|
190
|
-
X as BaseModalRenderer,
|
|
191
|
-
Q as useHookDialog
|
|
192
|
-
};
|
|
1
|
+
import{BaseModalRenderer as o}from"@rokku-x/react-hook-modal";import{default as a}from"./hooks/useHookDialog.esm.js";export{o as BaseModalRenderer,a as useHookDialog};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("../components/ModalWindow.cjs.js"),n=require("@rokku-x/react-hook-modal"),o=require("react"),t=require("zustand"),a=n.useBaseModal(),c=t.create((n,t)=>({instances:[],addInstance:c=>{n(e=>({instances:[...e.instances,c]})),c.id=a.pushModal(c.id,o.createElement(e.default,{config:c.config,modalWindowId:c.id,handleClose:t().handleClose,handleAction:t().handleAction}))},removeInstance:e=>n(n=>({instances:n.instances.filter(n=>n.id!==e)})),handleClose:(e,n=!1)=>{const o=t().getInstance(e);o&&(a.popModal(e),!1!==o.config.rejectOnCancel||n?o.reject(o.config.defaultCancelValue):o.resolve(o.config.defaultCancelValue),t().removeInstance(e))},handleAction:(e,n)=>{const o=t().getInstance(e);o&&(a.popModal(e),n.isCancel&&!1!==o.config.rejectOnCancel?o.reject(n.value):o.resolve(n.value),t().removeInstance(e))},getContext:e=>{const n=t().getInstance(e);if(!n)throw new Error(`Dialog instance with id "${e}" not found.`);return{id:n.id,config:n.config,forceCancel:(n=!0)=>t().handleClose(e,n),forceAction:n=>{t().handleAction(e,n)},forceDefault:()=>{const o=n.config.actions?.flat().find(e=>e.isFocused);if(!o)throw new Error(`No default action (isFocused) defined for dialog instance with id "${e}".`);t().handleAction(e,o)}}},getInstance:e=>t().instances.find(n=>n.id===e)}));exports.useDialogStore=c;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConfirmInstance, ValidValue } from '../types';
|
|
1
|
+
import { ConfirmInstance, ModalAction, ValidValue } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* Zustand store interface for managing dialog instances.
|
|
4
4
|
* Maintains a centralized list of active dialog instances.
|
|
@@ -11,8 +11,18 @@ interface DialogStore<T = ValidValue> {
|
|
|
11
11
|
addInstance: (instance: ConfirmInstance<T>) => void;
|
|
12
12
|
/** Remove a dialog instance from the store by ID */
|
|
13
13
|
removeInstance: (id: string) => void;
|
|
14
|
+
/** Handle closing a dialog instance */
|
|
15
|
+
handleClose: (id: string, isForceCancel?: boolean) => void;
|
|
16
|
+
/** Handle an action taken on a dialog instance */
|
|
17
|
+
handleAction: (id: string, action: ModalAction) => void;
|
|
14
18
|
/** Get a dialog instance by ID */
|
|
15
19
|
getInstance: (id: string) => ConfirmInstance<T> | undefined;
|
|
20
|
+
/** Get the context of a dialog instance by ID */
|
|
21
|
+
getContext: (id: string) => {
|
|
22
|
+
id: string;
|
|
23
|
+
config: any;
|
|
24
|
+
forceCancel: () => void;
|
|
25
|
+
} | undefined;
|
|
16
26
|
}
|
|
17
27
|
/**
|
|
18
28
|
* Zustand store for managing dialog instances.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"../components/ModalWindow.esm.js";import{useBaseModal as n}from"@rokku-x/react-hook-modal";import o from"react";import{create as t}from"zustand";const c=n(),a=t((n,t)=>({instances:[],addInstance:a=>{n(e=>({instances:[...e.instances,a]})),a.id=c.pushModal(a.id,o.createElement(e,{config:a.config,modalWindowId:a.id,handleClose:t().handleClose,handleAction:t().handleAction}))},removeInstance:e=>n(n=>({instances:n.instances.filter(n=>n.id!==e)})),handleClose:(e,n=!1)=>{const o=t().getInstance(e);o&&(c.popModal(e),!1!==o.config.rejectOnCancel||n?o.reject(o.config.defaultCancelValue):o.resolve(o.config.defaultCancelValue),t().removeInstance(e))},handleAction:(e,n)=>{const o=t().getInstance(e);o&&(c.popModal(e),n.isCancel&&!1!==o.config.rejectOnCancel?o.reject(n.value):o.resolve(n.value),t().removeInstance(e))},getContext:e=>{const n=t().getInstance(e);if(!n)throw new Error(`Dialog instance with id "${e}" not found.`);return{id:n.id,config:n.config,forceCancel:(n=!0)=>t().handleClose(e,n),forceAction:n=>{t().handleAction(e,n)},forceDefault:()=>{const o=n.config.actions?.flat().find(e=>e.isFocused);if(!o)throw new Error(`No default action (isFocused) defined for dialog instance with id "${e}".`);t().handleAction(e,o)}}},getInstance:e=>t().instances.find(n=>n.id===e)}));export{a as useDialogStore};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -235,4 +235,26 @@ interface HandleActionCallback {
|
|
|
235
235
|
* Friendly Form Data object type
|
|
236
236
|
*/
|
|
237
237
|
export type FormDataObject = Record<string, FormDataEntryValue | FormDataEntryValue[]>;
|
|
238
|
+
/** Function type for requesting a dialog*/
|
|
239
|
+
export interface RequestDialog<U> {
|
|
240
|
+
<U = FormDataObject>(config: ConfirmConfig & {
|
|
241
|
+
isReturnSubmit: true;
|
|
242
|
+
}): RequestDialogReturnType<U>;
|
|
243
|
+
<U = ValidValue>(config: ConfirmConfig): RequestDialogReturnType<U>;
|
|
244
|
+
}
|
|
245
|
+
/** Context information for a dialog instance */
|
|
246
|
+
export type DialogInstanceContext = {
|
|
247
|
+
id: string;
|
|
248
|
+
config: any;
|
|
249
|
+
forceCancel: (forceReject?: boolean) => void;
|
|
250
|
+
forceAction: (action: ModalAction) => void;
|
|
251
|
+
forceDefault: () => void;
|
|
252
|
+
};
|
|
253
|
+
/** Return type of the requestDialog function with added context property */
|
|
254
|
+
export type RequestDialogReturnType<T> = Promise<T> & {
|
|
255
|
+
id: string;
|
|
256
|
+
context: DialogInstanceContext;
|
|
257
|
+
};
|
|
258
|
+
/** Return type of the useHookDialog hook */
|
|
259
|
+
export type UseHookDialogReturnType<T> = [requestDialog: RequestDialog<T>, getContext: (id: string) => DialogInstanceContext];
|
|
238
260
|
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react");exports.FormDataToObject=function(e){const t={};return e.forEach((e,r)=>{if(r in t){const o=t[r];Array.isArray(o)?o.push(e):t[r]=[o,e]}else t[r]=e}),t},exports.IsForm=t=>e.isValidElement(t)&&("form"===t.type||"string"==typeof t.type&&"form"===t.type.toLowerCase());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import r from"react";function t(r){const t={};return r.forEach((r,e)=>{if(e in t){const o=t[e];Array.isArray(o)?o.push(r):t[e]=[o,r]}else t[e]=r}),t}const e=t=>r.isValidElement(t)&&("form"===t.type||"string"==typeof t.type&&"form"===t.type.toLowerCase());export{t as FormDataToObject,e as IsForm};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokku-x/react-hook-dialog",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"author": "rokku-x",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,24 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"main": "dist/index.cjs.js",
|
|
10
10
|
"module": "dist/index.esm.js",
|
|
11
|
-
"devDependencies": {
|
|
12
|
-
"@changesets/cli": "^2.29.8",
|
|
13
|
-
"@testing-library/jest-dom": "^6.9.1",
|
|
14
|
-
"@testing-library/react": "^14.3.1",
|
|
15
|
-
"@testing-library/user-event": "^14.6.1",
|
|
16
|
-
"@types/jest": "^30.0.0",
|
|
17
|
-
"@types/react": "^18.3.27",
|
|
18
|
-
"@types/react-dom": "^18.3.7",
|
|
19
|
-
"jsdom": "^24.1.3",
|
|
20
|
-
"@vitejs/plugin-react": "^5.1.2",
|
|
21
|
-
"typescript": "^5.9.3",
|
|
22
|
-
"vite": "^7.3.1",
|
|
23
|
-
"vite-plugin-dts": "^4.5.4",
|
|
24
|
-
"vitest": "^1.6.1",
|
|
25
|
-
"copyfiles": "^2.4.1"
|
|
26
|
-
},
|
|
27
11
|
"peerDependencies": {
|
|
28
|
-
"@rokku-x/react-hook-modal": "^0.
|
|
12
|
+
"@rokku-x/react-hook-modal": "^0.9.0",
|
|
29
13
|
"react": "^18.0.0",
|
|
30
14
|
"react-dom": "^18.0.0",
|
|
31
15
|
"zustand": "^5.0.10"
|
|
@@ -66,20 +50,6 @@
|
|
|
66
50
|
"react-modal"
|
|
67
51
|
],
|
|
68
52
|
"license": "MIT",
|
|
69
|
-
"publishConfig": {
|
|
70
|
-
"access": "public",
|
|
71
|
-
"provenance": true
|
|
72
|
-
},
|
|
73
|
-
"scripts": {
|
|
74
|
-
"build": "vite build && copyfiles -u 1 \"src/types/**/*.d.ts\" dist/ && copyfiles README.md LICENSE dist/",
|
|
75
|
-
"publish:first": "npm publish --access public --provenance false",
|
|
76
|
-
"dev": "vite",
|
|
77
|
-
"preview": "vite preview",
|
|
78
|
-
"test": "vitest",
|
|
79
|
-
"changeset": "changeset",
|
|
80
|
-
"version:changeset": "changeset version",
|
|
81
|
-
"publish:changeset": "changeset publish --access public"
|
|
82
|
-
},
|
|
83
53
|
"sideEffects": false,
|
|
84
54
|
"types": "dist/index.d.ts",
|
|
85
55
|
"typesVersions": {
|
|
@@ -89,4 +59,4 @@
|
|
|
89
59
|
]
|
|
90
60
|
}
|
|
91
61
|
}
|
|
92
|
-
}
|
|
62
|
+
}
|