@ai-matrx/design-system 0.37.1 → 0.38.1

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.
@@ -252,12 +252,14 @@ function useIsMobile() {
252
252
 
253
253
  // src/dialog.tsx
254
254
  var import_jsx_runtime5 = require("react/jsx-runtime");
255
+ var DialogWindowedByDefaultContext = React5.createContext(false);
255
256
  var Dialog = ({
256
257
  children,
257
258
  ...props
258
259
  }) => {
259
- const modal = props.modal ?? true;
260
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RadixDialogModalProvider, { modal, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DialogPrimitive.Root, { ...props, modal, children }) });
260
+ const isMobile = useIsMobile();
261
+ const modal = props.modal ?? isMobile;
262
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DialogWindowedByDefaultContext.Provider, { value: props.modal === void 0, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RadixDialogModalProvider, { modal, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DialogPrimitive.Root, { ...props, modal, children }) }) });
261
263
  };
262
264
  Dialog.displayName = "Dialog";
263
265
  var DialogPortal = ({
@@ -302,6 +304,11 @@ DialogContentPrimitive.displayName = "DialogContentPrimitive";
302
304
  var DIALOG_DESKTOP_CLASSES = "matrx-dialog-card fixed inset-0 z-[10000] m-auto grid h-fit min-w-0 w-full max-w-lg gap-4 max-h-[85dvh] overflow-y-auto overscroll-contain [--dialog-pad:1.5rem] overflow-x-clip border bg-background p-6 shadow-lg [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full sm:rounded-lg";
303
305
  var DIALOG_MOBILE_SHEET_CLASSES = "matrx-mobile-sheet matrx-dialog-card fixed inset-x-0 bottom-0 left-0 right-0 top-auto z-[10000] flex min-w-0 flex-col w-full max-w-full max-h-[90dvh] gap-4 [--dialog-pad:1rem] overflow-x-clip border-t bg-background p-4 pb-safe shadow-lg [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full rounded-t-2xl rounded-b-none overflow-y-auto overscroll-contain";
304
306
  var DIALOG_MOBILE_SHEET_OVERRIDE = "inset-x-0 bottom-0 left-0 right-0 top-auto translate-none w-full max-w-full max-h-[90dvh] rounded-b-none rounded-t-2xl overflow-y-auto";
307
+ var DialogWindowHandleContext = React5.createContext(null);
308
+ var WINDOW_KEEP_VISIBLE = 80;
309
+ function isInteractiveTarget(target) {
310
+ return target instanceof Element && Boolean(target.closest("button, a, input, textarea, select, [role='button'], [contenteditable='true']"));
311
+ }
305
312
  var DialogContent = React5.forwardRef(
306
313
  ({
307
314
  className,
@@ -310,15 +317,51 @@ var DialogContent = React5.forwardRef(
310
317
  animated = true,
311
318
  showCloseButton = true,
312
319
  container,
320
+ dismissOnOutsideClick = false,
321
+ onInteractOutside,
322
+ onEscapeKeyDown,
323
+ style,
313
324
  ...props
314
325
  }, ref) => {
315
326
  const isMobile = useIsMobile();
316
327
  const isModal = useRadixDialogModal();
317
328
  const injectedContainer = usePortalContainer(container);
318
329
  const asSheet = mobileSheet && isMobile;
330
+ const windowed = !isModal && !asSheet;
331
+ const windowedByDefault = React5.useContext(DialogWindowedByDefaultContext);
319
332
  const [containerEl, setContainerEl] = React5.useState(
320
333
  null
321
334
  );
335
+ const [offset, setOffset] = React5.useState({ x: 0, y: 0 });
336
+ const dragRef = React5.useRef(null);
337
+ const handle = React5.useMemo(() => {
338
+ if (!windowed) return null;
339
+ const clamp = (value, limit) => Math.max(-limit, Math.min(limit, value));
340
+ return {
341
+ onPointerDown: (event) => {
342
+ if (event.button !== 0 || isInteractiveTarget(event.target)) return;
343
+ dragRef.current = { x: event.clientX, y: event.clientY, baseX: offset.x, baseY: offset.y };
344
+ event.currentTarget.setPointerCapture?.(event.pointerId);
345
+ },
346
+ onPointerMove: (event) => {
347
+ const start = dragRef.current;
348
+ if (!start) return;
349
+ setOffset({
350
+ x: clamp(start.baseX + event.clientX - start.x, window.innerWidth / 2 - WINDOW_KEEP_VISIBLE / 2),
351
+ y: clamp(start.baseY + event.clientY - start.y, window.innerHeight / 2 - WINDOW_KEEP_VISIBLE / 2)
352
+ });
353
+ },
354
+ onPointerUp: (event) => {
355
+ if (!dragRef.current) return;
356
+ dragRef.current = null;
357
+ event.currentTarget.releasePointerCapture?.(event.pointerId);
358
+ },
359
+ onPointerCancel: () => {
360
+ dragRef.current = null;
361
+ },
362
+ onDoubleClick: () => setOffset({ x: 0, y: 0 })
363
+ };
364
+ }, [windowed, offset.x, offset.y]);
322
365
  const mergedRef = React5.useCallback(
323
366
  (node) => {
324
367
  setContainerEl(node);
@@ -346,19 +389,33 @@ var DialogContent = React5.forwardRef(
346
389
  // can sit beside a host's floating window manager (which starts at
347
390
  // z=1000); staying below that boundary makes a newly focused window
348
391
  // usable without coupling the two systems.
349
- !isModal && "z-[900]"
392
+ !isModal && !windowedByDefault && "z-[900]",
393
+ // Without an overlay the card needs its own separation from the page.
394
+ windowed && "shadow-2xl ring-1 ring-border/60"
350
395
  ),
396
+ style: windowed && (offset.x !== 0 || offset.y !== 0) ? { ...style, translate: `${offset.x}px ${offset.y}px` } : style,
397
+ onInteractOutside: (event) => {
398
+ onInteractOutside?.(event);
399
+ if (windowed && !dismissOnOutsideClick) event.preventDefault();
400
+ },
401
+ onEscapeKeyDown: (event) => {
402
+ onEscapeKeyDown?.(event);
403
+ const pressedIn = event.target instanceof Node && event.target !== document.body ? event.target : document.activeElement;
404
+ if (windowed && containerEl && !(pressedIn && containerEl.contains(pressedIn))) {
405
+ event.preventDefault();
406
+ }
407
+ },
351
408
  ...hasDescription ? {} : { "aria-describedby": void 0 },
352
409
  ...props,
353
410
  children: [
354
411
  hasTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VisuallyHidden.Root, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DialogPrimitive.Title, { children: "Dialog" }) }),
355
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DialogContainerContext.Provider, { value: containerEl, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
412
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DialogContainerContext.Provider, { value: containerEl, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DialogWindowHandleContext.Provider, { value: handle, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
356
413
  PortalContainerProvider,
357
414
  {
358
415
  container: containerEl ?? injectedContainer ?? null,
359
416
  children
360
417
  }
361
- ) }),
418
+ ) }) }),
362
419
  showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
