@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.
@@ -218,7 +218,8 @@ var Dialog = ({
218
218
  children,
219
219
  ...props
220
220
  }) => {
221
- const modal = props.modal ?? true;
221
+ const isMobile = useIsMobile();
222
+ const modal = props.modal ?? isMobile;
222
223
  return /* @__PURE__ */ jsx5(RadixDialogModalProvider, { modal, children: /* @__PURE__ */ jsx5(DialogPrimitive.Root, { ...props, modal, children }) });
223
224
  };
224
225
  Dialog.displayName = "Dialog";
@@ -264,6 +265,11 @@ DialogContentPrimitive.displayName = "DialogContentPrimitive";
264
265
  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";
265
266
  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";
266
267
  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";
268
+ var DialogWindowHandleContext = React5.createContext(null);
269
+ var WINDOW_KEEP_VISIBLE = 80;
270
+ function isInteractiveTarget(target) {
271
+ return target instanceof Element && Boolean(target.closest("button, a, input, textarea, select, [role='button'], [contenteditable='true']"));
272
+ }
267
273
  var DialogContent = React5.forwardRef(
268
274
  ({
269
275
  className,
@@ -272,15 +278,50 @@ var DialogContent = React5.forwardRef(
272
278
  animated = true,
273
279
  showCloseButton = true,
274
280
  container,
281
+ dismissOnOutsideClick = false,
282
+ onInteractOutside,
283
+ onEscapeKeyDown,
284
+ style,
275
285
  ...props
276
286
  }, ref) => {
277
287
  const isMobile = useIsMobile();
278
288
  const isModal = useRadixDialogModal();
279
289
  const injectedContainer = usePortalContainer(container);
280
290
  const asSheet = mobileSheet && isMobile;
291
+ const windowed = !isModal && !asSheet;
281
292
  const [containerEl, setContainerEl] = React5.useState(
282
293
  null
283
294
  );
295
+ const [offset, setOffset] = React5.useState({ x: 0, y: 0 });
296
+ const dragRef = React5.useRef(null);
297
+ const handle = React5.useMemo(() => {
298
+ if (!windowed) return null;
299
+ const clamp = (value, limit) => Math.max(-limit, Math.min(limit, value));
300
+ return {
301
+ onPointerDown: (event) => {
302
+ if (event.button !== 0 || isInteractiveTarget(event.target)) return;
303
+ dragRef.current = { x: event.clientX, y: event.clientY, baseX: offset.x, baseY: offset.y };
304
+ event.currentTarget.setPointerCapture?.(event.pointerId);
305
+ },
306
+ onPointerMove: (event) => {
307
+ const start = dragRef.current;
308
+ if (!start) return;
309
+ setOffset({
310
+ x: clamp(start.baseX + event.clientX - start.x, window.innerWidth / 2 - WINDOW_KEEP_VISIBLE / 2),
311
+ y: clamp(start.baseY + event.clientY - start.y, window.innerHeight / 2 - WINDOW_KEEP_VISIBLE / 2)
312
+ });
313
+ },
314
+ onPointerUp: (event) => {
315
+ if (!dragRef.current) return;
316
+ dragRef.current = null;
317
+ event.currentTarget.releasePointerCapture?.(event.pointerId);
318
+ },
319
+ onPointerCancel: () => {
320
+ dragRef.current = null;
321
+ },
322
+ onDoubleClick: () => setOffset({ x: 0, y: 0 })
323
+ };
324
+ }, [windowed, offset.x, offset.y]);
284
325
  const mergedRef = React5.useCallback(
285
326
  (node) => {
286
327
  setContainerEl(node);
@@ -308,19 +349,33 @@ var DialogContent = React5.forwardRef(
308
349
  // can sit beside a host's floating window manager (which starts at
309
350
  // z=1000); staying below that boundary makes a newly focused window
310
351
  // usable without coupling the two systems.
311
- !isModal && "z-[900]"
352
+ !isModal && "z-[900]",
353
+ // Without an overlay the card needs its own separation from the page.
354
+ windowed && "shadow-2xl ring-1 ring-border/60"
312
355
  ),
356
+ style: windowed && (offset.x !== 0 || offset.y !== 0) ? { ...style, translate: `${offset.x}px ${offset.y}px` } : style,
357
+ onInteractOutside: (event) => {
358
+ onInteractOutside?.(event);
359
+ if (windowed && !dismissOnOutsideClick) event.preventDefault();
360
+ },
361
+ onEscapeKeyDown: (event) => {
362
+ onEscapeKeyDown?.(event);
363
+ const pressedIn = event.target instanceof Node && event.target !== document.body ? event.target : document.activeElement;
364
+ if (windowed && containerEl && !(pressedIn && containerEl.contains(pressedIn))) {
365
+ event.preventDefault();
366
+ }
367
+ },
313
368
  ...hasDescription ? {} : { "aria-describedby": void 0 },
314
369
  ...props,
315
370
  children: [
316
371
  hasTitle ? null : /* @__PURE__ */ jsx5(VisuallyHidden.Root, { children: /* @__PURE__ */ jsx5(DialogPrimitive.Title, { children: "Dialog" }) }),
317
- /* @__PURE__ */ jsx5(DialogContainerContext.Provider, { value: containerEl, children: /* @__PURE__ */ jsx5(
372
+ /* @__PURE__ */ jsx5(DialogContainerContext.Provider, { value: containerEl, children: /* @__PURE__ */ jsx5(DialogWindowHandleContext.Provider, { value: handle, children: /* @__PURE__ */ jsx5(
318
373
  PortalContainerProvider,
319
374
  {
320
375
  container: containerEl ?? injectedContainer ?? null,
321
376
  children
322
377
  }
323
- ) }),
378
+ ) }) }),
324
379
  showCloseButton ? /* @__PURE__ */ jsxs2(
325
380
  DialogPrimitive.Close,
326
381
  {
@@ -343,19 +398,28 @@ DialogContent.displayName = DialogPrimitive.Content.displayName;
343
398
  var DialogHeader = ({
344
399
  className,
345
400
  ...props
346
- }) => /* @__PURE__ */ jsx5(
347
- "div",
348
- {
349
- "data-slot": "dialog-header",
350
- className: cn(
351
- // DialogContent owns an absolute close control. Reserve its hit area in
352
- // every header so trailing actions never render underneath it.
353
- "flex flex-col space-y-1.5 pr-12 text-center sm:text-left",
354
- className
355
- ),
356
- ...props
357
- }
358
- );
401
+ }) => {
402
+ const handle = React5.useContext(DialogWindowHandleContext);
403
+ return /* @__PURE__ */ jsx5(
404
+ "div",
405
+ {
406
+ "data-slot": "dialog-header",
407
+ ...handle ? {
408
+ ...handle,
409
+ "data-window-handle": "",
410
+ title: "Drag to move \xB7 double-click to recenter"
411
+ } : {},
412
+ className: cn(
413
+ // DialogContent owns an absolute close control. Reserve its hit area in
414
+ // every header so trailing actions never render underneath it.
415
+ "flex flex-col space-y-1.5 pr-12 text-center sm:text-left",
416
+ handle && "cursor-grab select-none active:cursor-grabbing",
417
+ className
418
+ ),
419
+ ...props
420
+ }
421
+ );
422
+ };
359
423
  DialogHeader.displayName = "DialogHeader";
360
424
  var DialogFooter = ({
361
425
  className,