@lcashe/react-modal-controller 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +340 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +42 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +1 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zakharov Vladyslav
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# react-modal-controller
|
|
2
|
+
|
|
3
|
+
Tiny and type-safe modal controller for React and Next.js.
|
|
4
|
+
|
|
5
|
+
- No global modal registry
|
|
6
|
+
- No reducers
|
|
7
|
+
- No boilerplate
|
|
8
|
+
- Full TypeScript inference
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Navigation
|
|
13
|
+
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [React Setup](#react-setup)
|
|
16
|
+
- [Next.js Setup](#nextjs-setup)
|
|
17
|
+
- [Important](#important)
|
|
18
|
+
- [Simple Example](#simple-example)
|
|
19
|
+
- [Modal With Props](#modal-with-props)
|
|
20
|
+
- [Dynamic Initial Props](#dynamic-initial-props)
|
|
21
|
+
- [Auto Close Example](#auto-close-example)
|
|
22
|
+
- [Multiple Modals](#multiple-modals)
|
|
23
|
+
- [API](#api)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @lcashe/react-modal-controller
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
# React Setup
|
|
36
|
+
|
|
37
|
+
Wrap your application with `ModalScope`.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import ReactDOM from 'react-dom/client';
|
|
41
|
+
|
|
42
|
+
import { ModalScope } from '@lcashe/react-modal-controller';
|
|
43
|
+
|
|
44
|
+
import { App } from './app';
|
|
45
|
+
|
|
46
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
47
|
+
<ModalScope>
|
|
48
|
+
<App />
|
|
49
|
+
</ModalScope>,
|
|
50
|
+
);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# Next.js Setup
|
|
56
|
+
|
|
57
|
+
`app/layout.tsx`
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { ModalScope } from '@lcashe/react-modal-controller';
|
|
61
|
+
|
|
62
|
+
export default function Page() {
|
|
63
|
+
return <ModalScope>// PAGE CONTENT</ModalScope>;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
# Important
|
|
70
|
+
|
|
71
|
+
Your modal component must accept:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
opened: boolean;
|
|
75
|
+
onClose: VoidFunction;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Optional:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
onExited?: VoidFunction;
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
type ModalProps = {
|
|
88
|
+
opened: boolean;
|
|
89
|
+
title: string;
|
|
90
|
+
onClose: VoidFunction;
|
|
91
|
+
};
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
# Simple Example
|
|
97
|
+
|
|
98
|
+
## Modal
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
'use client';
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
type ModalProps = {
|
|
105
|
+
opened: boolean;
|
|
106
|
+
onClose: VoidFunction;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const Modal = ({ opened, onClose }: ModalProps) => {
|
|
110
|
+
if (!opened) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div>
|
|
116
|
+
<h1>Modal</h1>
|
|
117
|
+
|
|
118
|
+
<button onClick={onClose}>Close</button>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Usage
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
'use client';
|
|
128
|
+
|
|
129
|
+
import { useModalController } from '@lcashe/react-modal-controller';
|
|
130
|
+
|
|
131
|
+
import { Modal } from './modal';
|
|
132
|
+
|
|
133
|
+
export const Component = () => {
|
|
134
|
+
const modal = useModalController(Modal);
|
|
135
|
+
|
|
136
|
+
return <button onClick={() => modal.open()}>Open modal</button>;
|
|
137
|
+
};
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
# Modal With Props
|
|
143
|
+
|
|
144
|
+
## Modal
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
'use client';
|
|
148
|
+
|
|
149
|
+
type ModalProps = {
|
|
150
|
+
opened: boolean;
|
|
151
|
+
title: string;
|
|
152
|
+
onClose: VoidFunction;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const Modal = ({ title, opened, onClose }: ModalProps) => {
|
|
156
|
+
if (!opened) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div>
|
|
162
|
+
<h1>{title}</h1>
|
|
163
|
+
|
|
164
|
+
<button onClick={onClose}>Close</button>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Usage
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
'use client';
|
|
174
|
+
|
|
175
|
+
import { useModalController } from '@lcashe/react-modal-controller';
|
|
176
|
+
|
|
177
|
+
import { Modal } from './modal';
|
|
178
|
+
|
|
179
|
+
export const Component = () => {
|
|
180
|
+
const modal = useModalController(Modal, {
|
|
181
|
+
title: 'Initial title',
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<button
|
|
186
|
+
onClick={() =>
|
|
187
|
+
modal.open({
|
|
188
|
+
title: 'Another title', // optional
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
>
|
|
192
|
+
Open modal
|
|
193
|
+
</button>
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
# TypeScript Inference
|
|
201
|
+
|
|
202
|
+
Props are inferred automatically.
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
modal.open({
|
|
206
|
+
title: 'Example title',
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Autocomplete works out of the box.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
# Dynamic Initial Props
|
|
215
|
+
|
|
216
|
+
`initialProps` stay synchronized automatically.
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
const modal = useModalController(Modal, {
|
|
220
|
+
title,
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
When `title` changes, the next `open()` call will use the latest value.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
# Auto Close Example
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
'use client';
|
|
232
|
+
|
|
233
|
+
import { useEffect } from 'react';
|
|
234
|
+
|
|
235
|
+
import { useModalController } from '@lcashe/react-modal-controller';
|
|
236
|
+
|
|
237
|
+
import { Modal } from './modal';
|
|
238
|
+
|
|
239
|
+
export const Component = () => {
|
|
240
|
+
const modal = useModalController(Modal);
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
modal.open();
|
|
244
|
+
|
|
245
|
+
const timeoutId = setTimeout(() => {
|
|
246
|
+
modal.close();
|
|
247
|
+
}, 2000);
|
|
248
|
+
|
|
249
|
+
return () => {
|
|
250
|
+
clearTimeout(timeoutId);
|
|
251
|
+
};
|
|
252
|
+
}, [modal]);
|
|
253
|
+
|
|
254
|
+
return null;
|
|
255
|
+
};
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
# Multiple Modals
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
const firstModal = useModalController(FirstModal);
|
|
264
|
+
|
|
265
|
+
const secondModal = useModalController(SecondModal);
|
|
266
|
+
|
|
267
|
+
const thirdModal = useModalController(ThirdModal);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
# Multiple Same Modals
|
|
273
|
+
|
|
274
|
+
Each `useModalController` call creates its own local modal instance.
|
|
275
|
+
|
|
276
|
+
Even if you pass the same modal component, every controller receives a unique internal id, so the modals are controlled separately.
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
const firstModal = useModalController(Modal);
|
|
280
|
+
|
|
281
|
+
const secondModal = useModalController(Modal);
|
|
282
|
+
|
|
283
|
+
...
|
|
284
|
+
|
|
285
|
+
secondModal.open() // <-- will open only the second modal
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
# API
|
|
291
|
+
|
|
292
|
+
## `useModalController(Component, initialProps?)`
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
{
|
|
298
|
+
open,
|
|
299
|
+
close,
|
|
300
|
+
remove,
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## `open(props?)`
|
|
305
|
+
|
|
306
|
+
Open modal with optional props.
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
modal.open();
|
|
310
|
+
|
|
311
|
+
modal.open({
|
|
312
|
+
title: 'New title',
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## `close()`
|
|
319
|
+
|
|
320
|
+
Close modal.
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
modal.close();
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## `remove()`
|
|
329
|
+
|
|
330
|
+
Remove modal from the store completely.
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
modal.remove();
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
# License
|
|
339
|
+
|
|
340
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var M=Object.defineProperty,b=Object.defineProperties,j=Object.getOwnPropertyDescriptor,S=Object.getOwnPropertyDescriptors,h=Object.getOwnPropertyNames,I=Object.getOwnPropertySymbols;var v=Object.prototype.hasOwnProperty,k=Object.prototype.propertyIsEnumerable;var T=(t,e,o)=>e in t?M(t,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[e]=o,m=(t,e)=>{for(var o in e||(e={}))v.call(e,o)&&T(t,o,e[o]);if(I)for(var o of I(e))k.call(e,o)&&T(t,o,e[o]);return t},C=(t,e)=>b(t,S(e));var g=(t,e)=>{for(var o in e)M(t,o,{get:e[o],enumerable:!0})},V=(t,e,o,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let p of h(e))!v.call(t,p)&&p!==o&&M(t,p,{get:()=>e[p],enumerable:!(r=j(e,p))||r.enumerable});return t};var w=t=>V(M({},"__esModule",{value:!0}),t);var W={};g(W,{ModalContext:()=>i,ModalScope:()=>N,useModalController:()=>R});module.exports=w(W);var a=require("react");var E=require("react"),i=(0,E.createContext)(null);var u=require("react/jsx-runtime"),N=({children:t})=>{let[e,o]=(0,a.useState)([]),r=(0,a.useCallback)(l=>{let d={id:l.id,opened:!0,Component:l.Component,props:l.props};o(n=>{let x=n.findIndex(y=>y.id===l.id);if(x===-1)return[...n,d];let f=[...n];return f[x]=d,f})},[]),p=(0,a.useCallback)(l=>{o(d=>d.map(n=>n.id===l?C(m({},n),{opened:!1}):n))},[]),c=(0,a.useCallback)(l=>{o(d=>d.filter(n=>n.id!==l))},[]),P=(0,a.useMemo)(()=>({open:r,close:p,remove:c}),[r,p,c]);return(0,u.jsxs)(i.Provider,{value:P,children:[t,e.map(({Component:l,id:d,opened:n,props:x})=>(0,u.jsx)(l,C(m({},x),{opened:n,onClose:()=>p(d),onExited:()=>c(d)}),d))]})};var s=require("react");var R=(t,e)=>{let o=(0,s.useContext)(i);if(!o)throw new Error("useModalController must be used inside <ModalScope>");let r=(0,s.useId)(),p=(0,s.useRef)(e);(0,s.useEffect)(()=>{p.current=e},[e]),(0,s.useEffect)(()=>()=>{o.remove(r)},[o,r]);let c=(0,s.useCallback)(d=>{let n=m(m({},p.current||{}),d||{});o.open({id:r,Component:t,props:n})},[o,r,t]),P=(0,s.useCallback)(()=>{o.close(r)},[o,r]),l=(0,s.useCallback)(()=>{o.remove(r)},[o,r]);return{open:c,close:P,remove:l}};0&&(module.exports={ModalContext,ModalScope,useModalController});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { PropsWithChildren, ComponentType } from 'react';
|
|
4
|
+
|
|
5
|
+
declare const ModalScope: ({ children }: PropsWithChildren) => react_jsx_runtime.JSX.Element;
|
|
6
|
+
|
|
7
|
+
type Nullable<T> = T | null;
|
|
8
|
+
type Maybe<T> = T | undefined;
|
|
9
|
+
type ModalInjectedProps = {
|
|
10
|
+
opened: boolean;
|
|
11
|
+
onClose: VoidFunction;
|
|
12
|
+
onExited?: VoidFunction;
|
|
13
|
+
};
|
|
14
|
+
type ModalComponent<TProps extends object = object> = ComponentType<TProps & ModalInjectedProps>;
|
|
15
|
+
type ExternalModalProps$1<TComponent> = TComponent extends ComponentType<infer TProps> ? Omit<TProps, keyof ModalInjectedProps> : never;
|
|
16
|
+
type ModalItem<TProps extends object = object> = {
|
|
17
|
+
id: string;
|
|
18
|
+
Component: ModalComponent<TProps>;
|
|
19
|
+
props: TProps;
|
|
20
|
+
};
|
|
21
|
+
type StoredModalItem = {
|
|
22
|
+
id: string;
|
|
23
|
+
opened: boolean;
|
|
24
|
+
props: object;
|
|
25
|
+
Component: ComponentType<object & ModalInjectedProps>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ExternalModalProps<TProps extends ModalInjectedProps> = Omit<TProps, keyof ModalInjectedProps>;
|
|
29
|
+
declare const useModalController: <TProps extends ModalInjectedProps>(Component: ComponentType<TProps>, initialProps?: ExternalModalProps<TProps>) => {
|
|
30
|
+
open: (props?: Partial<ExternalModalProps<TProps>>) => void;
|
|
31
|
+
close: () => void;
|
|
32
|
+
remove: () => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type ModalContextValue = {
|
|
36
|
+
open: <TProps extends object>(item: ModalItem<TProps>) => void;
|
|
37
|
+
close: (id: string) => void;
|
|
38
|
+
remove: (id: string) => void;
|
|
39
|
+
};
|
|
40
|
+
declare const ModalContext: react.Context<Nullable<ModalContextValue>>;
|
|
41
|
+
|
|
42
|
+
export { type ExternalModalProps$1 as ExternalModalProps, type Maybe, type ModalComponent, ModalContext, type ModalContextValue, type ModalInjectedProps, type ModalItem, ModalScope, type Nullable, type StoredModalItem, useModalController };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { PropsWithChildren, ComponentType } from 'react';
|
|
4
|
+
|
|
5
|
+
declare const ModalScope: ({ children }: PropsWithChildren) => react_jsx_runtime.JSX.Element;
|
|
6
|
+
|
|
7
|
+
type Nullable<T> = T | null;
|
|
8
|
+
type Maybe<T> = T | undefined;
|
|
9
|
+
type ModalInjectedProps = {
|
|
10
|
+
opened: boolean;
|
|
11
|
+
onClose: VoidFunction;
|
|
12
|
+
onExited?: VoidFunction;
|
|
13
|
+
};
|
|
14
|
+
type ModalComponent<TProps extends object = object> = ComponentType<TProps & ModalInjectedProps>;
|
|
15
|
+
type ExternalModalProps$1<TComponent> = TComponent extends ComponentType<infer TProps> ? Omit<TProps, keyof ModalInjectedProps> : never;
|
|
16
|
+
type ModalItem<TProps extends object = object> = {
|
|
17
|
+
id: string;
|
|
18
|
+
Component: ModalComponent<TProps>;
|
|
19
|
+
props: TProps;
|
|
20
|
+
};
|
|
21
|
+
type StoredModalItem = {
|
|
22
|
+
id: string;
|
|
23
|
+
opened: boolean;
|
|
24
|
+
props: object;
|
|
25
|
+
Component: ComponentType<object & ModalInjectedProps>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ExternalModalProps<TProps extends ModalInjectedProps> = Omit<TProps, keyof ModalInjectedProps>;
|
|
29
|
+
declare const useModalController: <TProps extends ModalInjectedProps>(Component: ComponentType<TProps>, initialProps?: ExternalModalProps<TProps>) => {
|
|
30
|
+
open: (props?: Partial<ExternalModalProps<TProps>>) => void;
|
|
31
|
+
close: () => void;
|
|
32
|
+
remove: () => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type ModalContextValue = {
|
|
36
|
+
open: <TProps extends object>(item: ModalItem<TProps>) => void;
|
|
37
|
+
close: (id: string) => void;
|
|
38
|
+
remove: (id: string) => void;
|
|
39
|
+
};
|
|
40
|
+
declare const ModalContext: react.Context<Nullable<ModalContextValue>>;
|
|
41
|
+
|
|
42
|
+
export { type ExternalModalProps$1 as ExternalModalProps, type Maybe, type ModalComponent, ModalContext, type ModalContextValue, type ModalInjectedProps, type ModalItem, ModalScope, type Nullable, type StoredModalItem, useModalController };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var v=Object.defineProperty,E=Object.defineProperties;var y=Object.getOwnPropertyDescriptors;var C=Object.getOwnPropertySymbols;var b=Object.prototype.hasOwnProperty,j=Object.prototype.propertyIsEnumerable;var f=(t,e,o)=>e in t?v(t,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[e]=o,d=(t,e)=>{for(var o in e||(e={}))b.call(e,o)&&f(t,o,e[o]);if(C)for(var o of C(e))j.call(e,o)&&f(t,o,e[o]);return t},x=(t,e)=>E(t,y(e));import{useCallback as M,useMemo as h,useState as k}from"react";import{createContext as S}from"react";var c=S(null);import{jsx as g,jsxs as V}from"react/jsx-runtime";var G=({children:t})=>{let[e,o]=k([]),p=M(n=>{let s={id:n.id,opened:!0,Component:n.Component,props:n.props};o(r=>{let m=r.findIndex(T=>T.id===n.id);if(m===-1)return[...r,s];let P=[...r];return P[m]=s,P})},[]),l=M(n=>{o(s=>s.map(r=>r.id===n?x(d({},r),{opened:!1}):r))},[]),a=M(n=>{o(s=>s.filter(r=>r.id!==n))},[]),i=h(()=>({open:p,close:l,remove:a}),[p,l,a]);return V(c.Provider,{value:i,children:[t,e.map(({Component:n,id:s,opened:r,props:m})=>g(n,x(d({},m),{opened:r,onClose:()=>l(s),onExited:()=>a(s)}),s))]})};import{useCallback as u,useContext as w,useEffect as I,useId as N,useRef as R}from"react";var X=(t,e)=>{let o=w(c);if(!o)throw new Error("useModalController must be used inside <ModalScope>");let p=N(),l=R(e);I(()=>{l.current=e},[e]),I(()=>()=>{o.remove(p)},[o,p]);let a=u(s=>{let r=d(d({},l.current||{}),s||{});o.open({id:p,Component:t,props:r})},[o,p,t]),i=u(()=>{o.close(p)},[o,p]),n=u(()=>{o.remove(p)},[o,p]);return{open:a,close:i,remove:n}};export{c as ModalContext,G as ModalScope,X as useModalController};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lcashe/react-modal-controller",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Tiny type-safe modal controller for React with inferred props and no registry.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup components/modal-controller/index.ts --format esm,cjs --dts --minify --external react --tsconfig tsconfig.build.json",
|
|
16
|
+
"dev": "tsup components/modal-controller/index.ts --format esm,cjs --dts --watch --external react --tsconfig tsconfig.build.json",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"react",
|
|
21
|
+
"modal",
|
|
22
|
+
"modal-controller",
|
|
23
|
+
"typescript",
|
|
24
|
+
"nextjs"
|
|
25
|
+
],
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"react": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/react": "^19",
|
|
31
|
+
"tsup": "latest",
|
|
32
|
+
"typescript": "^5"
|
|
33
|
+
}
|
|
34
|
+
}
|