@manhphi1309/dialog 0.1.1 → 0.2.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/dist/index.mjs CHANGED
@@ -1,34 +1,100 @@
1
- "use client";
1
+ import "react";
2
2
  import { XIcon } from "lucide-react";
3
3
  import { Dialog as Dialog$1 } from "radix-ui";
4
4
  import { Button } from "@manhphi1309/button";
5
5
  import { cn } from "@manhphi1309/utils";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
- //#region index.tsx
7
+ import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "@manhphi1309/drawer";
8
+ import { useIsMobile } from "@manhphi1309/hooks";
9
+ //#region src/dialog.tsx
10
+ /**
11
+ * Root dialog component. Controls open/close state and provides context
12
+ * to all child primitives via Radix `Dialog.Root`.
13
+ *
14
+ * Can be used as **uncontrolled** (via `defaultOpen`) or **controlled**
15
+ * (via `open` + `onOpenChange`).
16
+ *
17
+ * @example
18
+ * // Uncontrolled
19
+ * <Dialog>
20
+ * <DialogTrigger>Open</DialogTrigger>
21
+ * <DialogContent>...</DialogContent>
22
+ * </Dialog>
23
+ *
24
+ * @example
25
+ * // Controlled
26
+ * const [open, setOpen] = useState(false)
27
+ * <Dialog open={open} onOpenChange={setOpen}>
28
+ * <DialogContent>...</DialogContent>
29
+ * </Dialog>
30
+ */
8
31
  function Dialog({ ...props }) {
9
32
  return /* @__PURE__ */ jsx(Dialog$1.Root, {
10
33
  "data-slot": "dialog",
11
34
  ...props
12
35
  });
13
36
  }
37
+ /**
38
+ * The element that opens the dialog when activated (clicked / keyboard).
39
+ * Thin wrapper around Radix `Dialog.Trigger`.
40
+ *
41
+ * Use `asChild` to compose with your own element instead of the default button.
42
+ *
43
+ * @example
44
+ * <DialogTrigger asChild>
45
+ * <Button variant="outline">Open</Button>
46
+ * </DialogTrigger>
47
+ */
14
48
  function DialogTrigger({ ...props }) {
15
49
  return /* @__PURE__ */ jsx(Dialog$1.Trigger, {
16
50
  "data-slot": "dialog-trigger",
17
51
  ...props
18
52
  });
19
53
  }
54
+ /**
55
+ * Renders its children into a **portal** appended to `document.body`,
56
+ * outside the current DOM tree. This ensures the dialog always appears
57
+ * above other content and is never clipped by parent `overflow` or `z-index`.
58
+ *
59
+ * You rarely need to use this directly — `DialogContent` already wraps its
60
+ * output in a portal automatically.
61
+ */
20
62
  function DialogPortal({ ...props }) {
21
63
  return /* @__PURE__ */ jsx(Dialog$1.Portal, {
22
64
  "data-slot": "dialog-portal",
23
65
  ...props
24
66
  });
25
67
  }
68
+ /**
69
+ * A primitive close trigger. Closes the dialog when activated.
70
+ * Use `asChild` to compose with a custom element.
71
+ *
72
+ * Prefer the built-in `showCloseButton` prop on `DialogContent` or
73
+ * `DialogFooter` for standard close affordances. Use this component
74
+ * when you need a completely custom close action inside the dialog body.
75
+ *
76
+ * @example
77
+ * <DialogClose asChild>
78
+ * <Button variant="ghost">Cancel</Button>
79
+ * </DialogClose>
80
+ */
26
81
  function DialogClose({ ...props }) {
27
82
  return /* @__PURE__ */ jsx(Dialog$1.Close, {
28
83
  "data-slot": "dialog-close",
29
84
  ...props
30
85
  });
31
86
  }
