@windrun-huaiin/third-ui 29.1.0 → 29.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fuma/base/custom-header.js +6 -3
- package/dist/fuma/base/custom-header.mjs +6 -3
- package/dist/main/alert-dialog/confirm-dialog.d.ts +6 -3
- package/dist/main/alert-dialog/confirm-dialog.js +7 -7
- package/dist/main/alert-dialog/confirm-dialog.mjs +8 -8
- package/dist/main/alert-dialog/dialog-loading-action.d.ts +13 -0
- package/dist/main/alert-dialog/dialog-loading-action.js +42 -0
- package/dist/main/alert-dialog/dialog-loading-action.mjs +40 -0
- package/dist/main/alert-dialog/high-priority-confirm-dialog.d.ts +6 -3
- package/dist/main/alert-dialog/high-priority-confirm-dialog.js +10 -4
- package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +11 -5
- package/dist/main/alert-dialog/index.d.ts +1 -0
- package/dist/main/alert-dialog/info-dialog.d.ts +5 -2
- package/dist/main/alert-dialog/info-dialog.js +6 -5
- package/dist/main/alert-dialog/info-dialog.mjs +7 -6
- package/dist/main/alert-dialog/undoable-confirm-dialog.d.ts +7 -4
- package/dist/main/alert-dialog/undoable-confirm-dialog.js +18 -17
- package/dist/main/alert-dialog/undoable-confirm-dialog.mjs +19 -18
- package/dist/main/buttons/gradient-button.d.ts +3 -1
- package/dist/main/buttons/gradient-button.js +29 -3
- package/dist/main/buttons/gradient-button.mjs +29 -3
- package/dist/main/buttons/index.d.ts +1 -0
- package/dist/main/buttons/index.js +3 -0
- package/dist/main/buttons/index.mjs +1 -0
- package/dist/main/buttons/use-press-feedback.d.ts +18 -0
- package/dist/main/buttons/use-press-feedback.js +42 -0
- package/dist/main/buttons/use-press-feedback.mjs +39 -0
- package/dist/main/buttons/x-button.d.ts +3 -0
- package/dist/main/buttons/x-button.js +36 -6
- package/dist/main/buttons/x-button.mjs +36 -6
- package/dist/main/calendar/calendar-date-range-input.d.ts +17 -0
- package/dist/main/calendar/calendar-date-range-input.js +81 -0
- package/dist/main/calendar/calendar-date-range-input.mjs +79 -0
- package/dist/main/calendar/calendar-status-view.d.ts +23 -0
- package/dist/main/calendar/calendar-status-view.js +155 -0
- package/dist/main/calendar/calendar-status-view.mjs +153 -0
- package/dist/main/calendar/index.d.ts +3 -0
- package/dist/main/calendar/index.js +12 -0
- package/dist/main/calendar/index.mjs +4 -0
- package/dist/main/calendar/random-date-range-dialog.d.ts +18 -0
- package/dist/main/calendar/random-date-range-dialog.js +451 -0
- package/dist/main/calendar/random-date-range-dialog.mjs +449 -0
- package/package.json +6 -1
- package/src/fuma/base/custom-header.tsx +6 -3
- package/src/main/alert-dialog/confirm-dialog.tsx +54 -47
- package/src/main/alert-dialog/dialog-loading-action.tsx +78 -0
- package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +63 -48
- package/src/main/alert-dialog/index.ts +1 -0
- package/src/main/alert-dialog/info-dialog.tsx +52 -44
- package/src/main/alert-dialog/undoable-confirm-dialog.tsx +90 -82
- package/src/main/buttons/gradient-button.tsx +36 -3
- package/src/main/buttons/index.ts +1 -0
- package/src/main/buttons/use-press-feedback.ts +58 -0
- package/src/main/buttons/x-button.tsx +53 -11
- package/src/main/calendar/calendar-date-range-input.tsx +173 -0
- package/src/main/calendar/calendar-status-view.tsx +365 -0
- package/src/main/calendar/index.ts +5 -0
- package/src/main/calendar/random-date-range-dialog.tsx +753 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { Loading } from '../loading';
|
|
6
|
+
|
|
7
|
+
export type DialogLoadingAction = 'cancel' | 'confirm' | 'undo';
|
|
8
|
+
export type DialogActionHandler = () => void | Promise<void>;
|
|
9
|
+
|
|
10
|
+
interface UseDialogLoadingActionOptions {
|
|
11
|
+
loadingActions?: readonly DialogLoadingAction[];
|
|
12
|
+
loadingFullPage?: boolean;
|
|
13
|
+
onOpenChange: (open: boolean) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useDialogLoadingAction({
|
|
17
|
+
loadingActions,
|
|
18
|
+
loadingFullPage = false,
|
|
19
|
+
onOpenChange,
|
|
20
|
+
}: UseDialogLoadingActionOptions) {
|
|
21
|
+
const [mounted, setMounted] = React.useState(false);
|
|
22
|
+
const [loading, setLoading] = React.useState(false);
|
|
23
|
+
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
setMounted(true);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
const runDialogAction = React.useCallback(async (
|
|
29
|
+
action: DialogLoadingAction,
|
|
30
|
+
handler?: DialogActionHandler
|
|
31
|
+
) => {
|
|
32
|
+
onOpenChange(false);
|
|
33
|
+
|
|
34
|
+
if (!handler) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!loadingActions?.includes(action)) {
|
|
39
|
+
await handler();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setLoading(true);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await handler();
|
|
47
|
+
} finally {
|
|
48
|
+
setLoading(false);
|
|
49
|
+
}
|
|
50
|
+
}, [loadingActions, onOpenChange]);
|
|
51
|
+
|
|
52
|
+
const dialogLoading = mounted && loading
|
|
53
|
+
? createPortal(
|
|
54
|
+
loadingFullPage ? (
|
|
55
|
+
<div className="fixed inset-0 z-10000">
|
|
56
|
+
<Loading className="h-full w-full" />
|
|
57
|
+
</div>
|
|
58
|
+
) : (
|
|
59
|
+
<div className="pointer-events-none fixed inset-0 z-10000 flex items-center justify-center p-4">
|
|
60
|
+
<div className="pointer-events-auto overflow-hidden rounded-[28px] bg-neutral-50/58 shadow-[0_18px_56px_rgba(15,23,42,0.14)] backdrop-blur-md dark:bg-neutral-900/58 dark:shadow-[0_18px_56px_rgba(0,0,0,0.34)]">
|
|
61
|
+
<Loading
|
|
62
|
+
compact
|
|
63
|
+
label="Loading"
|
|
64
|
+
className="min-h-[250px] w-[min(22rem,calc(100vw-2rem))] bg-transparent"
|
|
65
|
+
labelClassName="text-foreground"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
),
|
|
70
|
+
document.body
|
|
71
|
+
)
|
|
72
|
+
: null;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
dialogLoading,
|
|
76
|
+
runDialogAction,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -15,16 +15,19 @@ import {
|
|
|
15
15
|
secondaryButtonClass,
|
|
16
16
|
} from "./dialog-styles";
|
|
17
17
|
import { themeBgColor, themeBorderColor, themeIconColor } from "@windrun-huaiin/base-ui/lib";
|
|
18
|
+
import { DialogLoadingAction, DialogActionHandler, useDialogLoadingAction } from "./dialog-loading-action";
|
|
18
19
|
|
|
19
20
|
interface HighPriorityConfirmDialogProps {
|
|
20
21
|
open: boolean;
|
|
21
22
|
onOpenChange: (open: boolean) => void;
|
|
22
|
-
onCancel:
|
|
23
|
-
onConfirm:
|
|
23
|
+
onCancel: DialogActionHandler;
|
|
24
|
+
onConfirm: DialogActionHandler;
|
|
24
25
|
title: string;
|
|
25
26
|
description: React.ReactNode;
|
|
26
27
|
confirmText?: string;
|
|
27
28
|
cancelText?: string;
|
|
29
|
+
loadingActions?: readonly DialogLoadingAction[];
|
|
30
|
+
loadingFullPage?: boolean;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export function HighPriorityConfirmDialog({
|
|
@@ -36,65 +39,77 @@ export function HighPriorityConfirmDialog({
|
|
|
36
39
|
description,
|
|
37
40
|
confirmText = "Confirm",
|
|
38
41
|
cancelText = "Cancel",
|
|
42
|
+
loadingActions,
|
|
43
|
+
loadingFullPage,
|
|
39
44
|
}: HighPriorityConfirmDialogProps) {
|
|
40
45
|
const [mounted, setMounted] = useState(false);
|
|
46
|
+
const { dialogLoading, runDialogAction } = useDialogLoadingAction({ loadingActions, loadingFullPage, onOpenChange });
|
|
41
47
|
|
|
42
48
|
useEffect(() => {
|
|
43
49
|
// Ensure portal target exists and prevent hydration mismatch
|
|
44
50
|
setTimeout(() => setMounted(true), 0);
|
|
45
51
|
}, []);
|
|
46
52
|
|
|
47
|
-
if (!
|
|
53
|
+
if (!mounted) return dialogLoading;
|
|
48
54
|
|
|
49
55
|
const handleClose = () => {
|
|
50
56
|
onOpenChange(false);
|
|
51
57
|
};
|
|
52
58
|
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
className=
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<h3 className={highPriorityTitleClass}>
|
|
63
|
-
<span className={cn('inline-flex size-9 shrink-0 items-center justify-center rounded-full ring-1', themeBgColor, themeBorderColor)}>
|
|
64
|
-
<FAQSIcon className={cn('size-5', themeIconColor)} />
|
|
65
|
-
</span>
|
|
66
|
-
<span className="min-w-0 truncate">{title}</span>
|
|
67
|
-
</h3>
|
|
68
|
-
<button
|
|
69
|
-
type="button"
|
|
70
|
-
className={closeButtonClass}
|
|
71
|
-
onClick={handleClose}
|
|
72
|
-
aria-label="Close"
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
{open && createPortal(
|
|
62
|
+
<div className="fixed inset-0 z-10000 flex items-center justify-center bg-black/60 backdrop-blur-sm animate-in fade-in duration-300">
|
|
63
|
+
<div
|
|
64
|
+
className={cn(highPrioritySurfaceClass, "scale-100")}
|
|
65
|
+
role="dialog"
|
|
66
|
+
aria-modal="true"
|
|
67
|
+
onClick={(e) => e.stopPropagation()}
|
|
73
68
|
>
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
69
|
+
<div className={dialogHeaderClass}>
|
|
70
|
+
<h3 className={highPriorityTitleClass}>
|
|
71
|
+
<span className={cn('inline-flex size-9 shrink-0 items-center justify-center rounded-full ring-1', themeBgColor, themeBorderColor)}>
|
|
72
|
+
<FAQSIcon className={cn('size-5', themeIconColor)} />
|
|
73
|
+
</span>
|
|
74
|
+
<span className="min-w-0 truncate">{title}</span>
|
|
75
|
+
</h3>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
className={closeButtonClass}
|
|
79
|
+
onClick={handleClose}
|
|
80
|
+
aria-label="Close"
|
|
81
|
+
>
|
|
82
|
+
<XIcon className="size-4" />
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
<div className={dialogDescriptionClass}>
|
|
86
|
+
{description}
|
|
87
|
+
</div>
|
|
88
|
+
<div className={dialogFooterClass}>
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
onClick={() => {
|
|
92
|
+
void runDialogAction('cancel', onCancel);
|
|
93
|
+
}}
|
|
94
|
+
className={secondaryButtonClass}
|
|
95
|
+
>
|
|
96
|
+
{cancelText}
|
|
97
|
+
</button>
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
onClick={() => {
|
|
101
|
+
void runDialogAction('confirm', onConfirm);
|
|
102
|
+
}}
|
|
103
|
+
className={cn(primaryButtonClass, "hover:scale-105 active:scale-95")}
|
|
104
|
+
>
|
|
105
|
+
{confirmText}
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>,
|
|
110
|
+
document.body
|
|
111
|
+
)}
|
|
112
|
+
{dialogLoading}
|
|
113
|
+
</>
|
|
99
114
|
);
|
|
100
115
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
dialogThemedOverlayClass,
|
|
26
26
|
dialogTitleClass,
|
|
27
27
|
} from './dialog-styles';
|
|
28
|
+
import { DialogLoadingAction, DialogActionHandler, useDialogLoadingAction } from './dialog-loading-action';
|
|
28
29
|
|
|
29
30
|
export type InfoDialogType = 'info' | 'warn' | 'success' | 'error';
|
|
30
31
|
type InfoDialogIcon = typeof BadgeInfoIcon;
|
|
@@ -36,7 +37,9 @@ interface InfoDialogProps {
|
|
|
36
37
|
title: React.ReactNode;
|
|
37
38
|
description: React.ReactNode;
|
|
38
39
|
confirmText?: string;
|
|
39
|
-
|
|
40
|
+
loadingActions?: readonly DialogLoadingAction[];
|
|
41
|
+
loadingFullPage?: boolean;
|
|
42
|
+
onConfirm?: DialogActionHandler;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
const infoTypeClassMap: Record<InfoDialogType, {
|
|
@@ -83,57 +86,62 @@ export function InfoDialog({
|
|
|
83
86
|
title,
|
|
84
87
|
description,
|
|
85
88
|
confirmText = 'OK',
|
|
89
|
+
loadingActions,
|
|
90
|
+
loadingFullPage,
|
|
86
91
|
onConfirm,
|
|
87
92
|
}: InfoDialogProps) {
|
|
88
93
|
const typeClass = infoTypeClassMap[type];
|
|
89
94
|
const Icon = typeClass.Icon;
|
|
90
95
|
const handleClose = () => onOpenChange(false);
|
|
96
|
+
const { dialogLoading, runDialogAction } = useDialogLoadingAction({ loadingActions, loadingFullPage, onOpenChange });
|
|
91
97
|
|
|
92
98
|
return (
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
<
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
99
|
+
<>
|
|
100
|
+
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
|
101
|
+
<AlertDialogContent
|
|
102
|
+
className={cn(dialogContentClass, typeClass.content)}
|
|
103
|
+
overlayClassName={dialogThemedOverlayClass}
|
|
104
|
+
onOverlayClick={handleClose}
|
|
105
|
+
>
|
|
106
|
+
<div className={dialogHeaderClass}>
|
|
107
|
+
<AlertDialogTitle asChild>
|
|
108
|
+
<div className={dialogTitleClass}>
|
|
109
|
+
<span className={cn('inline-flex size-9 shrink-0 items-center justify-center rounded-full ring-1', typeClass.iconWrap)}>
|
|
110
|
+
<Icon className={cn('size-5', typeClass.icon)} />
|
|
111
|
+
</span>
|
|
112
|
+
<span className="min-w-0 truncate">{title}</span>
|
|
113
|
+
</div>
|
|
114
|
+
</AlertDialogTitle>
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
className={closeButtonClass}
|
|
118
|
+
onClick={handleClose}
|
|
119
|
+
aria-label="Close"
|
|
120
|
+
>
|
|
121
|
+
<XIcon className="size-4" />
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
117
124
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
<AlertDialogDescription className={dialogDescriptionClass}>
|
|
126
|
+
{description}
|
|
127
|
+
</AlertDialogDescription>
|
|
121
128
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
</
|
|
135
|
-
</
|
|
136
|
-
</
|
|
137
|
-
|
|
129
|
+
<div className={dialogFooterClass}>
|
|
130
|
+
<AlertDialogAction
|
|
131
|
+
className={cn(
|
|
132
|
+
'inline-flex min-h-10 items-center justify-center rounded-full px-5 py-2 text-sm font-bold transition focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-60',
|
|
133
|
+
typeClass.action
|
|
134
|
+
)}
|
|
135
|
+
onClick={() => {
|
|
136
|
+
void runDialogAction('confirm', onConfirm);
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
{confirmText}
|
|
140
|
+
</AlertDialogAction>
|
|
141
|
+
</div>
|
|
142
|
+
</AlertDialogContent>
|
|
143
|
+
</AlertDialog>
|
|
144
|
+
{dialogLoading}
|
|
145
|
+
</>
|
|
138
146
|
);
|
|
139
147
|
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
secondaryButtonClass,
|
|
23
23
|
} from './dialog-styles';
|
|
24
24
|
import type { ConfirmDialogEmphasis } from './confirm-dialog';
|
|
25
|
+
import { DialogLoadingAction, DialogActionHandler, useDialogLoadingAction } from './dialog-loading-action';
|
|
25
26
|
|
|
26
27
|
export interface UndoableConfirmDialogProps {
|
|
27
28
|
open: boolean;
|
|
@@ -35,9 +36,11 @@ export interface UndoableConfirmDialogProps {
|
|
|
35
36
|
undoText?: string;
|
|
36
37
|
emphasis?: ConfirmDialogEmphasis;
|
|
37
38
|
countdownSeconds?: number;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
loadingActions?: readonly DialogLoadingAction[];
|
|
40
|
+
loadingFullPage?: boolean;
|
|
41
|
+
onCancel?: DialogActionHandler;
|
|
42
|
+
onConfirm: DialogActionHandler;
|
|
43
|
+
onUndo?: DialogActionHandler;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
export function UndoableConfirmDialog({
|
|
@@ -52,6 +55,8 @@ export function UndoableConfirmDialog({
|
|
|
52
55
|
undoText = 'Undo',
|
|
53
56
|
emphasis = 'confirm',
|
|
54
57
|
countdownSeconds = 5,
|
|
58
|
+
loadingActions,
|
|
59
|
+
loadingFullPage,
|
|
55
60
|
onCancel,
|
|
56
61
|
onConfirm,
|
|
57
62
|
onUndo,
|
|
@@ -64,6 +69,7 @@ export function UndoableConfirmDialog({
|
|
|
64
69
|
const intervalRef = React.useRef<number | null>(null);
|
|
65
70
|
const cancelButtonClass = emphasis === 'cancel' ? dangerButtonClass : secondaryButtonClass;
|
|
66
71
|
const confirmButtonClass = emphasis === 'cancel' ? secondaryButtonClass : dangerButtonClass;
|
|
72
|
+
const { dialogLoading, runDialogAction } = useDialogLoadingAction({ loadingActions, loadingFullPage, onOpenChange });
|
|
67
73
|
|
|
68
74
|
const clearTimers = React.useCallback(() => {
|
|
69
75
|
if (timeoutRef.current) {
|
|
@@ -100,12 +106,11 @@ export function UndoableConfirmDialog({
|
|
|
100
106
|
setConfirming(true);
|
|
101
107
|
|
|
102
108
|
try {
|
|
103
|
-
await onConfirm
|
|
104
|
-
onOpenChange(false);
|
|
109
|
+
await runDialogAction('confirm', onConfirm);
|
|
105
110
|
} finally {
|
|
106
111
|
setConfirming(false);
|
|
107
112
|
}
|
|
108
|
-
}, [clearTimers, onConfirm,
|
|
113
|
+
}, [clearTimers, onConfirm, runDialogAction]);
|
|
109
114
|
|
|
110
115
|
const startCountdown = () => {
|
|
111
116
|
clearTimers();
|
|
@@ -123,102 +128,105 @@ export function UndoableConfirmDialog({
|
|
|
123
128
|
|
|
124
129
|
const handleCancel = () => {
|
|
125
130
|
resetState();
|
|
126
|
-
|
|
127
|
-
onCancel?.();
|
|
131
|
+
void runDialogAction('cancel', onCancel);
|
|
128
132
|
};
|
|
129
133
|
const handleClose = React.useCallback(() => {
|
|
130
134
|
resetState();
|
|
131
135
|
onOpenChange(false);
|
|
132
136
|
}, [onOpenChange, resetState]);
|
|
133
137
|
|
|
134
|
-
const handleUndo = () => {
|
|
138
|
+
const handleUndo = async () => {
|
|
135
139
|
resetState();
|
|
136
|
-
|
|
137
|
-
onUndo?.();
|
|
140
|
+
await runDialogAction('undo', onUndo);
|
|
138
141
|
};
|
|
139
142
|
|
|
140
143
|
const displayTitle = pending ? pendingTitle ?? title : title;
|
|
141
144
|
const displayDescription = pending ? pendingDescription ?? description : description;
|
|
142
145
|
return (
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<
|
|
158
|
-
<
|
|
159
|
-
<
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
type="button"
|
|
167
|
-
className={closeButtonClass}
|
|
168
|
-
onClick={handleClose}
|
|
169
|
-
aria-label="Close"
|
|
170
|
-
disabled={confirming}
|
|
171
|
-
>
|
|
172
|
-
<XIcon className="size-4" />
|
|
173
|
-
</button>
|
|
174
|
-
</div>
|
|
175
|
-
|
|
176
|
-
<AlertDialogDescription className={cn(dialogDescriptionClass, 'min-h-[44px]')}>
|
|
177
|
-
<span>{displayDescription}</span>
|
|
178
|
-
</AlertDialogDescription>
|
|
179
|
-
|
|
180
|
-
<div className="flex h-12 items-center justify-center py-1">
|
|
181
|
-
<div className="flex items-baseline justify-center gap-2">
|
|
182
|
-
<span className={cn('text-4xl font-black leading-none tabular-nums', pending && 'animate-bounce', themeIconColor)}>
|
|
183
|
-
{pending ? remainingSeconds : safeCountdownSeconds}
|
|
184
|
-
</span>
|
|
185
|
-
<span className={cn('text-sm font-bold', themeIconColor)}>
|
|
186
|
-
s
|
|
187
|
-
</span>
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
<div className={cn(dialogFooterClass, 'min-h-[88px] sm:min-h-10 sm:items-center')}>
|
|
192
|
-
{pending ? (
|
|
146
|
+
<>
|
|
147
|
+
<AlertDialog open={open} onOpenChange={(nextOpen) => {
|
|
148
|
+
if (!nextOpen) {
|
|
149
|
+
handleClose();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
onOpenChange(nextOpen);
|
|
154
|
+
}}>
|
|
155
|
+
<AlertDialogContent
|
|
156
|
+
className={cn(dialogContentClass, 'border-red-300 dark:border-red-700')}
|
|
157
|
+
overlayClassName={dialogThemedOverlayClass}
|
|
158
|
+
onOverlayClick={pending ? undefined : handleClose}
|
|
159
|
+
>
|
|
160
|
+
<div className={dialogHeaderClass}>
|
|
161
|
+
<AlertDialogTitle asChild>
|
|
162
|
+
<div className={dialogTitleClass}>
|
|
163
|
+
<span className="inline-flex size-9 shrink-0 items-center justify-center rounded-full bg-red-100 text-red-600 ring-1 ring-red-200 dark:bg-red-950 dark:text-red-300 dark:ring-red-900">
|
|
164
|
+
{pending ? <Trash2Icon className="size-5" /> : <CircleAlertIcon className="size-5" />}
|
|
165
|
+
</span>
|
|
166
|
+
<span className="min-w-0 truncate">{displayTitle}</span>
|
|
167
|
+
</div>
|
|
168
|
+
</AlertDialogTitle>
|
|
193
169
|
<button
|
|
194
170
|
type="button"
|
|
195
|
-
|
|
196
|
-
|
|
171
|
+
className={closeButtonClass}
|
|
172
|
+
onClick={handleClose}
|
|
173
|
+
aria-label="Close"
|
|
197
174
|
disabled={confirming}
|
|
198
175
|
>
|
|
199
|
-
<
|
|
200
|
-
{undoText}
|
|
176
|
+
<XIcon className="size-4" />
|
|
201
177
|
</button>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<AlertDialogDescription className={cn(dialogDescriptionClass, 'min-h-[44px]')}>
|
|
181
|
+
<span>{displayDescription}</span>
|
|
182
|
+
</AlertDialogDescription>
|
|
183
|
+
|
|
184
|
+
<div className="flex h-12 items-center justify-center py-1">
|
|
185
|
+
<div className="flex items-baseline justify-center gap-2">
|
|
186
|
+
<span className={cn('text-4xl font-black leading-none tabular-nums', pending && 'animate-bounce', themeIconColor)}>
|
|
187
|
+
{pending ? remainingSeconds : safeCountdownSeconds}
|
|
188
|
+
</span>
|
|
189
|
+
<span className={cn('text-sm font-bold', themeIconColor)}>
|
|
190
|
+
s
|
|
191
|
+
</span>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div className={cn(dialogFooterClass, 'min-h-[88px] sm:min-h-10 sm:items-center')}>
|
|
196
|
+
{pending ? (
|
|
211
197
|
<button
|
|
212
198
|
type="button"
|
|
213
|
-
onClick={
|
|
214
|
-
|
|
199
|
+
onClick={() => {
|
|
200
|
+
void handleUndo();
|
|
201
|
+
}}
|
|
202
|
+
className={secondaryButtonClass}
|
|
203
|
+
disabled={confirming}
|
|
215
204
|
>
|
|
216
|
-
|
|
205
|
+
<Undo2Icon className="mr-1.5 size-4" />
|
|
206
|
+
{undoText}
|
|
217
207
|
</button>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
208
|
+
) : (
|
|
209
|
+
<>
|
|
210
|
+
<button
|
|
211
|
+
type="button"
|
|
212
|
+
onClick={handleCancel}
|
|
213
|
+
className={cancelButtonClass}
|
|
214
|
+
>
|
|
215
|
+
{cancelText}
|
|
216
|
+
</button>
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
onClick={startCountdown}
|
|
220
|
+
className={confirmButtonClass}
|
|
221
|
+
>
|
|
222
|
+
{confirmText}
|
|
223
|
+
</button>
|
|
224
|
+
</>
|
|
225
|
+
)}
|
|
226
|
+
</div>
|
|
227
|
+
</AlertDialogContent>
|
|
228
|
+
</AlertDialog>
|
|
229
|
+
{dialogLoading}
|
|
230
|
+
</>
|
|
223
231
|
);
|
|
224
232
|
}
|