modal-system 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/README.md +386 -0
- package/dist/cli/index.js +785 -0
- package/dist/cli/templates/daisyui/ConfirmModal.tsx.tpl +65 -0
- package/dist/cli/templates/daisyui/Modal.tsx.tpl +76 -0
- package/dist/cli/templates/daisyui/index.ts.tpl +37 -0
- package/dist/cli/templates/daisyui/modals.ts.tpl +37 -0
- package/dist/cli/templates/shadcn-baseui/ConfirmModal.tsx.tpl +68 -0
- package/dist/cli/templates/shadcn-baseui/index.ts.tpl +15 -0
- package/dist/cli/templates/shadcn-baseui/modals.ts.tpl +20 -0
- package/dist/cli/templates/shadcn-radix/ConfirmModal.tsx.tpl +63 -0
- package/dist/cli/templates/shadcn-radix/index.ts.tpl +19 -0
- package/dist/cli/templates/shadcn-radix/modals.ts.tpl +37 -0
- package/dist/index.d.mts +86 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +75 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# modal-system
|
|
2
|
+
|
|
3
|
+
Type-safe modal management for React. One provider, one hook, full TypeScript autocomplete for modal names and their data shapes.
|
|
4
|
+
|
|
5
|
+
Supports **shadcn/ui** (Radix UI or Base UI) and **DaisyUI** out of the box.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add modal-system
|
|
13
|
+
# or
|
|
14
|
+
npm install modal-system
|
|
15
|
+
# or
|
|
16
|
+
yarn add modal-system
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Peer dependencies** — make sure these are already in your project:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun add react react-dom
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
### 1. Run the interactive init
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bunx modal-system init
|
|
33
|
+
# or
|
|
34
|
+
npx modal-system init
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The CLI will ask:
|
|
38
|
+
- Which UI library? (`shadcn/ui` or `DaisyUI`)
|
|
39
|
+
- If shadcn — which primitive? (`Radix UI` or `Base UI`)
|
|
40
|
+
- Where to put the files? (default: `src/modals`)
|
|
41
|
+
|
|
42
|
+
It generates a ready-to-use `modals/` folder:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
src/modals/
|
|
46
|
+
index.ts ← MODALS registry + useModal hook
|
|
47
|
+
modals.ts ← data interfaces for each modal
|
|
48
|
+
ConfirmModal.tsx ← starter confirm modal component
|
|
49
|
+
Modal.tsx ← (DaisyUI only) native <dialog> primitive
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Wrap your app with `ModalProvider`
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// src/main.tsx (or app/layout.tsx for Next.js)
|
|
56
|
+
import { ModalProvider } from "modal-system";
|
|
57
|
+
import { MODALS } from "./modals";
|
|
58
|
+
|
|
59
|
+
export default function App() {
|
|
60
|
+
return (
|
|
61
|
+
<ModalProvider modals={MODALS}>
|
|
62
|
+
<YourApp />
|
|
63
|
+
</ModalProvider>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Open a modal from any component
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { useModal } from "./modals";
|
|
72
|
+
|
|
73
|
+
export function DeleteButton({ userId }: { userId: string }) {
|
|
74
|
+
const { openModal } = useModal();
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<button
|
|
78
|
+
onClick={() =>
|
|
79
|
+
openModal("confirm", {
|
|
80
|
+
title: "Delete account?",
|
|
81
|
+
description: "This action cannot be undone.",
|
|
82
|
+
actionText: "Delete",
|
|
83
|
+
isActionButtonDestructive: true,
|
|
84
|
+
onConfirm: () => deleteUser(userId),
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
>
|
|
88
|
+
Delete
|
|
89
|
+
</button>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Both `"confirm"` and the data object are **fully typed** — wrong keys or wrong value types are caught at compile time.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Adding a new modal
|
|
99
|
+
|
|
100
|
+
**1. Add a data interface in `modals.ts`:**
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
export interface DeleteUserModalData {
|
|
104
|
+
userId: string;
|
|
105
|
+
userName: string;
|
|
106
|
+
onDeleted?: () => void;
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**2. Create the component:**
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
// src/modals/DeleteUserModal.tsx
|
|
114
|
+
import type { BaseModalProps } from "modal-system";
|
|
115
|
+
import type { DeleteUserModalData } from "./modals";
|
|
116
|
+
|
|
117
|
+
export function DeleteUserModal({ isOpen, onClose, data }: BaseModalProps<DeleteUserModalData>) {
|
|
118
|
+
// data?.userId, data?.userName, data?.onDeleted
|
|
119
|
+
return (/* your JSX */);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**3. Register it in `index.ts`:**
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { DeleteUserModal } from "./DeleteUserModal";
|
|
127
|
+
|
|
128
|
+
export const MODALS = defineModals({
|
|
129
|
+
confirm: ConfirmModal,
|
|
130
|
+
deleteUser: DeleteUserModal, // ← add here
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`openModal("deleteUser", { userId: "123", userName: "Alice" })` is now fully typed.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## API
|
|
139
|
+
|
|
140
|
+
### `<ModalProvider modals={MODALS}>`
|
|
141
|
+
|
|
142
|
+
Renders all registered modals and exposes the modal context. Must wrap the part of the tree where `useModal` is called.
|
|
143
|
+
|
|
144
|
+
| Prop | Type | Description |
|
|
145
|
+
|----------|------------------|--------------------------------------|
|
|
146
|
+
| `modals` | `ModalRegistry` | Object mapping name → component |
|
|
147
|
+
| `children` | `ReactNode` | Your application tree |
|
|
148
|
+
|
|
149
|
+
### `createModalHook(MODALS)`
|
|
150
|
+
|
|
151
|
+
Factory that returns a typed `useModal` hook bound to your specific registry. **Import `useModal` from your own `modals/index.ts`**, not from `modal-system` directly.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
export const useModal = createModalHook(MODALS);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `useModal()`
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
|
|
161
|
+
| Field | Type | Description |
|
|
162
|
+
|--------------|-----------------------------------------------|--------------------------|
|
|
163
|
+
| `openModal` | `(name: K, data?: DataMap[K]) => void` | Open a modal by name |
|
|
164
|
+
| `closeModal` | `() => void` | Close the active modal |
|
|
165
|
+
|
|
166
|
+
### `defineModals(modals)`
|
|
167
|
+
|
|
168
|
+
Identity helper that enforces `ModalRegistry` type constraint and enables inference.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
export const MODALS = defineModals({
|
|
172
|
+
confirm: ConfirmModal,
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### `BaseModalProps<TData>`
|
|
177
|
+
|
|
178
|
+
Base props interface that every modal component should extend.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
interface BaseModalProps<TData = unknown> {
|
|
182
|
+
isOpen: boolean;
|
|
183
|
+
onClose: () => void;
|
|
184
|
+
data?: TData;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Supported UI libraries
|
|
191
|
+
|
|
192
|
+
| Template | Primitive | Install |
|
|
193
|
+
|------------------|-----------------------|------------------------------------------|
|
|
194
|
+
| `shadcn/ui` | Radix UI | `npx shadcn@latest add dialog button` |
|
|
195
|
+
| `shadcn/ui` | Base UI | `bun add @base-ui-components/react` |
|
|
196
|
+
| `DaisyUI` | Native `<dialog>` | `bun add daisyui` + Tailwind CSS |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## TypeScript
|
|
201
|
+
|
|
202
|
+
The library ships with bundled `.d.ts` declarations — no `@types/*` package needed.
|
|
203
|
+
|
|
204
|
+
Exported types:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import type { BaseModalProps, ModalRegistry, ModalDataMap } from "modal-system";
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
# modal-system (на русском)
|
|
215
|
+
|
|
216
|
+
Типобезопасное управление модальными окнами для React. Один провайдер, один хук, полный автокомплит TypeScript для имён модалок и форм данных.
|
|
217
|
+
|
|
218
|
+
Поддерживает **shadcn/ui** (Radix UI или Base UI) и **DaisyUI**.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Установка
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
bun add modal-system
|
|
226
|
+
# или
|
|
227
|
+
npm install modal-system
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Peer-зависимости** — должны уже быть в вашем проекте:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
bun add react react-dom
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Быстрый старт
|
|
239
|
+
|
|
240
|
+
### 1. Запустите интерактивную инициализацию
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
bunx modal-system init
|
|
244
|
+
# или
|
|
245
|
+
npx modal-system init
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
CLI задаст вопросы:
|
|
249
|
+
- Какая UI-библиотека? (`shadcn/ui` или `DaisyUI`)
|
|
250
|
+
- Если shadcn — какой примитив? (`Radix UI` или `Base UI`)
|
|
251
|
+
- Куда положить файлы? (по умолчанию: `src/modals`)
|
|
252
|
+
|
|
253
|
+
Будет создана папка `modals/` с готовыми файлами:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
src/modals/
|
|
257
|
+
index.ts ← реестр MODALS и хук useModal
|
|
258
|
+
modals.ts ← интерфейсы данных для каждой модалки
|
|
259
|
+
ConfirmModal.tsx ← стартовый компонент подтверждения
|
|
260
|
+
Modal.tsx ← (только DaisyUI) примитив на базе <dialog>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 2. Оберните приложение в `ModalProvider`
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
// src/main.tsx (или app/layout.tsx для Next.js)
|
|
267
|
+
import { ModalProvider } from "modal-system";
|
|
268
|
+
import { MODALS } from "./modals";
|
|
269
|
+
|
|
270
|
+
export default function App() {
|
|
271
|
+
return (
|
|
272
|
+
<ModalProvider modals={MODALS}>
|
|
273
|
+
<YourApp />
|
|
274
|
+
</ModalProvider>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### 3. Откройте модалку из любого компонента
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
import { useModal } from "./modals";
|
|
283
|
+
|
|
284
|
+
export function DeleteButton({ userId }: { userId: string }) {
|
|
285
|
+
const { openModal } = useModal();
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<button
|
|
289
|
+
onClick={() =>
|
|
290
|
+
openModal("confirm", {
|
|
291
|
+
title: "Удалить аккаунт?",
|
|
292
|
+
description: "Это действие нельзя отменить.",
|
|
293
|
+
actionText: "Удалить",
|
|
294
|
+
isActionButtonDestructive: true,
|
|
295
|
+
onConfirm: () => deleteUser(userId),
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
>
|
|
299
|
+
Удалить
|
|
300
|
+
</button>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
И `"confirm"`, и объект данных **полностью типизированы** — неверные ключи или типы значений обнаруживаются на этапе компиляции.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Добавление новой модалки
|
|
310
|
+
|
|
311
|
+
**1. Добавьте интерфейс данных в `modals.ts`:**
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
export interface DeleteUserModalData {
|
|
315
|
+
userId: string;
|
|
316
|
+
userName: string;
|
|
317
|
+
onDeleted?: () => void;
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**2. Создайте компонент:**
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
// src/modals/DeleteUserModal.tsx
|
|
325
|
+
import type { BaseModalProps } from "modal-system";
|
|
326
|
+
import type { DeleteUserModalData } from "./modals";
|
|
327
|
+
|
|
328
|
+
export function DeleteUserModal({ isOpen, onClose, data }: BaseModalProps<DeleteUserModalData>) {
|
|
329
|
+
// data?.userId, data?.userName, data?.onDeleted
|
|
330
|
+
return (/* ваш JSX */);
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**3. Зарегистрируйте в `index.ts`:**
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
import { DeleteUserModal } from "./DeleteUserModal";
|
|
338
|
+
|
|
339
|
+
export const MODALS = defineModals({
|
|
340
|
+
confirm: ConfirmModal,
|
|
341
|
+
deleteUser: DeleteUserModal, // ← добавить здесь
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Теперь `openModal("deleteUser", { userId: "123", userName: "Алиса" })` полностью типизирован.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## API
|
|
350
|
+
|
|
351
|
+
### `<ModalProvider modals={MODALS}>`
|
|
352
|
+
|
|
353
|
+
Рендерит все зарегистрированные модалки и предоставляет контекст. Должен оборачивать ту часть дерева, где вызывается `useModal`.
|
|
354
|
+
|
|
355
|
+
### `createModalHook(MODALS)`
|
|
356
|
+
|
|
357
|
+
Фабрика, возвращающая типизированный хук `useModal`, привязанный к вашему реестру. **Импортируйте `useModal` из своего `modals/index.ts`**, а не из `modal-system` напрямую.
|
|
358
|
+
|
|
359
|
+
### `useModal()`
|
|
360
|
+
|
|
361
|
+
| Поле | Тип | Описание |
|
|
362
|
+
|--------------|-----------------------------------------------|---------------------------------|
|
|
363
|
+
| `openModal` | `(name: K, data?: DataMap[K]) => void` | Открыть модалку по имени |
|
|
364
|
+
| `closeModal` | `() => void` | Закрыть активную модалку |
|
|
365
|
+
|
|
366
|
+
### `BaseModalProps<TData>`
|
|
367
|
+
|
|
368
|
+
Базовый интерфейс пропсов, который должен использовать каждый компонент модалки.
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
interface BaseModalProps<TData = unknown> {
|
|
372
|
+
isOpen: boolean;
|
|
373
|
+
onClose: () => void;
|
|
374
|
+
data?: TData;
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Поддерживаемые UI-библиотеки
|
|
381
|
+
|
|
382
|
+
| Шаблон | Примитив | Установка |
|
|
383
|
+
|------------------|-----------------------|------------------------------------------|
|
|
384
|
+
| `shadcn/ui` | Radix UI | `npx shadcn@latest add dialog button` |
|
|
385
|
+
| `shadcn/ui` | Base UI | `bun add @base-ui-components/react` |
|
|
386
|
+
| `DaisyUI` | Нативный `<dialog>` | `bun add daisyui` + Tailwind CSS |
|