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 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 |