@windrun-huaiin/third-ui 29.1.0 → 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 (58) 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 +5 -3
  4. package/dist/main/alert-dialog/confirm-dialog.js +7 -7
  5. package/dist/main/alert-dialog/confirm-dialog.mjs +8 -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 +5 -3
  10. package/dist/main/alert-dialog/high-priority-confirm-dialog.js +10 -4
  11. package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +11 -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 +6 -4
  17. package/dist/main/alert-dialog/undoable-confirm-dialog.js +18 -17
  18. package/dist/main/alert-dialog/undoable-confirm-dialog.mjs +19 -18
  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/package.json +8 -3
  44. package/src/fuma/base/custom-header.tsx +6 -3
  45. package/src/main/alert-dialog/confirm-dialog.tsx +52 -47
  46. package/src/main/alert-dialog/dialog-loading-action.tsx +63 -0
  47. package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +61 -48
  48. package/src/main/alert-dialog/index.ts +1 -0
  49. package/src/main/alert-dialog/info-dialog.tsx +50 -44
  50. package/src/main/alert-dialog/undoable-confirm-dialog.tsx +88 -82
  51. package/src/main/buttons/gradient-button.tsx +36 -3
  52. package/src/main/buttons/index.ts +1 -0
  53. package/src/main/buttons/use-press-feedback.ts +58 -0
  54. package/src/main/buttons/x-button.tsx +53 -11
  55. package/src/main/calendar/calendar-date-range-input.tsx +173 -0
  56. package/src/main/calendar/calendar-status-view.tsx +365 -0
  57. package/src/main/calendar/index.ts +5 -0
  58. package/src/main/calendar/random-date-range-dialog.tsx +741 -0
