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