@edux-design/modals 0.1.2 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,27 @@
1
1
  # @edux-design/modals
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add controlled confirmation dialog and anchored popover compositions with pending dismissal policy, error/retry content and safe focus handling. Generate declared public types for both packages.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @edux-design/buttons@0.3.0
13
+
14
+ ## 0.1.3
15
+
16
+ ### Patch Changes
17
+
18
+ - Rebuilt distribution artifacts.
19
+
3
20
  ## 0.1.2
4
21
 
5
22
  ### Patch Changes
6
23
 
7
- - Updated design tokens
24
+ - Updated design tokens.
8
25
 
9
26
  ## 0.1.1
10
27
 
package/README.md CHANGED
@@ -63,3 +63,17 @@ export function PublishModal() {
63
63
  Passing `onClose` to `Modal` also enables the default close icon inside `ModalHeader`.
64
64
 
65
65
  `ModalContent` accepts `size`, `backdropProps`, the legacy alias `backDropProps`, `className`, and `contentProps`. Storybook examples live in `src/demos/Modal.stories.jsx`.
66
+
67
+ ### Size contracts
68
+
69
+ The `small`, `medium`, `large`, and `full` values are named component-owned width
70
+ contracts. They intentionally use responsive `min()` and viewport constraints so
71
+ the modal keeps its current geometry across screen sizes. These values are not
72
+ global width tokens: the design-token standards keep calculated, component-specific
73
+ surface dimensions local until multiple unrelated components share the same role.
74
+
75
+ Unknown `size` values fall back to `medium` for compatibility.
76
+
77
+ ## Additional components
78
+
79
+ [Confirmation dialogs](./docs/confirmation.md).
@@ -0,0 +1,78 @@
1
+ import * as _radix_ui_react_dialog from '@radix-ui/react-dialog';
2
+ import React from 'react';
3
+
4
+ /** @type {React.FC<ModalProps>} */
5
+ declare const Modal: React.FC<ModalProps>;
6
+ declare const ModalTrigger: React.ForwardRefExoticComponent<_radix_ui_react_dialog.DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
7
+ declare const ModalClose: React.ForwardRefExoticComponent<_radix_ui_react_dialog.DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
8
+ declare const ModalPortal: React.FC<_radix_ui_react_dialog.DialogPortalProps>;
9
+ /** @type {React.ForwardRefExoticComponent<ModalOverlayProps & React.RefAttributes<HTMLDivElement>>} */
10
+ declare const ModalOverlay: React.ForwardRefExoticComponent<ModalOverlayProps & React.RefAttributes<HTMLDivElement>>;
11
+ /** @type {React.ForwardRefExoticComponent<ModalContentProps & React.RefAttributes<HTMLDivElement>>} */
12
+ declare const ModalContent: React.ForwardRefExoticComponent<ModalContentProps & React.RefAttributes<HTMLDivElement>>;
13
+ /** @type {React.ForwardRefExoticComponent<ModalHeaderProps & React.RefAttributes<HTMLDivElement>>} */
14
+ declare const ModalHeader: React.ForwardRefExoticComponent<ModalHeaderProps & React.RefAttributes<HTMLDivElement>>;
15
+ /** @type {React.ForwardRefExoticComponent<ModalBodyProps & React.RefAttributes<HTMLDivElement>>} */
16
+ declare const ModalBody: React.ForwardRefExoticComponent<ModalBodyProps & React.RefAttributes<HTMLDivElement>>;
17
+ /** @type {React.ForwardRefExoticComponent<ModalFooterProps & React.RefAttributes<HTMLDivElement>>} */
18
+ declare const ModalFooter: React.ForwardRefExoticComponent<ModalFooterProps & React.RefAttributes<HTMLDivElement>>;
19
+ /** @type {React.ForwardRefExoticComponent<ModalTitleProps & React.RefAttributes<HTMLHeadingElement>>} */
20
+ declare const ModalTitle: React.ForwardRefExoticComponent<ModalTitleProps & React.RefAttributes<HTMLHeadingElement>>;
21
+ /** @type {React.ForwardRefExoticComponent<ModalDescriptionProps & React.RefAttributes<HTMLParagraphElement>>} */
22
+ declare const ModalDescription: React.ForwardRefExoticComponent<ModalDescriptionProps & React.RefAttributes<HTMLParagraphElement>>;
23
+ type ModalProps = _radix_ui_react_dialog.DialogProps & {
24
+ onOpen?: () => void;
25
+ onClose?: () => void;
26
+ };
27
+ type ModalContentProps = _radix_ui_react_dialog.DialogContentProps & {
28
+ size?: "small" | "medium" | "large" | "full";
29
+ backdropProps?: _radix_ui_react_dialog.DialogOverlayProps;
30
+ backDropProps?: _radix_ui_react_dialog.DialogOverlayProps;
31
+ contentProps?: _radix_ui_react_dialog.DialogContentProps;
32
+ portalProps?: _radix_ui_react_dialog.DialogPortalProps;
33
+ };
34
+ type ModalOverlayProps = _radix_ui_react_dialog.DialogOverlayProps;
35
+ type ModalHeaderProps = React.HTMLAttributes<HTMLDivElement>;
36
+ type ModalBodyProps = React.HTMLAttributes<HTMLDivElement>;
37
+ type ModalFooterProps = React.HTMLAttributes<HTMLDivElement>;
38
+ type ModalTitleProps = _radix_ui_react_dialog.DialogTitleProps;
39
+ type ModalDescriptionProps = _radix_ui_react_dialog.DialogDescriptionProps;
40
+
41
+ type ConfirmationDialogActionProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children" | "disabled"> & React.RefAttributes<HTMLButtonElement>;
42
+ interface ConfirmationDialogProps {
43
+ open?: boolean;
44
+ defaultOpen?: boolean;
45
+ onOpenChange?: (open: boolean) => void;
46
+ modal?: boolean;
47
+ title: React.ReactNode;
48
+ description?: React.ReactNode;
49
+ children?: React.ReactNode;
50
+ /** A single ref-forwarding button. Omit for an externally opened dialog. */
51
+ trigger?: React.ReactElement;
52
+ onConfirm: React.MouseEventHandler<HTMLButtonElement>;
53
+ onCancel?: React.MouseEventHandler<HTMLButtonElement>;
54
+ confirmLabel?: React.ReactNode;
55
+ cancelLabel?: React.ReactNode;
56
+ pendingLabel?: React.ReactNode;
57
+ pending?: boolean;
58
+ error?: React.ReactNode;
59
+ confirmDisabled?: boolean;
60
+ cancelDisabled?: boolean;
61
+ /** Locks user dismissal. The controlled host may still close explicitly. */
62
+ dismissible?: boolean;
63
+ /** Explicit return target, useful when confirmation removes its own trigger. */
64
+ returnFocusRef?: React.RefObject<HTMLElement | null>;
65
+ isDanger?: boolean;
66
+ /** Pending confirmations block every user dismissal unless explicitly enabled. */
67
+ allowDismissWhilePending?: boolean;
68
+ size?: "small" | "medium" | "large" | "full";
69
+ contentProps?: Omit<_radix_ui_react_dialog.DialogContentProps, "children" | "asChild">;
70
+ backdropProps?: _radix_ui_react_dialog.DialogOverlayProps;
71
+ portalProps?: _radix_ui_react_dialog.DialogPortalProps;
72
+ confirmButtonProps?: ConfirmationDialogActionProps;
73
+ cancelButtonProps?: ConfirmationDialogActionProps;
74
+ }
75
+ /** Controlled confirmation. The host owns mutations, pending/error state and success closure. */
76
+ declare const ConfirmationDialog: React.ForwardRefExoticComponent<ConfirmationDialogProps & React.RefAttributes<HTMLDivElement>>;
77
+
78
+ export { ConfirmationDialog, type ConfirmationDialogActionProps, type ConfirmationDialogProps, Modal, ModalBody, type ModalBodyProps, ModalClose, ModalContent, type ModalContentProps, ModalDescription, type ModalDescriptionProps, ModalFooter, type ModalFooterProps, ModalHeader, type ModalHeaderProps, ModalOverlay, type ModalOverlayProps, ModalPortal, type ModalProps, ModalTitle, type ModalTitleProps, ModalTrigger };
@@ -0,0 +1,78 @@
1
+ import * as _radix_ui_react_dialog from '@radix-ui/react-dialog';
2
+ import React from 'react';
3
+
4
+ /** @type {React.FC<ModalProps>} */
5
+ declare const Modal: React.FC<ModalProps>;
6
+ declare const ModalTrigger: React.ForwardRefExoticComponent<_radix_ui_react_dialog.DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
7
+ declare const ModalClose: React.ForwardRefExoticComponent<_radix_ui_react_dialog.DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
8
+ declare const ModalPortal: React.FC<_radix_ui_react_dialog.DialogPortalProps>;
9
+ /** @type {React.ForwardRefExoticComponent<ModalOverlayProps & React.RefAttributes<HTMLDivElement>>} */
10
+ declare const ModalOverlay: React.ForwardRefExoticComponent<ModalOverlayProps & React.RefAttributes<HTMLDivElement>>;
11
+ /** @type {React.ForwardRefExoticComponent<ModalContentProps & React.RefAttributes<HTMLDivElement>>} */
12
+ declare const ModalContent: React.ForwardRefExoticComponent<ModalContentProps & React.RefAttributes<HTMLDivElement>>;
13
+ /** @type {React.ForwardRefExoticComponent<ModalHeaderProps & React.RefAttributes<HTMLDivElement>>} */
14
+ declare const ModalHeader: React.ForwardRefExoticComponent<ModalHeaderProps & React.RefAttributes<HTMLDivElement>>;
15
+ /** @type {React.ForwardRefExoticComponent<ModalBodyProps & React.RefAttributes<HTMLDivElement>>} */
16
+ declare const ModalBody: React.ForwardRefExoticComponent<ModalBodyProps & React.RefAttributes<HTMLDivElement>>;
17
+ /** @type {React.ForwardRefExoticComponent<ModalFooterProps & React.RefAttributes<HTMLDivElement>>} */
18
+ declare const ModalFooter: React.ForwardRefExoticComponent<ModalFooterProps & React.RefAttributes<HTMLDivElement>>;
19
+ /** @type {React.ForwardRefExoticComponent<ModalTitleProps & React.RefAttributes<HTMLHeadingElement>>} */
20
+ declare const ModalTitle: React.ForwardRefExoticComponent<ModalTitleProps & React.RefAttributes<HTMLHeadingElement>>;
21
+ /** @type {React.ForwardRefExoticComponent<ModalDescriptionProps & React.RefAttributes<HTMLParagraphElement>>} */
22
+ declare const ModalDescription: React.ForwardRefExoticComponent<ModalDescriptionProps & React.RefAttributes<HTMLParagraphElement>>;
23
+ type ModalProps = _radix_ui_react_dialog.DialogProps & {
24
+ onOpen?: () => void;
25
+ onClose?: () => void;
26
+ };
27
+ type ModalContentProps = _radix_ui_react_dialog.DialogContentProps & {
28
+ size?: "small" | "medium" | "large" | "full";
29
+ backdropProps?: _radix_ui_react_dialog.DialogOverlayProps;
30
+ backDropProps?: _radix_ui_react_dialog.DialogOverlayProps;
31
+ contentProps?: _radix_ui_react_dialog.DialogContentProps;
32
+ portalProps?: _radix_ui_react_dialog.DialogPortalProps;
33
+ };
34
+ type ModalOverlayProps = _radix_ui_react_dialog.DialogOverlayProps;
35
+ type ModalHeaderProps = React.HTMLAttributes<HTMLDivElement>;
36
+ type ModalBodyProps = React.HTMLAttributes<HTMLDivElement>;
37
+ type ModalFooterProps = React.HTMLAttributes<HTMLDivElement>;
38
+ type ModalTitleProps = _radix_ui_react_dialog.DialogTitleProps;
39
+ type ModalDescriptionProps = _radix_ui_react_dialog.DialogDescriptionProps;
40
+
41
+ type ConfirmationDialogActionProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children" | "disabled"> & React.RefAttributes<HTMLButtonElement>;
42
+ interface ConfirmationDialogProps {
43
+ open?: boolean;
44
+ defaultOpen?: boolean;
45
+ onOpenChange?: (open: boolean) => void;
46
+ modal?: boolean;
47
+ title: React.ReactNode;
48
+ description?: React.ReactNode;
49
+ children?: React.ReactNode;
50
+ /** A single ref-forwarding button. Omit for an externally opened dialog. */
51
+ trigger?: React.ReactElement;
52
+ onConfirm: React.MouseEventHandler<HTMLButtonElement>;
53
+ onCancel?: React.MouseEventHandler<HTMLButtonElement>;
54
+ confirmLabel?: React.ReactNode;
55
+ cancelLabel?: React.ReactNode;
56
+ pendingLabel?: React.ReactNode;
57
+ pending?: boolean;
58
+ error?: React.ReactNode;
59
+ confirmDisabled?: boolean;
60
+ cancelDisabled?: boolean;
61
+ /** Locks user dismissal. The controlled host may still close explicitly. */
62
+ dismissible?: boolean;
63
+ /** Explicit return target, useful when confirmation removes its own trigger. */
64
+ returnFocusRef?: React.RefObject<HTMLElement | null>;
65
+ isDanger?: boolean;
66
+ /** Pending confirmations block every user dismissal unless explicitly enabled. */
67
+ allowDismissWhilePending?: boolean;
68
+ size?: "small" | "medium" | "large" | "full";
69
+ contentProps?: Omit<_radix_ui_react_dialog.DialogContentProps, "children" | "asChild">;
70
+ backdropProps?: _radix_ui_react_dialog.DialogOverlayProps;
71
+ portalProps?: _radix_ui_react_dialog.DialogPortalProps;
72
+ confirmButtonProps?: ConfirmationDialogActionProps;
73
+ cancelButtonProps?: ConfirmationDialogActionProps;
74
+ }
75
+ /** Controlled confirmation. The host owns mutations, pending/error state and success closure. */
76
+ declare const ConfirmationDialog: React.ForwardRefExoticComponent<ConfirmationDialogProps & React.RefAttributes<HTMLDivElement>>;
77
+
78
+ export { ConfirmationDialog, type ConfirmationDialogActionProps, type ConfirmationDialogProps, Modal, ModalBody, type ModalBodyProps, ModalClose, ModalContent, type ModalContentProps, ModalDescription, type ModalDescriptionProps, ModalFooter, type ModalFooterProps, ModalHeader, type ModalHeaderProps, ModalOverlay, type ModalOverlayProps, ModalPortal, type ModalProps, ModalTitle, type ModalTitleProps, ModalTrigger };
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // src/index.js
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
+ ConfirmationDialog: () => ConfirmationDialog,
32
33
  Modal: () => Modal,
33
34
  ModalBody: () => ModalBody,
34
35
  ModalClose: () => ModalClose,
@@ -47,20 +48,49 @@ module.exports = __toCommonJS(index_exports);
47
48
  var import_react = __toESM(require("react"));
48
49
  var DialogPrimitive = __toESM(require("@radix-ui/react-dialog"));
49
50
  var import_utils = require("@edux-design/utils");
51
+ var import_buttons = require("@edux-design/buttons");
52
+ var import_icons = require("@edux-design/icons");
53
+ var import_jsx_runtime = require("react/jsx-runtime");
50
54
  var MODAL_OVERLAY_Z_INDEX = 2147483645;
51
55
  var MODAL_CONTENT_Z_INDEX = 2147483646;
56
+ var ModalContext = (0, import_react.createContext)({ hasOnClose: false });
57
+ var MODAL_SECTION_PADDING_X = "px-16";
58
+ var MODAL_SECTION_PADDING_Y = "py-16";
59
+ var MODAL_SURFACE_CLASSES = [
60
+ "fixed left-1/2 top-1/2 max-h-[92vh] -translate-x-1/2 -translate-y-1/2",
61
+ "bg-bg-base text-fg-base shadow-xl outline-none rounded-md border-2 border-border-subtle",
62
+ "flex flex-col overflow-hidden",
63
+ "data-[state=open]:animate-fade-in data-[state=closed]:animate-fade-out"
64
+ ].join(" ");
65
+ var MODAL_HEADER_CLASSES = [
66
+ "flex justify-between gap-12",
67
+ MODAL_SECTION_PADDING_X,
68
+ MODAL_SECTION_PADDING_Y,
69
+ "border-b border-border-subtle"
70
+ ].join(" ");
71
+ var MODAL_HEADER_CONTENT_CLASSES = "flex min-w-0 flex-1 flex-col gap-4";
72
+ var MODAL_BODY_CLASSES = [
73
+ "flex-1 overflow-auto",
74
+ MODAL_SECTION_PADDING_X,
75
+ MODAL_SECTION_PADDING_Y
76
+ ].join(" ");
77
+ var MODAL_FOOTER_CLASSES = [
78
+ "flex items-center justify-end gap-8",
79
+ MODAL_SECTION_PADDING_X,
80
+ MODAL_SECTION_PADDING_Y,
81
+ "border-t border-border-subtle"
82
+ ].join(" ");
83
+ var MODAL_TITLE_CLASSES = "text-lg font-semibold text-fg-base";
84
+ var MODAL_DESCRIPTION_CLASSES = "text-sm text-fg-subtle";
85
+ var MODAL_CLOSE_LABEL = "Close modal";
86
+ var MODAL_SIZE_CLASSES = {
87
+ small: "w-[min(92vw,420px)]",
88
+ medium: "w-[min(92vw,640px)]",
89
+ large: "w-[min(92vw,840px)]",
90
+ full: "w-[min(96vw,1200px)] h-[min(92vh,900px)]"
91
+ };
52
92
  var getSizeClasses = (size) => {
53
- switch (size) {
54
- case "small":
55
- return "w-[min(92vw,420px)]";
56
- case "large":
57
- return "w-[min(92vw,840px)]";
58
- case "full":
59
- return "w-[min(96vw,1200px)] h-[min(92vh,900px)]";
60
- case "medium":
61
- default:
62
- return "w-[min(92vw,640px)]";
63
- }
93
+ return MODAL_SIZE_CLASSES[size] ?? MODAL_SIZE_CLASSES.medium;
64
94
  };
65
95
  var Modal = ({
66
96
  open,
@@ -71,33 +101,46 @@ var Modal = ({
71
101
  children,
72
102
  ...props
73
103
  }) => {
104
+ const contextValue = (0, import_react.useMemo)(
105
+ () => ({ hasOnClose: typeof onClose === "function" }),
106
+ [onClose]
107
+ );
74
108
  const handleOpenChange = (0, import_react.useCallback)(
75
109
  (nextOpen) => {
76
- onOpenChange == null ? void 0 : onOpenChange(nextOpen);
110
+ onOpenChange?.(nextOpen);
77
111
  if (nextOpen) {
78
- onOpen == null ? void 0 : onOpen();
112
+ onOpen?.();
79
113
  return;
80
114
  }
81
- onClose == null ? void 0 : onClose();
115
+ onClose?.();
82
116
  },
83
117
  [onClose, onOpen, onOpenChange]
84
118
  );
85
- return /* @__PURE__ */ import_react.default.createElement(
119
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
86
120
  DialogPrimitive.Root,
87
121
  {
88
122
  open,
89
123
  defaultOpen,
90
124
  onOpenChange: handleOpenChange,
91
- ...props
92
- },
93
- children
94
- );
125
+ ...props,
126
+ children
127
+ }
128
+ ) });
95
129
  };
