@rokku-x/react-hook-dialog 1.1.0 → 1.2.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 CHANGED
@@ -1,38 +1,48 @@
1
1
  # react-hook-dialog
2
2
 
3
- [![CI](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml/badge.svg)](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml/badge.svg)](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml) [![npm version](https://img.shields.io/npm/v/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) [![license](https://img.shields.io/npm/l/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) [![downloads](https://img.shields.io/npm/dm/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) ![TS](https://img.shields.io/badge/TS-%E2%9C%93-blue)
4
4
 
5
- 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.
5
+ <p><a href="https://jgd.qzz.io/rhd.png"><img src="https://jgd.qzz.io/rhd.png" alt="react-hook-dialog Logo" width="600"/></a></p>
6
+
7
+ A lightweight, 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.
6
8
 
7
9
  ## Features
8
10
 
11
+ - ♿ **Accessibility Focused** - Keyboard navigation and ARIA support
12
+ - 🔄 **Asynchronous** - Async/await friendly dialog results
9
13
  - 🎯 **Hook-based API** - Simple and intuitive `useHookDialog()` hook
10
14
  - 🎨 **Rich Variants** - 7 button variants (primary, secondary, danger, success, warning, info, neutral)
11
- - 🧩 **Modular Components** - Composed from reusable Backdrop and DialogWindow components
12
15
  - 📝 **Dialog Actions** - Flexible action button system with left/right positioning
13
16
  - 💅 **Full Customization** - Injectable className and styles at every level
14
17
  - ⌨️ **Rich Configuration** - Default configs with per-call overrides
15
- - 🎁 **Zero Dependencies** - Only requires React, Zustand, and @rokku-x/react-hook-modal
16
18
  - 📱 **TypeScript Support** - Full type safety out of the box
17
- - **Backdrop Control** - Configurable backdrop click behavior
19
+ - 🧑‍🤝‍🧑 **Multiple Dialogs** - Support for multiple simultaneous dialogs
20
+ - 🛠️ **Programmatic Control** - Force actions and cancellations via dialog context
21
+ - 🖼️ **Rich Content Support** - Accepts React nodes for titles and content
22
+ - 📦 **Lightweight** - Minimal bundle size for fast load times
18
23
 
19
24
  ## Installation
20
25
 
21
26
  ```bash
22
27
  npm install @rokku-x/react-hook-dialog
23
- ```
24
-
25
- or with yarn:
26
-
27
- ```bash
28
+ # or
29
+ bun add @rokku-x/react-hook-dialog
30
+ # or
28
31
  yarn add @rokku-x/react-hook-dialog
32
+ # or
33
+ pnpm add @rokku-x/react-hook-dialog
29
34
  ```
30
35
 
31
36
  ## Quick Start
32
37
 
33
- ### 1. Setup BaseModalRenderer
38
+ ### 1. Setup BaseModalRenderer or BaseDialogRenderer
39
+
40
+ You can either use the upstream `BaseModalRenderer` directly (from `@rokku-x/react-hook-modal`) or use the convenience wrapper `BaseDialogRenderer` provided by this package.
41
+
42
+ - `BaseModalRenderer` (upstream): mount this at the root to render modal instances.
43
+ - `BaseDialogRenderer` (this package): a thin wrapper around `BaseModalRenderer` that lets you pass a `defaultConfig` prop to set default dialog options for all dialogs created by `useHookDialog()`.
34
44
 
35
- First, add the `BaseModalRenderer` at the root of your application (from `@rokku-x/react-hook-modal`):
45
+ Use `BaseModalRenderer` directly:
36
46
 
37
47
  ```tsx
38
48
  import { BaseModalRenderer } from '@rokku-x/react-hook-modal';
@@ -47,6 +57,23 @@ function App() {
47
57
  }
48
58
  ```
49
59
 
60
+ Or use the wrapper `BaseDialogRenderer` from this package to set defaults:
61
+
62
+ ```tsx
63
+ import { BaseDialogRenderer } from '@rokku-x/react-hook-dialog';
64
+
65
+ function App() {
66
+ return (
67
+ <>
68
+ <YourComponents />
69
+ <BaseDialogRenderer defaultConfig={{ showCloseButton: false, backdropCancel: false }} />
70
+ </>
71
+ );
72
+ }
73
+ ```
74
+
75
+ Either component is fine — `BaseDialogRenderer` simply sets the dialog defaults for you so consumers of `useHookDialog()` don't have to provide them on each call.
76
+
50
77
  ### 2. Use useHookDialog Hook
51
78
 
52
79
  ```tsx
@@ -91,63 +118,62 @@ useHookDialog(defaultConfig?: UseHookDialogConfig)
91
118
  #### Returns
92
119
 
93
120
  ```typescript
94
- [requestDialog]
121
+ [requestDialog, getContext]
95
122
  ```
96
123
 
97
124
  | Return Value | Type | Description |
98
125
  |---|---|---|
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. |
126
+ | `requestDialog` | `(config: ConfirmConfig) => RequestDialogReturnType<ValidValue>` | Open a dialog and receive a result. The return value is an **augmented Promise** (see `RequestDialogReturnType<T>`). |
127
+ | `getContext` | `(id: string) => DialogInstanceContext` | Retrieve the runtime context for an open dialog by its `id` (useful if you only have the `id`). |
100
128
 
101
129
  ```typescript
102
- // Convenience alias shown in the library
130
+ // Augmented promise returned by `requestDialog`
103
131
  type RequestDialogReturnType<T> = Promise<T> & { id: string; context: DialogInstanceContext };
104
132
  ```
105
133
 
134
+ > Important: awaiting the returned Promise resolves with the dialog result (`T`). The `id` and `context` properties are available immediately after calling `requestDialog(...)`, allowing programmatic control while the dialog is open.
135
+
106
136
  ### Dialog Context & Force Functions ✅
107
137
 
108
- The Promise returned from `requestDialog(...)` is augmented with:
138
+ The augmented Promise exposes two helpers:
109
139
 
110
140
  - `id: string` — unique identifier for the dialog instance
111
- - `context: DialogInstanceContext` — helper methods to programmatically control the dialog instance
141
+ - `context: DialogInstanceContext` — runtime control helpers
112
142
 
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)`).
143
+ `DialogInstanceContext` methods:
114
144
 
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.
145
+ | Method | Signature | Description |
146
+ |---|---|---|
147
+ | `forceCancel` | `forceCancel(forceReject?: boolean = true): void` | Close the dialog as a cancellation. **Default:** `forceReject = true` (the promise will be rejected by default). If set to `false`, the dialog follows the dialog's `rejectOnCancel` setting. |
148
+ | `forceAction` | `forceAction(action: ModalAction): void` | Programmatically trigger the specified action (resolves/rejects according to the action and dialog config). |
149
+ | `forceDefault` | `forceDefault(): void` | Trigger the dialog's default action (first action marked with `isFocused`). Throws if no default is defined. |
120
150
 
121
- Example usage:
151
+ Quick example:
122
152
 
123
153
  ```tsx
124
154
  const [requestDialog, getContext] = useHookDialog();
125
155
 
126
- // open a dialog and get the augmented promise
156
+ const actions: ModalAction[][] = [[ { title: 'Cancel', isCancel: true }, { title: 'OK', value: true, isFocused: true } ]];
157
+
127
158
  const p = requestDialog({
128
159
  title: 'Confirm',
129
160
  content: 'Proceed?',
130
- actions: [[
131
- { title: 'Cancel', isCancel: true },
132
- { title: 'OK', value: true, isFocused: true }
133
- ]]
161
+ actions
134
162
  });
135
163
 
164
+ // id available immediately
136
165
  console.log('dialog id:', p.id);
137
166
 
138
- // force the default action (same as clicking the focused action)
139
- p.context.forceDefault();
140
-
141
- // or cancel programmatically (forces reject by default)
167
+ // cancel programmatically (rejects by default)
142
168
  p.context.forceCancel();
143
169
 
144
- // you can also use the helper returned from the hook
145
- const ctx = getContext(p.id);
146
- ctx.forceAction({ title: 'OK', value: true });
170
+ // trigger a specific action by referencing the action in the array
171
+ getContext(p.id).forceAction(actions[0][1]); // triggers `okAction`
172
+ // or
173
+ p.context.forceAction(actions[0][0]); // triggers `cancelAction`
147
174
  ```
148
175
 
149
- > Note: `forceDefault()` will throw if no action is marked with `isFocused`. Use `forceAction(...)` to explicitly specify which action to run.
150
-
176
+ > Tip: use `forceAction(...)` when you want to trigger a specific action object. Use `forceDefault()` to trigger the focused/default action (if defined). The returned Promise still resolves with the action's `value` (or rejects when cancelled).
151
177
  #### Default Config Options
152
178
 
153
179
  | Property | Type | Default | Description |
@@ -263,6 +289,54 @@ Customize inline styles for all elements:
263
289
 
264
290
  ## Components
265
291
 
292
+ ### BaseDialogRenderer 🔧
293
+
294
+ A convenience wrapper around the upstream `BaseModalRenderer` that accepts a `defaultConfig` prop allowing you to specify default `UseHookDialogConfig` for all dialogs created by `useHookDialog()`.
295
+
296
+ ```tsx
297
+ import { BaseDialogRenderer } from '@rokku-x/react-hook-dialog';
298
+
299
+ <BaseDialogRenderer defaultConfig={{ showCloseButton: false, backdropCancel: false }} />
300
+ ```
301
+
302
+ > Note: `BaseModalRenderer` from `@rokku-x/react-hook-modal` can still be used directly if you prefer — this wrapper only adds `defaultConfig` convenience.
303
+
304
+ ### Multiple renderer instances 🔁
305
+
306
+ You can mount multiple modal renderers (either `BaseDialogRenderer` from this package or the upstream `BaseModalRenderer`) and give each a unique `id`. Use the `instanceId` option (in the hook's `defaultConfig` or per-call `ConfirmConfig`) to target which renderer/store should manage the dialog.
307
+
308
+ - Mount two renderers:
309
+
310
+ ```tsx
311
+ <BaseDialogRenderer id="primary" defaultConfig={{ showCloseButton: false }} />
312
+ <BaseDialogRenderer id="secondary" defaultConfig={{ showCloseButton: true }} />
313
+ ```
314
+
315
+ - Use the hook with a default instance:
316
+
317
+ ```tsx
318
+ const [requestDialog] = useHookDialog({ instanceId: 'secondary' });
319
+ await requestDialog({ title: 'Settings' });
320
+ ```
321
+
322
+ - Or target a renderer per call:
323
+
324
+ ```tsx
325
+ const [requestDialog] = useHookDialog();
326
+ await requestDialog({ title: 'Switch', instanceId: 'primary' });
327
+ ```
328
+
329
+ - Programmatic store access:
330
+
331
+ ```ts
332
+ import storeDialog from '@rokku-x/react-hook-dialog';
333
+ const primaryStore = storeDialog('primary'); // returns the zustand store hook
334
+ const state = primaryStore.getState();
335
+ primaryStore.setState({ rendererDefaultConfig: { /* ... */ } });
336
+ ```
337
+
338
+ > Note: The default instance id is `"default"`. Make sure you mount a renderer with the same `id` if you want dialogs to be visible. If a renderer with the requested id is not mounted, the store will still be created and you can use it programmatically, but dialogs will not be rendered until a matching renderer is mounted.
339
+
266
340
  ### Backdrop
267
341
 
268
342
  Overlay component that wraps dialog windows.
@@ -776,10 +850,10 @@ Custom styles for each variant type.
776
850
 
777
851
  ## Bundle Size
778
852
 
779
- - ESM: ~4.06 kB gzipped (13.48 kB raw)
780
- - CJS: ~3.48 kB gzipped (9.21 kB raw)
853
+ - ESM : ~6.69 kB gzipped (13.10 kB raw)
854
+ - CJS : ~7.35 kB gzipped (14.14 kB raw)
781
855
 
782
- Measured with Vite build for v0.0.1.
856
+ Measured with Vite build for the current branch.
783
857
 
784
858
  ## License
785
859
 
package/dist/README.md CHANGED
@@ -1,38 +1,48 @@
1
1
  # react-hook-dialog
2
2
 
3
- [![CI](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml/badge.svg)](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml/badge.svg)](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml) [![npm version](https://img.shields.io/npm/v/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) [![license](https://img.shields.io/npm/l/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) [![downloads](https://img.shields.io/npm/dm/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) ![TS](https://img.shields.io/badge/TS-%E2%9C%93-blue)
4
4
 
5
- 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.
5
+ <p><a href="https://jgd.qzz.io/rhd.png"><img src="https://jgd.qzz.io/rhd.png" alt="react-hook-dialog Logo" width="600"/></a></p>
6
+
7
+ A lightweight, 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.
6
8
 
7
9
  ## Features
8
10
 
11
+ - ♿ **Accessibility Focused** - Keyboard navigation and ARIA support
12
+ - 🔄 **Asynchronous** - Async/await friendly dialog results
9
13
  - 🎯 **Hook-based API** - Simple and intuitive `useHookDialog()` hook
10
14
  - 🎨 **Rich Variants** - 7 button variants (primary, secondary, danger, success, warning, info, neutral)
11
- - 🧩 **Modular Components** - Composed from reusable Backdrop and DialogWindow components
12
15
  - 📝 **Dialog Actions** - Flexible action button system with left/right positioning
13
16
  - 💅 **Full Customization** - Injectable className and styles at every level
14
17
  - ⌨️ **Rich Configuration** - Default configs with per-call overrides
15
- - 🎁 **Zero Dependencies** - Only requires React, Zustand, and @rokku-x/react-hook-modal
16
18
  - 📱 **TypeScript Support** - Full type safety out of the box
17
- - **Backdrop Control** - Configurable backdrop click behavior
19
+ - 🧑‍🤝‍🧑 **Multiple Dialogs** - Support for multiple simultaneous dialogs
20
+ - 🛠️ **Programmatic Control** - Force actions and cancellations via dialog context
21
+ - 🖼️ **Rich Content Support** - Accepts React nodes for titles and content
22
+ - 📦 **Lightweight** - Minimal bundle size for fast load times
18
23
 
19
24
  ## Installation
20
25
 
21
26
  ```bash
22
27
  npm install @rokku-x/react-hook-dialog
23
- ```
24
-
25
- or with yarn:
26
-
27
- ```bash
28
+ # or
29
+ bun add @rokku-x/react-hook-dialog
30
+ # or
28
31
  yarn add @rokku-x/react-hook-dialog
32
+ # or
33
+ pnpm add @rokku-x/react-hook-dialog
29
34
  ```
30
35
 
31
36
  ## Quick Start
32
37
 
33
- ### 1. Setup BaseModalRenderer
38
+ ### 1. Setup BaseModalRenderer or BaseDialogRenderer
39
+
40
+ You can either use the upstream `BaseModalRenderer` directly (from `@rokku-x/react-hook-modal`) or use the convenience wrapper `BaseDialogRenderer` provided by this package.
41
+
42
+ - `BaseModalRenderer` (upstream): mount this at the root to render modal instances.
43
+ - `BaseDialogRenderer` (this package): a thin wrapper around `BaseModalRenderer` that lets you pass a `defaultConfig` prop to set default dialog options for all dialogs created by `useHookDialog()`.
34
44
 
35
- First, add the `BaseModalRenderer` at the root of your application (from `@rokku-x/react-hook-modal`):
45
+ Use `BaseModalRenderer` directly:
36
46
 
37
47
  ```tsx
38
48
  import { BaseModalRenderer } from '@rokku-x/react-hook-modal';
@@ -47,6 +57,23 @@ function App() {
47
57
  }
48
58
  ```
49
59
 
60
+ Or use the wrapper `BaseDialogRenderer` from this package to set defaults:
61
+
62
+ ```tsx
63
+ import { BaseDialogRenderer } from '@rokku-x/react-hook-dialog';
64
+
65
+ function App() {
66
+ return (
67
+ <>
68
+ <YourComponents />
69
+ <BaseDialogRenderer defaultConfig={{ showCloseButton: false, backdropCancel: false }} />
70
+ </>
71
+ );
72
+ }
73
+ ```
74
+
75
+ Either component is fine — `BaseDialogRenderer` simply sets the dialog defaults for you so consumers of `useHookDialog()` don't have to provide them on each call.
76
+
50
77
  ### 2. Use useHookDialog Hook
51
78
 
52
79
  ```tsx
@@ -91,63 +118,62 @@ useHookDialog(defaultConfig?: UseHookDialogConfig)
91
118
  #### Returns
92
119
 
93
120
  ```typescript
94
- [requestDialog]
121
+ [requestDialog, getContext]
95
122
  ```
96
123
 
97
124
  | Return Value | Type | Description |
98
125
  |---|---|---|
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. |
126
+ | `requestDialog` | `(config: ConfirmConfig) => RequestDialogReturnType<ValidValue>` | Open a dialog and receive a result. The return value is an **augmented Promise** (see `RequestDialogReturnType<T>`). |
127
+ | `getContext` | `(id: string) => DialogInstanceContext` | Retrieve the runtime context for an open dialog by its `id` (useful if you only have the `id`). |
100
128
 
101
129
  ```typescript
102
- // Convenience alias shown in the library
130
+ // Augmented promise returned by `requestDialog`
103
131
  type RequestDialogReturnType<T> = Promise<T> & { id: string; context: DialogInstanceContext };
104
132
  ```
105
133
 
134
+ > Important: awaiting the returned Promise resolves with the dialog result (`T`). The `id` and `context` properties are available immediately after calling `requestDialog(...)`, allowing programmatic control while the dialog is open.
135
+
106
136
  ### Dialog Context & Force Functions ✅
107
137
 
108
- The Promise returned from `requestDialog(...)` is augmented with:
138
+ The augmented Promise exposes two helpers:
109
139
 
110
140
  - `id: string` — unique identifier for the dialog instance
111
- - `context: DialogInstanceContext` — helper methods to programmatically control the dialog instance
141
+ - `context: DialogInstanceContext` — runtime control helpers
112
142
 
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)`).
143
+ `DialogInstanceContext` methods:
114
144
 
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.
145
+ | Method | Signature | Description |
146
+ |---|---|---|
147
+ | `forceCancel` | `forceCancel(forceReject?: boolean = true): void` | Close the dialog as a cancellation. **Default:** `forceReject = true` (the promise will be rejected by default). If set to `false`, the dialog follows the dialog's `rejectOnCancel` setting. |
148
+ | `forceAction` | `forceAction(action: ModalAction): void` | Programmatically trigger the specified action (resolves/rejects according to the action and dialog config). |
149
+ | `forceDefault` | `forceDefault(): void` | Trigger the dialog's default action (first action marked with `isFocused`). Throws if no default is defined. |
120
150
 
121
- Example usage:
151
+ Quick example:
122
152
 
123
153
  ```tsx
124
154
  const [requestDialog, getContext] = useHookDialog();
125
155
 
126
- // open a dialog and get the augmented promise
156
+ const actions: ModalAction[][] = [[ { title: 'Cancel', isCancel: true }, { title: 'OK', value: true, isFocused: true } ]];
157
+
127
158
  const p = requestDialog({
128
159
  title: 'Confirm',
129
160
  content: 'Proceed?',
130
- actions: [[
131
- { title: 'Cancel', isCancel: true },
132
- { title: 'OK', value: true, isFocused: true }
133
- ]]
161
+ actions
134
162
  });
135
163
 
164
+ // id available immediately
136
165
  console.log('dialog id:', p.id);
137
166
 
138
- // force the default action (same as clicking the focused action)
139
- p.context.forceDefault();
140
-
141
- // or cancel programmatically (forces reject by default)
167
+ // cancel programmatically (rejects by default)
142
168
  p.context.forceCancel();
143
169
 
144
- // you can also use the helper returned from the hook
145
- const ctx = getContext(p.id);
146
- ctx.forceAction({ title: 'OK', value: true });
170
+ // trigger a specific action by referencing the action in the array
171
+ getContext(p.id).forceAction(actions[0][1]); // triggers `okAction`
172
+ // or
173
+ p.context.forceAction(actions[0][0]); // triggers `cancelAction`
147
174
  ```
148
175
 
149
- > Note: `forceDefault()` will throw if no action is marked with `isFocused`. Use `forceAction(...)` to explicitly specify which action to run.
150
-
176
+ > Tip: use `forceAction(...)` when you want to trigger a specific action object. Use `forceDefault()` to trigger the focused/default action (if defined). The returned Promise still resolves with the action's `value` (or rejects when cancelled).
151
177
  #### Default Config Options
152
178
 
153
179
  | Property | Type | Default | Description |
@@ -263,6 +289,54 @@ Customize inline styles for all elements:
263
289
 
264
290
  ## Components
265
291
 
292
+ ### BaseDialogRenderer 🔧
293
+
294
+ A convenience wrapper around the upstream `BaseModalRenderer` that accepts a `defaultConfig` prop allowing you to specify default `UseHookDialogConfig` for all dialogs created by `useHookDialog()`.
295
+
296
+ ```tsx
297
+ import { BaseDialogRenderer } from '@rokku-x/react-hook-dialog';
298
+
299
+ <BaseDialogRenderer defaultConfig={{ showCloseButton: false, backdropCancel: false }} />
300
+ ```
301
+
302
+ > Note: `BaseModalRenderer` from `@rokku-x/react-hook-modal` can still be used directly if you prefer — this wrapper only adds `defaultConfig` convenience.
303
+
304
+ ### Multiple renderer instances 🔁
305
+
306
+ You can mount multiple modal renderers (either `BaseDialogRenderer` from this package or the upstream `BaseModalRenderer`) and give each a unique `id`. Use the `instanceId` option (in the hook's `defaultConfig` or per-call `ConfirmConfig`) to target which renderer/store should manage the dialog.
307
+
308
+ - Mount two renderers:
309
+
310
+ ```tsx
311
+ <BaseDialogRenderer id="primary" defaultConfig={{ showCloseButton: false }} />
312
+ <BaseDialogRenderer id="secondary" defaultConfig={{ showCloseButton: true }} />
313
+ ```
314
+
315
+ - Use the hook with a default instance:
316
+
317
+ ```tsx
318
+ const [requestDialog] = useHookDialog({ instanceId: 'secondary' });
319
+ await requestDialog({ title: 'Settings' });
320
+ ```
321
+
322
+ - Or target a renderer per call:
323
+
324
+ ```tsx
325
+ const [requestDialog] = useHookDialog();
326
+ await requestDialog({ title: 'Switch', instanceId: 'primary' });
327
+ ```
328
+
329
+ - Programmatic store access:
330
+
331
+ ```ts
332
+ import storeDialog from '@rokku-x/react-hook-dialog';
333
+ const primaryStore = storeDialog('primary'); // returns the zustand store hook
334
+ const state = primaryStore.getState();
335
+ primaryStore.setState({ rendererDefaultConfig: { /* ... */ } });
336
+ ```
337
+
338
+ > Note: The default instance id is `"default"`. Make sure you mount a renderer with the same `id` if you want dialogs to be visible. If a renderer with the requested id is not mounted, the store will still be created and you can use it programmatically, but dialogs will not be rendered until a matching renderer is mounted.
339
+
266
340
  ### Backdrop
267
341
 
268
342
  Overlay component that wraps dialog windows.
@@ -776,10 +850,10 @@ Custom styles for each variant type.
776
850
 
777
851
  ## Bundle Size
778
852
 
779
- - ESM: ~4.06 kB gzipped (13.48 kB raw)
780
- - CJS: ~3.48 kB gzipped (9.21 kB raw)
853
+ - ESM : ~6.69 kB gzipped (13.10 kB raw)
854
+ - CJS : ~7.35 kB gzipped (14.14 kB raw)
781
855
 
782
- Measured with Vite build for v0.0.1.
856
+ Measured with Vite build for the current branch.
783
857
 
784
858
  ## License
785
859
 
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("react/jsx-runtime"),r=require("../store/dialog.cjs.js"),t=require("@rokku-x/react-hook-modal"),o=require("react");exports.default=function({defaultConfig:u,...s}){const{setDefaultConfig:a}=r.default(s.id)();return o.useEffect(()=>{a(u||{})},[u]),e.jsx(t.BaseModalRenderer,{...s})};
@@ -0,0 +1,13 @@
1
+ import { UseHookDialogConfig } from '../types';
2
+ import { ModalWindowProps } from '@rokku-x/react-hook-modal';
3
+ /**
4
+ * Wrapper component around the BaseModalRenderer to set default dialog configuration.
5
+ * @param props - Component props
6
+ * @param props.defaultConfig - Default configuration applied to all dialogs rendered by this component
7
+ * @param rest - Other props passed to BaseModalRenderer
8
+ *
9
+ * @internal
10
+ */
11
+ export default function BaseDialogRenderer({ defaultConfig, ...rest }: ModalWindowProps & {
12
+ defaultConfig?: UseHookDialogConfig;
13
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ import{jsx as o}from"react/jsx-runtime";import r from"../store/dialog.esm.js";import{BaseModalRenderer as t}from"@rokku-x/react-hook-modal";import{useEffect as e}from"react";function m({defaultConfig:m,...f}){const{setDefaultConfig:i}=r(f.id)();return e(()=>{i(m||{})},[m]),/* @__PURE__ */o(t,{...f})}export{m as default};
@@ -1 +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)})})]})})};
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"),s=require("react");;/* empty css */const a=n.ModalWindow;exports.default=function({modalWindowId:i,handleAction:l,handleClose:r,config:c}){const{actions:u=[],title:d,backdropCancel:f,showCloseButton:m,classNames:h={},styles:b={},variantStyles:y={}}=c;let{content:k}=c;const v=(u.length?u:[[{title:"OK",variant:"primary"}]]).filter(e=>e&&e.length),g={...t.baseVariantStyles,...y},p=s.useRef(null),x=s.useRef(null),j=s.useRef(null);return s.useEffect(()=>{const e=p.current;if(!e)return;const t=document.activeElement;if(x.current)x.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(i);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],s=o[o.length-1];t.shiftKey?document.activeElement===n&&(t.preventDefault(),s.focus()):document.activeElement===s&&(t.preventDefault(),n.focus())}};return e.addEventListener("keydown",o),()=>{e.removeEventListener("keydown",o),t&&t.focus&&t.focus()}},[r,i]),o.IsForm(k)&&(k=s.cloneElement(k,{ref:j})),e.jsx(n.ModalBackdrop,{onClick:()=>f&&r(i),className:h.backdrop||"",style:b.backdrop,children:e.jsxs(a,{ref:p,role:"dialog","aria-modal":"true","aria-labelledby":d?`${i}-title`:void 0,className:h.dialog||"",style:b.dialog,children:[m&&e.jsx("button",{type:"button",className:`hook-dialog-close-button ${h.closeButton||""}`,"aria-label":"Close",onClick:()=>r(i),style:b.closeButton,children:"×"}),d&&e.jsx("h3",{id:`${i}-title`,className:`hook-dialog-title ${h.title||""}`,style:b.title,children:d}),k&&e.jsx("div",{className:`hook-dialog-content ${h.content||""}`,style:b.content,children:k}),e.jsx("div",{className:`hook-dialog-actions ${h.actions||""}`,style:b.actions,children:v.map((t,n)=>{const s=t.filter(e=>e.isOnLeft),a=t.filter(e=>!e.isOnLeft),r=(t,n)=>{const s=g[t.variant||"secondary"]||g.secondary;return e.jsx("button",{ref:e=>{t.isFocused&&e&&(x.current=e)},"data-action-focused":t.isFocused?"true":void 0,className:`hook-dialog-action-button hook-dialog-action-${t.variant||"secondary"} ${h.actionButton||""} ${t.className||""}`,onClick:e=>{try{t.onClick?.(e,t)}catch{}return t.isSubmit&&c.isReturnSubmit&&j.current?l(i,t,o.FormDataToObject(new FormData(j.current))):(t.isSubmit&&j.current?.requestSubmit(),t.noActionReturn?e.stopPropagation():void l(i,t))},style:{...s,...b.actionButton,...t.style||{}},children:t.title},`${t.title}-${n}`)};return e.jsxs("div",{className:`hook-dialog-actions-row ${h.actionsRow||""}`,style:b.actionsRow,children:[e.jsx("div",{className:"hook-dialog-actions-left",children:s.map((e,t)=>r(e,t))}),e.jsx("div",{className:"hook-dialog-actions-right",children:a.map((e,t)=>r(e,t))})]},n)})})]})})};
@@ -1,6 +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};
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 a}from"../utils/utils.esm.js";import{ModalBackdrop as i,ModalWindow as l}from"@rokku-x/react-hook-modal";import s,{useRef as r,useEffect as c}from"react";/* empty css */const d=l;function u({modalWindowId:l,handleAction:u,handleClose:m,config:f}){const{actions:h=[],title:y,backdropCancel:p,showCloseButton:b,classNames:k={},styles:v={},variantStyles:g={}}=f;let{content:N}=f;const $=(h.length?h:[[{title:"OK",variant:"primary"}]]).filter(t=>t&&t.length),w={...o,...g},x=r(null),C=r(null),E=r(null);return c(()=>{const t=x.current;if(!t)return;const e=document.activeElement;if(C.current)C.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],a=o[o.length-1];e.shiftKey?document.activeElement===n&&(e.preventDefault(),a.focus()):document.activeElement===a&&(e.preventDefault(),n.focus())}};return t.addEventListener("keydown",o),()=>{t.removeEventListener("keydown",o),e&&e.focus&&e.focus()}},[m,l]),n(N)&&(N=s.cloneElement(N,{ref:E})),/* @__PURE__ */t(i,{onClick:()=>p&&m(l),className:k.backdrop||"",style:v.backdrop,children:/* @__PURE__ */e(d,{ref:x,role:"dialog","aria-modal":"true","aria-labelledby":y?`${l}-title`:void 0,className:k.dialog||"",style:v.dialog,children:[b&&/* @__PURE__ */t("button",{type:"button",className:`hook-dialog-close-button ${k.closeButton||""}`,"aria-label":"Close",onClick:()=>m(l),style:v.closeButton,children:"×"}),y&&/* @__PURE__ */t("h3",{id:`${l}-title`,className:`hook-dialog-title ${k.title||""}`,style:v.title,children:y}),N&&/* @__PURE__ */t("div",{className:`hook-dialog-content ${k.content||""}`,style:v.content,children:N}),
2
+ /* @__PURE__ */t("div",{className:`hook-dialog-actions ${k.actions||""}`,style:v.actions,children:$.map((o,n)=>{const i=o.filter(t=>t.isOnLeft),s=o.filter(t=>!t.isOnLeft),r=(e,o)=>{const n=w[e.variant||"secondary"]||w.secondary;/* @__PURE__ */
3
+ return t("button",{ref:t=>{e.isFocused&&t&&(C.current=t)},"data-action-focused":e.isFocused?"true":void 0,className:`hook-dialog-action-button hook-dialog-action-${e.variant||"secondary"} ${k.actionButton||""} ${e.className||""}`,onClick:t=>{try{e.onClick?.(t,e)}catch{}return e.isSubmit&&f.isReturnSubmit&&E.current?u(l,e,a(new FormData(E.current))):(e.isSubmit&&E.current?.requestSubmit(),e.noActionReturn?t.stopPropagation():void u(l,e))},style:{...n,...v.actionButton,...e.style||{}},children:e.title},`${e.title}-${o}`)};/* @__PURE__ */
4
+ return e("div",{className:`hook-dialog-actions-row ${k.actionsRow||""}`,style:v.actionsRow,children:[
5
+ /* @__PURE__ */t("div",{className:"hook-dialog-actions-left",children:i.map((t,e)=>r(t,e))}),
6
+ /* @__PURE__ */t("div",{className:"hook-dialog-actions-right",children:s.map((t,e)=>r(t,e))})]},n)})})]})})}export{u as default};
@@ -1 +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
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("react"),s=require("@rokku-x/react-hook-modal"),t=require("../components/ModalWindow.cjs.js"),a=require("../store/dialog.cjs.js");exports.default=function(n){const r=s.useBaseModal({rendererId:n?.instanceId}),{addInstance:o,handleAction:l,handleClose:d,rendererDefaultConfig:i}=a.default(n?.instanceId)();return[function(s){return new Promise((a,c)=>{const u=Math.random().toString(36).substring(2,6),m={...i,...n,...s,classNames:{...i?.classNames,...n?.classNames,...s.classNames},styles:{...i?.styles,...n?.styles,...s.styles},variantStyles:{...i?.variantStyles,...n?.variantStyles,...s.variantStyles}},f={id:u,config:m,resolve:a,reject:c};o(f),f.id=r.pushModal(u,e.createElement(t.default,{config:m,modalWindowId:u,handleClose:d,handleAction:l}))})}]};
@@ -1,4 +1,4 @@
1
- import { UseHookDialogConfig, ValidValue, UseHookDialogReturnType } from '../types';
1
+ import { FormDataObject, ConfirmConfig, UseHookDialogConfig, ValidValue } from '../types';
2
2
  /**
3
3
  * Hook for displaying confirmation dialogs and alerts.
4
4
  *
@@ -34,4 +34,9 @@ import { UseHookDialogConfig, ValidValue, UseHookDialogReturnType } from '../typ
34
34
  * });
35
35
  * ```
36
36
  */
