@manhphi1309/dialog 0.2.0 → 0.3.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 CHANGED
@@ -178,6 +178,29 @@ All props are forwarded to the underlying component unchanged. `showCloseButton`
178
178
 
179
179
  > **Note:** `ResponsiveDialogContent` does **not** forward `showCloseButton` to `DrawerContent` (the Drawer handles close via the handle bar and swipe gesture). The prop is only meaningful on desktop.
180
180
 
181
+ ### Drawer Snap Points (Mobile Only)
182
+
183
+ Because `ResponsiveDialog` forwards properties directly to the underlying Vaul `<Drawer>` on mobile devices, you can pass drawer-specific props like `snapPoints` directly to the `<ResponsiveDialog>` root component. The desktop `<Dialog>` will safely ignore them.
184
+
185
+ ```tsx
186
+ // Using React state to properly control the active snap point
187
+ const [snap, setSnap] = React.useState<number | string | null>("500px")
188
+
189
+ <ResponsiveDialog
190
+ snapPoints={["300px", "500px", 1]}
191
+ activeSnapPoint={snap}
192
+ setActiveSnapPoint={setSnap}
193
+ fadeFromIndex={1}
194
+ >
195
+ ...
196
+ </ResponsiveDialog>
197
+ ```
198
+
199
+ > [!WARNING]
200
+ > **Important Vaul Requirements:**
201
+ > 1. **Strict Types:** If you provide `snapPoints`, you are strictly required by Vaul to provide `fadeFromIndex` as a number.
202
+ > 2. **Controlled State:** If you hardcode `activeSnapPoint` as a string/number prop *without* passing a state setter to `setActiveSnapPoint`, the Drawer will glitch and snap back down when the user tries to drag it, because React will force it back to the hardcoded prop value on the next render. Always use a state hook (like `useState`) for the active point.
203
+
181
204
  ---
182
205
 
183
206
  ## Usage
package/dist/index.cjs CHANGED
@@ -1,6 +1,28 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
2
23
  //#endregion
3
- require("react");
24
+ let react = require("react");
25
+ react = __toESM(react);
4
26
  let lucide_react = require("lucide-react");
5
27
  let radix_ui = require("radix-ui");
6
28
  let _manhphi1309_button = require("@manhphi1309/button");
@@ -231,6 +253,7 @@ function DialogDescription({ className, ...props }) {
231
253
  }
232
254
  //#endregion
233
255
  //#region src/responsive-dialog.tsx
256
+ const ResponsiveDialogContext = react.createContext({ snap: true });
234
257
  /**
235
258
  * Adaptive root component that switches between `Dialog` and `Drawer`
236
259
  * depending on screen width:
@@ -241,6 +264,11 @@ function DialogDescription({ className, ...props }) {
241
264
  * `defaultOpen`, `onOpenChange`, and `modal` all behave identically to
242
265
  * the standard `Dialog` root.
243
266
  *
267
+ * **Drawer-Specific Props (Mobile Only)**
268
+ * You can pass Vaul-specific props like `snapPoints`. They are cleanly ignored on desktop.
269
+ * - **IMPORTANT:** If you provide `snapPoints`, you must also provide `fadeFromIndex` as a strict number.
270
+ * - **IMPORTANT:** If you pass `activeSnapPoint`, you must also pass `setActiveSnapPoint` and manage it via state (e.g. `useState`). Do not hardcode `activeSnapPoint` without a setter, otherwise the Drawer will glitch and aggressively snap back when dragged.
271
+ *
244
272
  * @example
245
273
  * <ResponsiveDialog>
246
274
  * <ResponsiveDialogTrigger asChild>
@@ -253,14 +281,44 @@ function DialogDescription({ className, ...props }) {
253
281
  * </ResponsiveDialogContent>
254
282
  * </ResponsiveDialog>
255
283
  */
