@duskmoon-dev/components 0.3.3 → 0.4.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.
Files changed (82) hide show
  1. package/README.md +61 -2
  2. package/dist/classes/chat.d.ts +9 -1
  3. package/dist/classes/console-page.d.ts +16 -0
  4. package/dist/classes/dropdown.d.ts +5 -5
  5. package/dist/classes/fab.d.ts +11 -0
  6. package/dist/classes/index.d.ts +5 -0
  7. package/dist/classes/index.js +180 -34
  8. package/dist/classes/megamenu.d.ts +10 -0
  9. package/dist/classes/otp-input.d.ts +12 -0
  10. package/dist/classes/popover.d.ts +1 -3
  11. package/dist/classes/swap.d.ts +12 -0
  12. package/dist/classes/tree-select.d.ts +6 -6
  13. package/dist/components/app/index.js +128 -68
  14. package/dist/components/avatar/Avatar.types.d.ts +1 -1
  15. package/dist/components/avatar/index.js +2 -1
  16. package/dist/components/badge/index.js +1 -1
  17. package/dist/components/button/Button.d.ts +1 -1
  18. package/dist/components/button/Button.types.d.ts +8 -0
  19. package/dist/components/button/index.js +253 -24
  20. package/dist/components/calendar/index.js +44 -19
  21. package/dist/components/chat/Chat.d.ts +9 -1
  22. package/dist/components/chat/Chat.types.d.ts +26 -0
  23. package/dist/components/chat/index.js +92 -3
  24. package/dist/components/color-picker/ColorPicker.types.d.ts +2 -1
  25. package/dist/components/color-picker/index.js +206 -9
  26. package/dist/components/console-page/ConsolePage.d.ts +2 -0
  27. package/dist/components/console-page/ConsolePage.types.d.ts +29 -0
  28. package/dist/components/console-page/index.d.ts +2 -0
  29. package/dist/components/console-page/index.js +205 -0
  30. package/dist/components/date-picker/DatePicker.types.d.ts +1 -0
  31. package/dist/components/date-picker/index.js +537 -35
  32. package/dist/components/date-picker/usePickerDropdown.d.ts +17 -0
  33. package/dist/components/dm-date-picker/index.js +545 -43
  34. package/dist/components/dm-drawer/index.js +253 -24
  35. package/dist/components/dm-layout/index.js +391 -24
  36. package/dist/components/dm-menu/index.js +380 -13
  37. package/dist/components/dm-page-header/index.js +404 -114
  38. package/dist/components/dm-pro-table/index.js +1183 -368
  39. package/dist/components/dm-query/DmQuery.types.d.ts +1 -1
  40. package/dist/components/dm-query/index.js +931 -188
  41. package/dist/components/dm-search/DmSearch.types.d.ts +4 -0
  42. package/dist/components/dm-search/index.js +917 -163
  43. package/dist/components/dm-table/index.js +1001 -247
  44. package/dist/components/dm-toolbar/index.js +400 -110
  45. package/dist/components/dm-truncate/index.js +115 -35
  46. package/dist/components/dropdown/Dropdown.types.d.ts +1 -1
  47. package/dist/components/dropdown/index.js +400 -110
  48. package/dist/components/fab/Fab.d.ts +4 -0
  49. package/dist/components/fab/Fab.types.d.ts +25 -0
  50. package/dist/components/fab/index.d.ts +2 -0
  51. package/dist/components/fab/index.js +480 -0
  52. package/dist/components/form/Form.types.d.ts +1 -0
  53. package/dist/components/form/index.js +14 -3
  54. package/dist/components/megamenu/Megamenu.d.ts +2 -0
  55. package/dist/components/megamenu/Megamenu.types.d.ts +32 -0
  56. package/dist/components/megamenu/index.d.ts +2 -0
  57. package/dist/components/megamenu/index.js +214 -0
  58. package/dist/components/modal/index.js +128 -68
  59. package/dist/components/otp-input/OtpInput.d.ts +2 -0
  60. package/dist/components/otp-input/OtpInput.types.d.ts +16 -0
  61. package/dist/components/otp-input/index.d.ts +2 -0
  62. package/dist/components/otp-input/index.js +112 -0
  63. package/dist/components/popconfirm/index.js +255 -26
  64. package/dist/components/popover/index.js +24 -4
  65. package/dist/components/swap/Swap.d.ts +3 -0
  66. package/dist/components/swap/Swap.types.d.ts +22 -0
  67. package/dist/components/swap/index.d.ts +2 -0
  68. package/dist/components/swap/index.js +109 -0
  69. package/dist/components/time-picker/TimePicker.types.d.ts +1 -0
  70. package/dist/components/time-picker/index.js +167 -29
  71. package/dist/components/tooltip/Tooltip.types.d.ts +4 -0
  72. package/dist/components/tooltip/index.js +107 -27
  73. package/dist/components/tour/index.js +253 -24
  74. package/dist/components/tree-select/index.js +8 -8
  75. package/dist/components/upload/Upload.types.d.ts +1 -0
  76. package/dist/components/upload/index.js +2 -0
  77. package/dist/generated/version.d.ts +1 -1
  78. package/dist/index.d.ts +5 -0
  79. package/dist/index.js +3330 -1632
  80. package/dist/infrastructure.js +1 -1
  81. package/dist/styles.css +1190 -906
  82. package/package.json +23 -3
