@windrun-huaiin/third-ui 29.0.4 → 29.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.
Files changed (64) hide show
  1. package/dist/fuma/base/custom-header.js +6 -3
  2. package/dist/fuma/base/custom-header.mjs +6 -3
  3. package/dist/main/alert-dialog/confirm-dialog.d.ts +7 -3
  4. package/dist/main/alert-dialog/confirm-dialog.js +11 -6
  5. package/dist/main/alert-dialog/confirm-dialog.mjs +13 -8
  6. package/dist/main/alert-dialog/dialog-loading-action.d.ts +12 -0
  7. package/dist/main/alert-dialog/dialog-loading-action.js +42 -0
  8. package/dist/main/alert-dialog/dialog-loading-action.mjs +40 -0
  9. package/dist/main/alert-dialog/high-priority-confirm-dialog.d.ts +6 -3
  10. package/dist/main/alert-dialog/high-priority-confirm-dialog.js +13 -4
  11. package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +14 -5
  12. package/dist/main/alert-dialog/index.d.ts +1 -0
  13. package/dist/main/alert-dialog/info-dialog.d.ts +4 -2
  14. package/dist/main/alert-dialog/info-dialog.js +6 -5
  15. package/dist/main/alert-dialog/info-dialog.mjs +7 -6
  16. package/dist/main/alert-dialog/undoable-confirm-dialog.d.ts +8 -4
  17. package/dist/main/alert-dialog/undoable-confirm-dialog.js +23 -16
  18. package/dist/main/alert-dialog/undoable-confirm-dialog.mjs +24 -17
  19. package/dist/main/buttons/gradient-button.d.ts +3 -1
  20. package/dist/main/buttons/gradient-button.js +29 -3
  21. package/dist/main/buttons/gradient-button.mjs +29 -3
  22. package/dist/main/buttons/index.d.ts +1 -0
  23. package/dist/main/buttons/index.js +3 -0
  24. package/dist/main/buttons/index.mjs +1 -0
  25. package/dist/main/buttons/use-press-feedback.d.ts +18 -0
  26. package/dist/main/buttons/use-press-feedback.js +42 -0
  27. package/dist/main/buttons/use-press-feedback.mjs +39 -0
  28. package/dist/main/buttons/x-button.d.ts +3 -0
  29. package/dist/main/buttons/x-button.js +36 -6
  30. package/dist/main/buttons/x-button.mjs +36 -6
  31. package/dist/main/calendar/calendar-date-range-input.d.ts +17 -0
  32. package/dist/main/calendar/calendar-date-range-input.js +81 -0
  33. package/dist/main/calendar/calendar-date-range-input.mjs +79 -0
  34. package/dist/main/calendar/calendar-status-view.d.ts +23 -0
  35. package/dist/main/calendar/calendar-status-view.js +155 -0
  36. package/dist/main/calendar/calendar-status-view.mjs +153 -0
  37. package/dist/main/calendar/index.d.ts +3 -0
  38. package/dist/main/calendar/index.js +12 -0
  39. package/dist/main/calendar/index.mjs +4 -0
  40. package/dist/main/calendar/random-date-range-dialog.d.ts +15 -0
  41. package/dist/main/calendar/random-date-range-dialog.js +447 -0
  42. package/dist/main/calendar/random-date-range-dialog.mjs +445 -0
  43. package/dist/main/features.js +2 -2
  44. package/dist/main/features.mjs +1 -1
  45. package/dist/main/usage.js +2 -2
  46. package/dist/main/usage.mjs +1 -1
  47. package/package.json +9 -4
  48. package/src/fuma/base/custom-header.tsx +6 -3
  49. package/src/main/alert-dialog/confirm-dialog.tsx +59 -46
  50. package/src/main/alert-dialog/dialog-loading-action.tsx +63 -0
  51. package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +67 -48
  52. package/src/main/alert-dialog/index.ts +1 -0
  53. package/src/main/alert-dialog/info-dialog.tsx +50 -44
  54. package/src/main/alert-dialog/undoable-confirm-dialog.tsx +96 -81
  55. package/src/main/buttons/gradient-button.tsx +36 -3
  56. package/src/main/buttons/index.ts +1 -0
  57. package/src/main/buttons/use-press-feedback.ts +58 -0
  58. package/src/main/buttons/x-button.tsx +53 -11
  59. package/src/main/calendar/calendar-date-range-input.tsx +173 -0
  60. package/src/main/calendar/calendar-status-view.tsx +365 -0
  61. package/src/main/calendar/index.ts +5 -0
  62. package/src/main/calendar/random-date-range-dialog.tsx +741 -0
  63. package/src/main/features.tsx +1 -1
  64. package/src/main/usage.tsx +1 -1