96
130
  var ModalTrigger = DialogPrimitive.Trigger;
97
131
  var ModalClose = DialogPrimitive.Close;
98
132
  var ModalPortal = DialogPrimitive.Portal;
133
+ var ModalHeaderCloseButton = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
134
+ import_buttons.IconButton,
135
+ {
136
+ size: "small",
137
+ isBare: true,
138
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.Close, { "aria-hidden": "true" }),
139
+ "aria-label": MODAL_CLOSE_LABEL
140
+ }
141
+ ) });
99
142
  var ModalOverlay = (0, import_react.forwardRef)(
100
- ({ className, style, ...props }, ref) => /* @__PURE__ */ import_react.default.createElement(
143
+ ({ className, style, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
101
144
  DialogPrimitive.Overlay,
102
145
  {
103
146
  ref,
@@ -121,90 +164,238 @@ var ModalContent = (0, import_react.forwardRef)(
121
164
  backdropProps,
122
165
  backDropProps,
123
166
  contentProps = {},
167
+ portalProps = {},
124
168
  ...restProps
125
169
  }, ref) => {
126
170
  const resolvedBackdropProps = backdropProps ?? backDropProps ?? {};
127
- return /* @__PURE__ */ import_react.default.createElement(ModalPortal, null, /* @__PURE__ */ import_react.default.createElement(
128
- ModalOverlay,
129
- {
130
- ...resolvedBackdropProps,
131
- className: (0, import_utils.cx)(resolvedBackdropProps.className),
132
- style: { ...resolvedBackdropProps.style }
133
- }
134
- ), /* @__PURE__ */ import_react.default.createElement(
135
- DialogPrimitive.Content,
136
- {
137
- ref,
138
- className: (0, import_utils.cx)(
139
- "fixed left-1/2 top-1/2 max-h-[92vh] -translate-x-1/2 -translate-y-1/2",
140
- "bg-bg-base text-fg-base shadow-xl outline-none rounded-2xl border border-border-subtle",
141
- "flex flex-col overflow-hidden",
142
- "data-[state=open]:animate-fade-in data-[state=closed]:animate-fade-out",
143
- getSizeClasses(size),
144
- className
145
- ),
146
- style: { zIndex: MODAL_CONTENT_Z_INDEX, ...style },
147
- ...contentProps,
148
- ...restProps
149
- },
150
- children
151
- ));
171
+ const { className: backdropClassName, style: backdropStyle, ...overlayProps } = resolvedBackdropProps;
172
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalPortal, { ...portalProps, children: [
173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
174
+ ModalOverlay,
175
+ {
176
+ ...overlayProps,
177
+ className: (0, import_utils.cx)(backdropClassName),
178
+ style: { ...backdropStyle }
179
+ }
180
+ ),
181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
182
+ DialogPrimitive.Content,
183
+ {
184
+ ref,
185
+ className: (0, import_utils.cx)(
186
+ MODAL_SURFACE_CLASSES,
187
+ getSizeClasses(size),
188
+ className
189
+ ),
190
+ style: { zIndex: MODAL_CONTENT_Z_INDEX, ...style },
191
+ ...contentProps,
192
+ ...restProps,
193
+ children
194
+ }
195
+ )
196
+ ] });
152
197
  }
153
198
  );
