@jmruthers/pace-core 0.6.4 → 0.6.5

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.
Files changed (101) hide show
  1. package/dist/{DataTable-E7YQZD7D.js → DataTable-AOVNCPTX.js} +8 -8
  2. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-QTFVrL-Z.d.ts} +65 -83
  3. package/dist/{UnifiedAuthProvider-QPXO24B4.js → UnifiedAuthProvider-4SBX4LU5.js} +4 -4
  4. package/dist/{api-6LVZTHDS.js → api-O6HTBX5Y.js} +3 -3
  5. package/dist/{chunk-I6DAQMWX.js → chunk-6COVEUS7.js} +130 -106
  6. package/dist/chunk-6COVEUS7.js.map +1 -0
  7. package/dist/{chunk-36LVWXB2.js → chunk-AFVQODI2.js} +37 -1
  8. package/dist/{chunk-36LVWXB2.js.map → chunk-AFVQODI2.js.map} +1 -1
  9. package/dist/{chunk-3LPHPB62.js → chunk-EFN2EIMK.js} +2 -2
  10. package/dist/{chunk-ATKZM7RX.js → chunk-G7QEZTYQ.js} +31 -31
  11. package/dist/{chunk-ATKZM7RX.js.map → chunk-G7QEZTYQ.js.map} +1 -1
  12. package/dist/{chunk-NN6WWZ5U.js → chunk-HU2C6SSC.js} +29 -18
  13. package/dist/chunk-HU2C6SSC.js.map +1 -0
  14. package/dist/{chunk-AVMLPIM7.js → chunk-IHB5DR3H.js} +102 -51
  15. package/dist/chunk-IHB5DR3H.js.map +1 -0
  16. package/dist/{chunk-7JPAB3T5.js → chunk-IVOFDYWT.js} +364 -208
  17. package/dist/chunk-IVOFDYWT.js.map +1 -0
  18. package/dist/{chunk-6SOIHG6Z.js → chunk-JGRYX5UX.js} +120 -20
  19. package/dist/chunk-JGRYX5UX.js.map +1 -0
  20. package/dist/{chunk-OEWDTMG7.js → chunk-NTM7ZSB6.js} +4 -4
  21. package/dist/chunk-NTM7ZSB6.js.map +1 -0
  22. package/dist/{chunk-5EC5MEWX.js → chunk-RGAWHO7N.js} +4 -4
  23. package/dist/chunk-RGAWHO7N.js.map +1 -0
  24. package/dist/{chunk-YKRAFF5K.js → chunk-UPPMRMYG.js} +3 -3
  25. package/dist/{chunk-YKRAFF5K.js.map → chunk-UPPMRMYG.js.map} +1 -1
  26. package/dist/components.d.ts +2 -3
  27. package/dist/components.js +24 -28
  28. package/dist/components.js.map +1 -1
  29. package/dist/{contextValidator-OOPCLPZW.js → contextValidator-5OGXSPKS.js} +2 -2
  30. package/dist/hooks.d.ts +3 -3
  31. package/dist/hooks.js +41 -139
  32. package/dist/hooks.js.map +1 -1
  33. package/dist/index.d.ts +27 -18
  34. package/dist/index.js +41 -50
  35. package/dist/index.js.map +1 -1
  36. package/dist/providers.js +3 -3
  37. package/dist/rbac/index.d.ts +16 -9
  38. package/dist/rbac/index.js +6 -6
  39. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-ClnV4tnv.d.ts} +8 -8
  40. package/dist/utils.js +1 -1
  41. package/docs/api/modules.md +210 -100
  42. package/package.json +1 -2
  43. package/scripts/validate-master.js +1 -1
  44. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  45. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  46. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  47. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  48. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  49. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  50. package/src/components/DateTimeField/DateTimeField.tsx +17 -19
  51. package/src/components/DateTimeField/README.md +5 -2
  52. package/src/components/Dialog/Dialog.test.tsx +248 -228
  53. package/src/components/Dialog/Dialog.tsx +455 -325
  54. package/src/components/Dialog/index.ts +3 -3
  55. package/src/components/FileDisplay/FileDisplay.test.tsx +41 -0
  56. package/src/components/FileDisplay/FileDisplay.tsx +5 -5
  57. package/src/components/Form/Form.test.tsx +3 -2
  58. package/src/components/Form/Form.tsx +4 -5
  59. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  60. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  61. package/src/components/LoginForm/LoginForm.tsx +2 -2
  62. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  63. package/src/components/PaceAppLayout/PaceAppLayout.tsx +32 -39
  64. package/src/components/PaceAppLayout/README.md +10 -9
  65. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  66. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  67. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  68. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  69. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  70. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  71. package/src/components/UserMenu/UserMenu.tsx +36 -34
  72. package/src/components/index.ts +3 -4
  73. package/src/hooks/useEventTheme.ts +4 -4
  74. package/src/hooks/useEvents.ts +11 -7
  75. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  76. package/src/hooks/useOrganisationPermissions.ts +4 -4
  77. package/src/hooks/useOrganisations.ts +13 -7
  78. package/src/index.ts +11 -1
  79. package/src/rbac/README.md +20 -20
  80. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  81. package/src/rbac/hooks/useRBAC.ts +4 -3
  82. package/src/rbac/hooks/useResourcePermissions.test.ts +125 -30
  83. package/src/rbac/hooks/useResourcePermissions.ts +57 -29
  84. package/src/rbac/permissions.ts +17 -17
  85. package/src/rbac/utils/contextValidator.ts +36 -0
  86. package/src/services/AuthService.ts +2 -5
  87. package/src/services/InactivityService.ts +139 -58
  88. package/src/styles/core.css +4 -0
  89. package/src/utils/formatting/formatTime.test.ts +3 -2
  90. package/dist/chunk-5EC5MEWX.js.map +0 -1
  91. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  92. package/dist/chunk-7JPAB3T5.js.map +0 -1
  93. package/dist/chunk-AVMLPIM7.js.map +0 -1
  94. package/dist/chunk-I6DAQMWX.js.map +0 -1
  95. package/dist/chunk-NN6WWZ5U.js.map +0 -1
  96. package/dist/chunk-OEWDTMG7.js.map +0 -1
  97. /package/dist/{DataTable-E7YQZD7D.js.map → DataTable-AOVNCPTX.js.map} +0 -0
  98. /package/dist/{UnifiedAuthProvider-QPXO24B4.js.map → UnifiedAuthProvider-4SBX4LU5.js.map} +0 -0
  99. /package/dist/{api-6LVZTHDS.js.map → api-O6HTBX5Y.js.map} +0 -0
  100. /package/dist/{chunk-3LPHPB62.js.map → chunk-EFN2EIMK.js.map} +0 -0
  101. /package/dist/{contextValidator-OOPCLPZW.js.map → contextValidator-5OGXSPKS.js.map} +0 -0