@@ -3,23 +3,23 @@ export declare const treeSelectBaseClass = "tree-select";
3
3
  export declare const treeSelectOpenClass = "tree-select-open";
4
4
  export declare const treeSelectDisabledClass = "tree-select-disabled";
5
5
  export declare const treeSelectMultipleClass = "tree-select-multiple";
6
- export declare const treeSelectSelectionClass = "tree-select-selection";
6
+ export declare const treeSelectSelectionClass = "tree-select-selection tree-select-value";
7
7
  export declare const treeSelectPlaceholderClass = "tree-select-placeholder";
8
8
  export declare const treeSelectTagClass = "tree-select-tag";
9
9
  export declare const treeSelectClearClass = "tree-select-clear";
10
- export declare const treeSelectIconClass = "tree-select-icon";
10
+ export declare const treeSelectIconClass = "tree-select-icon tree-select-arrow";
11
11
  export declare const treeSelectDropdownClass = "tree-select-dropdown";
12
- export declare const treeSelectSearchClass = "tree-select-search";
13
- export declare const treeSelectListClass = "tree-select-list";
12
+ export declare const treeSelectSearchClass = "tree-select-search-input";
13
+ export declare const treeSelectListClass = "tree-select-list tree-select-options";
14
14
  export declare const treeSelectEmptyClass = "tree-select-empty";
15
15
  export declare const treeSelectNodeClass = "tree-select-node";
16
16
  export declare const treeSelectNodeSelectedClass = "tree-select-node-selected";
17
17
  export declare const treeSelectNodeCheckedClass = "tree-select-node-checked";
18
18
  export declare const treeSelectNodeExpandedClass = "tree-select-node-expanded";
19
19
  export declare const treeSelectNodeDisabledClass = "tree-select-node-disabled";
20
- export declare const treeSelectSwitcherClass = "tree-select-switcher";
20
+ export declare const treeSelectSwitcherClass = "tree-select-switcher tree-select-node-toggle";
21
21
  export declare const treeSelectCheckboxClass = "tree-select-checkbox";
