@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.
@@ -159,12 +159,14 @@ function useIsMobile() {
159
159
 
160
160
  // src/dialog.tsx
161
161
  var import_jsx_runtime4 = require("react/jsx-runtime");
162
+ var DialogWindowedByDefaultContext = React4.createContext(false);
162
163
  var Dialog = ({
163
164
  children,
164
165
  ...props
165
166
  }) => {
166
- const modal = props.modal ?? true;
167
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RadixDialogModalProvider, { modal, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DialogPrimitive.Root, { ...props, modal, children }) });
167
+ const isMobile = useIsMobile();
168
+ const modal = props.modal ?? isMobile;
169
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DialogWindowedByDefaultContext.Provider, { value: props.modal === void 0, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RadixDialogModalProvider, { modal, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DialogPrimitive.Root, { ...props, modal, children }) }) });
168
170
  };
169
171
  Dialog.displayName = "Dialog";
170
172
  var DialogPortal = ({
@@ -209,6 +211,11 @@ DialogContentPrimitive.displayName = "DialogContentPrimitive";
209
211
  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";
210
212
  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";
211
213
  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";
214
+ var DialogWindowHandleContext = React4.createContext(null);
215
+ var WINDOW_KEEP_VISIBLE = 80;
216
+ function isInteractiveTarget(target) {
217
+ return target instanceof Element && Boolean(target.closest("button, a, input, textarea, select, [role='button'], [contenteditable='true']"));
218
+ }
212
219
  var DialogContent = React4.forwardRef(
213
220
  ({
214
221
  className,
@@ -217,15 +224,51 @@ var DialogContent = React4.forwardRef(
217
224
  animated = true,
218
225
  showCloseButton = true,
219
226
  container,
227
+ dismissOnOutsideClick = false,
228
+ onInteractOutside,
229
+ onEscapeKeyDown,
230
+ style,
220
231
  ...props
221
232
  }, ref) => {
222
233
  const isMobile = useIsMobile();
223
234
  const isModal = useRadixDialogModal();
224
235
  const injectedContainer = usePortalContainer(container);
225
236
  const asSheet = mobileSheet && isMobile;
237
+ const windowed = !isModal && !asSheet;
238
+ const windowedByDefault = React4.useContext(DialogWindowedByDefaultContext);
226
239
  const [containerEl, setContainerEl] = React4.useState(
227
240
  null
228
241
  );
242
+ const [offset, setOffset] = React4.useState({ x: 0, y: 0 });
243
+ const dragRef = React4.useRef(null);
244
+ const handle = React4.useMemo(() => {
245
+ if (!windowed) return null;
246
+ const clamp = (value, limit) => Math.max(-limit, Math.min(limit, value));
247
+ return {
248
+ onPointerDown: (event) => {
249
+ if (event.button !== 0 || isInteractiveTarget(event.target)) return;
250
+ dragRef.current = { x: event.clientX, y: event.clientY, baseX: offset.x, baseY: offset.y };
251
+ event.currentTarget.setPointerCapture?.(event.pointerId);
252
+ },
253
+ onPointerMove: (event) => {
254
+ const start = dragRef.current;
255
+ if (!start) return;
256
+ setOffset({
257
+ x: clamp(start.baseX + event.clientX - start.x, window.innerWidth / 2 - WINDOW_KEEP_VISIBLE / 2),
258
+ y: clamp(start.baseY + event.clientY - start.y, window.innerHeight / 2 - WINDOW_KEEP_VISIBLE / 2)
259
+ });
260
+ },
261
+ onPointerUp: (event) => {
262
+ if (!dragRef.current) return;
263
+ dragRef.current = null;
264
+ event.currentTarget.releasePointerCapture?.(event.pointerId);
265
+ },
266
+ onPointerCancel: () => {
267
+ dragRef.current = null;
268
+ },
269
+ onDoubleClick: () => setOffset({ x: 0, y: 0 })
270
+ };
271
+ }, [windowed, offset.x, offset.y]);
229
272
  const mergedRef = React4.useCallback(
230
273
  (node) => {
231
274
  setContainerEl(node);
@@ -253,19 +296,33 @@ var DialogContent = React4.forwardRef(
253
296
  // can sit beside a host's floating window manager (which starts at
254
297
  // z=1000); staying below that boundary makes a newly focused window
255
298
  // usable without coupling the two systems.
256
- !isModal && "z-[900]"
299
+ !isModal && !windowedByDefault && "z-[900]",
300
+ // Without an overlay the card needs its own separation from the page.
301
+ windowed && "shadow-2xl ring-1 ring-border/60"
257
302
  ),
303
+ style: windowed && (offset.x !== 0 || offset.y !== 0) ? { ...style, translate: `${offset.x}px ${offset.y}px` } : style,
304
+ onInteractOutside: (event) => {
305
+ onInteractOutside?.(event);
306
+ if (windowed && !dismissOnOutsideClick) event.preventDefault();
307
+ },
308
+ onEscapeKeyDown: (event) => {
309
+ onEscapeKeyDown?.(event);
310
+ const pressedIn = event.target instanceof Node && event.target !== document.body ? event.target : document.activeElement;
311
+ if (windowed && containerEl && !(pressedIn && containerEl.contains(pressedIn))) {
312
+ event.preventDefault();
313
+ }
314
+ },
258
315
  ...hasDescription ? {} : { "aria-describedby": void 0 },
259
316
  ...props,
260
317
  children: [
261
318
  hasTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VisuallyHidden.Root, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DialogPrimitive.Title, { children: "Dialog" }) }),
262
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DialogContainerContext.Provider, { value: containerEl, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
319
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DialogContainerContext.Provider, { value: containerEl, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DialogWindowHandleContext.Provider, { value: handle, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
263
320
  PortalContainerProvider,
264
321
  {
265
322
  container: containerEl ?? injectedContainer ?? null,
266
323
  children
267
324
  }
268
- ) }),
325
+ ) }) }),
269
326
  showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
270
327
  DialogPrimitive.Close,
271
328
  {
@@ -288,19 +345,28 @@ DialogContent.displayName = DialogPrimitive.Content.displayName;
288
345
  var DialogHeader = ({
289
346
  className,
290
347
  ...props
291
- }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
292
- "div",
293
- {
294
- "data-slot": "dialog-header",
295
- className: cn(
296
- // DialogContent owns an absolute close control. Reserve its hit area in
297
- // every header so trailing actions never render underneath it.
298
- "flex flex-col space-y-1.5 pr-12 text-center sm:text-left",
299
- className
300
- ),
301
- ...props
302
- }
303
- );
348
+ }) => {
349
+ const handle = React4.useContext(DialogWindowHandleContext);
350
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
351
+ "div",
352
+ {
353
+ "data-slot": "dialog-header",
354
+ ...handle ? {
355
+ ...handle,
356
+ "data-window-handle": "",
357
+ title: "Drag to move \xB7 double-click to recenter"
358
+ } : {},
359
+ className: cn(
360
+ // DialogContent owns an absolute close control. Reserve its hit area in
361
+ // every header so trailing actions never render underneath it.
362
+ "flex flex-col space-y-1.5 pr-12 text-center sm:text-left",
363
+ handle && "cursor-grab select-none active:cursor-grabbing",
364
+ className
365
+ ),
366
+ ...props
367
+ }
368
+ );
369
+ };
304
370
  DialogHeader.displayName = "DialogHeader";
305
371
  var DialogFooter = ({
306
372
  className,