@@ -4,7 +4,7 @@
4
4
  * @module Components/Dialog
5
5
  * @since 0.1.0
6
6
  *
7
- * A comprehensive dialog component system built on top of Radix UI primitives.
7
+ * A comprehensive dialog component system using native HTML `<dialog>` element.
8
8
  * Provides accessible modal dialogs with focus management and keyboard navigation.
9
9
  * Uses semantic HTML elements including native <dialog> element for maximum accessibility.
10
10
  *
@@ -24,8 +24,9 @@
24
24
  * - Sticky headers/footers with scrollable body
25
25
  * - Overlay backdrop with customization
26
26
  * - Close button with accessibility (optional)
27
- * - Header, footer, title, and description components
27
+ * - Header and footer components
28
28
  * - Configurable close behaviors
29
+ * - Native dialog title and aria-description attributes for accessibility
29
30
  *
30
31
  * @example
31
32
  * ```tsx
@@ -34,12 +35,9 @@
34
35
  * <DialogTrigger asChild>
35
36
  * <Button>Open Dialog</Button>
36
37
  * </DialogTrigger>
37
- * <DialogContent size="lg">
38
+ * <DialogContent size="lg" title="Edit Profile" description="Make changes to your profile here. Click save when you're done.">
38
39
  * <DialogHeader>
39
- * <DialogTitle>Edit Profile</DialogTitle>
40
- * <DialogDescription>
41
- * Make changes to your profile here. Click save when you're done.
42
- * </DialogDescription>
40
+ * <h2>Edit Profile</h2>
43
41
  * </DialogHeader>
44
42
  * <DialogBody>
45
43
  * <section className="space-y-4">
@@ -54,81 +52,12 @@
54
52
  * </DialogFooter>
55
53
  * </DialogContent>
56
54
  * </Dialog>
57
- *
58
- * // Dialog with semantic scrolling content
59
- * <Dialog>
60
- * <DialogTrigger asChild>
61
- * <Button>Scrollable Dialog</Button>
62
- * </DialogTrigger>
63
- * <DialogContent
64
- * size="lg"
65
- * enableScrolling={true}
66
- * maxHeightPercent={80}
67
- * >
68
- * <DialogHeader>
69
- * <DialogTitle>Large Content Dialog</DialogTitle>
70
- * <DialogDescription>
71
- * This dialog has lots of content and will scroll if needed.
72
- * </DialogDescription>
73
- * </DialogHeader>
74
- * <DialogBody>
75
- * <section className="space-y-4">
76
- * {Array.from({ length: 50 }, (_, i) => (
77
- * <article key={i}>
78
- * <h4>Content Item {i + 1}</h4>
79
- * <p>This is semantic content within the dialog body.</p>
80
- * </article>
81
- * ))}
82
- * </section>
83
- * </DialogBody>
84
- * <DialogFooter>
85
- * <Button>Save</Button>
86
- * </DialogFooter>
87
- * </DialogContent>
88
- * </Dialog>
89
- *
90
- * // Auto-sizing dialog that fits content
91
- * <Dialog>
92
- * <DialogTrigger asChild>
93
- * <Button>Auto Size Dialog</Button>
94
- * </DialogTrigger>
95
- * <DialogContent size="auto">
96
- * <DialogHeader>
97
- * <DialogTitle>Auto-Sized Dialog</DialogTitle>
98
- * <DialogDescription>
99
- * This dialog automatically adjusts its width to fit the content.
100
- * </DialogDescription>
101
- * </DialogHeader>
102
- * <DialogBody>
103
- * <section>
104
- * <p>Content that determines the dialog width...</p>
105
- * </section>
106
- * </DialogBody>
107
- * </DialogContent>
108
- * </Dialog>
109
- *
110
- * // Full-screen dialog with semantic structure
111
- * <Dialog>
112
- * <DialogTrigger asChild>
113
- * <Button>Full Screen</Button>
114
- * </DialogTrigger>
115
- * <DialogContent size="full">
116
- * <DialogHeader>
117
- * <DialogTitle>Full Screen Dialog</DialogTitle>
118
- * </DialogHeader>
119
- * <DialogBody>
120
- * <section>
121
- * <p>Full screen content with semantic structure...</p>
122
- * </section>
123
- * </DialogBody>
124
- * </DialogContent>
125
- * </Dialog>
126
55
  * ```
127
56
  *
128
57
  * @accessibility
129
58
  * - WCAG 2.1 AA compliant
130
59
  * - Uses semantic HTML structure (dialog, header, main, footer)
131
- * - Native dialog element with enhanced ARIA attributes via Radix UI
60
+ * - Native dialog element with proper ARIA attributes
132
61
  * - Focus trapping within dialog content
133
62
  * - Keyboard navigation support
134
63
  * - Screen reader announcements
@@ -147,18 +76,24 @@
147
76
  * - Optimized scroll handling
148
77
  *
149
78
  * @dependencies
150
- * - @radix-ui/react-dialog - Core dialog functionality
151
79
  * - lucide-react - Icons