87
+ /**
88
+ * The semi-transparent **backdrop** rendered behind the dialog panel.
89
+ * Covers the full viewport (`fixed inset-0`) with a slight blur and dark tint.
90
+ *
91
+ * Animates:
92
+ * - **Open** → `fade-in-0`
93
+ * - **Close** → `fade-out-0`
94
+ *
95
+ * You rarely need to use this directly — `DialogContent` renders it
96
+ * automatically via `DialogPortal`.
97
+ */
32
98
  function DialogOverlay({ className, ...props }) {
33
99
  return /* @__PURE__ */ jsx(Dialog$1.Overlay, {
34
100
  "data-slot": "dialog-overlay",
@@ -36,6 +102,29 @@ function DialogOverlay({ className, ...props }) {
36
102
  ...props
37
103
  });
38
104
  }
105
+ /**
106
+ * The **visible panel** of the dialog. Rendered centred on screen via
107
+ * `position: fixed; top: 50%; left: 50%; translate(-50%, -50%)`.
108
+ * Automatically wraps itself in `DialogPortal` and renders `DialogOverlay`
109
+ * behind it.
110
+ *
111
+ * By default a ghost ✕ button is positioned in the top-right corner.
112
+ * Pass `showCloseButton={false}` to remove it when you want to control
113
+ * close behaviour exclusively from the footer.
114
+ *
115
+ * Animates:
116
+ * - **Open** → `fade-in-0 + zoom-in-95`
117
+ * - **Close** → `fade-out-0 + zoom-out-95`
118
+ *
119
+ * @param showCloseButton - Whether to render the ✕ ghost button in the
120
+ * top-right corner. Defaults to `true`.
121
+ *
122
+ * @example
123
+ * <DialogContent showCloseButton={false}>
124
+ * <DialogHeader>...</DialogHeader>
125
+ * <DialogFooter showCloseButton>...</DialogFooter>
126
+ * </DialogContent>
127
+ */
39
128
  function DialogContent({ className, children, showCloseButton = true, ...props }) {
40
129
  return /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(Dialog$1.Content, {
41
130
  "data-slot": "dialog-content",
@@ -47,7 +136,8 @@ function DialogContent({ className, children, showCloseButton = true, ...props }
47
136
  children: /* @__PURE__ */ jsxs(Button, {
48
137
  variant: "ghost",
49
138
  className: "absolute top-2 right-2",
50
- size: "icon-sm",
139
+ size: "sm",
140
+ icon: true,
51
141
  children: [/* @__PURE__ */ jsx(XIcon, {}), /* @__PURE__ */ jsx("span", {
52
142
  className: "sr-only",
53
143
  children: "Close"
@@ -56,6 +146,17 @@ function DialogContent({ className, children, showCloseButton = true, ...props }
56
146
  })]
57
147
  })] });
58
148
  }
149
+ /**
150
+ * Layout wrapper for the **top section** of a dialog.
151
+ * Stacks children vertically with `gap-2`. Typically contains
152
+ * `DialogTitle` and optionally `DialogDescription`.
153
+ *
154
+ * @example
155
+ * <DialogHeader>
156
+ * <DialogTitle>Confirm deletion</DialogTitle>
157
+ * <DialogDescription>This action is irreversible.</DialogDescription>
158
+ * </DialogHeader>
159
+ */
59
160
  function DialogHeader({ className, ...props }) {
60
161
  return /* @__PURE__ */ jsx("div", {
61
162
  "data-slot": "dialog-header",
@@ -63,6 +164,20 @@ function DialogHeader({ className, ...props }) {
63
164
  ...props
64
165
  });
65
166
  }
167
+ /**
168
+ * Layout wrapper for the **bottom action area** of a dialog.
169
+ * Bleeds to the content panel edges (`-mx-4 -mb-4`), adds a top border
170
+ * and a subtle muted background tint. Actions stack vertically on mobile
171
+ * and align to the right on `sm+` screens.
172
+ *
173
+ * @param showCloseButton - When `true`, appends an outline `Close` button
174
+ * wired to `DialogClose` after any provided `children`. Defaults to `false`.
175
+ *
176
+ * @example
177
+ * <DialogFooter showCloseButton>
178
+ * <Button type="submit">Save</Button>
179
+ * </DialogFooter>
180
+ */
66
181
  function DialogFooter({ className, showCloseButton = false, children, ...props }) {
67
182
  return /* @__PURE__ */ jsxs("div", {
68
183
  "data-slot": "dialog-footer",
@@ -77,6 +192,16 @@ function DialogFooter({ className, showCloseButton = false, children, ...props }
77
192
  })]
78
193
  });
79
194
  }
195
+ /**
196
+ * The **accessible title** of the dialog. Rendered as a Radix `Dialog.Title`,
197
+ * which is automatically linked to the dialog content via `aria-labelledby`.
198
+ * Screen readers announce this text when the dialog opens.
199
+ *
200
+ * Styled with the heading font token at `text-base font-medium`.
201
+ *
202
+ * @remarks Required for accessibility. Every dialog should have a title,
203
+ * even if visually hidden with `className="sr-only"`.
204
+ */
80
205
  function DialogTitle({ className, ...props }) {
81
206
  return /* @__PURE__ */ jsx(Dialog$1.Title, {
82
207
  "data-slot": "dialog-title",
@@ -84,6 +209,17 @@ function DialogTitle({ className, ...props }) {
84
209
  ...props
85
210
  });
86
211
  }
212
+ /**
213
+ * The **accessible description** of the dialog. Rendered as a Radix
214
+ * `Dialog.Description`, which is linked to the dialog content via
215
+ * `aria-describedby`. Screen readers read this after the title.
216
+ *
217
+ * Styled as small muted text. Anchor tags (`<a>`) inside the description
218
+ * are automatically underlined and change colour on hover.
219
+ *
220
+ * @remarks Optional but recommended. Omit only if the dialog content itself
221
+ * is sufficiently self-descriptive.
222
+ */
87
223
  function DialogDescription({ className, ...props }) {
88
224
  return /* @__PURE__ */ jsx(Dialog$1.Description, {
89
225
  "data-slot": "dialog-description",
@@ -92,4 +228,212 @@ function DialogDescription({ className, ...props }) {
92
228
  });
93
229
  }
94
230
  //#endregion
95
- export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger };
231
+ //#region src/responsive-dialog.tsx
232
+ /**
233
+ * Adaptive root component that switches between `Dialog` and `Drawer`
234
+ * depending on screen width:
235
+ * - **Desktop (≥ 768 px)** → renders `Dialog` (centred modal overlay)
236
+ * - **Mobile (< 768 px)** → renders `Drawer` (bottom-sheet panel)
237
+ *
238
+ * All props are forwarded unchanged to the active primitive, so `open`,
239
+ * `defaultOpen`, `onOpenChange`, and `modal` all behave identically to
240
+ * the standard `Dialog` root.
241
+ *
242
+ * @example
243
+ * <ResponsiveDialog>
244
+ * <ResponsiveDialogTrigger asChild>
245
+ * <Button>Open</Button>
246
+ * </ResponsiveDialogTrigger>
247
+ * <ResponsiveDialogContent>
248
+ * <ResponsiveDialogHeader>
249
+ * <ResponsiveDialogTitle>Title</ResponsiveDialogTitle>
250
+ * </ResponsiveDialogHeader>
251
+ * </ResponsiveDialogContent>
252
+ * </ResponsiveDialog>
253
+ */
254
+ function ResponsiveDialog({ children, ...props }) {
255
+ if (useIsMobile()) return /* @__PURE__ */ jsx(Drawer, {
256
+ ...props,
257
+ children
258
+ });
259
+ return /* @__PURE__ */ jsx(Dialog, {
260
+ ...props,
261
+ children
262
+ });
263
+ }
264
+ /**
265
+ * The element that opens the responsive dialog/drawer when activated.
266
+ *
267
+ * - **Desktop** → `DialogTrigger` (Radix `Dialog.Trigger`)
268
+ * - **Mobile** → `DrawerTrigger` (Vaul `Drawer.Trigger`)
269
+ *
270
+ * Use `asChild` to render your own element as the trigger instead of
271
+ * the default button wrapper.
272
+ *
273
+ * @example
274
+ * <ResponsiveDialogTrigger asChild>
275
+ * <Button variant="outline">Open</Button>
276
+ * </ResponsiveDialogTrigger>
277
+ */
278
+ function ResponsiveDialogTrigger({ children, ...props }) {
279
+ if (useIsMobile()) return /* @__PURE__ */ jsx(DrawerTrigger, {
280
+ ...props,
281
+ children
282
+ });
283
+ return /* @__PURE__ */ jsx(DialogTrigger, {
284
+ ...props,
285
+ children
286
+ });
287
+ }
288
+ /**
289
+ * A close trigger that targets the correct primitive for the current
290
+ * viewport:
291
+ * - **Desktop** → `DialogClose` (Radix `Dialog.Close`)
292
+ * - **Mobile** → `DrawerClose` (Vaul `Drawer.Close`)
293
+ *
294
+ * Use `asChild` to wrap a custom element.
295
+ *
296
+ * @example
297
+ * <ResponsiveDialogClose asChild>
298
+ * <Button variant="ghost">Cancel</Button>
299
+ * </ResponsiveDialogClose>
300
+ */
301
+ function ResponsiveDialogClose({ children, ...props }) {
302
+ if (useIsMobile()) return /* @__PURE__ */ jsx(DrawerClose, {
303
+ ...props,
304
+ children
305
+ });
306
+ return /* @__PURE__ */ jsx(DialogClose, {
307
+ ...props,
308
+ children
309
+ });
310
+ }
311
+ /**
312
+ * The visible panel of the responsive dialog. Delegates to the
313
+ * appropriate primitive:
314
+ * - **Desktop** → `DialogContent` — centred modal with fade + zoom animations.
315
+ * The ✕ close button is shown by default (`showCloseButton={true}`).
316
+ * - **Mobile** → `DrawerContent` — bottom-sheet panel with a drag handle.
317
+ * `showCloseButton` has **no effect** on mobile; the drawer is closed
318
+ * via swipe gesture or the drag handle.
319
+ *
320
+ * @param showCloseButton - Show/hide the ✕ button in the top-right corner
321
+ * on desktop. Has no effect on mobile. Defaults to `true`.
322
+ *
323
+ * @example
324
+ * // Hide the ✕ button and force close via footer only (desktop)
325
+ * <ResponsiveDialogContent showCloseButton={false}>
326
+ * ...
327
+ * </ResponsiveDialogContent>
328
+ */
329
+ function ResponsiveDialogContent({ className, children, showCloseButton = true, ...props }) {
330
+ if (useIsMobile()) return /* @__PURE__ */ jsx(DrawerContent, {
331
+ className,
332
+ children
333
+ });
334
+ return /* @__PURE__ */ jsx(DialogContent, {
335
+ className,
336
+ showCloseButton,
337
+ ...props,
338
+ children
339
+ });
340
+ }
341
+ /**
342
+ * Layout wrapper for the **top section** of the responsive dialog/drawer.
343
+ *
344
+ * - **Desktop** → `DialogHeader` — vertical stack with `gap-2`
345
+ * - **Mobile** → `DrawerHeader` — vertically centred text for bottom/top
346
+ * drawers, left-aligned for side drawers
347
+ *
348
+ * Typically contains `ResponsiveDialogTitle` and optionally
349
+ * `ResponsiveDialogDescription`.
350
+ */
351
+ function ResponsiveDialogHeader({ className, ...props }) {
352
+ if (useIsMobile()) return /* @__PURE__ */ jsx(DrawerHeader, {
353
+ className,
354
+ ...props
355
+ });
356
+ return /* @__PURE__ */ jsx(DialogHeader, {
357
+ className,
358
+ ...props
359
+ });
360
+ }
361
+ /**
362
+ * Layout wrapper for the **bottom action area** of the responsive
363
+ * dialog/drawer.
364
+ *
365
+ * - **Desktop** → `DialogFooter` — bleeds to panel edges, top border,
366
+ * actions aligned right on `sm+`
367
+ * - **Mobile** → `DrawerFooter` — `mt-auto` stacked column at the bottom
368
+ * of the drawer
369
+ *
370
+ * @param showCloseButton - When `true`, appends a close button after
371
+ * `children`. On **desktop** this is a `DialogClose`-wrapped outline
372
+ * button; on **mobile** this is a `DrawerClose`-wrapped outline button.
373
+ * Defaults to `false`.
374
+ *
375
+ * @example
376
+ * <ResponsiveDialogFooter showCloseButton>
377
+ * <Button type="submit">Save</Button>
378
+ * </ResponsiveDialogFooter>
379
+ */
380
+ function ResponsiveDialogFooter({ className, children, showCloseButton = false, ...props }) {
381
+ if (useIsMobile()) return /* @__PURE__ */ jsxs(DrawerFooter, {
382
+ className,
383
+ ...props,
384
+ children: [children, showCloseButton && /* @__PURE__ */ jsx(DrawerClose, {
385
+ asChild: true,
386
+ children: /* @__PURE__ */ jsx(Button, {
387
+ variant: "outline",
388
+ children: "Close"
389
+ })
390
+ })]
391
+ });
392
+ return /* @__PURE__ */ jsx(DialogFooter, {
393
+ className,
394
+ showCloseButton,
395
+ ...props,
396
+ children
397
+ });
398
+ }
399
+ /**
400
+ * The **accessible title** of the responsive dialog/drawer.
401
+ *
402
+ * - **Desktop** → `DialogTitle` (linked via `aria-labelledby`)
403
+ * - **Mobile** → `DrawerTitle` (linked via `aria-labelledby`)
404
+ *
405
+ * Screen readers announce this text when the panel opens.
406
+ *
407
+ * @remarks Required for accessibility on both desktop and mobile.
408
+ */
409
+ function ResponsiveDialogTitle({ className, ...props }) {
410
+ if (useIsMobile()) return /* @__PURE__ */ jsx(DrawerTitle, {
411
+ className,
412
+ ...props
413
+ });
414
+ return /* @__PURE__ */ jsx(DialogTitle, {
415
+ className,
416
+ ...props
417
+ });
418
+ }
419
+ /**
420
+ * The **accessible description** of the responsive dialog/drawer.
421
+ *
422
+ * - **Desktop** → `DialogDescription` (linked via `aria-describedby`)
423
+ * - **Mobile** → `DrawerDescription` (linked via `aria-describedby`)
424
+ *
425
+ * @remarks Optional but recommended. Provides supplementary context for
426
+ * screen reader users after the title is announced.
427
+ */
428
+ function ResponsiveDialogDescription({ className, ...props }) {
429
+ if (useIsMobile()) return /* @__PURE__ */ jsx(DrawerDescription, {
430
+ className,
431
+ ...props
432
+ });
433
+ return /* @__PURE__ */ jsx(DialogDescription, {
434
+ className,
435
+ ...props
436
+ });
437
+ }
438
+ //#endregion
439
+ export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, ResponsiveDialog, ResponsiveDialogClose, ResponsiveDialogContent, ResponsiveDialogDescription, ResponsiveDialogFooter, ResponsiveDialogHeader, ResponsiveDialogTitle, ResponsiveDialogTrigger };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manhphi1309/dialog",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "sideEffects": false,
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -29,6 +29,8 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@manhphi1309/button": "*",
32
+ "@manhphi1309/drawer": "*",
33
+ "@manhphi1309/hooks": "*",
32
34
  "@manhphi1309/utils": "*",
33
35
  "class-variance-authority": "*"
34
36
  }