256
- function ResponsiveDialog({ children, ...props }) {
257
- if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.Drawer, {
258
- ...props,
259
- children
260
- });
261
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
262
- ...props,
263
- children
284
+ function ResponsiveDialog({ children, snapPoints, activeSnapPoint, setActiveSnapPoint, fadeFromIndex, shouldScaleBackground, dismissible, snap = true, ...props }) {
285
+ const isMobile = (0, _manhphi1309_hooks.useIsMobile)();
286
+ const [internalOpen, setInternalOpen] = react.useState(props.defaultOpen ?? false);
287
+ const isControlled = props.open !== void 0;
288
+ const open = isControlled ? props.open : internalOpen;
289
+ const { onOpenChange } = props;
290
+ const handleOpenChange = react.useCallback((newOpen) => {
291
+ if (!isControlled) setInternalOpen(newOpen);
292
+ onOpenChange?.(newOpen);
293
+ }, [isControlled, onOpenChange]);
294
+ if (isMobile) {
295
+ const drawerProps = {
296
+ ...props,
297
+ open,
298
+ onOpenChange: handleOpenChange,
299
+ snapPoints: snap ? snapPoints ?? [.5, 1] : snapPoints,
300
+ activeSnapPoint,
301
+ setActiveSnapPoint,
302
+ fadeFromIndex: snap ? fadeFromIndex ?? 0 : fadeFromIndex,
303
+ shouldScaleBackground,
304
+ dismissible
305
+ };
306
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResponsiveDialogContext.Provider, {
307
+ value: { snap },
308
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.Drawer, {
309
+ ...drawerProps,
310
+ children
311
+ })
312
+ });
313
+ }
314
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResponsiveDialogContext.Provider, {
315
+ value: { snap },
316
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
317
+ ...props,
318
+ open,
319
+ onOpenChange: handleOpenChange,
320
+ children
321
+ })
264
322
  });
265
323
  }
266
324
  /**
@@ -329,8 +387,11 @@ function ResponsiveDialogClose({ children, ...props }) {
329
387
  * </ResponsiveDialogContent>
330
388
  */
331
389
  function ResponsiveDialogContent({ className, children, showCloseButton = true, ...props }) {
332
- if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.DrawerContent, {
333
- className,
390
+ const isMobile = (0, _manhphi1309_hooks.useIsMobile)();
391
+ const { snap } = react.useContext(ResponsiveDialogContext);
392
+ if (isMobile) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.DrawerContent, {
393
+ className: (0, _manhphi1309_utils.cn)(snap && "h-[96dvh] !max-h-[96dvh]", className),
394
+ ...props,
334
395
  children
335
396
  });
336
397
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogContent, {
@@ -350,14 +411,18 @@ function ResponsiveDialogContent({ className, children, showCloseButton = true,
350
411
  * Typically contains `ResponsiveDialogTitle` and optionally
351
412
  * `ResponsiveDialogDescription`.
352
413
  */
353
- function ResponsiveDialogHeader({ className, ...props }) {
414
+ function ResponsiveDialogHeader({ className, leftNode, rightNode, children, ...props }) {
354
415
  if ((0, _manhphi1309_hooks.useIsMobile)()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_manhphi1309_drawer.DrawerHeader, {
416
+ leftNode,
417
+ rightNode,
355
418
  className,
356
- ...props
419
+ ...props,
420
+ children
357
421
  });
358
422
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogHeader, {
359
423
  className,
360
- ...props
424
+ ...props,
425
+ children
361
426
  });
362
427
  }
363
428
  /**
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { Dialog as Dialog$1 } from "radix-ui";
3
+ import { Drawer } from "@manhphi1309/drawer";
3
4
 
4
5
  //#region src/dialog.d.ts
5
6
  /**
@@ -191,6 +192,11 @@ declare function DialogDescription({
191
192
  * `defaultOpen`, `onOpenChange`, and `modal` all behave identically to
192
193
  * the standard `Dialog` root.
193
194
  *
195
+ * **Drawer-Specific Props (Mobile Only)**
196
+ * You can pass Vaul-specific props like `snapPoints`. They are cleanly ignored on desktop.
197
+ * - **IMPORTANT:** If you provide `snapPoints`, you must also provide `fadeFromIndex` as a strict number.
198
+ * - **IMPORTANT:** If you pass `activeSnapPoint`, you must also pass `setActiveSnapPoint` and manage it via state (e.g. `useState`). Do not hardcode `activeSnapPoint` without a setter, otherwise the Drawer will glitch and aggressively snap back when dragged.
199
+ *
194
200
  * @example
195
201
  * <ResponsiveDialog>
196
202
  * <ResponsiveDialogTrigger asChild>
@@ -205,8 +211,17 @@ declare function DialogDescription({
205
211
  */
206
212
  declare function ResponsiveDialog({
207
213
  children,
214
+ snapPoints,
215
+ activeSnapPoint,
216
+ setActiveSnapPoint,
217
+ fadeFromIndex,
218
+ shouldScaleBackground,
219
+ dismissible,
220
+ snap,
208
221
  ...props
209
- }: React.ComponentProps<typeof Dialog$1.Root>): React.JSX.Element;
222
+ }: React.ComponentProps<typeof Dialog$1.Root> & Partial<React.ComponentProps<typeof Drawer>> & {
223
+ snap?: boolean;
224
+ }): React.JSX.Element;
210
225
  /**
211
226
  * The element that opens the responsive dialog/drawer when activated.
212
227
  *
@@ -280,8 +295,14 @@ declare function ResponsiveDialogContent({
280
295
  */
281
296
  declare function ResponsiveDialogHeader({
282
297
  className,
298
+ leftNode,
299
+ rightNode,
300
+ children,
283
301
  ...props
284
- }: React.ComponentProps<"div">): React.JSX.Element;
302
+ }: React.ComponentProps<"div"> & {
303
+ leftNode?: React.ReactNode;
304
+ rightNode?: React.ReactNode;
305
+ }): React.JSX.Element;
285
306
  /**
286
307
  * Layout wrapper for the **bottom action area** of the responsive
287
308
  * dialog/drawer.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { Dialog as Dialog$1 } from "radix-ui";
3
+ import { Drawer } from "@manhphi1309/drawer";
3
4
 
4
5
  //#region src/dialog.d.ts
5
6
  /**
@@ -191,6 +192,11 @@ declare function DialogDescription({
191
192
  * `defaultOpen`, `onOpenChange`, and `modal` all behave identically to
192
193
  * the standard `Dialog` root.
193
194
  *
195
+ * **Drawer-Specific Props (Mobile Only)**
196
+ * You can pass Vaul-specific props like `snapPoints`. They are cleanly ignored on desktop.
197
+ * - **IMPORTANT:** If you provide `snapPoints`, you must also provide `fadeFromIndex` as a strict number.
198
+ * - **IMPORTANT:** If you pass `activeSnapPoint`, you must also pass `setActiveSnapPoint` and manage it via state (e.g. `useState`). Do not hardcode `activeSnapPoint` without a setter, otherwise the Drawer will glitch and aggressively snap back when dragged.
199
+ *
194
200
  * @example
195
201
  * <ResponsiveDialog>
196
202
  * <ResponsiveDialogTrigger asChild>
@@ -205,8 +211,17 @@ declare function DialogDescription({
205
211
  */