@@ -0,0 +1,365 @@
1
+ 'use client';
2
+
3
+ import { memo, useCallback, useMemo, type ReactNode } from 'react';
4
+ import {
5
+ CalendarHeartIcon,
6
+ ChevronLeftIcon,
7
+ ChevronRightIcon,
8
+ ChevronsLeftIcon,
9
+ ChevronsRightIcon,
10
+ } from '@windrun-huaiin/base-ui/icons';
11
+ import { cn } from '@windrun-huaiin/lib/utils';
12
+ import { usePressFeedback } from '../buttons/use-press-feedback';
13
+
14
+ export type CalendarDayTone = 'saved' | 'planned' | 'warning' | 'danger' | 'neutral';
15
+
16
+ export type CalendarDayState<TStateKey extends string = string> = {
17
+ key: TStateKey;
18
+ title?: string;
19
+ tone?: CalendarDayTone;
20
+ };
21
+
22
+ export type CalendarToolbarAction = {
23
+ icon: ReactNode;
24
+ label: string;
25
+ title?: string;
26
+ disabled?: boolean;
27
+ onPress: () => void;
28
+ };
29
+
30
+ type CalendarStatusViewProps<TStateKey extends string = string> = {
31
+ selectedDate: string;
32
+ dayStates?: Map<string, CalendarDayState<TStateKey>>;
33
+ action?: CalendarToolbarAction;
34
+ className?: string;
35
+ onSelectedDateChange: (date: string) => void;
36
+ };
37
+
38
+ const WEEKDAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
39
+ type CalendarToolbarButtonKey = 'prevYear' | 'prevMonth' | 'today' | 'action' | 'nextMonth' | 'nextYear';
40
+
41
+ const CALENDAR_TOOLBAR_BUTTON_BASE_CLASS_NAME =
42
+ 'inline-flex h-9 w-8 items-center justify-center border transition-[transform,background-color,color,box-shadow,border-color] duration-150 ease-out sm:w-9';
43
+ const CALENDAR_TOOLBAR_BUTTON_REST_CLASS_NAME =
44
+ 'border-black/10 text-slate-700 hover:bg-black/5 dark:border-white/10 dark:text-slate-200 dark:hover:bg-white/5';
45
+ const CALENDAR_TOOLBAR_BUTTON_PRESSED_CLASS_NAME =
46
+ 'translate-y-[2px] scale-[0.9] border-black/25 bg-black/10 text-slate-950 shadow-[inset_0_2px_4px_rgba(15,23,42,0.18)] dark:border-white/25 dark:bg-white/18 dark:text-white dark:shadow-[inset_0_2px_4px_rgba(255,255,255,0.14)]';
47
+
48
+ function parseDateString(value: string): Date {
49
+ return new Date(`${value}T00:00:00.000Z`);
50
+ }
51
+
52
+ function getTodayString(): string {
53
+ return new Date().toISOString().slice(0, 10);
54
+ }
55
+
56
+ function formatDateString(date: Date): string {
57
+ return date.toISOString().slice(0, 10);
58
+ }
59
+
60
+ function getMonthParts(date: Date): { year: string; month: string } {
61
+ return {
62
+ year: date.toLocaleDateString('en-US', {
63
+ year: 'numeric',
64
+ timeZone: 'UTC',
65
+ }),
66
+ month: date.toLocaleDateString('en-US', {
67
+ month: 'long',
68
+ timeZone: 'UTC',
69
+ }),
70
+ };
71
+ }
72
+
73
+ function buildMonthDays(currentMonth: Date): Date[] {
74
+ const year = currentMonth.getUTCFullYear();
75
+ const month = currentMonth.getUTCMonth();
76
+ const firstDay = new Date(Date.UTC(year, month, 1));
77
+ const startWeekday = firstDay.getUTCDay();
78
+ const gridStart = new Date(Date.UTC(year, month, 1 - startWeekday));
79
+
80
+ return Array.from(
81
+ { length: 42 },
82
+ (_, index) =>
83
+ new Date(Date.UTC(gridStart.getUTCFullYear(), gridStart.getUTCMonth(), gridStart.getUTCDate() + index))
84
+ );
85
+ }
86
+
87
+ function addMonthsClamped(value: string, months: number): string {
88
+ const source = parseDateString(value);
89
+ const sourceYear = source.getUTCFullYear();
90
+ const sourceMonth = source.getUTCMonth();
91
+ const sourceDay = source.getUTCDate();
92
+ const targetMonthIndex = sourceMonth + months;
93
+ const targetYear = sourceYear + Math.floor(targetMonthIndex / 12);
94
+ const normalizedMonth = ((targetMonthIndex % 12) + 12) % 12;
95
+ const targetMonthLastDay = new Date(Date.UTC(targetYear, normalizedMonth + 1, 0)).getUTCDate();
96
+ const targetDay = Math.min(sourceDay, targetMonthLastDay);
97
+
98
+ return formatDateString(new Date(Date.UTC(targetYear, normalizedMonth, targetDay)));
99
+ }
100
+
101
+ function getToneClassName(tone: CalendarDayTone | undefined, selected: boolean): string {
102
+ if (selected) {
103
+ return 'border-black/20 dark:border-white/20';
104
+ }
105
+
106
+ switch (tone) {
107
+ case 'saved':
108
+ return 'border-emerald-300 dark:border-emerald-400';
109
+ case 'planned':
110
+ return 'border-amber-300 dark:border-amber-400';
111
+ case 'warning':
112
+ return 'border-orange-300 dark:border-orange-400';
113
+ case 'danger':
114
+ return 'border-red-300 dark:border-red-400';
115
+ case 'neutral':
116
+ return 'border-slate-300 dark:border-slate-500';
117
+ default:
118
+ return 'border-black/10 dark:border-white/10';
119
+ }
120
+ }
121
+
122
+ function getToneDotClassName(tone: CalendarDayTone | undefined): string {
123
+ switch (tone) {
124
+ case 'saved':
125
+ return 'bg-emerald-500 dark:bg-emerald-300';
126
+ case 'planned':
127
+ return 'bg-amber-500 dark:bg-amber-300';
128
+ case 'warning':
129
+ return 'bg-orange-500 dark:bg-orange-300';
130
+ case 'danger':
131
+ return 'bg-red-500 dark:bg-red-300';
132
+ case 'neutral':
133
+ return 'bg-slate-400 dark:bg-slate-300';
134
+ default:
135
+ return '';
136
+ }
137
+ }
138
+
139
+ export const CalendarStatusView = memo(function CalendarStatusView<TStateKey extends string = string>({
140
+ selectedDate,
141
+ dayStates,
142
+ action,
143
+ className,
144
+ onSelectedDateChange,
145
+ }: CalendarStatusViewProps<TStateKey>) {
146
+ const calendarMonth = useMemo(() => parseDateString(`${selectedDate.slice(0, 7)}-01`), [selectedDate]);
147
+ const monthTitle = useMemo(() => getMonthParts(calendarMonth), [calendarMonth]);
148
+ const monthDays = useMemo(() => buildMonthDays(calendarMonth), [calendarMonth]);
149
+ const today = useMemo(() => getTodayString(), []);
150
+ const {
151
+ pressedKey: pressedToolbarButton,
152
+ flash: flashToolbarButtonPress,
153
+ getPressProps: getToolbarButtonPressProps,
154
+ } = usePressFeedback<CalendarToolbarButtonKey>();
155
+
156
+ function getToolbarButtonClassName(button: CalendarToolbarButtonKey, shapeClassName: string) {
157
+ return cn(
158
+ CALENDAR_TOOLBAR_BUTTON_BASE_CLASS_NAME,
159
+ shapeClassName,
160
+ pressedToolbarButton === button
161
+ ? CALENDAR_TOOLBAR_BUTTON_PRESSED_CLASS_NAME
162
+ : CALENDAR_TOOLBAR_BUTTON_REST_CLASS_NAME,
163
+ button === 'action' && action?.disabled ? 'pointer-events-none opacity-45' : ''
164
+ );
165
+ }
166
+
167
+ const handlePreviousYear = useCallback(() => {
168
+ onSelectedDateChange(addMonthsClamped(selectedDate, -12));
169
+ }, [onSelectedDateChange, selectedDate]);
170
+ const handlePreviousMonth = useCallback(() => {
171
+ onSelectedDateChange(addMonthsClamped(selectedDate, -1));
172
+ }, [onSelectedDateChange, selectedDate]);
173
+ const handleSelectToday = useCallback(() => {
174
+ onSelectedDateChange(today);
175
+ }, [onSelectedDateChange, today]);
176
+ const handleNextMonth = useCallback(() => {
177
+ onSelectedDateChange(addMonthsClamped(selectedDate, 1));
178
+ }, [onSelectedDateChange, selectedDate]);
179
+ const handleNextYear = useCallback(() => {
180
+ onSelectedDateChange(addMonthsClamped(selectedDate, 12));
181
+ }, [onSelectedDateChange, selectedDate]);
182
+ const handleDayPress = useCallback(
183
+ (nextDate: string) => {
184
+ onSelectedDateChange(nextDate);
185
+ },
186
+ [onSelectedDateChange]
187
+ );
188
+
189
+ return (
190
+ <div
191
+ className={cn(
192
+ 'flex h-full w-full min-w-0 max-w-full flex-col overflow-hidden rounded-3xl border border-black/10 p-3 dark:border-white/10 sm:p-4 xl:self-stretch',
193
+ className
194
+ )}
195
+ >
196
+ <div className="flex h-full flex-col space-y-4">
197
+ <div className="flex items-center justify-between gap-3">
198
+ <div className="flex shrink-0 items-center">
199
+ <button
200
+ type="button"
201
+ onClick={() => {
202
+ flashToolbarButtonPress('prevYear');
203
+ handlePreviousYear();
204
+ }}
205
+ className={getToolbarButtonClassName('prevYear', 'rounded-l-full')}
206
+ {...getToolbarButtonPressProps('prevYear')}
207
+ aria-label="Previous year"
208
+ title="Previous year"
209
+ >
210
+ <ChevronsLeftIcon className="h-4 w-4" />
211
+ </button>
212
+ <button
213
+ type="button"
214
+ onClick={() => {
215
+ flashToolbarButtonPress('prevMonth');
216
+ handlePreviousMonth();
217
+ }}
218
+ className={getToolbarButtonClassName('prevMonth', '-ml-px')}
219
+ {...getToolbarButtonPressProps('prevMonth')}
220
+ aria-label="Previous month"
221
+ title="Previous month"
222
+ >
223
+ <ChevronLeftIcon className="h-4 w-4" />
224
+ </button>
225
+ <button
226
+ type="button"
227
+ onClick={() => {
228
+ flashToolbarButtonPress('today');
229
+ handleSelectToday();
230
+ }}
231
+ className={getToolbarButtonClassName('today', '-ml-px rounded-r-full')}
232
+ {...getToolbarButtonPressProps('today')}
233
+ aria-label="Select today"
234
+ title="Select today"
235
+ >
236
+ <CalendarHeartIcon className="h-4 w-4" />
237
+ </button>
238
+ </div>
239
+ <div className="min-w-0 flex-1 px-2 text-center">
240
+ <div className="text-[11px] font-semibold leading-none text-slate-500 dark:text-slate-400 sm:text-xs">
241
+ {monthTitle.year}
242
+ </div>
243
+ <div className="mt-1 truncate text-sm font-semibold leading-none text-slate-900 dark:text-white">
244
+ {monthTitle.month}
245
+ </div>
246
+ </div>
247
+ <div className="flex shrink-0 items-center">
248
+ {action ? (
249
+ <button
250
+ type="button"
251
+ onClick={() => {
252
+ if (action.disabled) {
253
+ return;
254
+ }
255
+
256
+ flashToolbarButtonPress('action');
257
+ action.onPress();
258
+ }}
259
+ className={getToolbarButtonClassName('action', 'rounded-l-full')}
260
+ {...getToolbarButtonPressProps('action')}
261
+ aria-label={action.label}
262
+ title={action.title ?? action.label}
263
+ disabled={action.disabled}
264
+ >
265
+ {action.icon}
266
+ </button>
267
+ ) : null}
268
+ <button
269
+ type="button"
270
+ onClick={() => {
271
+ flashToolbarButtonPress('nextMonth');
272
+ handleNextMonth();
273
+ }}
274
+ className={getToolbarButtonClassName('nextMonth', action ? '-ml-px' : 'rounded-l-full')}
275
+ {...getToolbarButtonPressProps('nextMonth')}
276
+ aria-label="Next month"
277
+ title="Next month"
278
+ >
279
+ <ChevronRightIcon className="h-4 w-4" />
280
+ </button>
281
+ <button
282
+ type="button"
283
+ onClick={() => {
284
+ flashToolbarButtonPress('nextYear');
285
+ handleNextYear();
286
+ }}
287
+ className={getToolbarButtonClassName('nextYear', '-ml-px rounded-r-full')}
288
+ {...getToolbarButtonPressProps('nextYear')}
289
+ aria-label="Next year"
290
+ title="Next year"
291
+ >
292
+ <ChevronsRightIcon className="h-4 w-4" />
293
+ </button>
294
+ </div>
295
+ </div>
296
+
297
+ <div className="grid grid-cols-7 gap-1 text-center text-[11px] font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
298
+ {WEEKDAY_LABELS.map((label) => (
299
+ <div key={label} className="py-1">
300
+ {label}
301
+ </div>
302
+ ))}
303
+ </div>
304
+
305
+ <div className="grid grid-cols-7 gap-1">
306
+ {monthDays.map((day) => {
307
+ const date = formatDateString(day);
308
+ const state = dayStates?.get(date);
309
+
310
+ return (
311
+ <CalendarDayButton
312
+ key={date}
313
+ date={date}
314
+ label={String(day.getUTCDate())}
315
+ currentMonth={day.getUTCMonth() === calendarMonth.getUTCMonth()}
316
+ selected={date === selectedDate}
317
+ dayState={state}
318
+ onPress={handleDayPress}
319
+ />
320
+ );
321
+ })}
322
+ </div>
323
+ </div>
324
+ </div>
325
+ );
326
+ });
327
+
328
+ const CalendarDayButton = memo(function CalendarDayButton({
329
+ date,
330
+ label,
331
+ currentMonth,
332
+ selected,
333
+ dayState,
334
+ onPress,
335
+ }: {
336
+ date: string;
337
+ label: string;
338
+ currentMonth: boolean;
339
+ selected: boolean;
340
+ dayState?: CalendarDayState;
341
+ onPress: (date: string) => void;
342
+ }) {
343
+ const tone = dayState?.tone;
344
+
345
+ return (
346
+ <button
347
+ type="button"
348
+ onClick={() => onPress(date)}
349
+ className={cn(
350
+ 'relative flex h-11 select-none items-center justify-center rounded-2xl border text-sm transition',
351
+ selected
352
+ ? 'bg-black text-white dark:bg-white dark:text-slate-950'
353
+ : 'text-slate-700 hover:bg-black/5 dark:text-slate-200 dark:hover:bg-white/5',
354
+ getToneClassName(tone, selected),
355
+ currentMonth ? '' : 'opacity-45'
356
+ )}
357
+ title={dayState?.title ?? `Open ${date}`}
358
+ >
359
+ <span>{label}</span>
360
+ {tone ? (
361
+ <span className={cn('absolute bottom-1 h-1.5 w-1.5 rounded-full', getToneDotClassName(tone))} />
362
+ ) : null}
363
+ </button>
364
+ );
365
+ });
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+
3
+ export * from './calendar-status-view';
4
+ export * from './calendar-date-range-input';
5
+ export * from './random-date-range-dialog';