@@ -24,8 +24,10 @@ import {
24
24
  primaryButtonClass,
25
25
  secondaryButtonClass,
26
26
  } from './dialog-styles';
27
+ import { DialogLoadingAction, DialogActionHandler, useDialogLoadingAction } from './dialog-loading-action';
27
28
 
28
29
  export type ConfirmDialogType = 'normal' | 'danger';
30
+ export type ConfirmDialogEmphasis = 'confirm' | 'cancel';
29
31
 
30
32
  interface ConfirmDialogProps {
31
33
  open: boolean;
@@ -35,8 +37,10 @@ interface ConfirmDialogProps {
35
37
  description: React.ReactNode;
36
38
  cancelText?: string;
37
39
  confirmText?: string;
38
- onCancel?: () => void;
39
- onConfirm?: () => void;
40
+ emphasis?: ConfirmDialogEmphasis;
41
+ loadingActions?: readonly DialogLoadingAction[];
42
+ onCancel?: DialogActionHandler;
43
+ onConfirm?: DialogActionHandler;
40
44
  }
41
45
 
42
46
  const confirmTypeClassMap: Record<ConfirmDialogType, {
@@ -70,62 +74,71 @@ export function ConfirmDialog({
70
74
  description,
71
75
  cancelText = 'Cancel',
72
76
  confirmText = 'Confirm',
77
+ emphasis = 'confirm',
78
+ loadingActions,
73
79
  onCancel,
74
80
  onConfirm,
75
81
  }: ConfirmDialogProps) {
76
82
  const typeClass = confirmTypeClassMap[type];
77
83
  const Icon = typeClass.Icon;
84
+ const cancelButtonClass = emphasis === 'cancel' ? typeClass.action : secondaryButtonClass;
85
+ const confirmButtonClass = emphasis === 'cancel' ? secondaryButtonClass : typeClass.action;
86
+ const { dialogLoading, runDialogAction } = useDialogLoadingAction({ loadingActions, onOpenChange });
78
87
 
79
88
  const handleCancel = () => {
89
+ void runDialogAction('cancel', onCancel);
90
+ };
91
+ const handleClose = () => {
80
92
  onOpenChange(false);
81
- onCancel?.();
82
93
  };
83
94
 
84
95
  return (
85
- <AlertDialog open={open} onOpenChange={onOpenChange}>
86
- <AlertDialogContent
87
- className={cn(dialogContentClass, typeClass.content)}
88
- overlayClassName={dialogThemedOverlayClass}
89
- onOverlayClick={handleCancel}
90
- >
91
- <div className={dialogHeaderClass}>
92
- <AlertDialogTitle asChild>
93
- <div className={dialogTitleClass}>
94
- <span className={cn('inline-flex size-9 shrink-0 items-center justify-center rounded-full ring-1', typeClass.iconWrap)}>
95
- <Icon className={cn('size-5', typeClass.icon)} />
96
- </span>
97
- <span className="min-w-0 truncate">{title}</span>
98
- </div>
99
- </AlertDialogTitle>
100
- <button
101
- type="button"
102
- className={closeButtonClass}
103
- onClick={handleCancel}
104
- aria-label="Close"
105
- >
106
- <XIcon className="size-4" />
107
- </button>
108
- </div>
96
+ <>
97
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
98
+ <AlertDialogContent
99
+ className={cn(dialogContentClass, typeClass.content)}
100
+ overlayClassName={dialogThemedOverlayClass}
101
+ onOverlayClick={handleClose}
102
+ >
103
+ <div className={dialogHeaderClass}>
104
+ <AlertDialogTitle asChild>
105
+ <div className={dialogTitleClass}>
106
+ <span className={cn('inline-flex size-9 shrink-0 items-center justify-center rounded-full ring-1', typeClass.iconWrap)}>
107
+ <Icon className={cn('size-5', typeClass.icon)} />
108
+ </span>
109
+ <span className="min-w-0 truncate">{title}</span>
110
+ </div>
111
+ </AlertDialogTitle>
112
+ <button
113
+ type="button"
114
+ className={closeButtonClass}
115
+ onClick={handleClose}
116
+ aria-label="Close"
117
+ >
118
+ <XIcon className="size-4" />
119
+ </button>
120
+ </div>
109
121
 
110
- <AlertDialogDescription className={dialogDescriptionClass}>
111
- {description}
112
- </AlertDialogDescription>
122
+ <AlertDialogDescription className={dialogDescriptionClass}>
123
+ {description}
124
+ </AlertDialogDescription>
113
125
 
114
- <div className={dialogFooterClass}>
115
- <AlertDialogCancel className={secondaryButtonClass} onClick={handleCancel}>
116
- {cancelText}
117
- </AlertDialogCancel>
118
- <AlertDialogAction
119
- className={typeClass.action}
120
- onClick={() => {
121
- onOpenChange(false);
122
- onConfirm?.();
123
- }}
124
- >
125
- {confirmText}
126
- </AlertDialogAction>
127
- </div>
128
- </AlertDialogContent>
129
- </AlertDialog>
126
+ <div className={dialogFooterClass}>
127
+ <AlertDialogCancel className={cancelButtonClass} onClick={handleCancel}>
128
+ {cancelText}
129
+ </AlertDialogCancel>
130
+ <AlertDialogAction
131
+ className={confirmButtonClass}
132
+ onClick={() => {
133
+ void runDialogAction('confirm', onConfirm);
134
+ }}
135
+ >
136
+ {confirmText}
137
+ </AlertDialogAction>
138
+ </div>
139
+ </AlertDialogContent>
140
+ </AlertDialog>
141
+ {dialogLoading}
142
+ </>
130
143
  );
131
144
  }
@@ -0,0 +1,63 @@
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
+ onOpenChange: (open: boolean) => void;
13
+ }
14
+
15
+ export function useDialogLoadingAction({
16
+ loadingActions,
17
+ onOpenChange,
18
+ }: UseDialogLoadingActionOptions) {
19
+ const [mounted, setMounted] = React.useState(false);
20
+ const [loading, setLoading] = React.useState(false);
21
+
22
+ React.useEffect(() => {
23
+ setMounted(true);
24
+ }, []);
25
+
26
+ const runDialogAction = React.useCallback(async (
27
+ action: DialogLoadingAction,
28
+ handler?: DialogActionHandler
29
+ ) => {
30
+ onOpenChange(false);
31
+
32
+ if (!handler) {
33
+ return;
34
+ }
35
+
36
+ if (!loadingActions?.includes(action)) {
37
+ await handler();
38
+ return;
39
+ }
40
+
41
+ setLoading(true);
42
+
43
+ try {
44
+ await handler();
45
+ } finally {
46
+ setLoading(false);
47
+ }
48
+ }, [loadingActions, onOpenChange]);
49
+
50
+ const dialogLoading = mounted && loading
51
+ ? createPortal(
52
+ <div className="fixed inset-0 z-10000">
53
+ <Loading className="h-full w-full" />
54
+ </div>,
55
+ document.body
56
+ )
57
+ : null;
58
+
59
+ return {
60
+ dialogLoading,
61
+ runDialogAction,
62
+ };
63
+ }
@@ -15,80 +15,99 @@ 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
- onCancel: () => void;
22
- onConfirm: () => void;
22
+ onOpenChange: (open: boolean) => void;
23
+ onCancel: DialogActionHandler;
24
+ onConfirm: DialogActionHandler;
23
25
  title: string;
24
26
  description: React.ReactNode;
25
27
  confirmText?: string;
26
28
  cancelText?: string;
29
+ loadingActions?: readonly DialogLoadingAction[];
27
30
  }