206
212
  declare function ResponsiveDialog({
207
213
  children,
214
+ snapPoints,
215
+ activeSnapPoint,
216
+ setActiveSnapPoint,
217
+ fadeFromIndex,
218
+ shouldScaleBackground,
219
+ dismissible,
220
+ snap,
208
221
  ...props
209
- }: React.ComponentProps<typeof Dialog$1.Root>): React.JSX.Element;
222
+ }: React.ComponentProps<typeof Dialog$1.Root> & Partial<React.ComponentProps<typeof Drawer>> & {
223
+ snap?: boolean;
224
+ }): React.JSX.Element;
210
225
  /**
211
226
  * The element that opens the responsive dialog/drawer when activated.
212
227
  *
@@ -280,8 +295,14 @@ declare function ResponsiveDialogContent({
280
295
  */
281
296
  declare function ResponsiveDialogHeader({
282
297
  className,
298
+ leftNode,
299
+ rightNode,
300
+ children,
283
301
  ...props
284
- }: React.ComponentProps<"div">): React.JSX.Element;
302
+ }: React.ComponentProps<"div"> & {
303
+ leftNode?: React.ReactNode;
304
+ rightNode?: React.ReactNode;
305
+ }): React.JSX.Element;
285
306
  /**
286
307
  * Layout wrapper for the **bottom action area** of the responsive
287
308
  * dialog/drawer.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import "react";
1
+ import * as React from "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";
@@ -229,6 +229,7 @@ function DialogDescription({ className, ...props }) {
229
229
  }
230
230
  //#endregion
231
231
  //#region src/responsive-dialog.tsx
232
+ const ResponsiveDialogContext = React.createContext({ snap: true });
232
233
  /**
233
234
  * Adaptive root component that switches between `Dialog` and `Drawer`
234
235
  * depending on screen width:
@@ -239,6 +240,11 @@ function DialogDescription({ className, ...props }) {
239
240
  * `defaultOpen`, `onOpenChange`, and `modal` all behave identically to
240
241
  * the standard `Dialog` root.
241
242
  *
243
+ * **Drawer-Specific Props (Mobile Only)**
244
+ * You can pass Vaul-specific props like `snapPoints`. They are cleanly ignored on desktop.
245
+ * - **IMPORTANT:** If you provide `snapPoints`, you must also provide `fadeFromIndex` as a strict number.
246
+ * - **IMPORTANT:** If you pass `activeSnapPoint`, you must also pass `setActiveSnapPoint` and manage it via state (e.g. `useState`). Do not hardcode `activeSnapPoint` without a setter, otherwise the Drawer will glitch and aggressively snap back when dragged.
247
+ *
242
248
  * @example
243
249
  * <ResponsiveDialog>
244
250
  * <ResponsiveDialogTrigger asChild>
@@ -251,14 +257,44 @@ function DialogDescription({ className, ...props }) {
251
257
  * </ResponsiveDialogContent>
252
258
  * </ResponsiveDialog>
253
259
  */
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
260
+ function ResponsiveDialog({ children, snapPoints, activeSnapPoint, setActiveSnapPoint, fadeFromIndex, shouldScaleBackground, dismissible, snap = true, ...props }) {
261
+ const isMobile = useIsMobile();
262
+ const [internalOpen, setInternalOpen] = React.useState(props.defaultOpen ?? false);
263
+ const isControlled = props.open !== void 0;
264
+ const open = isControlled ? props.open : internalOpen;
265
+ const { onOpenChange } = props;
266
+ const handleOpenChange = React.useCallback((newOpen) => {
267
+ if (!isControlled) setInternalOpen(newOpen);
268
+ onOpenChange?.(newOpen);
269
+ }, [isControlled, onOpenChange]);
270
+ if (isMobile) {
271
+ const drawerProps = {
272
+ ...props,
273
+ open,
274
+ onOpenChange: handleOpenChange,
275
+ snapPoints: snap ? snapPoints ?? [.5, 1] : snapPoints,
276
+ activeSnapPoint,
277
+ setActiveSnapPoint,
278
+ fadeFromIndex: snap ? fadeFromIndex ?? 0 : fadeFromIndex,
279
+ shouldScaleBackground,
280
+ dismissible
281
+ };
282
+ return /* @__PURE__ */ jsx(ResponsiveDialogContext.Provider, {
283
+ value: { snap },
284
+ children: /* @__PURE__ */ jsx(Drawer, {
285
+ ...drawerProps,
286
+ children
287
+ })
288
+ });
289
+ }
290
+ return /* @__PURE__ */ jsx(ResponsiveDialogContext.Provider, {
291
+ value: { snap },
292
+ children: /* @__PURE__ */ jsx(Dialog, {
293
+ ...props,
294
+ open,
295
+ onOpenChange: handleOpenChange,
296
+ children
297
+ })
262
298
  });
263
299
  }
