@windrun-huaiin/third-ui 29.2.1 → 30.0.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/dist/fuma/mdx/cheet-table.d.ts +13 -0
- package/dist/fuma/mdx/cheet-table.js +295 -0
- package/dist/fuma/mdx/cheet-table.mjs +293 -0
- package/dist/fuma/mdx/index.d.ts +1 -0
- package/dist/fuma/mdx/index.js +2 -0
- package/dist/fuma/mdx/index.mjs +1 -0
- package/dist/fuma/server/features/widgets.js +2 -0
- package/dist/fuma/server/features/widgets.mjs +2 -0
- package/dist/lib/fuma-schema-check-util.d.ts +1 -1
- package/dist/main/alert-dialog/confirm-dialog.js +1 -1
- package/dist/main/alert-dialog/confirm-dialog.mjs +2 -2
- package/dist/main/alert-dialog/dialog-loading-action.js +5 -2
- package/dist/main/alert-dialog/dialog-loading-action.mjs +5 -2
- package/dist/main/alert-dialog/dialog-styles.d.ts +4 -2
- package/dist/main/alert-dialog/dialog-styles.js +8 -4
- package/dist/main/alert-dialog/dialog-styles.mjs +7 -5
- package/dist/main/alert-dialog/high-priority-confirm-dialog.js +5 -5
- package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +6 -6
- package/dist/main/alert-dialog/info-dialog.js +1 -1
- package/dist/main/alert-dialog/info-dialog.mjs +2 -2
- package/dist/main/alert-dialog/undoable-confirm-dialog.js +2 -2
- package/dist/main/alert-dialog/undoable-confirm-dialog.mjs +3 -3
- package/dist/main/anime/anime-beam-frame.d.ts +3 -0
- package/dist/main/anime/anime-beam-frame.js +63 -0
- package/dist/main/anime/anime-beam-frame.mjs +61 -0
- package/dist/main/anime/anime-spiral-loading.d.ts +10 -0
- package/dist/main/anime/anime-spiral-loading.js +77 -0
- package/dist/main/anime/anime-spiral-loading.mjs +75 -0
- package/dist/main/anime/index.d.ts +2 -0
- package/dist/main/anime/index.js +10 -0
- package/dist/main/anime/index.mjs +3 -0
- package/dist/main/beam-frame/animate.d.ts +3 -0
- package/dist/main/beam-frame/animate.js +63 -0
- package/dist/main/beam-frame/animate.mjs +61 -0
- package/dist/main/beam-frame/beam-frame.d.ts +4 -0
- package/dist/main/beam-frame/beam-frame.js +262 -0
- package/dist/main/beam-frame/beam-frame.mjs +258 -0
- package/dist/main/beam-frame/index.d.ts +4 -0
- package/dist/main/beam-frame/index.js +11 -0
- package/dist/main/beam-frame/index.mjs +3 -0
- package/dist/main/beam-frame/motion.d.ts +3 -0
- package/dist/main/beam-frame/motion.js +61 -0
- package/dist/main/beam-frame/motion.mjs +59 -0
- package/dist/main/beam-frame/share-config.d.ts +54 -0
- package/dist/main/beam-frame/share-config.js +161 -0
- package/dist/main/beam-frame/share-config.mjs +152 -0
- package/dist/main/beam-frame-config.d.ts +54 -0
- package/dist/main/beam-frame-config.js +161 -0
- package/dist/main/beam-frame-config.mjs +152 -0
- package/dist/main/calendar/random-date-range-dialog.js +177 -51
- package/dist/main/calendar/random-date-range-dialog.mjs +178 -52
- package/dist/main/cta.js +17 -1
- package/dist/main/cta.mjs +18 -2
- package/dist/main/delayed-img.d.ts +1 -1
- package/dist/main/delayed-img.js +8 -5
- package/dist/main/delayed-img.mjs +8 -5
- package/dist/main/info-tooltip.js +70 -9
- package/dist/main/info-tooltip.mjs +70 -9
- package/dist/main/loading-frame/index.d.ts +1 -0
- package/dist/main/loading.d.ts +2 -1
- package/dist/main/loading.js +64 -26
- package/dist/main/loading.mjs +64 -26
- package/dist/main/motion/index.d.ts +1 -0
- package/dist/main/motion/index.js +9 -0
- package/dist/main/motion/index.mjs +2 -0
- package/dist/main/motion/motion-beam-frame.d.ts +3 -0
- package/dist/main/motion/motion-beam-frame.js +61 -0
- package/dist/main/motion/motion-beam-frame.mjs +59 -0
- package/dist/main/snake-loading-frame.d.ts +7 -3
- package/dist/main/snake-loading-frame.js +44 -252
- package/dist/main/snake-loading-frame.mjs +46 -254
- package/package.json +16 -5
- package/src/fuma/mdx/cheet-table.tsx +650 -0
- package/src/fuma/mdx/index.ts +1 -0
- package/src/fuma/server/features/widgets.tsx +2 -0
- package/src/main/alert-dialog/confirm-dialog.tsx +2 -1
- package/src/main/alert-dialog/dialog-loading-action.tsx +7 -5
- package/src/main/alert-dialog/dialog-styles.ts +13 -3
- package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +26 -23
- package/src/main/alert-dialog/info-dialog.tsx +2 -1
- package/src/main/alert-dialog/undoable-confirm-dialog.tsx +18 -17
- package/src/main/anime/anime-beam-frame.tsx +128 -0
- package/src/main/anime/anime-spiral-loading.tsx +123 -0
- package/src/main/anime/index.ts +9 -0
- package/src/main/beam-frame-config.tsx +341 -0
- package/src/main/calendar/random-date-range-dialog.tsx +225 -69
- package/src/main/cta.tsx +50 -21
- package/src/main/delayed-img.tsx +9 -4
- package/src/main/info-tooltip.tsx +116 -20
- package/src/main/loading-frame/index.ts +4 -0
- package/src/main/loading.tsx +75 -24
- package/src/main/motion/index.ts +8 -0
- package/src/main/motion/motion-beam-frame.tsx +137 -0
- package/src/main/snake-loading-frame.tsx +95 -496
- package/src/styles/cta.css +21 -4
- package/src/styles/third-ui.css +0 -20
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
ChevronsRightIcon,
|
|
11
11
|
XIcon,
|
|
12
12
|
} from '@windrun-huaiin/base-ui/icons';
|
|
13
|
+
import { themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
13
14
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
14
15
|
import { DialogLoadingAction, DialogActionHandler, useDialogLoadingAction } from '../alert-dialog/dialog-loading-action';
|
|
16
|
+
import { XButton } from '../buttons/x-button';
|
|
15
17
|
import { usePressFeedback } from '../buttons/use-press-feedback';
|
|
16
18
|
|
|
17
19
|
export type RandomCalendarRange = {
|
|
@@ -35,11 +37,11 @@ type QuickRangeDays = 7 | 10 | 15 | 30;
|
|
|
35
37
|
type DialogNavButtonKey = 'prevYear' | 'prevMonth' | 'nextMonth' | 'nextYear';
|
|
36
38
|
|
|
37
39
|
const DEFAULT_RANGE_DAYS = 7;
|
|
38
|
-
const MAX_RANGE_DAYS =
|
|
39
|
-
const
|
|
40
|
-
const
|
|
40
|
+
const MAX_RANGE_DAYS = 31;
|
|
41
|
+
const VISIBLE_TRACK_DAYS = 36;
|
|
42
|
+
const EDGE_OVERFLOW_PIXELS_PER_DAY = 24;
|
|
41
43
|
const DIALOG_ICON_BUTTON_CLASS_NAME =
|
|
42
|
-
'inline-flex h-8 w-8 items-center justify-center rounded-full
|
|
44
|
+
'inline-flex h-8 w-8 items-center justify-center rounded-full bg-white text-slate-500 transition duration-150 hover:bg-black/5 hover:text-slate-900 dark:bg-slate-950 dark:text-slate-300 dark:hover:bg-white/5 dark:hover:text-white';
|
|
43
45
|
const DIALOG_NAV_BUTTON_CLASS_NAME =
|
|
44
46
|
'inline-flex h-8 w-8 items-center justify-center rounded-full transition-[transform,background-color,color,box-shadow,border-color] duration-150 ease-out';
|
|
45
47
|
const DIALOG_NAV_BUTTON_REST_CLASS_NAME =
|
|
@@ -98,13 +100,38 @@ function clampWindowDays(days: number): number {
|
|
|
98
100
|
|
|
99
101
|
function buildTrackRange(referenceDate: string, windowDays = DEFAULT_RANGE_DAYS): RandomCalendarRange {
|
|
100
102
|
const resolvedWindowDays = clampWindowDays(windowDays);
|
|
101
|
-
const
|
|
102
|
-
const daysBefore = Math.floor((resolvedTotalDays - resolvedWindowDays) / 3);
|
|
103
|
+
const daysBefore = Math.floor((VISIBLE_TRACK_DAYS - resolvedWindowDays) / 3);
|
|
103
104
|
const startDate = addDays(referenceDate, -daysBefore);
|
|
104
|
-
const endDate = addDays(startDate,
|
|
105
|
+
const endDate = addDays(startDate, VISIBLE_TRACK_DAYS - 1);
|
|
105
106
|
return { startDate, endDate };
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
function ensureRangeVisibleOnTrack(range: RandomCalendarRange, bounds: RandomCalendarRange): RandomCalendarRange {
|
|
110
|
+
if (!range.startDate || !range.endDate || !bounds.startDate || !bounds.endDate) {
|
|
111
|
+
return bounds;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let nextStartDate = bounds.startDate;
|
|
115
|
+
|
|
116
|
+
if (compareDateStrings(range.startDate, nextStartDate) < 0) {
|
|
117
|
+
nextStartDate = range.startDate;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const nextEndDate = addDays(nextStartDate, VISIBLE_TRACK_DAYS - 1);
|
|
121
|
+
if (compareDateStrings(range.endDate, nextEndDate) > 0) {
|
|
122
|
+
nextStartDate = addDays(range.endDate, -(VISIBLE_TRACK_DAYS - 1));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (nextStartDate === bounds.startDate && addDays(nextStartDate, VISIBLE_TRACK_DAYS - 1) === bounds.endDate) {
|
|
126
|
+
return bounds;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
startDate: nextStartDate,
|
|
131
|
+
endDate: addDays(nextStartDate, VISIBLE_TRACK_DAYS - 1),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
108
135
|
function clampDateToRange(date: string, bounds: RandomCalendarRange): string {
|
|
109
136
|
if (!bounds.startDate || !bounds.endDate) {
|
|
110
137
|
return date;
|
|
@@ -133,6 +160,23 @@ function getDateByRatio(bounds: RandomCalendarRange, ratio: number): string {
|
|
|
133
160
|
}
|
|
134
161
|
|
|
135
162
|
const totalDays = Math.max(1, getDaysBetween(bounds.startDate, bounds.endDate));
|
|
163
|
+
return addDays(bounds.startDate, Math.round(totalDays * Math.max(0, Math.min(1, ratio))));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getDateByOverflowRatio(bounds: RandomCalendarRange, ratio: number, trackWidth: number): string {
|
|
167
|
+
if (!bounds.startDate || !bounds.endDate) {
|
|
168
|
+
return getTodayString();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const totalDays = Math.max(1, getDaysBetween(bounds.startDate, bounds.endDate));
|
|
172
|
+
if (ratio < 0) {
|
|
173
|
+
return addDays(bounds.startDate, Math.floor((ratio * trackWidth) / EDGE_OVERFLOW_PIXELS_PER_DAY));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (ratio > 1) {
|
|
177
|
+
return addDays(bounds.endDate, Math.ceil(((ratio - 1) * trackWidth) / EDGE_OVERFLOW_PIXELS_PER_DAY));
|
|
178
|
+
}
|
|
179
|
+
|
|
136
180
|
return addDays(bounds.startDate, Math.round(totalDays * ratio));
|
|
137
181
|
}
|
|
138
182
|
|
|
@@ -177,6 +221,39 @@ function getMonthEnd(value: string): string {
|
|
|
177
221
|
return formatDateString(new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)));
|
|
178
222
|
}
|
|
179
223
|
|
|
224
|
+
function RollingMonthLabel({ value }: { value: string }) {
|
|
225
|
+
const [displayValue, setDisplayValue] = useState(value);
|
|
226
|
+
const [previousValue, setPreviousValue] = useState<string | null>(null);
|
|
227
|
+
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (value === displayValue) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
setPreviousValue(displayValue);
|
|
234
|
+
setDisplayValue(value);
|
|
235
|
+
|
|
236
|
+
const timeout = window.setTimeout(() => {
|
|
237
|
+
setPreviousValue(null);
|
|
238
|
+
}, 180);
|
|
239
|
+
|
|
240
|
+
return () => window.clearTimeout(timeout);
|
|
241
|
+
}, [displayValue, value]);
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<span className="relative inline-block h-5 min-w-10 overflow-hidden align-bottom">
|
|
245
|
+
{previousValue ? (
|
|
246
|
+
<span className="rd-date-range-month-out absolute inset-x-0 top-0 text-center">
|
|
247
|
+
{previousValue}
|
|
248
|
+
</span>
|
|
249
|
+
) : null}
|
|
250
|
+
<span className={cn('absolute inset-x-0 top-0 text-center', previousValue && 'rd-date-range-month-in')}>
|
|
251
|
+
{displayValue}
|
|
252
|
+
</span>
|
|
253
|
+
</span>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
180
257
|
export function RandomDateRangeDialog({
|
|
181
258
|
open,
|
|
182
259
|
value,
|
|
@@ -209,6 +286,8 @@ export function RandomDateRangeDialog({
|
|
|
209
286
|
const resultLabelRef = useRef<HTMLDivElement | null>(null);
|
|
210
287
|
const selectionDaysRef = useRef<HTMLDivElement | null>(null);
|
|
211
288
|
const dragPreviewRef = useRef<RandomCalendarRange | null>(null);
|
|
289
|
+
const trackBoundsRef = useRef<RandomCalendarRange>(trackBounds);
|
|
290
|
+
const dragStartTrackBoundsRef = useRef<RandomCalendarRange | null>(null);
|
|
212
291
|
const frameRef = useRef<number | null>(null);
|
|
213
292
|
const pendingClientXRef = useRef<number | null>(null);
|
|
214
293
|
const syncPreviewDomRef = useRef<(range: RandomCalendarRange) => void>(() => {});
|
|
@@ -226,28 +305,30 @@ export function RandomDateRangeDialog({
|
|
|
226
305
|
const isSingleDay = (draftRange.startDate ?? null) === (draftRange.endDate ?? null);
|
|
227
306
|
const startHandlePercent = isSingleDay ? Math.max(leftPercent - 0.8, 0) : leftPercent;
|
|
228
307
|
const endHandlePercent = isSingleDay ? Math.min(rightPercent + 0.8, 100) : rightPercent;
|
|
229
|
-
const trackTickCount =
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
.filter((item): item is string => Boolean(item))
|
|
233
|
-
.map((item) => formatMonthShort(item));
|
|
234
|
-
|
|
235
|
-
return [...new Set(values)];
|
|
236
|
-
}, [trackBounds.endDate, trackBounds.startDate]);
|
|
308
|
+
const trackTickCount = VISIBLE_TRACK_DAYS;
|
|
309
|
+
const leftMonthLabel = formatMonthShort(trackBounds.startDate ?? baseReferenceDate);
|
|
310
|
+
const rightMonthLabel = formatMonthShort(trackBounds.endDate ?? baseReferenceDate);
|
|
237
311
|
|
|
238
312
|
const handleApply = useCallback<DialogActionHandler>(() => {
|
|
239
313
|
return onApply(draftRange);
|
|
240
314
|
}, [draftRange, onApply]);
|
|
241
315
|
|
|
316
|
+
function commitTrackBounds(nextTrackBounds: RandomCalendarRange) {
|
|
317
|
+
trackBoundsRef.current = nextTrackBounds;
|
|
318
|
+
setTrackBounds(nextTrackBounds);
|
|
319
|
+
}
|
|
320
|
+
|
|
242
321
|
useEffect(() => {
|
|
243
322
|
if (open && !previousOpenRef.current) {
|
|
323
|
+
const nextTrackBounds = buildTrackRange(baseReferenceDate, resolvedDefaultRangeDays);
|
|
244
324
|
const nextRange = {
|
|
245
325
|
startDate: baseReferenceDate,
|
|
246
326
|
endDate: addDays(baseReferenceDate, resolvedDefaultRangeDays - 1),
|
|
247
327
|
};
|
|
248
328
|
setDraftRange(nextRange);
|
|
249
329
|
setReferenceDate(baseReferenceDate);
|
|
250
|
-
|
|
330
|
+
trackBoundsRef.current = nextTrackBounds;
|
|
331
|
+
setTrackBounds(nextTrackBounds);
|
|
251
332
|
setWindowDays(resolvedDefaultRangeDays);
|
|
252
333
|
dragStartRangeRef.current = null;
|
|
253
334
|
dragModeRef.current = null;
|
|
@@ -266,16 +347,19 @@ export function RandomDateRangeDialog({
|
|
|
266
347
|
setReferenceDate(nextReferenceDate);
|
|
267
348
|
setWindowDays(clampedWindowDays);
|
|
268
349
|
setDraftRange(nextRange);
|
|
269
|
-
if (
|
|
270
|
-
|
|
350
|
+
if (options?.preserveTrack) {
|
|
351
|
+
commitTrackBounds(ensureRangeVisibleOnTrack(nextRange, trackBoundsRef.current));
|
|
352
|
+
} else {
|
|
353
|
+
commitTrackBounds(buildTrackRange(nextReferenceDate, clampedWindowDays));
|
|
271
354
|
}
|
|
272
355
|
}
|
|
273
356
|
|
|
274
357
|
const getPreviewPercents = useCallback((range: RandomCalendarRange) => {
|
|
275
358
|
const start = range.startDate ?? baseReferenceDate;
|
|
276
359
|
const end = range.endDate ?? start;
|
|
277
|
-
const
|
|
278
|
-
const
|
|
360
|
+
const currentTrackBounds = trackBoundsRef.current;
|
|
361
|
+
const startR = getRatioByDate(start, currentTrackBounds);
|
|
362
|
+
const endR = getRatioByDate(end, currentTrackBounds);
|
|
279
363
|
const left = Math.min(startR, endR) * 100;
|
|
280
364
|
const right = Math.max(startR, endR) * 100;
|
|
281
365
|
const width = Math.max(right - left, 0.5);
|
|
@@ -288,7 +372,7 @@ export function RandomDateRangeDialog({
|
|
|
288
372
|
startHandle: single ? Math.max(left - 0.8, 0) : left,
|
|
289
373
|
endHandle: single ? Math.min(right + 0.8, 100) : right,
|
|
290
374
|
};
|
|
291
|
-
}, [baseReferenceDate
|
|
375
|
+
}, [baseReferenceDate]);
|
|
292
376
|
|
|
293
377
|
const syncPreviewDom = useCallback((range: RandomCalendarRange) => {
|
|
294
378
|
const percents = getPreviewPercents(range);
|
|
@@ -315,19 +399,23 @@ export function RandomDateRangeDialog({
|
|
|
315
399
|
syncPreviewDom(draftRange);
|
|
316
400
|
}, [draftRange, syncPreviewDom]);
|
|
317
401
|
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
trackBoundsRef.current = trackBounds;
|
|
404
|
+
}, [trackBounds]);
|
|
405
|
+
|
|
318
406
|
function resetReferenceFromClientX(clientX: number) {
|
|
319
407
|
if (!trackRef.current) {
|
|
320
408
|
return;
|
|
321
409
|
}
|
|
322
410
|
|
|
323
411
|
const rect = trackRef.current.getBoundingClientRect();
|
|
324
|
-
const ratio =
|
|
412
|
+
const ratio = (clientX - rect.left) / rect.width;
|
|
325
413
|
const nextReferenceDate = getDateByRatio(trackBounds, ratio);
|
|
326
414
|
updateRangeByReference(nextReferenceDate, resolvedDefaultRangeDays, { preserveTrack: true });
|
|
327
415
|
}
|
|
328
416
|
|
|
329
417
|
function applyQuickRange(dayCount: QuickRangeDays) {
|
|
330
|
-
updateRangeByReference(referenceDate, dayCount);
|
|
418
|
+
updateRangeByReference(referenceDate, dayCount, { preserveTrack: true });
|
|
331
419
|
}
|
|
332
420
|
|
|
333
421
|
function shiftReferenceDateByMonths(monthOffset: number) {
|
|
@@ -344,6 +432,7 @@ export function RandomDateRangeDialog({
|
|
|
344
432
|
pointerIdRef.current = pointerId;
|
|
345
433
|
dragStartRangeRef.current = { ...draftRange };
|
|
346
434
|
dragPreviewRef.current = { ...draftRange };
|
|
435
|
+
dragStartTrackBoundsRef.current = { ...trackBoundsRef.current };
|
|
347
436
|
|
|
348
437
|
if (
|
|
349
438
|
mode === 'window' &&
|
|
@@ -351,12 +440,12 @@ export function RandomDateRangeDialog({
|
|
|
351
440
|
trackRef.current &&
|
|
352
441
|
draftRange.startDate &&
|
|
353
442
|
draftRange.endDate &&
|
|
354
|
-
|
|
355
|
-
|
|
443
|
+
dragStartTrackBoundsRef.current.startDate &&
|
|
444
|
+
dragStartTrackBoundsRef.current.endDate
|
|
356
445
|
) {
|
|
357
446
|
const rect = trackRef.current.getBoundingClientRect();
|
|
358
|
-
const ratio =
|
|
359
|
-
const pointerDate = getDateByRatio(
|
|
447
|
+
const ratio = (clientX - rect.left) / rect.width;
|
|
448
|
+
const pointerDate = getDateByRatio(dragStartTrackBoundsRef.current, ratio);
|
|
360
449
|
windowDragOffsetDaysRef.current = getDaysBetween(draftRange.startDate, pointerDate);
|
|
361
450
|
} else {
|
|
362
451
|
windowDragOffsetDaysRef.current = 0;
|
|
@@ -364,13 +453,23 @@ export function RandomDateRangeDialog({
|
|
|
364
453
|
}
|
|
365
454
|
|
|
366
455
|
const buildDraggedRange = useCallback((clientX: number): RandomCalendarRange | null => {
|
|
367
|
-
|
|
456
|
+
const currentTrackBounds = trackBoundsRef.current;
|
|
457
|
+
const dragStartTrackBounds = dragStartTrackBoundsRef.current;
|
|
458
|
+
if (
|
|
459
|
+
!dragModeRef.current ||
|
|
460
|
+
!dragStartRangeRef.current ||
|
|
461
|
+
!dragStartTrackBounds?.startDate ||
|
|
462
|
+
!dragStartTrackBounds.endDate ||
|
|
463
|
+
!currentTrackBounds.startDate ||
|
|
464
|
+
!currentTrackBounds.endDate ||
|
|
465
|
+
!trackRef.current
|
|
466
|
+
) {
|
|
368
467
|
return null;
|
|
369
468
|
}
|
|
370
469
|
|
|
371
470
|
const rect = trackRef.current.getBoundingClientRect();
|
|
372
|
-
const ratio =
|
|
373
|
-
const pointerDate =
|
|
471
|
+
const ratio = (clientX - rect.left) / rect.width;
|
|
472
|
+
const pointerDate = getDateByOverflowRatio(dragStartTrackBounds, ratio, rect.width);
|
|
374
473
|
const currentRange = dragStartRangeRef.current;
|
|
375
474
|
|
|
376
475
|
if (!currentRange.startDate || !currentRange.endDate) {
|
|
@@ -381,24 +480,42 @@ export function RandomDateRangeDialog({
|
|
|
381
480
|
const earliestStart = addDays(currentRange.endDate, -(MAX_RANGE_DAYS - 1));
|
|
382
481
|
const boundedPointerDate = compareDateStrings(pointerDate, earliestStart) < 0 ? earliestStart : pointerDate;
|
|
383
482
|
const nextStart = compareDateStrings(boundedPointerDate, currentRange.endDate) > 0 ? currentRange.endDate : boundedPointerDate;
|
|
384
|
-
|
|
483
|
+
const nextRange = { startDate: nextStart, endDate: currentRange.endDate };
|
|
484
|
+
const nextTrackBounds = ensureRangeVisibleOnTrack(nextRange, currentTrackBounds);
|
|
485
|
+
if (nextTrackBounds !== currentTrackBounds) {
|
|
486
|
+
trackBoundsRef.current = nextTrackBounds;
|
|
487
|
+
setDraftRange(nextRange);
|
|
488
|
+
setTrackBounds(nextTrackBounds);
|
|
489
|
+
}
|
|
490
|
+
return nextRange;
|
|
385
491
|
}
|
|
386
492
|
|
|
387
493
|
if (dragModeRef.current === 'end') {
|
|
388
494
|
const latestEnd = addDays(currentRange.startDate, MAX_RANGE_DAYS - 1);
|
|
389
495
|
const boundedPointerDate = compareDateStrings(pointerDate, latestEnd) > 0 ? latestEnd : pointerDate;
|
|
390
496
|
const nextEnd = compareDateStrings(boundedPointerDate, currentRange.startDate) < 0 ? currentRange.startDate : boundedPointerDate;
|
|
391
|
-
|
|
497
|
+
const nextRange = { startDate: currentRange.startDate, endDate: nextEnd };
|
|
498
|
+
const nextTrackBounds = ensureRangeVisibleOnTrack(nextRange, currentTrackBounds);
|
|
499
|
+
if (nextTrackBounds !== currentTrackBounds) {
|
|
500
|
+
trackBoundsRef.current = nextTrackBounds;
|
|
501
|
+
setDraftRange(nextRange);
|
|
502
|
+
setTrackBounds(nextTrackBounds);
|
|
503
|
+
}
|
|
504
|
+
return nextRange;
|
|
392
505
|
}
|
|
393
506
|
|
|
394
507
|
const spanDays = getDaysBetween(currentRange.startDate, currentRange.endDate);
|
|
395
|
-
const nextStart =
|
|
396
|
-
startDate: trackBounds.startDate,
|
|
397
|
-
endDate: addDays(trackBounds.endDate, -spanDays),
|
|
398
|
-
});
|
|
508
|
+
const nextStart = addDays(pointerDate, -windowDragOffsetDaysRef.current);
|
|
399
509
|
const nextEnd = addDays(nextStart, spanDays);
|
|
400
|
-
|
|
401
|
-
|
|
510
|
+
const nextRange = { startDate: nextStart, endDate: nextEnd };
|
|
511
|
+
const nextTrackBounds = ensureRangeVisibleOnTrack(nextRange, currentTrackBounds);
|
|
512
|
+
if (nextTrackBounds !== currentTrackBounds) {
|
|
513
|
+
trackBoundsRef.current = nextTrackBounds;
|
|
514
|
+
setDraftRange(nextRange);
|
|
515
|
+
setTrackBounds(nextTrackBounds);
|
|
516
|
+
}
|
|
517
|
+
return nextRange;
|
|
518
|
+
}, []);
|
|
402
519
|
|
|
403
520
|
useEffect(() => {
|
|
404
521
|
syncPreviewDomRef.current = syncPreviewDom;
|
|
@@ -415,6 +532,7 @@ export function RandomDateRangeDialog({
|
|
|
415
532
|
|
|
416
533
|
const nextRange = dragPreviewRef.current;
|
|
417
534
|
dragStartRangeRef.current = null;
|
|
535
|
+
dragStartTrackBoundsRef.current = null;
|
|
418
536
|
dragModeRef.current = null;
|
|
419
537
|
pointerIdRef.current = null;
|
|
420
538
|
windowDragOffsetDaysRef.current = 0;
|
|
@@ -422,6 +540,7 @@ export function RandomDateRangeDialog({
|
|
|
422
540
|
setDraftRange(nextRange);
|
|
423
541
|
setReferenceDate(nextRange.startDate);
|
|
424
542
|
setWindowDays(getInclusiveDayCount(nextRange));
|
|
543
|
+
commitTrackBounds(ensureRangeVisibleOnTrack(nextRange, trackBoundsRef.current));
|
|
425
544
|
}
|
|
426
545
|
}
|
|
427
546
|
|
|
@@ -490,9 +609,9 @@ export function RandomDateRangeDialog({
|
|
|
490
609
|
<div className="fixed inset-0 z-120 flex select-none items-center justify-center bg-slate-950/60 px-3 py-6 backdrop-blur-sm">
|
|
491
610
|
<div className="w-full max-w-2xl overflow-hidden rounded-3xl border border-black/10 bg-white shadow-2xl dark:border-white/10 dark:bg-slate-950">
|
|
492
611
|
<div className="space-y-5 p-4">
|
|
493
|
-
<div className="relative flex items-center justify-center px-
|
|
494
|
-
<div ref={resultLabelRef} className="select-none text-base font-semibold text-slate-900 dark:text-white">{getRangeLabel(draftRange)}</div>
|
|
495
|
-
<div className="absolute right-0 top-1/2 flex -translate-y-1/2 items-center
|
|
612
|
+
<div className="relative flex items-center justify-center px-9 text-center sm:px-16">
|
|
613
|
+
<div ref={resultLabelRef} className="min-w-0 select-none truncate text-base font-semibold text-slate-900 dark:text-white">{getRangeLabel(draftRange)}</div>
|
|
614
|
+
<div className="absolute right-0 top-1/2 flex -translate-y-1/2 translate-x-1 items-center sm:translate-x-0">
|
|
496
615
|
<button
|
|
497
616
|
type="button"
|
|
498
617
|
onClick={() => onOpenChange(false)}
|
|
@@ -501,25 +620,10 @@ export function RandomDateRangeDialog({
|
|
|
501
620
|
>
|
|
502
621
|
<XIcon className="h-4 w-4" />
|
|
503
622
|
</button>
|
|
504
|
-
<button
|
|
505
|
-
type="button"
|
|
506
|
-
onClick={() => {
|
|
507
|
-
void runDialogAction('confirm', handleApply);
|
|
508
|
-
}}
|
|
509
|
-
disabled={!draftRange.startDate || !draftRange.endDate}
|
|
510
|
-
className={cn(
|
|
511
|
-
DIALOG_ICON_BUTTON_CLASS_NAME,
|
|
512
|
-
'text-slate-700 dark:text-slate-100',
|
|
513
|
-
'disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:border-black/10 disabled:hover:bg-white disabled:hover:text-slate-700 dark:disabled:hover:border-white/10 dark:disabled:hover:bg-slate-950 dark:disabled:hover:text-slate-100'
|
|
514
|
-
)}
|
|
515
|
-
aria-label="Apply"
|
|
516
|
-
>
|
|
517
|
-
<CheckCheckIcon className="h-4 w-4" />
|
|
518
|
-
</button>
|
|
519
623
|
</div>
|
|
520
624
|
</div>
|
|
521
625
|
|
|
522
|
-
<div className="space-y-
|
|
626
|
+
<div className="space-y-3">
|
|
523
627
|
<div className="flex items-center justify-between gap-2">
|
|
524
628
|
<div className="flex items-center gap-1">
|
|
525
629
|
<button
|
|
@@ -569,14 +673,14 @@ export function RandomDateRangeDialog({
|
|
|
569
673
|
endDate: addDays(baseReferenceDate, resolvedDefaultRangeDays - 1),
|
|
570
674
|
};
|
|
571
675
|
setReferenceDate(baseReferenceDate);
|
|
572
|
-
|
|
676
|
+
commitTrackBounds(buildTrackRange(baseReferenceDate, resolvedDefaultRangeDays));
|
|
573
677
|
setWindowDays(resolvedDefaultRangeDays);
|
|
574
678
|
setDraftRange(nextRange);
|
|
575
679
|
onClear?.(nextRange);
|
|
576
680
|
}}
|
|
577
681
|
className={DIALOG_PILL_BUTTON_CLASS_NAME}
|
|
578
682
|
>
|
|
579
|
-
|
|
683
|
+
Today
|
|
580
684
|
</button>
|
|
581
685
|
<button
|
|
582
686
|
type="button"
|
|
@@ -594,7 +698,8 @@ export function RandomDateRangeDialog({
|
|
|
594
698
|
};
|
|
595
699
|
setDraftRange(normalizedRange);
|
|
596
700
|
setWindowDays(getInclusiveDayCount(normalizedRange));
|
|
597
|
-
|
|
701
|
+
setReferenceDate(normalizedRange.startDate);
|
|
702
|
+
commitTrackBounds(buildTrackRange(normalizedRange.startDate, getInclusiveDayCount(normalizedRange)));
|
|
598
703
|
}}
|
|
599
704
|
className={DIALOG_PILL_BUTTON_CLASS_NAME}
|
|
600
705
|
>
|
|
@@ -642,10 +747,10 @@ export function RandomDateRangeDialog({
|
|
|
642
747
|
</div>
|
|
643
748
|
</div>
|
|
644
749
|
|
|
645
|
-
<div className="relative h-
|
|
750
|
+
<div className="relative h-21">
|
|
646
751
|
<div className="absolute inset-x-0 top-0 grid grid-cols-[3.5rem_minmax(0,1fr)_3.5rem] items-center gap-2 text-sm font-semibold text-slate-500 dark:text-slate-400">
|
|
647
752
|
<span className="relative block select-none text-center">
|
|
648
|
-
{
|
|
753
|
+
<RollingMonthLabel value={leftMonthLabel} />
|
|
649
754
|
<span className="pointer-events-none absolute left-1/2 top-7 h-2.5 w-2.5 -translate-x-1/2 rounded-full bg-slate-400 dark:bg-slate-500" />
|
|
650
755
|
<span className="pointer-events-none absolute left-1/2 top-[1.95rem] h-9 w-0.5 -translate-x-1/2 bg-slate-400 dark:bg-slate-500" />
|
|
651
756
|
</span>
|
|
@@ -667,7 +772,7 @@ export function RandomDateRangeDialog({
|
|
|
667
772
|
))}
|
|
668
773
|
</div>
|
|
669
774
|
<span className="relative block select-none text-center">
|
|
670
|
-
{
|
|
775
|
+
<RollingMonthLabel value={rightMonthLabel} />
|
|
671
776
|
<span className="pointer-events-none absolute right-1/2 top-7 h-2.5 w-2.5 translate-x-1/2 rounded-full bg-slate-400 dark:bg-slate-500" />
|
|
672
777
|
<span className="pointer-events-none absolute right-1/2 top-[1.95rem] h-9 w-0.5 translate-x-1/2 bg-slate-400 dark:bg-slate-500" />
|
|
673
778
|
</span>
|
|
@@ -704,8 +809,8 @@ export function RandomDateRangeDialog({
|
|
|
704
809
|
</div>
|
|
705
810
|
<div
|
|
706
811
|
ref={selectionRef}
|
|
707
|
-
className="absolute top-1/2 z-10 h-4 touch-none -translate-y-1/2 overflow-visible rounded-md border
|
|
708
|
-
style={{ left: `${leftPercent}%`, width: `${widthPercent}
|
|
812
|
+
className="absolute top-1/2 z-10 h-4 touch-none -translate-y-1/2 overflow-visible rounded-md border bg-white dark:bg-slate-950"
|
|
813
|
+
style={{ left: `${leftPercent}%`, width: `${widthPercent}%`, borderColor: themeSvgIconColor }}
|
|
709
814
|
onPointerDown={(event) => {
|
|
710
815
|
event.stopPropagation();
|
|
711
816
|
beginDrag('window', event.pointerId, event.clientX);
|
|
@@ -719,8 +824,8 @@ export function RandomDateRangeDialog({
|
|
|
719
824
|
<button
|
|
720
825
|
ref={startHandleRef}
|
|
721
826
|
type="button"
|
|
722
|
-
className="absolute top-1/2 z-20 h-6 w-6 touch-none -translate-x-1/2 -translate-y-1/2 rounded-full border
|
|
723
|
-
style={{ left: `${startHandlePercent}
|
|
827
|
+
className="absolute top-1/2 z-20 h-6 w-6 touch-none -translate-x-1/2 -translate-y-1/2 rounded-full border bg-white shadow-sm dark:bg-slate-950"
|
|
828
|
+
style={{ left: `${startHandlePercent}%`, borderColor: themeSvgIconColor }}
|
|
724
829
|
onPointerDown={(event) => {
|
|
725
830
|
event.stopPropagation();
|
|
726
831
|
beginDrag('start', event.pointerId);
|
|
@@ -730,8 +835,8 @@ export function RandomDateRangeDialog({
|
|
|
730
835
|
<button
|
|
731
836
|
ref={endHandleRef}
|
|
732
837
|
type="button"
|
|
733
|
-
className="absolute top-1/2 z-20 h-6 w-6 touch-none -translate-x-1/2 -translate-y-1/2 rounded-full border
|
|
734
|
-
style={{ left: `${endHandlePercent}
|
|
838
|
+
className="absolute top-1/2 z-20 h-6 w-6 touch-none -translate-x-1/2 -translate-y-1/2 rounded-full border bg-white shadow-sm dark:bg-slate-950"
|
|
839
|
+
style={{ left: `${endHandlePercent}%`, borderColor: themeSvgIconColor }}
|
|
735
840
|
onPointerDown={(event) => {
|
|
736
841
|
event.stopPropagation();
|
|
737
842
|
beginDrag('end', event.pointerId);
|
|
@@ -742,10 +847,61 @@ export function RandomDateRangeDialog({
|
|
|
742
847
|
</div>
|
|
743
848
|
</div>
|
|
744
849
|
|
|
850
|
+
<div className="flex justify-end">
|
|
851
|
+
<XButton
|
|
852
|
+
type="single"
|
|
853
|
+
variant="soft"
|
|
854
|
+
minWidth="min-w-[110px]"
|
|
855
|
+
className="w-auto"
|
|
856
|
+
iconClassName="h-4 w-4"
|
|
857
|
+
button={{
|
|
858
|
+
icon: <CheckCheckIcon />,
|
|
859
|
+
text: 'Apply',
|
|
860
|
+
disabled: !draftRange.startDate || !draftRange.endDate,
|
|
861
|
+
onClick: () => {
|
|
862
|
+
void runDialogAction('confirm', handleApply);
|
|
863
|
+
},
|
|
864
|
+
}}
|
|
865
|
+
/>
|
|
866
|
+
</div>
|
|
867
|
+
|
|
745
868
|
</div>
|
|
746
869
|
</div>
|
|
747
870
|
</div>
|
|
748
871
|
</div>
|
|
872
|
+
<style>
|
|
873
|
+
{`
|
|
874
|
+
@keyframes rd-date-range-month-in {
|
|
875
|
+
from {
|
|
876
|
+
opacity: 0;
|
|
877
|
+
transform: translateY(-0.45rem);
|
|
878
|
+
}
|
|
879
|
+
to {
|
|
880
|
+
opacity: 1;
|
|
881
|
+
transform: translateY(0);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
@keyframes rd-date-range-month-out {
|
|
886
|
+
from {
|
|
887
|
+
opacity: 1;
|
|
888
|
+
transform: translateY(0);
|
|
889
|
+
}
|
|
890
|
+
to {
|
|
891
|
+
opacity: 0;
|
|
892
|
+
transform: translateY(0.45rem);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.rd-date-range-month-in {
|
|
897
|
+
animation: rd-date-range-month-in 180ms ease-out both;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
.rd-date-range-month-out {
|
|
901
|
+
animation: rd-date-range-month-out 180ms ease-out both;
|
|
902
|
+
}
|
|
903
|
+
`}
|
|
904
|
+
</style>
|
|
749
905
|
{dialogLoading}
|
|
750
906
|
</>,
|
|
751
907
|
document.body
|
package/src/main/cta.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { getTranslations } from 'next-intl/server';
|
|
2
2
|
import { GradientButton } from "./buttons";
|
|
3
3
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
4
|
-
import { themeIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
4
|
+
import { themeIconColor, themeName, themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
5
5
|
import { richText } from './rich-text-expert';
|
|
6
6
|
import { responsiveSection } from './section-layout';
|
|
7
|
+
import type { CSSProperties } from 'react';
|
|
7
8
|
|
|
8
9
|
interface CTAData {
|
|
9
10
|
title: string;
|
|
@@ -14,6 +15,29 @@ interface CTAData {
|
|
|
14
15
|
url: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
type CTAThemePalette = {
|
|
19
|
+
b: string;
|
|
20
|
+
c: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const CTA_THEME_PALETTES: Record<string, CTAThemePalette> = {
|
|
24
|
+
purple: { b: '#EC4899', c: '#6366F1' },
|
|
25
|
+
orange: { b: '#F59E0B', c: '#EF4444' },
|
|
26
|
+
indigo: { b: '#3B82F6', c: '#06B6D4' },
|
|
27
|
+
emerald: { b: '#14B8A6', c: '#22C55E' },
|
|
28
|
+
rose: { b: '#EC4899', c: '#FB7185' },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function createCTAStyle(): CSSProperties {
|
|
32
|
+
const palette = CTA_THEME_PALETTES[themeName] ?? CTA_THEME_PALETTES.purple;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
'--cta-color-a': themeSvgIconColor,
|
|
36
|
+
'--cta-color-b': palette.b,
|
|
37
|
+
'--cta-color-c': palette.c,
|
|
38
|
+
} as CSSProperties;
|
|
39
|
+
}
|
|
40
|
+
|
|
17
41
|
export async function CTA({
|
|
18
42
|
locale,
|
|
19
43
|
sectionClassName
|
|
@@ -34,26 +58,31 @@ export async function CTA({
|
|
|
34
58
|
|
|
35
59
|
return (
|
|
36
60
|
<section id="cta" className={cn(responsiveSection, sectionClassName)}>
|
|
37
|
-
<div
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
<div
|
|
62
|
+
className="
|
|
63
|
+
third-ui-cta-surface
|
|
64
|
+
relative overflow-hidden rounded-2xl border border-black/5 py-3 text-center shadow-sm
|
|
65
|
+
animate-cta-gradient-wave
|
|
66
|
+
sm:py-6 md:py-8
|
|
67
|
+
dark:border-white/10 dark:shadow-none
|
|
68
|
+
"
|
|
69
|
+
style={createCTAStyle()}
|
|
70
|
+
>
|
|
71
|
+
<div className="relative z-10 px-4 sm:px-6">
|
|
72
|
+
<h2 className="mb-6 text-3xl font-bold text-neutral-950 md:text-4xl dark:text-neutral-50">
|
|
73
|
+
{data.title} <span className={themeIconColor}>{data.eyesOn}</span>?
|
|
74
|
+
</h2>
|
|
75
|
+
<p className="mx-auto mb-8 max-w-3xl text-base text-neutral-700 sm:text-xl dark:text-neutral-300">
|
|
76
|
+
{data.description1}
|
|
77
|
+
<br />
|
|
78
|
+
<span className={cn(themeIconColor, "text-xl sm:text-2xl")}>{data.description2}</span>
|
|
79
|
+
</p>
|
|
80
|
+
<GradientButton
|
|
81
|
+
title={data.button}
|
|
82
|
+
href={data.url}
|
|
83
|
+
align="center"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
57
86
|
</div>
|
|
58
87
|
</section>
|
|
59
88
|
)
|
package/src/main/delayed-img.tsx
CHANGED
|
@@ -25,12 +25,13 @@ export function DelayedImg({
|
|
|
25
25
|
wrapperClassName,
|
|
26
26
|
placeholderClassName,
|
|
27
27
|
className,
|
|
28
|
+
onError,
|
|
28
29
|
onLoad,
|
|
29
30
|
...imageProps
|
|
30
31
|
}: DelayedImgProps) {
|
|
31
32
|
const shouldDelay = ENV_DELAY_ENABLED && ENV_DELAY_MS > 0
|
|
32
33
|
const [isMounted, setIsMounted] = useState(!shouldDelay)
|
|
33
|
-
const [
|
|
34
|
+
const [isSettled, setIsSettled] = useState(false)
|
|
34
35
|
|
|
35
36
|
useEffect(() => {
|
|
36
37
|
if (!shouldDelay || isMounted) {
|
|
@@ -46,7 +47,7 @@ export function DelayedImg({
|
|
|
46
47
|
|
|
47
48
|
return (
|
|
48
49
|
<div className={cn("relative", wrapperClassName)}>
|
|
49
|
-
{(!isMounted || !
|
|
50
|
+
{(!isMounted || !isSettled) && (
|
|
50
51
|
<SnakeLoadingFrame
|
|
51
52
|
shape="rounded-rect"
|
|
52
53
|
loading
|
|
@@ -68,13 +69,17 @@ export function DelayedImg({
|
|
|
68
69
|
<Image
|
|
69
70
|
{...imageProps}
|
|
70
71
|
alt={alt}
|
|
72
|
+
onError={(event) => {
|
|
73
|
+
setIsSettled(true)
|
|
74
|
+
onError?.(event)
|
|
75
|
+
}}
|
|
71
76
|
onLoad={(event) => {
|
|
72
|
-
|
|
77
|
+
setIsSettled(true)
|
|
73
78
|
onLoad?.(event)
|
|
74
79
|
}}
|
|
75
80
|
className={cn(
|
|
76
81
|
"transition duration-300",
|
|
77
|
-
|
|
82
|
+
isSettled ? "opacity-100" : "opacity-0",
|
|
78
83
|
className,
|
|
79
84
|
)}
|
|
80
85
|
/>
|