152
- * - React 19+ - Hooks and refs
80
+ * - React 19+ - Hooks, refs, and createPortal
153
81
  * - Tailwind CSS - Styling and animations
82
+ *
83
+ * @note
84
+ * This component uses native HTML dialog element with manual focus management.
85
+ * Title and description are provided via props on DialogContent, which set the native
86
+ * title and aria-description attributes on the dialog element for accessibility.
87
+ * See https://developer.mozilla.org/en-US/docs/Web/API/Element/ariaDescription for details.
154
88
  */
155
89
 
156
90
  import * as React from 'react';
157
- import * as DialogPrimitive from '@radix-ui/react-dialog';
91
+ import { createPortal } from 'react-dom';
158
92
  import { X } from 'lucide-react';
159
93
  import { cn } from '../../utils/core/cn';
160
94
  import { renderSafeHtml } from '../../utils/validation/htmlSanitization';
161
- import { useState, useEffect } from 'react';
95
+ import { useState, useEffect, useRef, useCallback, useId } from 'react';
96
+ import { useFocusTrap } from '../../hooks/useFocusTrap';
162
97
 
163
98
  /**
164
99
  * Simple debounce function that matches lodash debounce API
@@ -195,30 +130,62 @@ function debounce<T extends (...args: any[]) => void>(
195
130
  */
196
131
  export type DialogSize = 'sm' | 'md' | 'lg' | 'xl' | 'full' | 'auto';
197
132
 
133
+ /**
134
+ * Dialog context value
135
+ */
136
+ interface DialogContextValue {
137
+ open: boolean;
138
+ onOpenChange: (open: boolean) => void;
139
+ dialogRef: React.RefObject<HTMLDialogElement | null>;
140
+ titleId: string;
141
+ descriptionId: string;
142
+ }
143
+
144
+ const DialogContext = React.createContext<DialogContextValue | null>(null);
145
+
146
+ /**
147
+ * Hook to access Dialog context
148
+ */
149
+ function useDialogContext(): DialogContextValue {
150
+ const context = React.useContext(DialogContext);
151
+ if (!context) {
152
+ throw new Error('Dialog components must be used within a Dialog');
153
+ }
154
+ return context;
155
+ }
156
+
198
157
  /**
199
158
  * Props for the Dialog root component
200
159
  * @public
201
160
  */
202
- export interface DialogProps extends DialogPrimitive.DialogProps {}
161
+ export interface DialogProps {
162
+ children: React.ReactNode;
163
+ open?: boolean;
164
+ defaultOpen?: boolean;
165
+ onOpenChange?: (open: boolean) => void;
166
+ }
203
167
 
204
168
  /**
205
169
  * Props for the DialogTrigger component
206
170
  * @public
207
171
  */
208
- export interface DialogTriggerProps extends DialogPrimitive.DialogTriggerProps {}
172
+ export interface DialogTriggerProps {
173
+ children: React.ReactNode;
174
+ asChild?: boolean;
175
+ className?: string;
176
+ onClick?: (e: React.MouseEvent) => void;
177
+ }
209
178
 
210
179
  /**
211
180
  * Enhanced props for the DialogContent component with size variants and customization
212
- * Uses semantic HTML dialog element with Radix UI accessibility features
181
+ * Uses semantic HTML dialog element
213
182
  * @public
214
183
  */
215
- export interface DialogContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
184
+ export interface DialogContentProps extends React.HTMLAttributes<HTMLDialogElement> {
216
185
  /** Dialog size variant */
217
186
  size?: DialogSize;
218
187
  /** Whether to show the close button */
219
188
  showCloseButton?: boolean;
220
- /** Custom className for the overlay */
221
- overlayClassName?: string;
222
189
  /** Whether to prevent closing on escape key */
223
190
  preventCloseOnEscape?: boolean;
224
191
  /** Whether to prevent closing on outside click */
@@ -237,13 +204,31 @@ export interface DialogContentProps extends React.ComponentPropsWithoutRef<typeo
237
204
  minHeight?: string;
238
205
  /** Minimum width in CSS units */
239
206
  minWidth?: string;
207
+ /** Dialog title for accessibility (sets native title attribute) */
208
+ title?: string;
209
+ /** Dialog description for accessibility (sets aria-description attribute) */
210
+ description?: string;
240
211
  }
241
212
 
242
213
  /**
243
214
  * Props for the DialogOverlay component
244
215
  * @public
245
216
  */
246
- export interface DialogOverlayProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}
217
+ export interface DialogOverlayProps extends React.HTMLAttributes<HTMLDivElement> {}
218
+
219
+ /**
220
+ * Props for the DialogPortal component
221
+ * @public
222
+ */
223
+ export interface DialogPortalProps {
224
+ children: React.ReactNode;
225
+ }
226
+
227
+ /**
228
+ * Props for the DialogClose component
229
+ * @public
230
+ */
231
+ export interface DialogCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
247
232
 
248
233
  /**
249
234
  * Props for the DialogHeader component (semantic header element)
@@ -284,7 +269,7 @@ export interface DialogBodyProps extends React.HTMLAttributes<HTMLElement> {
284
269
  * Props for the DialogTitle component
285
270
  * @public
286
271
  */