37
- export default function useHookDialog<T = ValidValue>(defaultConfig?: UseHookDialogConfig): UseHookDialogReturnType<T>;
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
+ }[];
@@ -1 +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};
1
+ import e from"react";import{useBaseModal as s}from"@rokku-x/react-hook-modal";import t from"../components/ModalWindow.esm.js";import n from"../store/dialog.esm.js";function a(a){const o=s({rendererId:a?.instanceId}),{addInstance:r,handleAction:l,handleClose:i,rendererDefaultConfig:d}=n(a?.instanceId)();return[function(s){return new Promise((n,c)=>{const m=Math.random().toString(36).substring(2,6),f={...d,...a,...s,classNames:{...d?.classNames,...a?.classNames,...s.classNames},styles:{...d?.styles,...a?.styles,...s.styles},variantStyles:{...d?.variantStyles,...a?.variantStyles,...s.variantStyles}},u={id:m,config:f,resolve:n,reject:c};r(u),u.id=o.pushModal(m,e.createElement(t,{config:f,modalWindowId:m,handleClose:i,handleAction:l}))})}]}export{a as default};
package/dist/index.cjs.js CHANGED
@@ -1 +1,4 @@
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;
1
+ "use client";
2
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@rokku-x/react-hook-modal"),o=require("./components/BaseDialogRenderer.cjs.js"),r=require("./hooks/useHookDialog.cjs.js");Object.defineProperty(exports,"BaseModalRenderer",{enumerable:!0,get:()=>e.BaseModalRenderer}),exports.BaseDialogRenderer=o.default,exports.useHookDialog=r.default;
3
+
4
+ if (typeof document !== 'undefined') { const style = document.createElement('style'); style.textContent = ".hook-dialog-close-button{position:absolute;top:0;right:0;transform:translate(75%,-75%);width:32px;height:32px;background:none;border:none;font-size:21px;cursor:pointer;line-height:1;color:#555}.hook-dialog-title{margin:0 0 15px;font-size:20px}.hook-dialog-content{margin-bottom:15px;color:#555}.hook-dialog-actions{display:flex;flex-direction:column;gap:10px;padding-top:15px}.hook-dialog-actions-row{display:flex;gap:8px;justify-content:space-between}.hook-dialog-actions-left,.hook-dialog-actions-right{display:flex;gap:8px}.hook-dialog-action-button{border:none;border-radius:15px;padding:10px 18px;font-size:14px;font-weight:800;cursor:pointer}\n"; document.head.appendChild(style); }
package/dist/index.d.ts CHANGED
@@ -36,12 +36,16 @@
36
36
  * ```
37
37
  */
38
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.
39
+ * Re-exports of renderer components.
40
+ * - `BaseModalRenderer` is the upstream renderer from `@rokku-x/react-hook-modal`.
41
+ * - `BaseDialogRenderer` is a small wrapper provided by this package that allows setting a `defaultConfig` prop.
42
+ * Both should be mounted at the root of your application for dialogs to render.
41
43
  */
42
44
  export { BaseModalRenderer } from '@rokku-x/react-hook-modal';
45
+ export { default as BaseDialogRenderer } from './components/BaseDialogRenderer';
43
46
  /**
44
47
  * Main hook for displaying confirmation dialogs and alerts.
45
48
  * @see {@link useHookDialog}
46
49
  */
47
50
  export { default as useHookDialog } from './hooks/useHookDialog';
51
+ export type { UseHookDialogConfig, ConfirmConfig, ModalAction, RequestDialogReturnType, DialogInstanceContext, RequestDialog, UseHookDialogReturnType } from './types';
package/dist/index.esm.js CHANGED
@@ -1 +1,4 @@
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};
1
+ "use client";
2
+ import{BaseModalRenderer as o}from"@rokku-x/react-hook-modal";import{default as e}from"./components/BaseDialogRenderer.esm.js";import{default as a}from"./hooks/useHookDialog.esm.js";export{e as BaseDialogRenderer,o as BaseModalRenderer,a as useHookDialog};
3
+
4
+ if (typeof document !== 'undefined') { const style = document.createElement('style'); style.textContent = ".hook-dialog-close-button{position:absolute;top:0;right:0;transform:translate(75%,-75%);width:32px;height:32px;background:none;border:none;font-size:21px;cursor:pointer;line-height:1;color:#555}.hook-dialog-title{margin:0 0 15px;font-size:20px}.hook-dialog-content{margin-bottom:15px;color:#555}.hook-dialog-actions{display:flex;flex-direction:column;gap:10px;padding-top:15px}.hook-dialog-actions-row{display:flex;gap:8px;justify-content:space-between}.hook-dialog-actions-left,.hook-dialog-actions-right{display:flex;gap:8px}.hook-dialog-action-button{border:none;border-radius:15px;padding:10px 18px;font-size:14px;font-weight:800;cursor:pointer}\n"; document.head.appendChild(style); }
@@ -0,0 +1 @@
1
+ .hook-dialog-close-button{position:absolute;top:0;right:0;transform:translate(75%,-75%);width:32px;height:32px;background:none;border:none;font-size:21px;cursor:pointer;line-height:1;color:#555}.hook-dialog-title{margin:0 0 15px;font-size:20px}.hook-dialog-content{margin-bottom:15px;color:#555}.hook-dialog-actions{display:flex;flex-direction:column;gap:10px;padding-top:15px}.hook-dialog-actions-row{display:flex;gap:8px;justify-content:space-between}.hook-dialog-actions-left,.hook-dialog-actions-right{display:flex;gap:8px}.hook-dialog-action-button{border:none;border-radius:15px;padding:10px 18px;font-size:14px;font-weight:800;cursor:pointer}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("../components/ModalWindow.cjs.js"),n=require("@rokku-x/react-hook-modal"),t=require("react"),o=require("zustand");const a=new Map;exports.default=function(c="default"){return a.has(c)||a.set(c,function(a="default"){const c=n.storeBaseModal(a);return o.create((n,o)=>({instances:[],rendererDefaultConfig:{},setDefaultConfig:e=>{n(()=>({rendererDefaultConfig:e}))},addInstance:a=>{n(e=>({instances:[...e.instances,a]})),a.id=c.getState().actions.pushModal(a.id,t.createElement(e.default,{config:a.config,modalWindowId:a.id,handleClose:o().handleClose,handleAction:o().handleAction}))},removeInstance:e=>n(n=>({instances:n.instances.filter(n=>n.id!==e)})),handleClose:(e,n=!1)=>{const t=o().getInstance(e);t&&(c.getState().actions.popModal(e),!1!==t.config.rejectOnCancel||n?t.reject(t.config.defaultCancelValue):t.resolve(t.config.defaultCancelValue),o().removeInstance(e))},handleAction:(e,n)=>{const t=o().getInstance(e);t&&(c.getState().actions.popModal(e),n.isCancel&&!1!==t.config.rejectOnCancel?t.reject(n.value):t.resolve(n.value),o().removeInstance(e))},getContext:e=>{const n=o().getInstance(e);if(!n)throw new Error(`Dialog instance with id "${e}" not found.`);return{id:n.id,config:n.config,forceCancel:(n=!0)=>o().handleClose(e,n),forceAction:n=>{o().handleAction(e,n)},forceDefault:()=>{const t=n.config.actions?.flat().find(e=>e.isFocused);if(!t)throw new Error(`No default action (isFocused) defined for dialog instance with id "${e}".`);o().handleAction(e,t)}}},getInstance:e=>o().instances.find(n=>n.id===e)}))}(c)),a.get(c)};
@@ -1,4 +1,4 @@
1
- import { ConfirmInstance, ModalAction, ValidValue } from '../types';
1
+ import { ConfirmInstance, ModalAction, UseHookDialogConfig, ValidValue } from '../types';
2
2
  /**
3
3
  * Zustand store interface for managing dialog instances.
4
4
  * Maintains a centralized list of active dialog instances.
@@ -7,6 +7,10 @@ import { ConfirmInstance, ModalAction, ValidValue } from '../types';
7
7
  interface DialogStore<T = ValidValue> {
8
8
  /** Array of currently active dialog instances */
9
9
  instances: ConfirmInstance<T>[];
10
+ /** Default configuration applied to all dialogs */
11
+ rendererDefaultConfig: UseHookDialogConfig;
12
+ /** Set the default configuration for dialogs */
13
+ setDefaultConfig: (config: UseHookDialogConfig) => void;
10
14
  /** Add a new dialog instance to the store */
11
15
  addInstance: (instance: ConfirmInstance<T>) => void;
12
16
  /** Remove a dialog instance from the store by ID */
@@ -24,15 +28,7 @@ interface DialogStore<T = ValidValue> {
24
28
  forceCancel: () => void;
25
29
  } | undefined;
26
30
  }
27
- /**
28
- * Zustand store for managing dialog instances.
29
- * Provides centralized state management for all active dialogs.
30
- *
31
- * @internal
32
- * @example
33
- * ```tsx
34
- * const { addInstance, removeInstance, getInstance } = useDialogStore();
35
- * ```
36
- */
37
- export declare const useDialogStore: import('zustand').UseBoundStore<import('zustand').StoreApi<DialogStore<any>>>;
31
+ declare function createStore(instanceId?: string): import('zustand').UseBoundStore<import('zustand').StoreApi<DialogStore<any>>>;
32
+ export type BaseDialogStoreInstance = ReturnType<typeof createStore>;
33
+ export default function BaseDialogStore(instanceId?: string): BaseDialogStoreInstance;
38
34
  export {};
@@ -0,0 +1 @@
1
+ import e from"../components/ModalWindow.esm.js";import{storeBaseModal as n}from"@rokku-x/react-hook-modal";import t from"react";import{create as o}from"zustand";const a=/* @__PURE__ */new Map;function c(c="default"){return a.has(c)||a.set(c,function(a="default"){const c=n(a);return o((n,o)=>({instances:[],rendererDefaultConfig:{},setDefaultConfig:e=>{n(()=>({rendererDefaultConfig:e}))},addInstance:a=>{n(e=>({instances:[...e.instances,a]})),a.id=c.getState().actions.pushModal(a.id,t.createElement(e,{config:a.config,modalWindowId:a.id,handleClose:o().handleClose,handleAction:o().handleAction}))},removeInstance:e=>n(n=>({instances:n.instances.filter(n=>n.id!==e)})),handleClose:(e,n=!1)=>{const t=o().getInstance(e);t&&(c.getState().actions.popModal(e),!1!==t.config.rejectOnCancel||n?t.reject(t.config.defaultCancelValue):t.resolve(t.config.defaultCancelValue),o().removeInstance(e))},handleAction:(e,n)=>{const t=o().getInstance(e);t&&(c.getState().actions.popModal(e),n.isCancel&&!1!==t.config.rejectOnCancel?t.reject(n.value):t.resolve(n.value),o().removeInstance(e))},getContext:e=>{const n=o().getInstance(e);if(!n)throw new Error(`Dialog instance with id "${e}" not found.`);return{id:n.id,config:n.config,forceCancel:(n=!0)=>o().handleClose(e,n),forceAction:n=>{o().handleAction(e,n)},forceDefault:()=>{const t=n.config.actions?.flat().find(e=>e.isFocused);if(!t)throw new Error(`No default action (isFocused) defined for dialog instance with id "${e}".`);o().handleAction(e,t)}}},getInstance:e=>o().instances.find(n=>n.id===e)}))}(c)),a.get(c)}export{c as default};
@@ -183,6 +183,8 @@ export interface ConfirmConfig {
183
183
  * ```