154
199
  ModalContent.displayName = "ModalContent";
155
- var ModalHeader = (0, import_react.forwardRef)(({ className, ...props }, ref) => /* @__PURE__ */ import_react.default.createElement(
156
- "div",
157
- {
158
- ref,
159
- className: (0, import_utils.cx)(
160
- "flex flex-col gap-4 px-20 py-16 border-b border-border-subtle",
161
- className
162
- ),
163
- ...props
164
- }
165
- ));
200
+ var ModalHeader = (0, import_react.forwardRef)(({ className, children, ...props }, ref) => {
201
+ const { hasOnClose } = (0, import_react.useContext)(ModalContext);
202
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
203
+ "div",
204
+ {
205
+ ref,
206
+ className: (0, import_utils.cx)(MODAL_HEADER_CLASSES, className),
207
+ ...props,
208
+ children: [
209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: MODAL_HEADER_CONTENT_CLASSES, children }),
210
+ hasOnClose ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHeaderCloseButton, {}) : null
211
+ ]
212
+ }
213
+ );
214
+ });
166
215
  ModalHeader.displayName = "ModalHeader";
167
- var ModalBody = (0, import_react.forwardRef)(({ className, ...props }, ref) => /* @__PURE__ */ import_react.default.createElement(
216
+ var ModalBody = (0, import_react.forwardRef)(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
168
217
  "div",
169
218
  {
170
219
  ref,
171
- className: (0, import_utils.cx)("flex-1 overflow-auto px-20 py-16", className),
220
+ className: (0, import_utils.cx)(MODAL_BODY_CLASSES, className),
172
221
  ...props
173
222
  }
174
223
  ));
175
224
  ModalBody.displayName = "ModalBody";
176
- var ModalFooter = (0, import_react.forwardRef)(({ className, ...props }, ref) => /* @__PURE__ */ import_react.default.createElement(
225
+ var ModalFooter = (0, import_react.forwardRef)(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
177
226
  "div",
178
227
  {
179
228
  ref,
180
- className: (0, import_utils.cx)(
181
- "flex items-center justify-end gap-8 px-20 py-16 border-t border-border-subtle",
182
- className
183
- ),
229
+ className: (0, import_utils.cx)(MODAL_FOOTER_CLASSES, className),
184
230
  ...props
185
231
  }
186
232
  ));
187
233
  ModalFooter.displayName = "ModalFooter";
188
- var ModalTitle = (0, import_react.forwardRef)(({ className, ...props }, ref) => /* @__PURE__ */ import_react.default.createElement(
234
+ var ModalTitle = (0, import_react.forwardRef)(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
189
235
  DialogPrimitive.Title,
190
236
  {
191
237
  ref,
192
- className: (0, import_utils.cx)("text-lg font-semibold text-fg-base", className),
238
+ className: (0, import_utils.cx)(MODAL_TITLE_CLASSES, className),
193
239
  ...props
194
240
  }
195
241
  ));
196
242
  ModalTitle.displayName = "ModalTitle";
197
- var ModalDescription = (0, import_react.forwardRef)(({ className, ...props }, ref) => /* @__PURE__ */ import_react.default.createElement(
243
+ var ModalDescription = (0, import_react.forwardRef)(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
198
244
  DialogPrimitive.Description,
199
245
  {
200
246
  ref,
201
- className: (0, import_utils.cx)("text-sm text-fg-subtle", className),
247
+ className: (0, import_utils.cx)(MODAL_DESCRIPTION_CLASSES, className),
202
248
  ...props
203
249
  }
204
250
  ));
205
251
  ModalDescription.displayName = "ModalDescription";
252
+
253
+ // src/elements/ConfirmationDialog.tsx
254
+ var import_react2 = require("react");
255
+ var import_buttons2 = require("@edux-design/buttons");
256
+ var import_utils2 = require("@edux-design/utils");
257
+ var import_jsx_runtime2 = require("react/jsx-runtime");
258
+ var ConfirmationDialog = (0, import_react2.forwardRef)(function ConfirmationDialog2({
259
+ open,
260
+ defaultOpen = false,
261
+ onOpenChange,
262
+ modal = true,
263
+ title,
264
+ description,
265
+ children,
266
+ trigger,
267
+ onConfirm,
268
+ onCancel,
269
+ confirmLabel = "Confirm",
270
+ cancelLabel = "Cancel",
271
+ pendingLabel = "Working\u2026",
272
+ pending = false,
273
+ error,
274
+ confirmDisabled = false,
275
+ cancelDisabled = false,
276
+ dismissible = true,
277
+ returnFocusRef,
278
+ isDanger = false,
279
+ allowDismissWhilePending = false,
280
+ size = "small",
281
+ contentProps = {},
282
+ backdropProps,
283
+ portalProps,
284
+ confirmButtonProps = {},
285
+ cancelButtonProps = {}
286
+ }, ref) {
287
+ const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react2.useState)(defaultOpen);
288
+ const resolvedOpen = open ?? uncontrolledOpen;
289
+ const cancelRef = (0, import_react2.useRef)(null);
290
+ const previousFocusRef = (0, import_react2.useRef)(null);
291
+ const statusId = (0, import_react2.useId)();
292
+ const locked = !dismissible || pending && !allowDismissWhilePending;
293
+ const hasBody = children != null || Boolean(error) || pending;
294
+ const {
295
+ onClick: onCancelClick,
296
+ ref: actionRef,
297
+ ...cancelProps
298
+ } = cancelButtonProps;
299
+ const { onClick: onConfirmClick, ...confirmProps } = confirmButtonProps;
300
+ const requestOpen = (next) => {
301
+ if (!next && locked) return;
302
+ if (open === void 0) setUncontrolledOpen(next);
303
+ onOpenChange?.(next);
304
+ };
305
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Modal, { open: resolvedOpen, onOpenChange: requestOpen, modal, children: [
306
+ trigger && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ModalTrigger, { asChild: true, children: trigger }),
307
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
308
+ ModalContent,
309
+ {
310
+ ...contentProps,
311
+ ref,
312
+ size,
313
+ backdropProps,
314
+ portalProps,
315
+ ...!description && !contentProps["aria-describedby"] ? { "aria-describedby": void 0 } : {},
316
+ "aria-busy": pending || void 0,
317
+ className: (0, import_utils2.cx)("motion-reduce:animate-none", contentProps.className),
318
+ onOpenAutoFocus: (event) => {
319
+ previousFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
320
+ contentProps.onOpenAutoFocus?.(event);
321
+ if (!event.defaultPrevented) {
322
+ event.preventDefault();
323
+ if (!locked && !cancelDisabled) cancelRef.current?.focus();
324
+ else if (event.target instanceof HTMLElement) event.target.focus();
325
+ }
326
+ },
327
+ onCloseAutoFocus: (event) => {
328
+ contentProps.onCloseAutoFocus?.(event);
329
+ const target = returnFocusRef?.current ?? (!trigger ? previousFocusRef.current : null);
330
+ if (!event.defaultPrevented && target?.isConnected) {
331
+ event.preventDefault();
332
+ target.focus();
333
+ }
334
+ },
335
+ onEscapeKeyDown: (event) => {
336
+ contentProps.onEscapeKeyDown?.(event);
337
+ if (locked) event.preventDefault();
338
+ },
339
+ onInteractOutside: (event) => {
340
+ contentProps.onInteractOutside?.(event);
341
+ if (locked) event.preventDefault();
342
+ },
343
+ children: [
344
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(ModalHeader, { className: !hasBody ? "border-b-0" : void 0, children: [
345
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ModalTitle, { children: title }),
346
+ description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ModalDescription, { children: description })
347
+ ] }),
348
+ hasBody && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(ModalBody, { className: "min-h-0 text-sm text-fg-base", children: [
349
+ children,
350
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { role: "alert", className: "mt-8 text-fg-danger-emphasis", children: error }),
351
+ pending && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { id: statusId, role: "status", className: "sr-only", children: pendingLabel })
352
+ ] }),
353
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(ModalFooter, { className: "flex-wrap", children: [
354
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
355
+ import_buttons2.Button,
356
+ {
357
+ ...cancelProps,
358
+ ref: (node) => {
359
+ cancelRef.current = node;
360
+ if (typeof actionRef === "function") actionRef(node);
361
+ else if (actionRef) actionRef.current = node;
362
+ },
363
+ type: "button",
364
+ size: "small",
365
+ isSecondary: true,
366
+ disabled: locked || cancelDisabled,
367
+ onClick: (event) => {
368
+ onCancelClick?.(event);
369
+ if (!event.defaultPrevented) onCancel?.(event);
370
+ if (!event.defaultPrevented) requestOpen(false);
371
+ },
372
+ children: cancelLabel
373
+ }
374
+ ),
375
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
376
+ import_buttons2.Button,
377
+ {
378
+ ...confirmProps,
379
+ type: "button",
380
+ size: "small",
381
+ isDanger,
382
+ disabled: pending || confirmDisabled,
383
+ onClick: (event) => {
384
+ onConfirmClick?.(event);
385
+ if (!event.defaultPrevented) onConfirm(event);
386
+ },
387
+ children: pending ? pendingLabel : confirmLabel
388
+ }
389
+ )
390
+ ] })
391
+ ]
392
+ }
393
+ )
394
+ ] });
395
+ });
206
396
  // Annotate the CommonJS export names for ESM import in node:
207
397
  0 && (module.exports = {
398
+ ConfirmationDialog,
208
399
  Modal,
209
400
  ModalBody,
210
401
  ModalClose,