264
300
  /**
@@ -327,8 +363,11 @@ function ResponsiveDialogClose({ children, ...props }) {
327
363
  * </ResponsiveDialogContent>
328
364
  */
329
365
  function ResponsiveDialogContent({ className, children, showCloseButton = true, ...props }) {
330
- if (useIsMobile()) return /* @__PURE__ */ jsx(DrawerContent, {
331
- className,
366
+ const isMobile = useIsMobile();
367
+ const { snap } = React.useContext(ResponsiveDialogContext);
368
+ if (isMobile) return /* @__PURE__ */ jsx(DrawerContent, {
369
+ className: cn(snap && "h-[96dvh] !max-h-[96dvh]", className),
370
+ ...props,
332
371
  children
333
372
  });
334
373
  return /* @__PURE__ */ jsx(DialogContent, {
@@ -348,14 +387,18 @@ function ResponsiveDialogContent({ className, children, showCloseButton = true,
348
387
  * Typically contains `ResponsiveDialogTitle` and optionally
349
388
  * `ResponsiveDialogDescription`.
350
389
  */
351
- function ResponsiveDialogHeader({ className, ...props }) {
390
+ function ResponsiveDialogHeader({ className, leftNode, rightNode, children, ...props }) {
352
391
  if (useIsMobile()) return /* @__PURE__ */ jsx(DrawerHeader, {
392
+ leftNode,
393
+ rightNode,
353
394
  className,
354
- ...props
395
+ ...props,
396
+ children
355
397
  });
356
398
  return /* @__PURE__ */ jsx(DialogHeader, {
357
399
  className,
358
- ...props
400
+ ...props,
401
+ children
359
402
  });
360
403
  }
361
404
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manhphi1309/dialog",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "sideEffects": false,
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",