363
420
  DialogPrimitive.Close,
364
421
  {
@@ -381,19 +438,28 @@ DialogContent.displayName = DialogPrimitive.Content.displayName;
381
438
  var DialogHeader = ({
382
439
  className,
383
440
  ...props
384
- }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
385
- "div",
386
- {
387
- "data-slot": "dialog-header",
388
- className: cn(
389
- // DialogContent owns an absolute close control. Reserve its hit area in
390
- // every header so trailing actions never render underneath it.
391
- "flex flex-col space-y-1.5 pr-12 text-center sm:text-left",
392
- className
393
- ),
394
- ...props
395
- }
396
- );
441
+ }) => {
442
+ const handle = React5.useContext(DialogWindowHandleContext);
443
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
444
+ "div",
445
+ {
446
+ "data-slot": "dialog-header",
447
+ ...handle ? {
448
+ ...handle,
449
+ "data-window-handle": "",
450
+ title: "Drag to move \xB7 double-click to recenter"
451
+ } : {},
452
+ className: cn(
453
+ // DialogContent owns an absolute close control. Reserve its hit area in
454
+ // every header so trailing actions never render underneath it.
455
+ "flex flex-col space-y-1.5 pr-12 text-center sm:text-left",
456
+ handle && "cursor-grab select-none active:cursor-grabbing",
457
+ className
458
+ ),
459
+ ...props
460
+ }
461
+ );
462
+ };
397
463
  DialogHeader.displayName = "DialogHeader";
398
464
  var DialogFooter = ({
399
465
  className,