184
184
  */
185
185
  export interface UseHookDialogConfig {
186
+ /** Optional instance ID for the dialog store (default: 'default') */
187
+ instanceId?: string;
186
188
  /** If true, allows closing dialogs by clicking the backdrop (default: false) */
187
189
  backdropCancel?: boolean;
188
190
  /** If true, rejects the promise on cancel instead of resolving (default: true) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokku-x/react-hook-dialog",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "author": "rokku-x",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,10 +9,10 @@
9
9
  "main": "dist/index.cjs.js",
10
10
  "module": "dist/index.esm.js",
11
11
  "peerDependencies": {
12
- "@rokku-x/react-hook-modal": "^0.9.0",
13
12
  "react": "^18.0.0",
14
13
  "react-dom": "^18.0.0",
15
- "zustand": "^5.0.10"
14
+ "zustand": "^5.0.10",
15
+ "@rokku-x/react-hook-modal": "^0.10.0"
16
16
  },
17
17
  "exports": {
18
18
  ".": {
@@ -20,6 +20,8 @@
20
20
  "import": "./dist/index.esm.js",
21
21
  "require": "./dist/index.cjs.js"
22
22
  },
23
+ "./react-hook-dialog.css": "./dist/react-hook-dialog.css",
24
+ "./dist/react-hook-dialog.css": "./dist/react-hook-dialog.css",
23
25
  "./package.json": "./package.json"
24
26
  },
25
27
  "bugs": {
@@ -39,6 +41,7 @@
39
41
  "confirmation",
40
42
  "alert",
41
43
  "prompt",
44
+ "confirm",
42
45
  "hook",
43
46
  "typescript",
44
47
  "zustand",
@@ -47,10 +50,29 @@
47
50
  "dialog-box",
48
51
  "confirmation-dialog",
49
52
  "react-dialog",
50
- "react-modal"
53
+ "react-modal",
54
+ "hooks",
55
+ "modal-dialog",
56
+ "modal-window",
57
+ "overlay",
58
+ "overlays",
59
+ "popup",
60
+ "toast",
61
+ "form",
62
+ "spinner",
63
+ "loading",
64
+ "ui-blocker",
65
+ "blocker",
66
+ "portal",
67
+ "accessibility",
68
+ "a11y",
69
+ "library"
51
70
  ],
52
71
  "license": "MIT",
53
- "sideEffects": false,
72
+ "sideEffects": [
73
+ "*.css",
74
+ "*.scss"
75
+ ],
54
76
  "types": "dist/index.d.ts",
55
77
  "typesVersions": {
56
78
  "*": {
@@ -1 +0,0 @@
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 +0,0 @@
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};