22
- export declare const treeSelectTitleClass = "tree-select-title";
22
+ export declare const treeSelectTitleClass = "tree-select-title tree-select-node-label";
23
23
  export declare function getTreeSelectContainerClasses({ open, disabled, multiple, className, }: {
24
24
  open?: boolean;
25
25
  disabled?: boolean;
@@ -224,27 +224,18 @@ import {
224
224
  forwardRef,
225
225
  useCallback,
226
226
  useEffect,
227
+ useId,
227
228
  useRef,
228
229
  useState
229
230
  } from "react";
230
231
 
231
232
  // src/classes/modal.ts
232
- var modalBackdropClass = "modal-backdrop";
233
- var modalOpenClass = "modal-open";
234
- var modalBackdropCenterClass = "modal-backdrop-center";
235
233
  var modalClass = "modal";
236
234
  var modalHeaderClass = "modal-header";
237
235
  var modalTitleClass = "modal-title";
238
236
  var modalCloseClass = "modal-close";
239
237
  var modalBodyClass = "modal-body";
240
238
  var modalFooterClass = "modal-footer";
241
- function getModalBackdropClasses({
242
- open,
243
- centered,
244
- className
245
- }) {
246
- return cn(modalBackdropClass, open && modalOpenClass, centered && modalBackdropCenterClass, className);
247
- }
248
239
  function getModalClasses({ className }) {
249
240
  return cn(modalClass, className);
250
241
  }
@@ -275,6 +266,13 @@ function getModalStyle({
275
266
  ...width !== undefined ? { width } : undefined
276
267
  };
277
268
  }
269
+ function showNativeModal(dialog) {
270
+ if (!dialog.isConnected || dialog.matches(":modal"))
271
+ return;
272
+ if (dialog.open)
273
+ dialog.close();
274
+ dialog.showModal();
275
+ }
278
276
  var ModalBase = forwardRef(({
279
277
  open,
280
278
  defaultOpen = false,
@@ -297,15 +295,41 @@ var ModalBase = forwardRef(({
297
295
  maskClassName,
298
296
  closeIcon = defaultCloseIcon,
299
297
  style,
300
- role = "dialog",
298
+ role,
301
299
  ...props
302
300
  }, ref) => {
303
301
  const controlled = open !== undefined;
304
302
  const [innerOpen, setInnerOpen] = useState(defaultOpen);
305
303
  const mergedOpen = controlled ? Boolean(open) : innerOpen;
306
304
  const previousOpen = useRef(mergedOpen);
305
+ const desiredOpen = useRef(mergedOpen);
306
+ desiredOpen.current = mergedOpen;
307
+ const dialogRef = useRef(null);
308
+ const cancelRequestRef = useRef(null);
309
+ const programmaticClose = useRef(false);
310
+ const titleId = useId();
311
+ const surfaceWidth = width ?? style?.width;
312
+ const callerNamed = props["aria-label"] != null || props["aria-labelledby"] != null;
307
313
  const showClose = closable && closeIcon !== null && closeIcon !== false;
308
314
  const body = children ?? content;
315
+ useEffect(() => {
316
+ const dialog = dialogRef.current;
317
+ if (!dialog)
318
+ return;
319
+ if (mergedOpen) {
320
+ programmaticClose.current = false;
321
+ showNativeModal(dialog);
322
+ } else if (dialog.open) {
323
+ programmaticClose.current = true;
324
+ dialog.close();
325
+ }
326
+ return () => {
327
+ if (dialog.open) {
328
+ programmaticClose.current = true;
329
+ dialog.close();
330
+ }
331
+ };
332
+ }, [mergedOpen]);
309
333
  useEffect(() => {
310
334
  if (previousOpen.current === mergedOpen) {
311
335
  return;
@@ -322,73 +346,109 @@ var ModalBase = forwardRef(({
322
346
  const handleOk = useCallback((event) => {
323
347
  onOk?.(event);
324
348
  }, [onOk]);
325
- const handleMaskClick = useCallback((event) => {
326
- if (event.target !== event.currentTarget || !maskClosable) {
349
+ const requestCancel = useCallback(() => {
350
+ cancelRequestRef.current?.click();
351
+ }, []);
352
+ const handleNativeClose = useCallback(() => {
353
+ const dialog = dialogRef.current;
354
+ if (!dialog || dialog.open || programmaticClose.current) {
355
+ programmaticClose.current = false;
327
356
  return;
328
357
  }
329
- close(event);
330
- }, [close, maskClosable]);
358
+ if (desiredOpen.current) {
359
+ requestCancel();
360
+ if (controlled) {
361
+ queueMicrotask(() => {
362
+ if (desiredOpen.current) {
363
+ showNativeModal(dialog);
364
+ }
365
+ });
366
+ }
367
+ }
368
+ }, [controlled, requestCancel]);
331
369
  if (!mergedOpen && destroyOnClose) {
332
370
  return null;
333
371
  }
334
- return /* @__PURE__ */ jsx2("div", {
335
- "aria-hidden": mergedOpen ? undefined : true,
336
- role: "presentation",
337
- className: getModalBackdropClasses({
338
- open: mergedOpen,
339
- centered,
340
- className: maskClassName
341
- }),
342
- onMouseDown: handleMaskClick,
343
- children: /* @__PURE__ */ jsxs2("div", {
344
- ...props,
345
- ref,
346
- role,
347
- "aria-modal": mergedOpen ? true : undefined,
348
- className: getModalClasses({ className }),
349
- style: getModalStyle({ width, style }),
350
- children: [
351
- title !== undefined || showClose ? /* @__PURE__ */ jsxs2("div", {
352
- className: modalHeaderClass,
353
- children: [
354
- title !== undefined ? /* @__PURE__ */ jsx2("h2", {
355
- className: modalTitleClass,
356
- children: title
357
- }) : null,
358
- showClose ? /* @__PURE__ */ jsx2("button", {
359
- type: "button",
360
- className: modalCloseClass,
361
- "aria-label": "Close",
362
- onClick: close,
363
- children: closeIcon
364
- }) : null
365
- ]
366
- }) : null,
367
- /* @__PURE__ */ jsx2("div", {
368
- className: modalBodyClass,
369
- children: body
370
- }),
371
- footer === null ? null : /* @__PURE__ */ jsx2("div", {
372
- className: modalFooterClass,
373
- children: footer !== undefined ? footer : /* @__PURE__ */ jsxs2(Fragment, {
372
+ return /* @__PURE__ */ jsxs2("dialog", {
373
+ ref: dialogRef,
374
+ role: role === "alertdialog" ? role : undefined,
375
+ "aria-label": props["aria-label"] ?? (!callerNamed && title == null ? "Modal" : undefined),
376
+ "aria-labelledby": props["aria-labelledby"] ?? (!callerNamed && title != null ? titleId : undefined),
377
+ "aria-describedby": props["aria-describedby"],
378
+ className: getModalClasses({ className: cn(maskClassName, centered ? "modal-middle" : "modal-top") }),
379
+ style: surfaceWidth !== undefined ? { width: surfaceWidth, maxWidth: surfaceWidth } : undefined,
380
+ onCancel: (event) => {
381
+ event.preventDefault();
382
+ requestCancel();
383
+ },
384
+ onClose: handleNativeClose,
385
+ onMouseDown: (event) => {
386
+ if (!maskClosable || event.target !== event.currentTarget)
387
+ return;
388
+ const { left, right, top, bottom } = event.currentTarget.getBoundingClientRect();
389
+ if (event.clientX < left || event.clientX > right || event.clientY < top || event.clientY > bottom) {
390
+ requestCancel();
391
+ }
392
+ },
393
+ children: [
394
+ /* @__PURE__ */ jsx2("button", {
395
+ ref: cancelRequestRef,
396
+ type: "button",
397
+ hidden: true,
398
+ tabIndex: -1,
399
+ "aria-hidden": "true",
400
+ onClick: close
401
+ }),
402
+ /* @__PURE__ */ jsxs2("div", {
403
+ ...props,
404
+ ref,
405
+ role,
406
+ className: cn("modal-box", "react-modal-box", className),
407
+ style: getModalStyle({ width, style }),
408
+ children: [
409
+ title !== undefined || showClose ? /* @__PURE__ */ jsxs2("div", {
410
+ className: modalHeaderClass,
374
411
  children: [
375
- /* @__PURE__ */ jsx2("button", {
412
+ title !== undefined ? /* @__PURE__ */ jsx2("h2", {
413
+ id: titleId,
414
+ className: modalTitleClass,
415
+ children: title
416
+ }) : null,
417
+ showClose ? /* @__PURE__ */ jsx2("button", {
376
418
  type: "button",
419
+ className: modalCloseClass,
420
+ "aria-label": "Close",
377
421
  onClick: close,
378
- children: cancelText
379
- }),
380
- /* @__PURE__ */ jsx2("button", {
381
- type: "button",
382
- "aria-busy": confirmLoading,
383
- disabled: confirmLoading,
384
- onClick: handleOk,
385
- children: okText
386
- })
422
+ children: closeIcon
423
+ }) : null
387
424
  ]
425
+ }) : null,
426
+ /* @__PURE__ */ jsx2("div", {
427
+ className: modalBodyClass,
428
+ children: body
429
+ }),
430
+ footer === null ? null : /* @__PURE__ */ jsx2("div", {
431
+ className: modalFooterClass,
432
+ children: footer !== undefined ? footer : /* @__PURE__ */ jsxs2(Fragment, {
433
+ children: [
434
+ /* @__PURE__ */ jsx2("button", {
435
+ type: "button",
436
+ onClick: close,
437
+ children: cancelText
438
+ }),
439
+ /* @__PURE__ */ jsx2("button", {
440
+ type: "button",
441
+ "aria-busy": confirmLoading,
442
+ disabled: confirmLoading,
443
+ onClick: handleOk,
444
+ children: okText
445
+ })
446
+ ]
447
+ })
388
448
  })
389
- })
390
- ]
391
- })
449
+ ]
450
+ })
451
+ ]
392
452
  });
393
453
  });
394
454
  ModalBase.displayName = "Modal";
@@ -1,5 +1,5 @@
1
1
  import type { ComponentProps } from "react";
2
- export type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl";
2
+ export type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
3
3
  export type AvatarShape = "circle" | "square";
4
4
  export interface AvatarProps extends ComponentProps<"span"> {
5
5
  size?: AvatarSize;
@@ -35,7 +35,8 @@ var avatarSizeClasses = {
35
35
  sm: "avatar-sm",
36
36
  md: "avatar-md",
37
37
  lg: "avatar-lg",
38
- xl: "avatar-xl"
38
+ xl: "avatar-xl",
39
+ "2xl": "avatar-2xl"
39
40
  };
40
41
  var avatarShapeClasses = {
41
42
  circle: "avatar-circle",
@@ -41,7 +41,7 @@ var badgeColorClasses = {
41
41
  };
42
42
  var badgeAppearanceClasses = {
43
43
  filled: "",
44
- outline: "badge-outline",
44
+ outline: "badge-outlined badge-outline",
45
45
  tonal: "badge-tonal",
46
46
  ghost: "badge-ghost"
47
47
  };
@@ -1,3 +1,3 @@
1
1
  import React from "react";
2
2
  import type { ButtonProps } from "./Button.types";
3
- export declare function Button({ color, appearance, shape, size, block, isLoading, leftIcon, rightIcon, className, children, disabled, ref, ...props }: ButtonProps): React.JSX.Element;
3
+ export declare function Button({ color, appearance, shape, size, block, isLoading, leftIcon, rightIcon, className, children, disabled, ref, confirm, onClick, ...props }: ButtonProps): React.JSX.Element;
@@ -12,4 +12,12 @@ export interface ButtonProps extends ComponentProps<"button"> {
12
12
  isLoading?: boolean;
13
13
  leftIcon?: React.ReactNode;
14
14
  rightIcon?: React.ReactNode;
15
+ confirm?: boolean | {
16
+ title?: React.ReactNode;
17
+ component?: React.ReactElement;
18
+ message?: React.ReactNode;
19
+ description?: React.ReactNode;
20
+ confirmText?: React.ReactNode;
21
+ cancelText?: React.ReactNode;
22
+ };
15
23
  }
@@ -1,5 +1,9 @@
1
1
  "use client";
2
2
 
3
+ // src/components/button/Button.tsx
4
+ import React from "react";
5
+ import { createPortal } from "react-dom";
6
+
3
7
  // ../../node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
4
8
  function r(e) {
5
9
  var t, f, n = "";
@@ -48,7 +52,7 @@ var buttonAppearanceClasses = {
48
52
  };
49
53
  var buttonShapeClasses = {
50
54
  rect: "",
51
- circle: "btn-circle",
55
+ circle: "btn-circle btn-icon",
52
56
  square: "btn-square"
53
57
  };
54
58
  var buttonSizeClasses = {
@@ -68,11 +72,14 @@ function getButtonClasses({
68
72
  isLoading,
69
73
  className
70
74
  }) {
71
- return cn(buttonBaseClass, buttonColorClasses[color], buttonAppearanceClasses[appearance], buttonShapeClasses[shape], buttonSizeClasses[size], block && buttonBlockClass, isLoading && buttonIsLoadingClass, className);
75
+ return cn(buttonBaseClass, buttonColorClasses[color], buttonAppearanceClasses[appearance], buttonShapeClasses[shape], buttonSizeClasses[size], shape === "circle" && size === "xs" && "btn-icon-sm", shape === "circle" && size === "sm" && "btn-icon-sm", shape === "circle" && size === "lg" && "btn-icon-lg", block && buttonBlockClass, isLoading && buttonIsLoadingClass, className);
72
76
  }
73
77
 
74
78
  // src/components/button/Button.tsx
75
- import { jsx, jsxs } from "react/jsx-runtime";
79
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
80
+ function isPromiseLike(value) {
81
+ return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
82
+ }
76
83
  function Button({
77
84
  color = "primary",
78
85
  appearance = "filled",
@@ -86,8 +93,155 @@ function Button({
86
93
  children,
87
94
  disabled,
88
95
  ref,
96
+ confirm,
97
+ onClick,
89
98
  ...props
90
99
  }) {
100
+ const buttonRef = React.useRef(null);
101
+ const popoverRef = React.useRef(null);
102
+ const cancelRef = React.useRef(null);
103
+ const approvalRef = React.useRef("idle");
104
+ const pendingRef = React.useRef(false);
105
+ const actionRef = React.useRef(0);
106
+ const operationRef = React.useRef(0);
107
+ const mountedRef = React.useRef(false);
108
+ const [portalTarget, setPortalTarget] = React.useState(null);
109
+ const [confirmLoading, setConfirmLoading] = React.useState(false);
110
+ const [confirmError, setConfirmError] = React.useState(false);
111
+ const generatedId = React.useId().replace(/:/g, "");
112
+ const popoverId = `button-confirm-${generatedId}`;
113
+ const titleId = `${popoverId}-title`;
114
+ const descriptionId = `${popoverId}-description`;
115
+ const anchor = `--${popoverId}-anchor`;
116
+ const hasConfirm = Boolean(confirm);
117
+ const confirmation = confirm && typeof confirm === "object" ? confirm : undefined;
118
+ const title = confirmation ? confirmation.title : "Confirm action";
119
+ const hasTitle = title !== undefined && title !== null && title !== false && title !== "";
120
+ const content = confirmation?.component ?? confirmation?.message ?? confirmation?.description;
121
+ const hasContent = content !== undefined && content !== null && content !== false && content !== "";
122
+ const mergedRef = React.useCallback((node) => {
123
+ buttonRef.current = node;
124
+ if (!node) {
125
+ if (typeof ref === "function")
126
+ ref(null);
127
+ else if (ref)
128
+ ref.current = null;
129
+ return;
130
+ }
131
+ const cleanup = typeof ref === "function" ? ref(node) : undefined;
132
+ if (ref && typeof ref !== "function")
133
+ ref.current = node;
134
+ return () => {
135
+ buttonRef.current = null;
136
+ if (typeof cleanup === "function")
137
+ cleanup();
138
+ else if (typeof ref === "function")
139
+ ref(null);
140
+ else if (ref)
141
+ ref.current = null;
142
+ };
143
+ }, [ref]);
144
+ React.useEffect(() => {
145
+ mountedRef.current = true;
146
+ return () => {
147
+ mountedRef.current = false;
148
+ operationRef.current += 1;
149
+ };
150
+ }, []);
151
+ React.useEffect(() => {
152
+ operationRef.current += 1;
153
+ approvalRef.current = "idle";
154
+ setConfirmError(false);
155
+ setPortalTarget(hasConfirm ? document.body : null);
156
+ }, [hasConfirm]);
157
+ function closePopover() {
158
+ if (popoverRef.current?.matches(":popover-open")) {
159
+ popoverRef.current.hidePopover();
160
+ }
161
+ buttonRef.current?.focus();
162
+ }
163
+ function handleClick(event) {
164
+ if (!confirm) {
165
+ onClick?.(event);
166
+ return;
167
+ }
168
+ if (approvalRef.current === "native") {
169
+ approvalRef.current = "idle";
170
+ return;
171
+ }
172
+ if (approvalRef.current === "action") {
173
+ approvalRef.current = "idle";
174
+ let result;
175
+ try {
176
+ result = onClick?.(event);
177
+ } catch {
178
+ event.preventDefault();
179
+ pendingRef.current = false;
180
+ setConfirmError(true);
181
+ return;
182
+ }
183
+ if (!isPromiseLike(result)) {
184
+ pendingRef.current = false;
185
+ closePopover();
186
+ return;
187
+ }
188
+ const userPreventedDefault = event.defaultPrevented;
189
+ event.preventDefault();
190
+ setConfirmLoading(true);
191
+ const action = actionRef.current;
192
+ const operation = operationRef.current;
193
+ Promise.resolve(result).then(() => {
194
+ if (!mountedRef.current || action !== actionRef.current)
195
+ return;
196
+ pendingRef.current = false;
197
+ setConfirmLoading(false);
198
+ if (operation !== operationRef.current)
199
+ return;
200
+ closePopover();
201
+ if (!userPreventedDefault && !buttonRef.current?.disabled) {
202
+ approvalRef.current = "native";
203
+ buttonRef.current?.click();
204
+ approvalRef.current = "idle";
205
+ }
206
+ }, () => {
207
+ if (!mountedRef.current || action !== actionRef.current)
208
+ return;
209
+ pendingRef.current = false;
210
+ setConfirmLoading(false);
211
+ if (operation !== operationRef.current)
212
+ return;
213
+ setConfirmError(true);
214
+ cancelRef.current?.focus();
215
+ });
216
+ return;
217
+ }
218
+ event.preventDefault();
219
+ if (disabled || isLoading || pendingRef.current)
220
+ return;
221
+ if (popoverRef.current?.matches(":popover-open")) {
222
+ closePopover();
223
+ return;
224
+ }
225
+ setConfirmError(false);
226
+ popoverRef.current?.showPopover();
227
+ cancelRef.current?.focus();
228
+ }
229
+ function handleConfirm(event) {
230
+ event.preventDefault();
231
+ event.stopPropagation();
232
+ const trigger = buttonRef.current;
233
+ if (disabled || isLoading || pendingRef.current || !trigger || trigger.disabled || !event.currentTarget.matches(":popover-open"))
234
+ return;
235
+ setConfirmError(false);
236
+ pendingRef.current = true;
237
+ actionRef.current += 1;
238
+ approvalRef.current = "action";
239
+ trigger.click();
240
+ if (approvalRef.current === "action") {
241
+ approvalRef.current = "idle";
242
+ pendingRef.current = false;
243
+ }
244
+ }
91
245
  const classes = getButtonClasses({
92
246
  color,
93
247
  appearance,
@@ -97,29 +251,104 @@ function Button({
97
251
  isLoading,
98
252
  className
99
253
  });
100
- return /* @__PURE__ */ jsxs("button", {
101
- ref,
102
- className: classes,
103
- disabled: disabled || isLoading,
104
- "aria-busy": isLoading,
105
- "aria-disabled": disabled || isLoading,
106
- ...props,
254
+ const confirmClasses = getButtonClasses({
255
+ color,
256
+ size: "sm",
257
+ isLoading: confirmLoading
258
+ });
259
+ return /* @__PURE__ */ jsxs(Fragment, {
107
260
  children: [
108
- isLoading && /* @__PURE__ */ jsx("span", {
109
- className: "btn-spinner"
110
- }),
111
- !isLoading && leftIcon && /* @__PURE__ */ jsx("span", {
112
- className: "btn-icon-left",
113
- children: leftIcon
114
- }),
115
- /* @__PURE__ */ jsx("span", {
116
- className: "btn-content",
117
- children
261
+ /* @__PURE__ */ jsxs("button", {
262
+ ref: confirm ? mergedRef : ref,
263
+ className: classes,
264
+ disabled: disabled || isLoading,
265
+ "aria-busy": isLoading,
266
+ "aria-disabled": disabled || isLoading,
267
+ ...props,
268
+ style: confirm ? { ...props.style, anchorName: anchor } : props.style,
269
+ "aria-haspopup": confirm ? "dialog" : props["aria-haspopup"],
270
+ "aria-controls": confirm ? popoverId : props["aria-controls"],
271
+ onClick: confirm ? handleClick : onClick,
272
+ children: [
273
+ isLoading && /* @__PURE__ */ jsx("span", {
274
+ className: "btn-spinner"
275
+ }),
276
+ !isLoading && leftIcon && /* @__PURE__ */ jsx("span", {
277
+ className: "btn-icon-left",
278
+ children: leftIcon
279
+ }),
280
+ /* @__PURE__ */ jsx("span", {
281
+ className: "btn-content",
282
+ children
283
+ }),
284
+ !isLoading && rightIcon && /* @__PURE__ */ jsx("span", {
285
+ className: "btn-icon-right",
286
+ children: rightIcon
287
+ })
288
+ ]
118
289
  }),
119
- !isLoading && rightIcon && /* @__PURE__ */ jsx("span", {
120
- className: "btn-icon-right",
121
- children: rightIcon
122
- })
290
+ confirm && portalTarget && createPortal(/* @__PURE__ */ jsxs("form", {
291
+ id: popoverId,
292
+ ref: popoverRef,
293
+ popover: "auto",
294
+ role: "dialog",
295
+ "aria-labelledby": hasTitle ? titleId : undefined,
296
+ "aria-label": hasTitle ? undefined : "Confirm action",
297
+ "aria-describedby": hasContent ? descriptionId : undefined,
298
+ className: "popover popover-confirm popover-bottom",
299
+ style: { positionAnchor: anchor },
300
+ onSubmit: handleConfirm,
301
+ onClick: (event) => event.stopPropagation(),
302
+ onToggle: (event) => {
303
+ if (!event.currentTarget.matches(":popover-open")) {
304
+ operationRef.current += 1;
305
+ if (event.currentTarget.contains(document.activeElement) || document.activeElement === document.body) {
306
+ buttonRef.current?.focus();
307
+ }
308
+ }
309
+ },
310
+ children: [
311
+ hasTitle && /* @__PURE__ */ jsx("div", {
312
+ className: "popover-header",
313
+ children: /* @__PURE__ */ jsx("h3", {
314
+ id: titleId,
315
+ className: "popover-title",
316
+ children: title
317
+ })
318
+ }),
319
+ hasContent && /* @__PURE__ */ jsx("div", {
320
+ id: descriptionId,
321
+ className: "popover-body",
322
+ children: content
323
+ }),
324
+ confirmError && /* @__PURE__ */ jsx("div", {
325
+ role: "alert",
326
+ className: "popover-body",
327
+ children: "The action failed. Please try again."
328
+ }),
329
+ /* @__PURE__ */ jsxs("div", {
330
+ className: "popover-footer",
331
+ children: [
332
+ /* @__PURE__ */ jsx("button", {
333
+ ref: cancelRef,
334
+ type: "button",
335
+ className: "btn btn-text btn-sm",
336
+ popoverTarget: popoverId,
337
+ popoverTargetAction: "hide",
338
+ disabled: confirmLoading,
339
+ children: confirmation?.cancelText ?? "Cancel"
340
+ }),
341
+ /* @__PURE__ */ jsx("button", {
342
+ type: "submit",
343
+ className: confirmClasses,
344
+ disabled: disabled || isLoading || confirmLoading,
345
+ "aria-busy": confirmLoading,
346
+ children: confirmation?.confirmText ?? "Confirm"
347
+ })
348
+ ]
349
+ })
350
+ ]
351
+ }), portalTarget)
123
352
  ]
124
353
  });
125
354
  }