@edux-design/modals 0.1.3 → 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 +13 -2
- package/README.md +14 -0
- package/dist/index.d.mts +78 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +196 -43
- package/dist/index.mjs +195 -43
- package/docs/confirmation.md +117 -0
- package/package.json +11 -18
- package/src/demos/Modal.stories.jsx +0 -304
- package/src/elements/Modal.jsx +0 -228
- package/src/elements/Modal.test.jsx +0 -139
- package/src/index.js +0 -13
package/dist/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
|
10
10
|
import { cx } from "@edux-design/utils";
|
|
11
11
|
import { IconButton } from "@edux-design/buttons";
|
|
12
12
|
import { Close as Close2 } from "@edux-design/icons";
|
|
13
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
14
|
var MODAL_OVERLAY_Z_INDEX = 2147483645;
|
|
14
15
|
var MODAL_CONTENT_Z_INDEX = 2147483646;
|
|
15
16
|
var ModalContext = createContext({ hasOnClose: false });
|
|
@@ -66,40 +67,40 @@ var Modal = ({
|
|
|
66
67
|
);
|
|
67
68
|
const handleOpenChange = useCallback(
|
|
68
69
|
(nextOpen) => {
|
|
69
|
-
onOpenChange
|
|
70
|
+
onOpenChange?.(nextOpen);
|
|
70
71
|
if (nextOpen) {
|
|
71
|
-
onOpen
|
|
72
|
+
onOpen?.();
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
74
|
-
onClose
|
|
75
|
+
onClose?.();
|
|
75
76
|
},
|
|
76
77
|
[onClose, onOpen, onOpenChange]
|
|
77
78
|
);
|
|
78
|
-
return /* @__PURE__ */
|
|
79
|
+
return /* @__PURE__ */ jsx(ModalContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
79
80
|
DialogPrimitive.Root,
|
|
80
81
|
{
|
|
81
82
|
open,
|
|
82
83
|
defaultOpen,
|
|
83
84
|
onOpenChange: handleOpenChange,
|
|
84
|
-
...props
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
));
|
|
85
|
+
...props,
|
|
86
|
+
children
|
|
87
|
+
}
|
|
88
|
+
) });
|
|
88
89
|
};
|
|
89
90
|
var ModalTrigger = DialogPrimitive.Trigger;
|
|
90
91
|
var ModalClose = DialogPrimitive.Close;
|
|
91
92
|
var ModalPortal = DialogPrimitive.Portal;
|
|
92
|
-
var ModalHeaderCloseButton = () => /* @__PURE__ */
|
|
93
|
+
var ModalHeaderCloseButton = () => /* @__PURE__ */ jsx(ModalClose, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
93
94
|
IconButton,
|
|
94
95
|
{
|
|
95
96
|
size: "small",
|
|
96
97
|
isBare: true,
|
|
97
|
-
icon: /* @__PURE__ */
|
|
98
|
+
icon: /* @__PURE__ */ jsx(Close2, { "aria-hidden": "true" }),
|
|
98
99
|
"aria-label": MODAL_CLOSE_LABEL
|
|
99
100
|
}
|
|
100
|
-
));
|
|
101
|
+
) });
|
|
101
102
|
var ModalOverlay = forwardRef(
|
|
102
|
-
({ className, style, ...props }, ref) => /* @__PURE__ */
|
|
103
|
+
({ className, style, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
103
104
|
DialogPrimitive.Overlay,
|
|
104
105
|
{
|
|
105
106
|
ref,
|
|
@@ -123,50 +124,56 @@ var ModalContent = forwardRef(
|
|
|
123
124
|
backdropProps,
|
|
124
125
|
backDropProps,
|
|
125
126
|
contentProps = {},
|
|
127
|
+
portalProps = {},
|
|
126
128
|
...restProps
|
|
127
129
|
}, ref) => {
|
|
128
130
|
const resolvedBackdropProps = backdropProps ?? backDropProps ?? {};
|
|
129
131
|
const { className: backdropClassName, style: backdropStyle, ...overlayProps } = resolvedBackdropProps;
|
|
130
|
-
return /* @__PURE__ */
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
132
|
+
return /* @__PURE__ */ jsxs(ModalPortal, { ...portalProps, children: [
|
|
133
|
+
/* @__PURE__ */ jsx(
|
|
134
|
+
ModalOverlay,
|
|
135
|
+
{
|
|
136
|
+
...overlayProps,
|
|
137
|
+
className: cx(backdropClassName),
|
|
138
|
+
style: { ...backdropStyle }
|
|
139
|
+
}
|
|
140
|
+
),
|
|
141
|
+
/* @__PURE__ */ jsx(
|
|
142
|
+
DialogPrimitive.Content,
|
|
143
|
+
{
|
|
144
|
+
ref,
|
|
145
|
+
className: cx(
|
|
146
|
+
MODAL_SURFACE_CLASSES,
|
|
147
|
+
getSizeClasses(size),
|
|
148
|
+
className
|
|
149
|
+
),
|
|
150
|
+
style: { zIndex: MODAL_CONTENT_Z_INDEX, ...style },
|
|
151
|
+
...contentProps,
|
|
152
|
+
...restProps,
|
|
153
|
+
children
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
] });
|
|
152
157
|
}
|
|
153
158
|
);
|
|
154
159
|
ModalContent.displayName = "ModalContent";
|
|
155
160
|
var ModalHeader = forwardRef(({ className, children, ...props }, ref) => {
|
|
156
161
|
const { hasOnClose } = useContext(ModalContext);
|
|
157
|
-
return /* @__PURE__ */
|
|
162
|
+
return /* @__PURE__ */ jsxs(
|
|
158
163
|
"div",
|
|
159
164
|
{
|
|
160
165
|
ref,
|
|
161
166
|
className: cx(MODAL_HEADER_CLASSES, className),
|
|
162
|
-
...props
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
...props,
|
|
168
|
+
children: [
|
|
169
|
+
/* @__PURE__ */ jsx("div", { className: MODAL_HEADER_CONTENT_CLASSES, children }),
|
|
170
|
+
hasOnClose ? /* @__PURE__ */ jsx(ModalHeaderCloseButton, {}) : null
|
|
171
|
+
]
|
|
172
|
+
}
|
|
166
173
|
);
|
|
167
174
|
});
|
|
168
175
|
ModalHeader.displayName = "ModalHeader";
|
|
169
|
-
var ModalBody = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
|
|
176
|
+
var ModalBody = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
170
177
|
"div",
|
|
171
178
|
{
|
|
172
179
|
ref,
|
|
@@ -175,7 +182,7 @@ var ModalBody = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ Rea
|
|
|
175
182
|
}
|
|
176
183
|
));
|
|
177
184
|
ModalBody.displayName = "ModalBody";
|
|
178
|
-
var ModalFooter = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
|
|
185
|
+
var ModalFooter = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
179
186
|
"div",
|
|
180
187
|
{
|
|
181
188
|
ref,
|
|
@@ -184,7 +191,7 @@ var ModalFooter = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ R
|
|
|
184
191
|
}
|
|
185
192
|
));
|
|
186
193
|
ModalFooter.displayName = "ModalFooter";
|
|
187
|
-
var ModalTitle = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
|
|
194
|
+
var ModalTitle = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
188
195
|
DialogPrimitive.Title,
|
|
189
196
|
{
|
|
190
197
|
ref,
|
|
@@ -193,7 +200,7 @@ var ModalTitle = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ Re
|
|
|
193
200
|
}
|
|
194
201
|
));
|
|
195
202
|
ModalTitle.displayName = "ModalTitle";
|
|
196
|
-
var ModalDescription = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
|
|
203
|
+
var ModalDescription = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
197
204
|
DialogPrimitive.Description,
|
|
198
205
|
{
|
|
199
206
|
ref,
|
|
@@ -202,7 +209,152 @@ var ModalDescription = forwardRef(({ className, ...props }, ref) => /* @__PURE__
|
|
|
202
209
|
}
|
|
203
210
|
));
|
|
204
211
|
ModalDescription.displayName = "ModalDescription";
|
|
212
|
+
|
|
213
|
+
// src/elements/ConfirmationDialog.tsx
|
|
214
|
+
import { forwardRef as forwardRef2, useId, useRef, useState } from "react";
|
|
215
|
+
import { Button } from "@edux-design/buttons";
|
|
216
|
+
import { cx as cx2 } from "@edux-design/utils";
|
|
217
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
218
|
+
var ConfirmationDialog = forwardRef2(function ConfirmationDialog2({
|
|
219
|
+
open,
|
|
220
|
+
defaultOpen = false,
|
|
221
|
+
onOpenChange,
|
|
222
|
+
modal = true,
|
|
223
|
+
title,
|
|
224
|
+
description,
|
|
225
|
+
children,
|
|
226
|
+
trigger,
|
|
227
|
+
onConfirm,
|
|
228
|
+
onCancel,
|
|
229
|
+
confirmLabel = "Confirm",
|
|
230
|
+
cancelLabel = "Cancel",
|
|
231
|
+
pendingLabel = "Working\u2026",
|
|
232
|
+
pending = false,
|
|
233
|
+
error,
|
|
234
|
+
confirmDisabled = false,
|
|
235
|
+
cancelDisabled = false,
|
|
236
|
+
dismissible = true,
|
|
237
|
+
returnFocusRef,
|
|
238
|
+
isDanger = false,
|
|
239
|
+
allowDismissWhilePending = false,
|
|
240
|
+
size = "small",
|
|
241
|
+
contentProps = {},
|
|
242
|
+
backdropProps,
|
|
243
|
+
portalProps,
|
|
244
|
+
confirmButtonProps = {},
|
|
245
|
+
cancelButtonProps = {}
|
|
246
|
+
}, ref) {
|
|
247
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
|
|
248
|
+
const resolvedOpen = open ?? uncontrolledOpen;
|
|
249
|
+
const cancelRef = useRef(null);
|
|
250
|
+
const previousFocusRef = useRef(null);
|
|
251
|
+
const statusId = useId();
|
|
252
|
+
const locked = !dismissible || pending && !allowDismissWhilePending;
|
|
253
|
+
const hasBody = children != null || Boolean(error) || pending;
|
|
254
|
+
const {
|
|
255
|
+
onClick: onCancelClick,
|
|
256
|
+
ref: actionRef,
|
|
257
|
+
...cancelProps
|
|
258
|
+
} = cancelButtonProps;
|
|
259
|
+
const { onClick: onConfirmClick, ...confirmProps } = confirmButtonProps;
|
|
260
|
+
const requestOpen = (next) => {
|
|
261
|
+
if (!next && locked) return;
|
|
262
|
+
if (open === void 0) setUncontrolledOpen(next);
|
|
263
|
+
onOpenChange?.(next);
|
|
264
|
+
};
|
|
265
|
+
return /* @__PURE__ */ jsxs2(Modal, { open: resolvedOpen, onOpenChange: requestOpen, modal, children: [
|
|
266
|
+
trigger && /* @__PURE__ */ jsx2(ModalTrigger, { asChild: true, children: trigger }),
|
|
267
|
+
/* @__PURE__ */ jsxs2(
|
|
268
|
+
ModalContent,
|
|
269
|
+
{
|
|
270
|
+
...contentProps,
|
|
271
|
+
ref,
|
|
272
|
+
size,
|
|
273
|
+
backdropProps,
|
|
274
|
+
portalProps,
|
|
275
|
+
...!description && !contentProps["aria-describedby"] ? { "aria-describedby": void 0 } : {},
|
|
276
|
+
"aria-busy": pending || void 0,
|
|
277
|
+
className: cx2("motion-reduce:animate-none", contentProps.className),
|
|
278
|
+
onOpenAutoFocus: (event) => {
|
|
279
|
+
previousFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
280
|
+
contentProps.onOpenAutoFocus?.(event);
|
|
281
|
+
if (!event.defaultPrevented) {
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
if (!locked && !cancelDisabled) cancelRef.current?.focus();
|
|
284
|
+
else if (event.target instanceof HTMLElement) event.target.focus();
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
onCloseAutoFocus: (event) => {
|
|
288
|
+
contentProps.onCloseAutoFocus?.(event);
|
|
289
|
+
const target = returnFocusRef?.current ?? (!trigger ? previousFocusRef.current : null);
|
|
290
|
+
if (!event.defaultPrevented && target?.isConnected) {
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
target.focus();
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
onEscapeKeyDown: (event) => {
|
|
296
|
+
contentProps.onEscapeKeyDown?.(event);
|
|
297
|
+
if (locked) event.preventDefault();
|
|
298
|
+
},
|
|
299
|
+
onInteractOutside: (event) => {
|
|
300
|
+
contentProps.onInteractOutside?.(event);
|
|
301
|
+
if (locked) event.preventDefault();
|
|
302
|
+
},
|
|
303
|
+
children: [
|
|
304
|
+
/* @__PURE__ */ jsxs2(ModalHeader, { className: !hasBody ? "border-b-0" : void 0, children: [
|
|
305
|
+
/* @__PURE__ */ jsx2(ModalTitle, { children: title }),
|
|
306
|
+
description && /* @__PURE__ */ jsx2(ModalDescription, { children: description })
|
|
307
|
+
] }),
|
|
308
|
+
hasBody && /* @__PURE__ */ jsxs2(ModalBody, { className: "min-h-0 text-sm text-fg-base", children: [
|
|
309
|
+
children,
|
|
310
|
+
error && /* @__PURE__ */ jsx2("div", { role: "alert", className: "mt-8 text-fg-danger-emphasis", children: error }),
|
|
311
|
+
pending && /* @__PURE__ */ jsx2("p", { id: statusId, role: "status", className: "sr-only", children: pendingLabel })
|
|
312
|
+
] }),
|
|
313
|
+
/* @__PURE__ */ jsxs2(ModalFooter, { className: "flex-wrap", children: [
|
|
314
|
+
/* @__PURE__ */ jsx2(
|
|
315
|
+
Button,
|
|
316
|
+
{
|
|
317
|
+
...cancelProps,
|
|
318
|
+
ref: (node) => {
|
|
319
|
+
cancelRef.current = node;
|
|
320
|
+
if (typeof actionRef === "function") actionRef(node);
|
|
321
|
+
else if (actionRef) actionRef.current = node;
|
|
322
|
+
},
|
|
323
|
+
type: "button",
|
|
324
|
+
size: "small",
|
|
325
|
+
isSecondary: true,
|
|
326
|
+
disabled: locked || cancelDisabled,
|
|
327
|
+
onClick: (event) => {
|
|
328
|
+
onCancelClick?.(event);
|
|
329
|
+
if (!event.defaultPrevented) onCancel?.(event);
|
|
330
|
+
if (!event.defaultPrevented) requestOpen(false);
|
|
331
|
+
},
|
|
332
|
+
children: cancelLabel
|
|
333
|
+
}
|
|
334
|
+
),
|
|
335
|
+
/* @__PURE__ */ jsx2(
|
|
336
|
+
Button,
|
|
337
|
+
{
|
|
338
|
+
...confirmProps,
|
|
339
|
+
type: "button",
|
|
340
|
+
size: "small",
|
|
341
|
+
isDanger,
|
|
342
|
+
disabled: pending || confirmDisabled,
|
|
343
|
+
onClick: (event) => {
|
|
344
|
+
onConfirmClick?.(event);
|
|
345
|
+
if (!event.defaultPrevented) onConfirm(event);
|
|
346
|
+
},
|
|
347
|
+
children: pending ? pendingLabel : confirmLabel
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
] })
|
|
351
|
+
]
|
|
352
|
+
}
|
|
353
|
+
)
|
|
354
|
+
] });
|
|
355
|
+
});
|
|
205
356
|
export {
|
|
357
|
+
ConfirmationDialog,
|
|
206
358
|
Modal,
|
|
207
359
|
ModalBody,
|
|
208
360
|
ModalClose,
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# ConfirmationDialog
|
|
2
|
+
|
|
3
|
+
Import `ConfirmationDialog` and `ConfirmationDialogProps` from `@edux-design/modals`.
|
|
4
|
+
It composes the existing Modal root, surface, title/description and sections, retaining
|
|
5
|
+
its focus trap, overlay, size presets and high overlay stacking order.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
const [pending, setPending] = useState(false);
|
|
10
|
+
const [error, setError] = useState("");
|
|
11
|
+
|
|
12
|
+
<ConfirmationDialog
|
|
13
|
+
open={open}
|
|
14
|
+
onOpenChange={setOpen}
|
|
15
|
+
title="Remove this upload?"
|
|
16
|
+
description="This permanently removes the unpublished font file."
|
|
17
|
+
trigger={<Button>Review removal</Button>}
|
|
18
|
+
confirmLabel="Remove upload"
|
|
19
|
+
pendingLabel="Removing…"
|
|
20
|
+
isDanger
|
|
21
|
+
pending={pending}
|
|
22
|
+
error={error}
|
|
23
|
+
onConfirm={async () => {
|
|
24
|
+
setPending(true);
|
|
25
|
+
setError("");
|
|
26
|
+
try {
|
|
27
|
+
if (await removeUpload()) setOpen(false);
|
|
28
|
+
else setError("The upload could not be removed. Please try again.");
|
|
29
|
+
} catch {
|
|
30
|
+
setError("The upload could not be removed. Please try again.");
|
|
31
|
+
} finally {
|
|
32
|
+
setPending(false);
|
|
33
|
+
}
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<p>{filename}</p>
|
|
37
|
+
</ConfirmationDialog>;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`trigger` is optional for an editor/API-opened request. A supplied trigger is a
|
|
41
|
+
single ref-forwarding element composed with `ModalTrigger asChild`, preserving its
|
|
42
|
+
existing handlers and ref. Triggerless dialogs remember the previously focused
|
|
43
|
+
connected element; supplied triggers use Radix return focus. Use an explicit
|
|
44
|
+
`returnFocusRef` when deleting that origin, and the preventable close-autofocus hook
|
|
45
|
+
when the host intentionally focuses the editor.
|
|
46
|
+
|
|
47
|
+
`size` defaults to `small`; `medium`, `large` and `full` retain existing Modal bounds.
|
|
48
|
+
`backdropProps` forwards the overlay's native attributes/classes.
|
|
49
|
+
`portalProps` forwards Radix Dialog Portal options, including a theme-scoped container.
|
|
50
|
+
The additive `ModalContent portalProps` capability is also public for ordinary Modal
|
|
51
|
+
consumers; default Modal portal behaviour is unchanged.
|
|
52
|
+
|
|
53
|
+
Both confirmations leave mutation policy in the host. They never await, interpret,
|
|
54
|
+
catch, or auto-close after `onConfirm`: synchronous commands, promises, false results,
|
|
55
|
+
blocking document errors and retriable service errors can keep their own lifecycle.
|
|
56
|
+
An async handler must catch its own rejection and update its own `pending`/`error`.
|
|
57
|
+
Nothing schedules asynchronous component state after the callback.
|
|
58
|
+
|
|
59
|
+
`open` is authoritative when supplied. Omit it and use `defaultOpen` for an
|
|
60
|
+
uncontrolled presentation; `onOpenChange(next)` reports user requests in either mode.
|
|
61
|
+
Confirm does not close even in uncontrolled mode: use controlled state when success
|
|
62
|
+
must close the presentation. `onCancel(event)` only reports the Cancel button;
|
|
63
|
+
Escape and outside dismissal report through `onOpenChange`, without pretending to
|
|
64
|
+
be a Cancel activation.
|
|
65
|
+
|
|
66
|
+
Actions use Edux `Button`, default to `type="button"`, and wrap on narrow surfaces.
|
|
67
|
+
The default confirm action is neutral. Set `isDanger` for destructive actions.
|
|
68
|
+
`confirmDisabled` and `cancelDisabled` independently disable their respective
|
|
69
|
+
buttons. An error renders a `role="alert"`; an error alone does not disable retry.
|
|
70
|
+
For a stale editor/document error, the host also sets `confirmDisabled`.
|
|
71
|
+
|
|
72
|
+
While `pending`, both actions are disabled, the surface has `aria-busy`, and
|
|
73
|
+
`pendingLabel` (default “Working…”) replaces the confirm text and is exposed as a
|
|
74
|
+
status. Escape, outside pointer/focus dismissal, Cancel, and trigger toggle requests
|
|
75
|
+
cannot close the presentation. `allowDismissWhilePending` explicitly unlocks user
|
|
76
|
+
cancellation while confirm remains disabled; use it only when the host permits that
|
|
77
|
+
policy. `dismissible={false}` locks user dismissal regardless of pending. A controlled
|
|
78
|
+
host can always set `open={false}`, for example after confirmed success. There is no
|
|
79
|
+
implicit header X action to bypass these rules.
|
|
80
|
+
|
|
81
|
+
Safe initial focus goes to Cancel. If Cancel is unavailable, focus stays on the
|
|
82
|
+
surface rather than choosing the destructive action. Native `contentProps`
|
|
83
|
+
`onOpenAutoFocus`/`onCloseAutoFocus` can call `event.preventDefault()` to replace the
|
|
84
|
+
focus policy; these hooks run first. `returnFocusRef` supplies an explicit return
|
|
85
|
+
target when the originating row might disappear. For an editor command that places
|
|
86
|
+
its own selection/focus, prevent close autofocus so automatic restoration cannot
|
|
87
|
+
undo that command. A missing or disconnected return target is not focused.
|
|
88
|
+
|
|
89
|
+
`contentProps` forwards native attributes, style/className, keyboard/pointer events,
|
|
90
|
+
and Radix focus/dismissal hooks. The surface ref is an `HTMLDivElement`.
|
|
91
|
+
`confirmButtonProps`/`cancelButtonProps` forward native button attributes, refs and
|
|
92
|
+
mouse/keyboard handlers. Their click handlers run before `onConfirm`/`onCancel`;
|
|
93
|
+
`preventDefault()` vetoes the corresponding action, and `stopPropagation()` remains
|
|
94
|
+
available for editable/sortable row adapters. Forced action type, disabled state and
|
|
95
|
+
pending guards take precedence. Caller dismissal hooks run first, then the internal
|
|
96
|
+
lock always prevents dismissal while locked; a handler cannot accidentally undo it.
|
|
97
|
+
|
|
98
|
+
Each surface is a labelled `dialog`, with a meaningful description when provided.
|
|
99
|
+
Confirmation is a decision, so it uses ordinary buttons rather than menu navigation.
|
|
100
|
+
It does not automatically claim urgent `alertdialog` semantics. A caller may set
|
|
101
|
+
`contentProps.role="alertdialog"` when that semantic is appropriate. Error messages
|
|
102
|
+
have alert semantics separately. IDs remain unique across instances.
|
|
103
|
+
|
|
104
|
+
Use current semantic surface/foreground/border/action/danger tokens. There are no
|
|
105
|
+
new tokens or package-owned palettes, and theme switching does not change geometry.
|
|
106
|
+
Pass `portalProps.container` when a brand/mode scope is below the document root;
|
|
107
|
+
put `brand--blue`/`brand--teal` and `theme--light`/`theme--dark` on that same scope.
|
|
108
|
+
Default portals inherit the application root theme. Native reduced-motion rules
|
|
109
|
+
apply; the compositions also disable their surface animation for reduced motion.
|
|
110
|
+
The primitives keep collision/viewport bounds, content scrolls, and footer actions
|
|
111
|
+
wrap. Domain checks, permissions, editor resolver registration, persistence, source
|
|
112
|
+
warnings, server mutation and cancellation remain local.
|
|
113
|
+
|
|
114
|
+
Storybook: `Modals/ConfirmationDialog` includes destructive and neutral actions,
|
|
115
|
+
bodyless content, warnings, async pending/error/retry, blocking errors, narrow content,
|
|
116
|
+
and light/dark blue/teal scopes. Component tests exercise real Radix focus and dismissal,
|
|
117
|
+
not mocked primitives. Public type/runtime fixtures live under `test/`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edux-design/modals",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css"
|
|
@@ -8,18 +8,11 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
11
12
|
"import": "./dist/index.mjs",
|
|
12
|
-
"require": "./dist/index.js"
|
|
13
|
-
"types": "./dist/index.d.ts"
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
-
"scripts": {
|
|
17
|
-
"lint": "eslint . --max-warnings 0",
|
|
18
|
-
"build": "tsup src/index.js --format esm,cjs --external react,react-dom,@edux-design/utils,@edux-design/buttons,@edux-design/icons,@radix-ui/react-dialog",
|
|
19
|
-
"dev": "tsup src/index.js --watch --format esm,cjs --external react,react-dom,@edux-design/utils,@edux-design/buttons,@edux-design/icons,@radix-ui/react-dialog",
|
|
20
|
-
"generate:component": "turbo gen react-component",
|
|
21
|
-
"check-types": "tsc --noEmit"
|
|
22
|
-
},
|
|
23
16
|
"dependencies": {
|
|
24
17
|
"@edux-design/buttons": "*",
|
|
25
18
|
"@edux-design/icons": "*"
|
|
@@ -30,12 +23,12 @@
|
|
|
30
23
|
"@edux-design/utils": "*",
|
|
31
24
|
"@radix-ui/react-dialog": "1.1.5"
|
|
32
25
|
},
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"docs",
|
|
29
|
+
"README.md",
|
|
30
|
+
"CHANGELOG.md",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"LICENSE.md"
|
|
33
|
+
]
|
|
41
34
|
}
|