cynx-lib 1.3.68 → 1.3.69
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/package.json +1 -1
- package/ui/components/DatePicker.jsx +172 -29
- package/vue/components/DatePicker.vue +1136 -978
- package/vue/components/LinearProgress.vue +268 -0
- package/vue/components/Sidebar.vue +4 -3
- package/vue/components/StatusTag.vue +60 -0
- package/vue/index.js +6 -0
package/package.json
CHANGED
|
@@ -91,6 +91,7 @@ export function DatePicker({
|
|
|
91
91
|
showSeconds = false,
|
|
92
92
|
timePlaceholder = '00:00',
|
|
93
93
|
timeProps = {},
|
|
94
|
+
range = false,
|
|
94
95
|
}) {
|
|
95
96
|
const loc = typeof locale === 'object' && locale !== null ? locale : (LOCALES[locale] || LOCALES.en);
|
|
96
97
|
const DAYS = loc.days;
|
|
@@ -98,8 +99,11 @@ export function DatePicker({
|
|
|
98
99
|
const s = SIZES[size] || SIZES.md;
|
|
99
100
|
const [open, setOpen] = useState(false);
|
|
100
101
|
const [view, setView] = useState('day'); // 'day' | 'month' | 'year'
|
|
101
|
-
const [cursor, setCursor] = useState(() =>
|
|
102
|
+
const [cursor, setCursor] = useState(() => new Date());
|
|
102
103
|
const [pos, setPos] = useState({ top: 0, left: 0, width: 0 });
|
|
104
|
+
const [rangeStart, setRangeStart] = useState(null);
|
|
105
|
+
const [rangeEnd, setRangeEnd] = useState(null);
|
|
106
|
+
const [hoverDate, setHoverDate] = useState(null);
|
|
103
107
|
const ref = useRef(null);
|
|
104
108
|
const btnRef = useRef(null);
|
|
105
109
|
const dropRef = useRef(null);
|
|
@@ -160,11 +164,31 @@ export function DatePicker({
|
|
|
160
164
|
return () => window.removeEventListener('datepicker-pos-changed', onPosChanged);
|
|
161
165
|
}, []);
|
|
162
166
|
|
|
163
|
-
// Sync cursor when value changes externally
|
|
167
|
+
// Sync cursor and range when value changes externally
|
|
164
168
|
useEffect(() => {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
if (range) {
|
|
170
|
+
if (Array.isArray(value)) {
|
|
171
|
+
const sD = toDate(value[0]);
|
|
172
|
+
const eD = toDate(value[1]);
|
|
173
|
+
setRangeStart(sD);
|
|
174
|
+
setRangeEnd(eD);
|
|
175
|
+
if (sD) setCursor(sD);
|
|
176
|
+
} else if (typeof value === 'string' && value.includes(',')) {
|
|
177
|
+
const parts = value.split(',');
|
|
178
|
+
const sD = toDate(parts[0]);
|
|
179
|
+
const eD = toDate(parts[1]);
|
|
180
|
+
setRangeStart(sD);
|
|
181
|
+
setRangeEnd(eD);
|
|
182
|
+
if (sD) setCursor(sD);
|
|
183
|
+
} else {
|
|
184
|
+
setRangeStart(null);
|
|
185
|
+
setRangeEnd(null);
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
const d = toDate(value);
|
|
189
|
+
if (d) setCursor(d);
|
|
190
|
+
}
|
|
191
|
+
}, [value, range]);
|
|
168
192
|
|
|
169
193
|
const calcPos = useCallback(() => {
|
|
170
194
|
if (!btnRef.current) return;
|
|
@@ -205,6 +229,92 @@ export function DatePicker({
|
|
|
205
229
|
setOpen(o => !o);
|
|
206
230
|
};
|
|
207
231
|
|
|
232
|
+
function formatDateStr(d) {
|
|
233
|
+
if (!d) return '';
|
|
234
|
+
const y = d.getFullYear();
|
|
235
|
+
const m = pad(d.getMonth() + 1);
|
|
236
|
+
const day = pad(d.getDate());
|
|
237
|
+
return `${y}-${m}-${day}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isRangeStartActive(d) {
|
|
241
|
+
if (!range || !rangeStart) return false;
|
|
242
|
+
if (rangeEnd) {
|
|
243
|
+
const sTime = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate()).getTime();
|
|
244
|
+
const eTime = new Date(rangeEnd.getFullYear(), rangeEnd.getMonth(), rangeEnd.getDate()).getTime();
|
|
245
|
+
const minD = sTime <= eTime ? rangeStart : rangeEnd;
|
|
246
|
+
return isSameDay(d, minD);
|
|
247
|
+
}
|
|
248
|
+
if (hoverDate) {
|
|
249
|
+
const sTime = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate()).getTime();
|
|
250
|
+
const hTime = new Date(hoverDate.getFullYear(), hoverDate.getMonth(), hoverDate.getDate()).getTime();
|
|
251
|
+
const minD = sTime <= hTime ? rangeStart : hoverDate;
|
|
252
|
+
return isSameDay(d, minD);
|
|
253
|
+
}
|
|
254
|
+
return isSameDay(d, rangeStart);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function isRangeEndActive(d) {
|
|
258
|
+
if (!range || !rangeStart) return false;
|
|
259
|
+
if (rangeEnd) {
|
|
260
|
+
const sTime = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate()).getTime();
|
|
261
|
+
const eTime = new Date(rangeEnd.getFullYear(), rangeEnd.getMonth(), rangeEnd.getDate()).getTime();
|
|
262
|
+
const maxD = sTime <= eTime ? rangeEnd : rangeStart;
|
|
263
|
+
return isSameDay(d, maxD);
|
|
264
|
+
}
|
|
265
|
+
if (hoverDate) {
|
|
266
|
+
const sTime = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate()).getTime();
|
|
267
|
+
const hTime = new Date(hoverDate.getFullYear(), hoverDate.getMonth(), hoverDate.getDate()).getTime();
|
|
268
|
+
const maxD = sTime <= hTime ? hoverDate : rangeStart;
|
|
269
|
+
return isSameDay(d, maxD);
|
|
270
|
+
}
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isRangeEndpoint(d) {
|
|
275
|
+
return isRangeStartActive(d) || isRangeEndActive(d);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function isRangeBetween(d) {
|
|
279
|
+
if (!range || !rangeStart) return false;
|
|
280
|
+
const dTime = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
281
|
+
if (rangeEnd) {
|
|
282
|
+
const sTime = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate()).getTime();
|
|
283
|
+
const eTime = new Date(rangeEnd.getFullYear(), rangeEnd.getMonth(), rangeEnd.getDate()).getTime();
|
|
284
|
+
const minT = Math.min(sTime, eTime);
|
|
285
|
+
const maxT = Math.max(sTime, eTime);
|
|
286
|
+
return dTime > minT && dTime < maxT;
|
|
287
|
+
}
|
|
288
|
+
if (hoverDate) {
|
|
289
|
+
const sTime = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate()).getTime();
|
|
290
|
+
const hTime = new Date(hoverDate.getFullYear(), hoverDate.getMonth(), hoverDate.getDate()).getTime();
|
|
291
|
+
const minT = Math.min(sTime, hTime);
|
|
292
|
+
const maxT = Math.max(sTime, hTime);
|
|
293
|
+
return dTime > minT && dTime < maxT;
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function getRangeRadius(d) {
|
|
299
|
+
if (!range) return 'var(--radxs, 6px)';
|
|
300
|
+
const isStart = isRangeStartActive(d);
|
|
301
|
+
const isEnd = isRangeEndActive(d);
|
|
302
|
+
const dayOfWeek = d.getDay();
|
|
303
|
+
const isRowStart = dayOfWeek === 1;
|
|
304
|
+
const isRowEnd = dayOfWeek === 0;
|
|
305
|
+
|
|
306
|
+
if (isStart && isEnd) return 'var(--radxs, 6px)';
|
|
307
|
+
if (isStart) return 'var(--radxs, 6px) 0 0 var(--radxs, 6px)';
|
|
308
|
+
if (isEnd) return '0 var(--radxs, 6px) var(--radxs, 6px) 0';
|
|
309
|
+
if (isRangeBetween(d)) {
|
|
310
|
+
if (isRowStart && isRowEnd) return 'var(--radxs, 6px)';
|
|
311
|
+
if (isRowStart) return 'var(--radxs, 6px) 0 0 var(--radxs, 6px)';
|
|
312
|
+
if (isRowEnd) return '0 var(--radxs, 6px) var(--radxs, 6px) 0';
|
|
313
|
+
return '0';
|
|
314
|
+
}
|
|
315
|
+
return 'var(--radxs, 6px)';
|
|
316
|
+
}
|
|
317
|
+
|
|
208
318
|
function pick(date) {
|
|
209
319
|
if (!date) return;
|
|
210
320
|
const minD = toDate(minDate);
|
|
@@ -212,6 +322,29 @@ export function DatePicker({
|
|
|
212
322
|
if (minD && date < new Date(new Date(minD).setHours(0, 0, 0, 0))) return;
|
|
213
323
|
if (maxD && date > new Date(new Date(maxD).setHours(23, 59, 59, 999))) return;
|
|
214
324
|
|
|
325
|
+
if (range) {
|
|
326
|
+
if (!rangeStart || (rangeStart && rangeEnd)) {
|
|
327
|
+
setRangeStart(date);
|
|
328
|
+
setRangeEnd(null);
|
|
329
|
+
} else {
|
|
330
|
+
let start = rangeStart;
|
|
331
|
+
let end = date;
|
|
332
|
+
if (start > end) {
|
|
333
|
+
const tmp = start;
|
|
334
|
+
start = end;
|
|
335
|
+
end = tmp;
|
|
336
|
+
}
|
|
337
|
+
setRangeStart(start);
|
|
338
|
+
setRangeEnd(end);
|
|
339
|
+
|
|
340
|
+
const result = [formatDateStr(start), formatDateStr(end)];
|
|
341
|
+
onChange?.(result);
|
|
342
|
+
setOpen(false);
|
|
343
|
+
setView('day');
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
215
348
|
if (typeof value === 'string') {
|
|
216
349
|
const y = date.getFullYear();
|
|
217
350
|
const m = pad(date.getMonth() + 1);
|
|
@@ -282,26 +415,25 @@ export function DatePicker({
|
|
|
282
415
|
|
|
283
416
|
const valDate = toDate(value);
|
|
284
417
|
const formattedTime = showTime && timeValue ? ` ${timeValue}` : '';
|
|
285
|
-
const formatted =
|
|
418
|
+
const formatted = range
|
|
419
|
+
? (rangeStart && rangeEnd ? `${formatDateStr(rangeStart)} — ${formatDateStr(rangeEnd)}` : rangeStart ? `${formatDateStr(rangeStart)} — ...` : null)
|
|
420
|
+
: valDate
|
|
286
421
|
? `${MONTHS[valDate.getMonth()]} ${valDate.getDate()}, ${valDate.getFullYear()}${formattedTime}`
|
|
287
422
|
: (typeof value === 'string' && value ? `${value}${formattedTime}` : null);
|
|
288
423
|
|
|
289
424
|
// ── Day grid ─────────────────────────────────────────────────
|
|
290
425
|
function renderDays() {
|
|
291
426
|
const y = cursor.getFullYear(), m = cursor.getMonth();
|
|
292
|
-
const first = new Date(y, m, 1).getDay();
|
|
293
|
-
const offset = first === 0 ? 6 : first === 1 ? 7 : first - 1;
|
|
427
|
+
const first = new Date(y, m, 1).getDay();
|
|
428
|
+
const offset = first === 0 ? 6 : first === 1 ? 7 : first - 1;
|
|
294
429
|
const daysInMonth = new Date(y, m + 1, 0).getDate();
|
|
295
430
|
const prevDays = new Date(y, m, 0).getDate();
|
|
296
431
|
|
|
297
432
|
const cells = [];
|
|
298
|
-
// Prev month filler
|
|
299
433
|
for (let i = offset - 1; i >= 0; i--)
|
|
300
434
|
cells.push({ day: prevDays - i, cur: false, next: false });
|
|
301
|
-
// Current month
|
|
302
435
|
for (let d = 1; d <= daysInMonth; d++)
|
|
303
436
|
cells.push({ day: d, cur: true, next: false });
|
|
304
|
-
// Next month filler — complete last row + always show at least one full next month week
|
|
305
437
|
const totalSoFar = cells.length;
|
|
306
438
|
let remaining = (7 - (totalSoFar % 7)) % 7;
|
|
307
439
|
if (remaining === 0) remaining = 7;
|
|
@@ -320,7 +452,7 @@ export function DatePicker({
|
|
|
320
452
|
))}
|
|
321
453
|
</div>
|
|
322
454
|
{/* Day cells */}
|
|
323
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap:
|
|
455
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '2px 0' }}>
|
|
324
456
|
{cells.map((c, i) => {
|
|
325
457
|
if (!c.cur) {
|
|
326
458
|
const isPrev = !c.next;
|
|
@@ -334,18 +466,22 @@ export function DatePicker({
|
|
|
334
466
|
<div
|
|
335
467
|
key={i}
|
|
336
468
|
onClick={() => { if (!dis) pick(date); }}
|
|
469
|
+
onMouseEnter={() => { if (range && rangeStart && !rangeEnd) setHoverDate(date); }}
|
|
337
470
|
style={{
|
|
338
471
|
textAlign: 'center',
|
|
339
472
|
padding: '6px 2px',
|
|
340
473
|
fontSize: '.78rem',
|
|
341
474
|
color: dis ? 'var(--border, #e5e7eb)' : 'var(--muted, #797b8d)',
|
|
342
475
|
cursor: dis ? 'not-allowed' : 'pointer',
|
|
343
|
-
borderRadius:
|
|
476
|
+
borderRadius: getRangeRadius(date),
|
|
477
|
+
background: (range ? isRangeEndpoint(date) : false)
|
|
478
|
+
? 'var(--accent, #f99e2c)'
|
|
479
|
+
: isRangeBetween(date)
|
|
480
|
+
? 'rgba(18, 103, 93, 0.14)'
|
|
481
|
+
: 'transparent',
|
|
344
482
|
transition: 'background .12s',
|
|
345
483
|
opacity: dis ? 0.35 : 0.7,
|
|
346
484
|
}}
|
|
347
|
-
onMouseEnter={e => { if (!dis) e.currentTarget.style.background = 'var(--grey-1, #f5f5f8)'; }}
|
|
348
|
-
onMouseLeave={e => { if (!dis) e.currentTarget.style.background = 'transparent'; }}
|
|
349
485
|
>
|
|
350
486
|
{c.day}
|
|
351
487
|
</div>
|
|
@@ -354,30 +490,37 @@ export function DatePicker({
|
|
|
354
490
|
const date = new Date(y, m, c.day);
|
|
355
491
|
const key = toKey(y, m, c.day);
|
|
356
492
|
const badge = badges[key];
|
|
357
|
-
const sel = isSameDay(date, value);
|
|
493
|
+
const sel = range ? isRangeEndpoint(date) : isSameDay(date, value);
|
|
358
494
|
const today = isToday(y, m, c.day);
|
|
359
495
|
const disMin = minD && date < new Date(new Date(minD).setHours(0,0,0,0));
|
|
360
496
|
const disMax = maxD && date > new Date(new Date(maxD).setHours(23,59,59,999));
|
|
361
497
|
const dis = disMin || disMax;
|
|
362
498
|
return (
|
|
363
|
-
<div
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
499
|
+
<div
|
|
500
|
+
key={i}
|
|
501
|
+
onClick={() => !dis && pick(date)}
|
|
502
|
+
onMouseEnter={() => { if (range && rangeStart && !rangeEnd) setHoverDate(date); }}
|
|
503
|
+
style={{
|
|
504
|
+
position: 'relative', textAlign: 'center', padding: '6px 2px',
|
|
505
|
+
fontSize: '.8rem', fontWeight: sel ? 700 : today ? 600 : 400,
|
|
506
|
+
borderRadius: getRangeRadius(date), cursor: dis ? 'not-allowed' : 'pointer',
|
|
507
|
+
background: sel
|
|
508
|
+
? 'var(--accent, #12675D)'
|
|
509
|
+
: isRangeBetween(date)
|
|
510
|
+
? 'rgba(18, 103, 93, 0.14)'
|
|
511
|
+
: 'transparent',
|
|
512
|
+
color: sel ? '#ffffff' : dis ? 'var(--border, #e2e8f0)' : today ? 'var(--accent, #12675D)' : 'var(--primary-text, #0f172a)',
|
|
513
|
+
border: today && !sel ? '1.5px solid var(--accent, #12675D)' : '1.5px solid transparent',
|
|
514
|
+
transition: 'background .12s, color .12s',
|
|
515
|
+
opacity: dis ? 0.35 : 1,
|
|
516
|
+
}}
|
|
517
|
+
>
|
|
375
518
|
{c.day}
|
|
376
519
|
{badge && (
|
|
377
520
|
<span style={{
|
|
378
521
|
position: 'absolute', bottom: 2, left: '50%', transform: 'translateX(-50%)',
|
|
379
522
|
width: 4, height: 4, borderRadius: '50%',
|
|
380
|
-
background: badge.color || 'var(--accent, #
|
|
523
|
+
background: badge.color || 'var(--accent, #12675D)',
|
|
381
524
|
}} />
|
|
382
525
|
)}
|
|
383
526
|
</div>
|