287
- export interface DialogTitleProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {
272
+ export interface DialogTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
288
273
  /** HTML content to render as title (will be sanitized for security) */
289
274
  htmlContent?: string;
290
275
  /** Whether to allow HTML content rendering (default: true) */
@@ -295,7 +280,7 @@ export interface DialogTitleProps extends React.ComponentPropsWithoutRef<typeof
295
280
  * Props for the DialogDescription component
296
281
  * @public
297
282
  */
298
- export interface DialogDescriptionProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> {
283
+ export interface DialogDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
299
284
  /** HTML content to render as description (will be sanitized for security) */
300
285
  htmlContent?: string;
301
286
  /** Whether to allow HTML content rendering (default: true) */
@@ -312,27 +297,117 @@ const sizeClasses = {
312
297
  auto: 'max-w-none w-auto min-w-0'
313
298
  };
314
299
 
315
- // Root Dialog components from Radix
316
- const Dialog = DialogPrimitive.Root;
317
- const DialogTrigger = DialogPrimitive.Trigger;
318
- const DialogPortal = DialogPrimitive.Portal;
319
- const DialogClose = DialogPrimitive.Close;
320
-
321
- // DialogOverlay component
322
- const DialogOverlay = React.forwardRef<
323
- React.ElementRef<typeof DialogPrimitive.Overlay>,
324
- DialogOverlayProps
325
- >(({ className, ...props }, ref) => (
326
- <DialogPrimitive.Overlay
327
- ref={ref}
328
- className={cn(
329
- 'fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
330
- className
331
- )}
332
- {...props}
333
- />
334
- ));
335
- DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
300
+ /**
301
+ * Dialog root component
302
+ * Provides context for dialog state management
303
+ */
304
+ const Dialog = React.memo<DialogProps>(function Dialog({
305
+ children,
306
+ open: controlledOpen,
307
+ defaultOpen = false,
308
+ onOpenChange,
309
+ }) {
310
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
311
+ const dialogRef = useRef<HTMLDialogElement | null>(null);
312
+ const titleId = useId();
313
+ const descriptionId = useId();
314
+
315
+ const isControlled = controlledOpen !== undefined;
316
+ const open = isControlled ? controlledOpen : internalOpen;
317
+
318
+ const handleOpenChange = useCallback((newOpen: boolean) => {
319
+ if (!isControlled) {
320
+ setInternalOpen(newOpen);
321
+ }
322
+ onOpenChange?.(newOpen);
323
+ }, [isControlled, onOpenChange]);
324
+
325
+ const contextValue = React.useMemo<DialogContextValue>(() => ({
326
+ open,
327
+ onOpenChange: handleOpenChange,
328
+ dialogRef,
329
+ titleId,
330
+ descriptionId,
331
+ }), [open, handleOpenChange, titleId, descriptionId]);
332
+
333
+ return (
334
+ <DialogContext.Provider value={contextValue}>
335
+ {children}
336
+ </DialogContext.Provider>
337
+ );
338
+ });
339
+ Dialog.displayName = 'Dialog';
340
+
341
+ /**
342
+ * DialogTrigger component
343
+ * Opens the dialog when clicked
344
+ */
345
+ const DialogTrigger = React.forwardRef<HTMLElement, DialogTriggerProps>(
346
+ ({ children, asChild = false, className, onClick, ...props }, ref) => {
347
+ const { onOpenChange } = useDialogContext();
348
+
349
+ const handleClick = useCallback((e: React.MouseEvent) => {
350
+ onClick?.(e);
351
+ onOpenChange(true);
352
+ }, [onOpenChange, onClick]);
353
+
354
+ if (asChild && React.isValidElement(children)) {
355
+ return React.cloneElement(children as React.ReactElement<any>, {
356
+ ref,
357
+ onClick: handleClick,
358
+ className: cn(className, (children as any).props?.className),
359
+ ...props,
360
+ });
361
+ }
362
+
363
+ return (
364
+ <button
365
+ ref={ref as React.RefObject<HTMLButtonElement>}
366
+ type="button"
367
+ onClick={handleClick}
368
+ className={className}
369
+ {...props}
370
+ >
371
+ {children}
372
+ </button>
373
+ );
374
+ }
375
+ );
376
+ DialogTrigger.displayName = 'DialogTrigger';
377
+
378
+ /**
379
+ * DialogPortal component
380
+ * Portals dialog content to document.body
381
+ */
382
+ const DialogPortal: React.FC<DialogPortalProps> = ({ children }) => {
383
+ const [mounted, setMounted] = useState(false);
384
+
385
+ useEffect(() => {
386
+ setMounted(true);
387
+ return () => setMounted(false);
388
+ }, []);
389
+
390
+ if (!mounted) return null;
391
+
392
+ return createPortal(children, document.body);
393
+ };
394
+ DialogPortal.displayName = 'DialogPortal';
395
+
396
+ /**
397
+ * DialogOverlay component
398
+ * Backdrop overlay for the dialog (optional, native dialog provides ::backdrop)
399
+ * This component is kept for backward compatibility but may not be needed
400
+ * when using native dialog element which provides ::backdrop automatically
401
+ */
402
+ const DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlayProps>(
403
+ ({ className, ...props }, ref) => {
404
+ // Note: Native dialog element provides ::backdrop automatically
405
+ // This component is kept for API compatibility but may not render
406
+ // The native dialog's ::backdrop is styled via CSS
407
+ return null;
408
+ }
409
+ );
410
+ DialogOverlay.displayName = 'DialogOverlay';
336
411
 
337
412
  /**
338
413
  * Custom hook for managing smart dialog dimensions
@@ -367,7 +442,7 @@ const useSmartDimensions = ({
367
442
 
368
443
  // Handle height constraints
369
444
  if (maxHeightPercent && typeof maxHeightPercent === 'number') {
370
- const constrainedHeight = Math.min(maxHeightPercent, 95); // Cap at 95% to ensure some margin
445
+ const constrainedHeight = Math.min(maxHeightPercent, 95);
371
446
  result.maxHeight = `${constrainedHeight}vh`;
372
447
  } else if (maxHeight) {
373
448
  result.maxHeight = maxHeight;
@@ -375,7 +450,7 @@ const useSmartDimensions = ({
375
450
 
376
451
  // Handle width constraints
377
452
  if (maxWidthPercent && typeof maxWidthPercent === 'number') {
378
- const constrainedWidth = Math.min(maxWidthPercent, 95); // Cap at 95% to ensure some margin
453
+ const constrainedWidth = Math.min(maxWidthPercent, 95);
379
454
  result.maxWidth = `${constrainedWidth}vw`;
380
455
  } else if (maxWidth) {
381
456
  result.maxWidth = maxWidth;
@@ -407,7 +482,7 @@ const useSmartDimensions = ({
407
482
  };
408
483
  }, [maxHeightPercent, maxWidthPercent, maxHeight, maxWidth, minHeight, minWidth, enableScrolling]);
409
484
 
410
- // Only return dimensions if we have something to constrain
485
+ // Return dimensions
411
486
  const result: React.CSSProperties = {};
412
487
 
413
488
  // Handle height constraints
@@ -440,146 +515,229 @@ const useSmartDimensions = ({
440
515
  /**
441
516
  * DialogContent component
442
517
  * The main content container using semantic HTML <dialog> element with enhanced features
443
- * Built on Radix UI primitives for accessibility while providing semantic structure
444
518
  *
445
519
  * @param props - Content configuration and styling
446
520
  * @param ref - Forwarded ref to the dialog element
447
521
  * @returns JSX.Element - The semantic dialog content with overlay and optional close button
448
- *
449
- * @example
450
- * ```tsx
451
- * <DialogContent size="lg" enableScrolling={true} maxHeightPercent={80}>
452
- * <DialogHeader>
453
- * <DialogTitle>Scrollable Dialog</DialogTitle>
454
- * <DialogDescription>This dialog will scroll if content overflows.</DialogDescription>
455
- * </DialogHeader>
456
- * <DialogBody>
457
- * <section>Large amount of semantic content here...</section>
458
- * </DialogBody>
459
- * <DialogFooter>
460
- * <Button>Save</Button>
461
- * </DialogFooter>
462
- * </DialogContent>
463
- * ```
464
522
  */
465
- const DialogContent = React.forwardRef<
466
- React.ElementRef<typeof DialogPrimitive.Content>,
467
- DialogContentProps
468
- >(({
469
- className,
470
- children,
471
- size = 'md',
472
- showCloseButton = true,
473
- overlayClassName,
474
- preventCloseOnEscape = false,
475
- preventCloseOnOutsideClick = false,
476
- maxHeightPercent,
477
- maxWidthPercent,
478
- enableScrolling = false,
479
- maxHeight,
480
- maxWidth,
481
- minHeight,
482
- minWidth,
483
- style,
484
- ...props
485
- }, ref) => {
486
- const smartDimensions = useSmartDimensions({
487
- maxHeightPercent,
523
+ const DialogContent = React.forwardRef<HTMLDialogElement, DialogContentProps>(
524
+ ({
525
+ className,
526
+ children,
527
+ size = 'md',
528
+ showCloseButton = true,
529
+ preventCloseOnEscape = false,
530
+ preventCloseOnOutsideClick = false,
531
+ maxHeightPercent,
488
532
  maxWidthPercent,
489
- maxHeight,
533
+ enableScrolling = false,
534
+ maxHeight,
490
535
  maxWidth,
491
536
  minHeight,
492
537
  minWidth,
493
- enableScrolling
494
- });
538
+ title,
539
+ description,
540
+ style,
541
+ ...props
542
+ }, ref) => {
543
+ const { open, onOpenChange, dialogRef, titleId, descriptionId } = useDialogContext();
544
+ const internalRef = useRef<HTMLDialogElement>(null);
545
+
546
+ // Use the dialogRef from context, or fall back to internal ref or forwarded ref
547
+ const actualDialogRef = dialogRef.current ? dialogRef : (ref ? (ref as React.RefObject<HTMLDialogElement>) : internalRef);
548
+
549
+ const smartDimensions = useSmartDimensions({
550
+ maxHeightPercent,
551
+ maxWidthPercent,
552
+ maxHeight,
553
+ maxWidth,
554
+ minHeight,
555
+ minWidth,
556
+ enableScrolling
557
+ });
495
558
 
496
- // React Compiler handles memoization automatically
497
- const handleEscapeKeyDown = (event: KeyboardEvent) => {
498
- if (preventCloseOnEscape) {
499
- event.preventDefault();
500
- }
501
- };
559
+ // Focus trap
560
+ const { containerRef } = useFocusTrap({
561
+ isActive: open,
562
+ autoFocus: true,
563
+ restoreFocus: true,
564
+ onEscape: preventCloseOnEscape ? undefined : () => onOpenChange(false),
565
+ });
502
566
 
503
- const handlePointerDownOutside = (event: Event) => {
504
- if (preventCloseOnOutsideClick) {
505
- event.preventDefault();
506
- }
507
- };
567
+ // Merge refs
568
+ const mergedRef = useCallback((node: HTMLDialogElement | null) => {
569
+ // Set context dialog ref
570
+ if (dialogRef && 'current' in dialogRef) {
571
+ (dialogRef as React.MutableRefObject<HTMLDialogElement | null>).current = node;
572
+ }
573
+ // Set internal ref
574
+ if (internalRef && 'current' in internalRef) {
575
+ internalRef.current = node;
576
+ }
577
+ // Set focus trap container ref
578
+ if (containerRef && 'current' in containerRef) {
579
+ (containerRef as React.MutableRefObject<HTMLElement | null>).current = node;
580
+ }
581
+ // Handle forwarded ref
582
+ if (typeof ref === 'function') {
583
+ ref(node);
584
+ } else if (ref && 'current' in ref) {
585
+ (ref as React.MutableRefObject<HTMLDialogElement | null>).current = node;
586
+ }
587
+ }, [dialogRef, containerRef, ref]);
588
+
589
+ // Handle dialog open/close
590
+ useEffect(() => {
591
+ const dialog = dialogRef.current || internalRef.current;
592
+ if (!dialog) return;
593
+
594
+ if (open) {
595
+ // Use requestAnimationFrame to ensure DOM is ready
596
+ requestAnimationFrame(() => {
597
+ if (dialog && open) {
598
+ dialog.showModal();
599
+ }
600
+ });
601
+ } else {
602
+ // Close dialog before it's removed from DOM
603
+ if (dialog.open) {
604
+ dialog.close();
605
+ }
606
+ }
607
+ }, [open, dialogRef]);
608
+
609
+ // Handle close event - sync state when dialog is closed externally
610
+ useEffect(() => {
611
+ const dialog = dialogRef.current || internalRef.current;
612
+ if (!dialog) return;
613
+
614
+ const handleClose = () => {
615
+ // Only update state if dialog was closed externally (not via our state change)
616
+ // Check if dialog is actually closed and our state says it should be open
617
+ if (!dialog.open && open) {
618
+ onOpenChange(false);
619
+ }
620
+ };
621
+
622
+ dialog.addEventListener('close', handleClose);
623
+ return () => {
624
+ dialog.removeEventListener('close', handleClose);
625
+ };
626
+ }, [open, onOpenChange, dialogRef]);
627
+
628
+ // Handle cancel event (Escape or backdrop click)
629
+ useEffect(() => {
630
+ const dialog = dialogRef.current || internalRef.current;
631
+ if (!dialog) return;
632
+
633
+ const handleCancel = (e: Event) => {
634
+ if (preventCloseOnEscape || preventCloseOnOutsideClick) {
635
+ e.preventDefault();
636
+ return;
637
+ }
638
+ onOpenChange(false);
639
+ };
640
+
641
+ dialog.addEventListener('cancel', handleCancel);
642
+ return () => {
643
+ dialog.removeEventListener('cancel', handleCancel);
644
+ };
645
+ }, [preventCloseOnEscape, preventCloseOnOutsideClick, onOpenChange, dialogRef]);
646
+
647
+ // Merge smart dimensions with provided style
648
+ const mergedStyle = React.useMemo(() => {
649
+ if (Object.keys(smartDimensions).length === 0) {
650
+ return style;
651
+ }
508
652
 
509
- // Merge smart dimensions with provided style
510
- const mergedStyle = React.useMemo(() => {
511
- // If no smart dimensions are active, just return the provided style
512
- if (Object.keys(smartDimensions).length === 0) {
513
- return style;
514
- }
653
+ const finalStyle: React.CSSProperties = { ...smartDimensions, ...style };
654
+
655
+ if (!maxWidth && !maxWidthPercent) {
656
+ const { maxWidth: _, ...styleWithoutMaxWidth } = finalStyle;
657
+ return styleWithoutMaxWidth;
658
+ }
659
+
660
+ return finalStyle;
661
+ }, [smartDimensions, style, maxWidth, maxWidthPercent]);
662
+
663
+ return (
664
+ <DialogPortal>
665
+ {open && (
666
+ <dialog
667
+ ref={mergedRef}
668
+ className={cn(
669
+ 'fixed left-[50%] top-[50%] z-[51] w-full translate-x-[-50%] translate-y-[-50%] border bg-background shadow-lg duration-200',
670
+ 'animate-in fade-in-0 zoom-in-95 slide-in-from-left-1/2 slide-in-from-top-[48%]',
671
+ 'sm:rounded-lg',
672
+ // Reset native dialog styles
673
+ 'm-0 p-0 max-w-none max-h-none w-auto h-auto border-0 bg-transparent outline-none',
674
+ // Apply our custom styling
675
+ 'border bg-background shadow-lg',
676
+ // Style native backdrop pseudo-element (Tailwind v4 supports arbitrary variants)
677
+ '[&::backdrop]:bg-black/50 [&::backdrop]:animate-in [&::backdrop]:fade-in-0',
678
+ // Only apply size classes if not using smart width
679
+ !maxWidth && !maxWidthPercent && sizeClasses[size],
680
+ // Auto size gets special handling
681
+ size === 'auto' && 'w-fit max-w-[90vw] sm:max-w-[80vw]',
682
+ // Layout classes based on scrolling mode
683
+ enableScrolling ? 'flex flex-col px-6' : 'grid gap-4 p-6',
684
+ // Full screen handling
685
+ size === 'full' && 'sm:left-[50%] sm:top-[50%] sm:translate-x-[-50%] sm:translate-y-[-50%] left-0 top-0 translate-x-0 translate-y-0 h-full rounded-none sm:h-auto sm:rounded-lg',
686
+ // Overflow handling for scrolling mode
687
+ enableScrolling && 'overflow-hidden',
688
+ className
689
+ )}
690
+ style={mergedStyle}
691
+ role="dialog"
692
+ aria-modal="true"
693
+ aria-labelledby={titleId}
694
+ aria-describedby={descriptionId}
695
+ title={title}
696
+ aria-description={description}
697
+ {...props}
698
+ >
699
+ {children}
700
+ {showCloseButton && (
701
+ <DialogClose />
702
+ )}
703
+ </dialog>
704
+ )}
705
+ </DialogPortal>
706
+ );
707
+ }
708
+ );
709
+ DialogContent.displayName = 'DialogContent';
515
710
 
516
- // Start with smart dimensions
517
- const finalStyle: React.CSSProperties = { ...smartDimensions, ...style };
518
-
519
- // If not using smart width and no maxWidth override, don't include maxWidth
520
- if (!maxWidth && !maxWidthPercent) {
521
- const { maxWidth: _, ...styleWithoutMaxWidth } = finalStyle;
522
- return styleWithoutMaxWidth;
523
- }
524
-
525
- return finalStyle;
526
- }, [smartDimensions, style, maxWidth, maxWidthPercent]);
711
+ /**
712
+ * DialogClose component
713
+ * Button to close the dialog
714
+ */
715
+ const DialogClose = React.forwardRef<HTMLButtonElement, DialogCloseProps>(
716
+ ({ className, ...props }, ref) => {
717
+ const { onOpenChange } = useDialogContext();
527
718
 
528
- return (
529
- <DialogPortal>
530
- <DialogOverlay className={overlayClassName} />
531
- <DialogPrimitive.Content
719
+ return (
720
+ <button
532
721
  ref={ref}
533
- onEscapeKeyDown={preventCloseOnEscape ? handleEscapeKeyDown : undefined}
534
- onPointerDownOutside={handlePointerDownOutside}
722
+ type="button"
723
+ onClick={() => onOpenChange(false)}
535
724
  className={cn(
536
- 'fixed left-[50%] top-[50%] z-[51] w-full translate-x-[-50%] translate-y-[-50%] border bg-background shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
537
- // Reset native dialog styles that interfere with our custom styling
538
- 'm-0 p-0 max-w-none max-h-none w-auto h-auto border-0 bg-transparent outline-none',
539
- // Apply our custom styling
540
- 'border bg-background shadow-lg',
541
- // Only apply size classes if not using smart width
542
- !maxWidth && !maxWidthPercent && sizeClasses[size],
543
- // Auto size gets special handling for content fitting
544
- size === 'auto' && 'w-fit max-w-[90vw] sm:max-w-[80vw]',
545
- // Layout classes based on scrolling mode
546
- enableScrolling ? 'flex flex-col' : 'grid gap-4 p-6',
547
- // Full screen handling
548
- size === 'full' && 'sm:left-[50%] sm:top-[50%] sm:translate-x-[-50%] sm:translate-y-[-50%] left-0 top-0 translate-x-0 translate-y-0 h-full rounded-none sm:h-auto sm:rounded-lg',
549
- // Overflow handling for scrolling mode
550
- enableScrolling && 'overflow-hidden',
725
+ 'absolute right-4 top-4 z-10 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none',
551
726
  className
552
727
  )}
553
- style={mergedStyle}
554
728
  {...props}
555
729
  >
556
- {children}
557
- {showCloseButton && (
558
- <DialogPrimitive.Close className="absolute right-4 top-4 z-10 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
559
- <X className="size-4" />
560
- <span className="sr-only">Close</span>
561
- </DialogPrimitive.Close>
562
- )}
563
- </DialogPrimitive.Content>
564
- </DialogPortal>
565
- );
566
- });
567
- DialogContent.displayName = DialogPrimitive.Content.displayName;
730
+ <X className="size-4" />
731
+ <span className="sr-only">Close</span>
732
+ </button>
733
+ );
734
+ }
735
+ );
736
+ DialogClose.displayName = 'DialogClose';
568
737
 
569
738
  /**
570
739
  * DialogHeader component
571
740
  * Semantic header container for dialog title and description with optional sticky behavior
572
- *
573
- * @param props - Header configuration and styling
574
- * @returns JSX.Element - The dialog header container using semantic <header> element
575
- *
576
- * @example
577
- * ```tsx
578
- * <DialogHeader sticky={true}>
579
- * <DialogTitle>Sticky Header</DialogTitle>
580
- * <DialogDescription>This header stays visible while scrolling.</DialogDescription>
581
- * </DialogHeader>
582
- * ```
583
741
  */
584
742
  const DialogHeader = ({
585
743
  className,
@@ -589,7 +747,7 @@ const DialogHeader = ({
589
747
  <header
590
748
  className={cn(
591
749
  'flex flex-col space-y-1.5 text-center sm:text-left',
592
- sticky ? 'sticky top-0 z-10 bg-background p-6 pb-4 border-b' : 'p-6 pb-4',
750
+ sticky ? 'sticky top-0 z-10 bg-background pt-6 pb-4 border-b' : 'py-2',
593
751
  className
594
752
  )}
595
753
  {...props}
@@ -600,27 +758,6 @@ DialogHeader.displayName = 'DialogHeader';
600
758
  /**
601
759
  * DialogBody component
602
760
  * Semantic main content area for dialog body content with scrollable functionality
603
- * Supports both React children and safe HTML content rendering
604
- *
605
- * @param props - Body configuration and styling
606
- * @returns JSX.Element - The scrollable dialog body container using semantic <main> element
607
- *
608
- * @example
609
- * ```tsx
610
- * // Using React children
611
- * <DialogBody>
612
- * <section className="space-y-4">
613
- * <h4>Content Title</h4>
614
- * <p>Long content that will scroll...</p>
615
- * </section>
616
- * </DialogBody>
617
- *
618
- * // Using HTML content
619
- * <DialogBody
620
- * htmlContent="<h2>Import Instructions</h2><p>Upload a CSV file with the following format:</p><ul><li>Required columns: name, email</li><li>Optional columns: phone, address</li></ul>"
621
- * allowHtml={true}
622
- * />
623
- * ```
624
761
  */
625
762
  const DialogBody = ({
626
763
  className,
@@ -640,7 +777,6 @@ const DialogBody = ({
640
777
  };
641
778
  }, [maxHeight, style]);
642
779
 
643
- // Process HTML content if provided
644
780
  const processedHtmlContent = React.useMemo(() => {
645
781
  if (!htmlContent || !allowHtml) {
646
782
  return null;
@@ -654,13 +790,12 @@ const DialogBody = ({
654
790
  return result.html;
655
791
  }, [htmlContent, allowHtml, strictSanitization, logWarnings]);
656
792
 
657
- // Determine if htmlContent was provided (even if processing failed)
658
793
  const hasHtmlContent = Boolean(htmlContent && allowHtml);
659
794
 
660
795
  return (
661
796
  <main
662
797
  className={cn(
663
- 'overflow-y-auto px-6 py-2',
798
+ 'overflow-y-auto py-2',
664
799
  className
665
800
  )}
666
801
  style={mergedStyle}
@@ -669,16 +804,16 @@ const DialogBody = ({
669
804
  {...props}
670
805
  >
671
806
  {processedHtmlContent ? (
672
- <div
807
+ <p
673
808
  dangerouslySetInnerHTML={{ __html: processedHtmlContent }}
674
809
  className="prose prose-sm max-w-none"
675
810
  />
676
811
  ) : (
677
812
  <>
678
813
  {hasHtmlContent && !processedHtmlContent && (
679
- <div className="text-acc-500 mb-2">
814
+ <p className="text-acc-500 mb-2">
680
815
  No HTML content processed. Showing children instead.
681
- </div>
816
+ </p>
682
817
  )}
683
818
  {children}
684
819
  </>
@@ -691,17 +826,6 @@ DialogBody.displayName = 'DialogBody';
691
826
  /**
692
827
  * DialogFooter component
693
828
  * Semantic footer container for dialog action buttons with optional sticky behavior
694
- *
695
- * @param props - Footer configuration and styling
696
- * @returns JSX.Element - The dialog footer container using semantic <footer> element
697
- *
698
- * @example
699
- * ```tsx
700
- * <DialogFooter sticky={true}>
701
- * <Button variant="outline">Cancel</Button>
702
- * <Button>Save changes</Button>
703
- * </DialogFooter>
704
- * ```
705
829
  */
706
830
  const DialogFooter = ({
707
831
  className,
@@ -710,9 +834,8 @@ const DialogFooter = ({
710
834
  }: DialogFooterProps) => (
711
835
  <footer
712
836
  className={cn(
713
- // Only apply default layout classes if no custom className is provided
714
837
  !className && 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
715
- !className && (sticky ? 'sticky bottom-0 z-10 bg-background p-6 pt-4 border-t' : 'p-6 pt-4'),
838
+ !className && (sticky ? 'sticky bottom-0 z-10 bg-background pt-4 pb-6 border-t' : 'py-2'),
716
839
  className
717
840
  )}
718
841
  {...props}
@@ -720,62 +843,70 @@ const DialogFooter = ({
720
843
  );
721
844
  DialogFooter.displayName = 'DialogFooter';
722
845
 
723
- const DialogTitle = React.forwardRef<
724
- React.ElementRef<typeof DialogPrimitive.Title>,
725
- DialogTitleProps
726
- >(({ className, htmlContent, allowHtml = true, children, ...props }, ref) => {
727
- const processedHtmlContent = React.useMemo(() => {
728
- if (!htmlContent || !allowHtml) {
729
- return null;
730
- }
846
+ /**
847
+ * DialogTitle component
848
+ * Title element with ARIA support
849
+ */
850
+ const DialogTitle = React.forwardRef<HTMLHeadingElement, DialogTitleProps>(
851
+ ({ className, htmlContent, allowHtml = true, children, ...props }, ref) => {
852
+ const { titleId } = useDialogContext();
731
853
 
732
- const result = renderSafeHtml(htmlContent, {
733
- strict: true,
734
- logWarnings: false
735
- });
854
+ const processedHtmlContent = React.useMemo(() => {
855
+ if (!htmlContent || !allowHtml) {
856
+ return null;
857
+ }
736
858
 
737
- return result.html;
738
- }, [htmlContent, allowHtml]);
859
+ const result = renderSafeHtml(htmlContent, {
860
+ strict: true,
861
+ logWarnings: false
862
+ });
739
863
 
740
- return (
741
- <DialogPrimitive.Title
742
- ref={ref}
743
- className={cn(
744
- className
745
- )}
746
- {...props}
747
- >
748
- {processedHtmlContent ? (
749
- <span dangerouslySetInnerHTML={{ __html: processedHtmlContent }} />
750
- ) : (
751
- children
752
- )}
753
- </DialogPrimitive.Title>
754
- );
755
- });
756
- DialogTitle.displayName = DialogPrimitive.Title.displayName;
864
+ return result.html;
865
+ }, [htmlContent, allowHtml]);
757
866
 
758
- const DialogDescription = React.forwardRef<
759
- HTMLHeadingElement,
760
- DialogDescriptionProps
761
- >(({ className, htmlContent, allowHtml = true, children, ...props }, ref) => {
762
- const processedHtmlContent = React.useMemo(() => {
763
- if (!htmlContent || !allowHtml) {
764
- return null;
765
- }
867
+ return (
868
+ <h2
869
+ ref={ref}
870
+ id={titleId}
871
+ className={cn(className)}
872
+ {...props}
873
+ >
874
+ {processedHtmlContent ? (
875
+ <span dangerouslySetInnerHTML={{ __html: processedHtmlContent }} />
876
+ ) : (
877
+ children
878
+ )}
879
+ </h2>
880
+ );
881
+ }
882
+ );
883
+ DialogTitle.displayName = 'DialogTitle';
766
884
 
767
- const result = renderSafeHtml(htmlContent, {
768
- strict: true,
769
- logWarnings: false
770
- });
885
+ /**
886
+ * DialogDescription component
887
+ * Description element with ARIA support
888
+ */
889
+ const DialogDescription = React.forwardRef<HTMLParagraphElement, DialogDescriptionProps>(
890
+ ({ className, htmlContent, allowHtml = true, children, ...props }, ref) => {
891
+ const { descriptionId } = useDialogContext();
771
892
 
772
- return result.html;
773
- }, [htmlContent, allowHtml]);
893
+ const processedHtmlContent = React.useMemo(() => {
894
+ if (!htmlContent || !allowHtml) {
895
+ return null;
896
+ }
774
897
 
775
- return (
776
- <DialogPrimitive.Description asChild>
777
- <h5
898
+ const result = renderSafeHtml(htmlContent, {
899
+ strict: true,
900
+ logWarnings: false
901
+ });
902
+
903
+ return result.html;
904
+ }, [htmlContent, allowHtml]);
905
+
906
+ return (
907
+ <p
778
908
  ref={ref}
909
+ id={descriptionId}
779
910
  className={cn(className)}
780
911
  {...props}
781
912
  >
@@ -784,16 +915,15 @@ const DialogDescription = React.forwardRef<
784
915
  ) : (
785
916
  children
786
917
  )}
787
- </h5>
788
- </DialogPrimitive.Description>
789
- );
790
- });
791
- DialogDescription.displayName = DialogPrimitive.Description.displayName;
918
+ </p>
919
+ );
920
+ }
921
+ );
922
+ DialogDescription.displayName = 'DialogDescription';
792
923
 
793
924
  export {
794
925
  Dialog,
795
926
  DialogPortal,
796
- DialogOverlay,
797
927
  DialogClose,
798
928
  DialogTrigger,
799
929
  DialogContent,