28
31
 
29
32
  export function HighPriorityConfirmDialog({
30
33
  open,
34
+ onOpenChange,
31
35
  onCancel,
32
36
  onConfirm,
33
37
  title,
34
38
  description,
35
39
  confirmText = "Confirm",
36
40
  cancelText = "Cancel",
41
+ loadingActions,
37
42
  }: HighPriorityConfirmDialogProps) {
38
43
  const [mounted, setMounted] = useState(false);
44
+ const { dialogLoading, runDialogAction } = useDialogLoadingAction({ loadingActions, onOpenChange });
39
45
 
40
46
  useEffect(() => {
41
47
  // Ensure portal target exists and prevent hydration mismatch
42
48
  setTimeout(() => setMounted(true), 0);
43
49
  }, []);
44
50
 
45
- if (!open || !mounted) return null;
51
+ if (!mounted) return dialogLoading;
46
52
 
47
- return createPortal(
48
- <div className="fixed inset-0 z-10000 flex items-center justify-center bg-black/60 backdrop-blur-sm animate-in fade-in duration-300">
49
- <div
50
- className={cn(highPrioritySurfaceClass, "scale-100")}
51
- role="dialog"
52
- aria-modal="true"
53
- onClick={(e) => e.stopPropagation()}
54
- >
55
- <div className={dialogHeaderClass}>
56
- <h3 className={highPriorityTitleClass}>
57
- <span className={cn('inline-flex size-9 shrink-0 items-center justify-center rounded-full ring-1', themeBgColor, themeBorderColor)}>
58
- <FAQSIcon className={cn('size-5', themeIconColor)} />
59
- </span>
60
- <span className="min-w-0 truncate">{title}</span>
61
- </h3>
62
- <button
63
- type="button"
64
- className={closeButtonClass}
65
- onClick={onCancel}
66
- aria-label="Close"
67
- >
68
- <XIcon className="size-4" />
69
- </button>
70
- </div>
71
- <div className={dialogDescriptionClass}>
72
- {description}
73
- </div>
74
- <div className={dialogFooterClass}>
75
- <button
76
- type="button"
77
- onClick={onCancel}
78
- className={secondaryButtonClass}
79
- >
80
- {cancelText}
81
- </button>
82
- <button
83
- type="button"
84
- onClick={onConfirm}
85
- className={cn(primaryButtonClass, "hover:scale-105 active:scale-95")}
53
+ const handleClose = () => {
54
+ onOpenChange(false);
55
+ };
56
+
57
+ return (
58
+ <>
59
+ {open && createPortal(
60
+ <div className="fixed inset-0 z-10000 flex items-center justify-center bg-black/60 backdrop-blur-sm animate-in fade-in duration-300">
61
+ <div
62
+ className={cn(highPrioritySurfaceClass, "scale-100")}
63
+ role="dialog"
64
+ aria-modal="true"
65
+ onClick={(e) => e.stopPropagation()}
86
66
  >
87
- {confirmText}
88
- </button>
89
- </div>
90
- </div>
91
- </div>,
92
- document.body
67
+ <div className={dialogHeaderClass}>
68
+ <h3 className={highPriorityTitleClass}>
69
+ <span className={cn('inline-flex size-9 shrink-0 items-center justify-center rounded-full ring-1', themeBgColor, themeBorderColor)}>
70
+ <FAQSIcon className={cn('size-5', themeIconColor)} />
71
+ </span>
72
+ <span className="min-w-0 truncate">{title}</span>
73
+ </h3>
74
+ <button
75
+ type="button"
76
+ className={closeButtonClass}
77
+ onClick={handleClose}
78
+ aria-label="Close"
79
+ >
80
+ <XIcon className="size-4" />
81
+ </button>
82
+ </div>
83
+ <div className={dialogDescriptionClass}>
84
+ {description}
85
+ </div>
86
+ <div className={dialogFooterClass}>
87
+ <button
88
+ type="button"
89
+ onClick={() => {
90
+ void runDialogAction('cancel', onCancel);
91
+ }}
92
+ className={secondaryButtonClass}
93
+ >
94
+ {cancelText}
95
+ </button>
96
+ <button
97
+ type="button"
98
+ onClick={() => {
99
+ void runDialogAction('confirm', onConfirm);
100
+ }}
101
+ className={cn(primaryButtonClass, "hover:scale-105 active:scale-95")}
102
+ >
103
+ {confirmText}
104
+ </button>
105
+ </div>
106
+ </div>
107
+ </div>,
108
+ document.body
109
+ )}
110
+ {dialogLoading}
111
+ </>
93
112
  );
94
113
  }
@@ -5,3 +5,4 @@ export * from './confirm-dialog';
5
5
  export * from './high-priority-confirm-dialog';
6
6
  export * from './info-dialog';
7
7
  export * from './undoable-confirm-dialog';
8
+ export type { DialogLoadingAction } from './dialog-loading-action';
@@ -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,8 @@ interface InfoDialogProps {
36
37
  title: React.ReactNode;
37
38
  description: React.ReactNode;
38
39
  confirmText?: string;
39
- onConfirm?: () => void;
40
+ loadingActions?: readonly DialogLoadingAction[];
41
+ onConfirm?: DialogActionHandler;
40
42
  }
41
43
 
42
44
  const infoTypeClassMap: Record<InfoDialogType, {
@@ -83,57 +85,61 @@ export function InfoDialog({
83
85
  title,
84
86
  description,
85
87
  confirmText = 'OK',
88
+ loadingActions,
86
89
  onConfirm,
87
90
  }: InfoDialogProps) {
88
91
  const typeClass = infoTypeClassMap[type];
89
92
  const Icon = typeClass.Icon;
90
93
  const handleClose = () => onOpenChange(false);
94
+ const { dialogLoading, runDialogAction } = useDialogLoadingAction({ loadingActions, onOpenChange });
91
95
 
92
96
  return (
93
- <AlertDialog open={open} onOpenChange={onOpenChange}>
94
- <AlertDialogContent
95
- className={cn(dialogContentClass, typeClass.content)}
96
- overlayClassName={dialogThemedOverlayClass}
97
- onOverlayClick={handleClose}
98
- >
99
- <div className={dialogHeaderClass}>
100
- <AlertDialogTitle asChild>
101
- <div className={dialogTitleClass}>
102
- <span className={cn('inline-flex size-9 shrink-0 items-center justify-center rounded-full ring-1', typeClass.iconWrap)}>
103
- <Icon className={cn('size-5', typeClass.icon)} />
104
- </span>
105
- <span className="min-w-0 truncate">{title}</span>
106
- </div>
107
- </AlertDialogTitle>
108
- <button
109
- type="button"
110
- className={closeButtonClass}
111
- onClick={handleClose}
112
- aria-label="Close"
113
- >
114
- <XIcon className="size-4" />
115
- </button>
116
- </div>
97
+ <>
98
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
99
+ <AlertDialogContent
100
+ className={cn(dialogContentClass, typeClass.content)}
101
+ overlayClassName={dialogThemedOverlayClass}
102
+ onOverlayClick={handleClose}
103
+ >
104
+ <div className={dialogHeaderClass}>
105
+ <AlertDialogTitle asChild>
106
+ <div className={dialogTitleClass}>
107
+ <span className={cn('inline-flex size-9 shrink-0 items-center justify-center rounded-full ring-1', typeClass.iconWrap)}>
108
+ <Icon className={cn('size-5', typeClass.icon)} />
109
+ </span>
110
+ <span className="min-w-0 truncate">{title}</span>
111
+ </div>
112
+ </AlertDialogTitle>
113
+ <button
114
+ type="button"
115
+ className={closeButtonClass}
116
+ onClick={handleClose}
117
+ aria-label="Close"
118
+ >
119
+ <XIcon className="size-4" />
120
+ </button>
121
+ </div>
117
122
 
118
- <AlertDialogDescription className={dialogDescriptionClass}>
119
- {description}
120
- </AlertDialogDescription>
123
+ <AlertDialogDescription className={dialogDescriptionClass}>
124
+ {description}
125
+ </AlertDialogDescription>
121
126
 
122
- <div className={dialogFooterClass}>
123
- <AlertDialogAction
124
- className={cn(
125
- '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',
126
- typeClass.action
127
- )}
128
- onClick={() => {
129
- onOpenChange(false);
130
- onConfirm?.();
131
- }}
132
- >
133
- {confirmText}
134
- </AlertDialogAction>
135
- </div>
136
- </AlertDialogContent>
137
- </AlertDialog>
127
+ <div className={dialogFooterClass}>
128
+ <AlertDialogAction
129
+ className={cn(
130
+ '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',
131
+ typeClass.action
132
+ )}
133
+ onClick={() => {
134
+ void runDialogAction('confirm', onConfirm);
135
+ }}
136
+ >
137
+ {confirmText}
138
+ </AlertDialogAction>
139
+ </div>
140
+ </AlertDialogContent>
141
+ </AlertDialog>
142
+ {dialogLoading}
143
+ </>
138
144
  );
139
145
  }