cynx-lib 1.3.67 → 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 +335 -52
- package/vue/components/Badge.vue +8 -0
- package/vue/components/DatePicker.vue +1136 -0
- 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 +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useRef, useEffect, useLayoutEffect, useCallback } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
|
-
import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react';
|
|
3
|
+
import { ChevronLeft, ChevronRight, Calendar, Clock } from 'lucide-react';
|
|
4
|
+
import { TimePicker } from './TimePicker.jsx';
|
|
4
5
|
|
|
5
6
|
// DatePicker — reusable date picker component
|
|
6
7
|
// Props:
|
|
@@ -12,9 +13,28 @@ import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react';
|
|
|
12
13
|
// maxDate – Date object, days after this are disabled
|
|
13
14
|
// disabled – boolean
|
|
14
15
|
// style – container style overrides
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// showTime – boolean, enables integrated TimePicker in dropdown
|
|
17
|
+
// timeValue – string, time string e.g. "14:30"
|
|
18
|
+
// onTimeChange – function(timeString)
|
|
19
|
+
// timeFormat – "24h" | "12h"
|
|
20
|
+
// showSeconds – boolean
|
|
21
|
+
|
|
22
|
+
const LOCALES = {
|
|
23
|
+
en: {
|
|
24
|
+
days: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
|
|
25
|
+
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
|
26
|
+
today: 'Today',
|
|
27
|
+
time: 'Time:',
|
|
28
|
+
ok: 'OK',
|
|
29
|
+
},
|
|
30
|
+
ru: {
|
|
31
|
+
days: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'],
|
|
32
|
+
months: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
|
|
33
|
+
today: 'Сегодня',
|
|
34
|
+
time: 'Время:',
|
|
35
|
+
ok: 'OK',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
18
38
|
|
|
19
39
|
function pad(n) { return String(n).padStart(2, '0'); }
|
|
20
40
|
function toKey(y, m, d) { return `${y}-${pad(m+1)}-${pad(d)}`; }
|
|
@@ -42,11 +62,48 @@ function isSameDay(a, b) {
|
|
|
42
62
|
}
|
|
43
63
|
function isToday(y, m, d) { const t = new Date(); return t.getFullYear()===y && t.getMonth()===m && t.getDate()===d; }
|
|
44
64
|
|
|
45
|
-
|
|
65
|
+
const SIZES = {
|
|
66
|
+
xs: { height: 24, fontSize: 11, paddingH: 6, iconSize: 11, btnSize: 24, closeSize: 11 },
|
|
67
|
+
sm: { height: 28, fontSize: 11.5, paddingH: 8, iconSize: 12, btnSize: 28, closeSize: 12 },
|
|
68
|
+
md: { height: 32, fontSize: 13, paddingH: 10, iconSize: 13, btnSize: 32, closeSize: 13 },
|
|
69
|
+
lg: { height: 36, fontSize: 14, paddingH: 12, iconSize: 14, btnSize: 36, closeSize: 14 },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export function DatePicker({
|
|
73
|
+
value,
|
|
74
|
+
onChange,
|
|
75
|
+
label,
|
|
76
|
+
placeholder = 'Select date',
|
|
77
|
+
badges = {},
|
|
78
|
+
minDate,
|
|
79
|
+
maxDate,
|
|
80
|
+
disabled,
|
|
81
|
+
size = 'md',
|
|
82
|
+
showStepper = false,
|
|
83
|
+
stepperPosition = 'off',
|
|
84
|
+
locale = 'en',
|
|
85
|
+
style,
|
|
86
|
+
fullWidth = false,
|
|
87
|
+
showTime = false,
|
|
88
|
+
timeValue,
|
|
89
|
+
onTimeChange,
|
|
90
|
+
timeFormat = '24h',
|
|
91
|
+
showSeconds = false,
|
|
92
|
+
timePlaceholder = '00:00',
|
|
93
|
+
timeProps = {},
|
|
94
|
+
range = false,
|
|
95
|
+
}) {
|
|
96
|
+
const loc = typeof locale === 'object' && locale !== null ? locale : (LOCALES[locale] || LOCALES.en);
|
|
97
|
+
const DAYS = loc.days;
|
|
98
|
+
const MONTHS = loc.months;
|
|
99
|
+
const s = SIZES[size] || SIZES.md;
|
|
46
100
|
const [open, setOpen] = useState(false);
|
|
47
101
|
const [view, setView] = useState('day'); // 'day' | 'month' | 'year'
|
|
48
|
-
const [cursor, setCursor] = useState(() =>
|
|
102
|
+
const [cursor, setCursor] = useState(() => new Date());
|
|
49
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);
|
|
50
107
|
const ref = useRef(null);
|
|
51
108
|
const btnRef = useRef(null);
|
|
52
109
|
const dropRef = useRef(null);
|
|
@@ -77,10 +134,24 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
77
134
|
function close(e) {
|
|
78
135
|
if (ref.current?.contains(e.target)) return;
|
|
79
136
|
if (dropRef.current?.contains(e.target)) return;
|
|
137
|
+
if (e.target?.closest?.('.cynx-timepicker-drop') || e.target?.closest?.('.cynx-timepicker-cell')) return;
|
|
138
|
+
if (document.querySelector('.cynx-timepicker-drop')) return;
|
|
139
|
+
if (typeof window !== 'undefined') window.__cynx_last_portal_closed_at = Date.now();
|
|
80
140
|
setOpen(false); setView('day');
|
|
81
141
|
}
|
|
142
|
+
function onKey(e) {
|
|
143
|
+
if (e.key === 'Escape') {
|
|
144
|
+
if (document.querySelector('.cynx-timepicker-drop')) return;
|
|
145
|
+
if (typeof window !== 'undefined') window.__cynx_last_portal_closed_at = Date.now();
|
|
146
|
+
setOpen(false); setView('day');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
82
149
|
document.addEventListener('mousedown', close, true);
|
|
83
|
-
|
|
150
|
+
document.addEventListener('keydown', onKey);
|
|
151
|
+
return () => {
|
|
152
|
+
document.removeEventListener('mousedown', close, true);
|
|
153
|
+
document.removeEventListener('keydown', onKey);
|
|
154
|
+
};
|
|
84
155
|
}, [open]);
|
|
85
156
|
|
|
86
157
|
const [stepperPosState, setStepperPosState] = useState(() => typeof window !== 'undefined' ? localStorage.getItem('datepicker_stepper_position') : null);
|
|
@@ -93,11 +164,31 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
93
164
|
return () => window.removeEventListener('datepicker-pos-changed', onPosChanged);
|
|
94
165
|
}, []);
|
|
95
166
|
|
|
96
|
-
// Sync cursor when value changes externally
|
|
167
|
+
// Sync cursor and range when value changes externally
|
|
97
168
|
useEffect(() => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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]);
|
|
101
192
|
|
|
102
193
|
const calcPos = useCallback(() => {
|
|
103
194
|
if (!btnRef.current) return;
|
|
@@ -138,6 +229,92 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
138
229
|
setOpen(o => !o);
|
|
139
230
|
};
|
|
140
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
|
+
|
|
141
318
|
function pick(date) {
|
|
142
319
|
if (!date) return;
|
|
143
320
|
const minD = toDate(minDate);
|
|
@@ -145,6 +322,29 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
145
322
|
if (minD && date < new Date(new Date(minD).setHours(0, 0, 0, 0))) return;
|
|
146
323
|
if (maxD && date > new Date(new Date(maxD).setHours(23, 59, 59, 999))) return;
|
|
147
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
|
+
|
|
148
348
|
if (typeof value === 'string') {
|
|
149
349
|
const y = date.getFullYear();
|
|
150
350
|
const m = pad(date.getMonth() + 1);
|
|
@@ -153,8 +353,10 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
153
353
|
} else {
|
|
154
354
|
onChange?.(date);
|
|
155
355
|
}
|
|
156
|
-
|
|
157
|
-
|
|
356
|
+
if (!showTime) {
|
|
357
|
+
setOpen(false);
|
|
358
|
+
setView('day');
|
|
359
|
+
}
|
|
158
360
|
}
|
|
159
361
|
|
|
160
362
|
const isNavNextDisabled = () => {
|
|
@@ -212,26 +414,26 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
212
414
|
}
|
|
213
415
|
|
|
214
416
|
const valDate = toDate(value);
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
417
|
+
const formattedTime = showTime && timeValue ? ` ${timeValue}` : '';
|
|
418
|
+
const formatted = range
|
|
419
|
+
? (rangeStart && rangeEnd ? `${formatDateStr(rangeStart)} — ${formatDateStr(rangeEnd)}` : rangeStart ? `${formatDateStr(rangeStart)} — ...` : null)
|
|
420
|
+
: valDate
|
|
421
|
+
? `${MONTHS[valDate.getMonth()]} ${valDate.getDate()}, ${valDate.getFullYear()}${formattedTime}`
|
|
422
|
+
: (typeof value === 'string' && value ? `${value}${formattedTime}` : null);
|
|
218
423
|
|
|
219
424
|
// ── Day grid ─────────────────────────────────────────────────
|
|
220
425
|
function renderDays() {
|
|
221
426
|
const y = cursor.getFullYear(), m = cursor.getMonth();
|
|
222
|
-
const first = new Date(y, m, 1).getDay();
|
|
223
|
-
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;
|
|
224
429
|
const daysInMonth = new Date(y, m + 1, 0).getDate();
|
|
225
430
|
const prevDays = new Date(y, m, 0).getDate();
|
|
226
431
|
|
|
227
432
|
const cells = [];
|
|
228
|
-
// Prev month filler
|
|
229
433
|
for (let i = offset - 1; i >= 0; i--)
|
|
230
434
|
cells.push({ day: prevDays - i, cur: false, next: false });
|
|
231
|
-
// Current month
|
|
232
435
|
for (let d = 1; d <= daysInMonth; d++)
|
|
233
436
|
cells.push({ day: d, cur: true, next: false });
|
|
234
|
-
// Next month filler — complete last row + always show at least one full next month week
|
|
235
437
|
const totalSoFar = cells.length;
|
|
236
438
|
let remaining = (7 - (totalSoFar % 7)) % 7;
|
|
237
439
|
if (remaining === 0) remaining = 7;
|
|
@@ -250,7 +452,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
250
452
|
))}
|
|
251
453
|
</div>
|
|
252
454
|
{/* Day cells */}
|
|
253
|
-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap:
|
|
455
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '2px 0' }}>
|
|
254
456
|
{cells.map((c, i) => {
|
|
255
457
|
if (!c.cur) {
|
|
256
458
|
const isPrev = !c.next;
|
|
@@ -264,18 +466,22 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
264
466
|
<div
|
|
265
467
|
key={i}
|
|
266
468
|
onClick={() => { if (!dis) pick(date); }}
|
|
469
|
+
onMouseEnter={() => { if (range && rangeStart && !rangeEnd) setHoverDate(date); }}
|
|
267
470
|
style={{
|
|
268
471
|
textAlign: 'center',
|
|
269
472
|
padding: '6px 2px',
|
|
270
473
|
fontSize: '.78rem',
|
|
271
474
|
color: dis ? 'var(--border, #e5e7eb)' : 'var(--muted, #797b8d)',
|
|
272
475
|
cursor: dis ? 'not-allowed' : 'pointer',
|
|
273
|
-
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',
|
|
274
482
|
transition: 'background .12s',
|
|
275
483
|
opacity: dis ? 0.35 : 0.7,
|
|
276
484
|
}}
|
|
277
|
-
onMouseEnter={e => { if (!dis) e.currentTarget.style.background = 'var(--grey-1, #f5f5f8)'; }}
|
|
278
|
-
onMouseLeave={e => { if (!dis) e.currentTarget.style.background = 'transparent'; }}
|
|
279
485
|
>
|
|
280
486
|
{c.day}
|
|
281
487
|
</div>
|
|
@@ -284,30 +490,37 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
284
490
|
const date = new Date(y, m, c.day);
|
|
285
491
|
const key = toKey(y, m, c.day);
|
|
286
492
|
const badge = badges[key];
|
|
287
|
-
const sel = isSameDay(date, value);
|
|
493
|
+
const sel = range ? isRangeEndpoint(date) : isSameDay(date, value);
|
|
288
494
|
const today = isToday(y, m, c.day);
|
|
289
495
|
const disMin = minD && date < new Date(new Date(minD).setHours(0,0,0,0));
|
|
290
496
|
const disMax = maxD && date > new Date(new Date(maxD).setHours(23,59,59,999));
|
|
291
497
|
const dis = disMin || disMax;
|
|
292
498
|
return (
|
|
293
|
-
<div
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
+
>
|
|
305
518
|
{c.day}
|
|
306
519
|
{badge && (
|
|
307
520
|
<span style={{
|
|
308
521
|
position: 'absolute', bottom: 2, left: '50%', transform: 'translateX(-50%)',
|
|
309
522
|
width: 4, height: 4, borderRadius: '50%',
|
|
310
|
-
background: badge.color || 'var(--accent, #
|
|
523
|
+
background: badge.color || 'var(--accent, #12675D)',
|
|
311
524
|
}} />
|
|
312
525
|
)}
|
|
313
526
|
</div>
|
|
@@ -450,7 +663,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
450
663
|
onClick={(e) => { e.stopPropagation(); changeDay(-1); }}
|
|
451
664
|
style={{
|
|
452
665
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
453
|
-
width:
|
|
666
|
+
width: s.btnSize, height: s.btnSize, borderRadius: 'var(--rads, 8px)',
|
|
454
667
|
border: '1px solid var(--border, #e2e8f0)', background: 'var(--surface, #ffffff)',
|
|
455
668
|
cursor: dis ? 'not-allowed' : 'pointer', opacity: dis ? 0.35 : 1,
|
|
456
669
|
color: 'var(--muted, #797b8d)', transition: 'all .15s', boxShadow: 'var(--shadow, 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06))',
|
|
@@ -460,7 +673,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
460
673
|
onMouseEnter={e => { if (!dis) { e.currentTarget.style.borderColor = 'var(--accent, #f99e2c)'; e.currentTarget.style.color = 'var(--accent, #f99e2c)'; } }}
|
|
461
674
|
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border, #e2e8f0)'; e.currentTarget.style.color = 'var(--muted, #797b8d)'; }}
|
|
462
675
|
>
|
|
463
|
-
<ChevronLeft size={
|
|
676
|
+
<ChevronLeft size={s.iconSize + 2} />
|
|
464
677
|
</button>
|
|
465
678
|
);
|
|
466
679
|
};
|
|
@@ -474,7 +687,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
474
687
|
onClick={(e) => { e.stopPropagation(); changeDay(1); }}
|
|
475
688
|
style={{
|
|
476
689
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
477
|
-
width:
|
|
690
|
+
width: s.btnSize, height: s.btnSize, borderRadius: 'var(--rads, 8px)',
|
|
478
691
|
border: '1px solid var(--border, #e2e8f0)', background: 'var(--surface, #ffffff)',
|
|
479
692
|
cursor: dis ? 'not-allowed' : 'pointer', opacity: dis ? 0.35 : 1,
|
|
480
693
|
color: 'var(--muted, #797b8d)', transition: 'all .15s', boxShadow: 'var(--shadow, 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06))',
|
|
@@ -484,7 +697,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
484
697
|
onMouseEnter={e => { if (!dis) { e.currentTarget.style.borderColor = 'var(--accent, #f99e2c)'; e.currentTarget.style.color = 'var(--accent, #f99e2c)'; } }}
|
|
485
698
|
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border, #e2e8f0)'; e.currentTarget.style.color = 'var(--muted, #797b8d)'; }}
|
|
486
699
|
>
|
|
487
|
-
<ChevronRight size={
|
|
700
|
+
<ChevronRight size={s.iconSize + 2} />
|
|
488
701
|
</button>
|
|
489
702
|
);
|
|
490
703
|
};
|
|
@@ -492,21 +705,24 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
492
705
|
const renderTriggerBtn = () => (
|
|
493
706
|
<button ref={btnRef} type="button" onClick={toggleOpen} style={{
|
|
494
707
|
display: 'flex', alignItems: 'center', gap: 7,
|
|
495
|
-
padding:
|
|
708
|
+
padding: `0 ${s.paddingH}px`, borderRadius: 'var(--rads, 8px)',
|
|
496
709
|
border: '1px solid var(--border, #e2e8f0)', background: 'var(--surface, #ffffff)',
|
|
497
710
|
cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.5 : 1,
|
|
498
|
-
fontSize:
|
|
711
|
+
fontSize: s.fontSize, color: formatted ? 'var(--primary-text, #0f172a)' : 'var(--muted, #797b8d)',
|
|
499
712
|
fontFamily: 'var(--sans, inherit)', transition: 'border-color .15s',
|
|
500
|
-
whiteSpace: 'nowrap', minWidth:
|
|
713
|
+
whiteSpace: 'nowrap', minWidth: 0, flex: 1, height: s.height, boxShadow: 'var(--shadow, 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06))',
|
|
714
|
+
overflow: 'hidden'
|
|
501
715
|
}}
|
|
502
716
|
onMouseEnter={e => { if (!disabled) e.currentTarget.style.borderColor = 'var(--accent, #f99e2c)'; }}
|
|
503
717
|
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border, #e2e8f0)'; }}>
|
|
504
|
-
<Calendar size={
|
|
505
|
-
<span style={{ flex: 1, textAlign: 'left'
|
|
718
|
+
<Calendar size={s.iconSize} style={{ color: 'var(--muted, #797b8d)', flexShrink: 0 }} />
|
|
719
|
+
<span style={{ flex: 1, textAlign: 'left', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 }}>
|
|
720
|
+
{formatted || placeholder}
|
|
721
|
+
</span>
|
|
506
722
|
{value && (
|
|
507
723
|
<span
|
|
508
724
|
onClick={e => { e.stopPropagation(); onChange?.(null); }}
|
|
509
|
-
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--muted, #797b8d)', cursor: 'pointer', flexShrink: 0, fontSize:
|
|
725
|
+
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--muted, #797b8d)', cursor: 'pointer', flexShrink: 0, fontSize: s.closeSize, lineHeight: 1, padding: '0 2px' }}
|
|
510
726
|
onMouseEnter={e => { e.currentTarget.style.color = 'var(--danger, #f64747)'; }}
|
|
511
727
|
onMouseLeave={e => { e.currentTarget.style.color = 'var(--muted, #797b8d)'; }}>
|
|
512
728
|
✕
|
|
@@ -522,13 +738,30 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
522
738
|
: 'around';
|
|
523
739
|
|
|
524
740
|
return (
|
|
525
|
-
<div style={{
|
|
741
|
+
<div style={{
|
|
742
|
+
display: fullWidth || style?.display === 'flex' ? 'flex' : 'inline-flex',
|
|
743
|
+
flexDirection: 'column',
|
|
744
|
+
position: 'relative',
|
|
745
|
+
width: style?.width || (fullWidth ? '100%' : 'auto'),
|
|
746
|
+
maxWidth: style?.maxWidth || (fullWidth ? '100%' : 320),
|
|
747
|
+
boxSizing: 'border-box',
|
|
748
|
+
minWidth: 0,
|
|
749
|
+
...style,
|
|
750
|
+
}}>
|
|
526
751
|
{label && (
|
|
527
752
|
<label style={{ fontSize: 11, fontWeight: 600, color: 'var(--t2, var(--muted, #797b8d))', textTransform: 'uppercase', letterSpacing: '.08em', display: 'block', marginBottom: 4 }}>
|
|
528
753
|
{label}
|
|
529
754
|
</label>
|
|
530
755
|
)}
|
|
531
|
-
<div ref={ref} style={{
|
|
756
|
+
<div ref={ref} style={{
|
|
757
|
+
position: 'relative',
|
|
758
|
+
display: 'inline-flex',
|
|
759
|
+
alignItems: 'center',
|
|
760
|
+
gap: 4,
|
|
761
|
+
width: '100%',
|
|
762
|
+
maxWidth: '100%',
|
|
763
|
+
minWidth: 0,
|
|
764
|
+
}}>
|
|
532
765
|
{showStepperEffective && posMode === 'around' && renderPrevBtn()}
|
|
533
766
|
{showStepperEffective && posMode === 'left' && renderPrevBtn()}
|
|
534
767
|
{showStepperEffective && posMode === 'left' && renderNextBtn()}
|
|
@@ -542,7 +775,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
542
775
|
|
|
543
776
|
{/* Dropdown via portal */}
|
|
544
777
|
{open && createPortal(
|
|
545
|
-
<div ref={dropRef} style={{
|
|
778
|
+
<div ref={dropRef} className="cynx-datepicker-drop" style={{
|
|
546
779
|
position: 'fixed', top: pos.top, left: pos.left, width: pos.width, zIndex: 9000,
|
|
547
780
|
background: 'var(--surface, #ffffff)', border: '1px solid var(--border, #e2e8f0)',
|
|
548
781
|
borderRadius: 'var(--rad, 10px)', boxShadow: 'var(--shadow-lg, 0 10px 15px -3px rgba(0,0,0,.1), 0 4px 6px -4px rgba(0,0,0,.06))',
|
|
@@ -595,6 +828,56 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
|
|
|
595
828
|
{view === 'month' && renderMonths()}
|
|
596
829
|
{view === 'year' && renderYears()}
|
|
597
830
|
|
|
831
|
+
{/* Integrated Time Picker footer */}
|
|
832
|
+
{showTime && (
|
|
833
|
+
<div
|
|
834
|
+
style={{
|
|
835
|
+
borderTop: '1px solid var(--border-light, #ebebf0)',
|
|
836
|
+
marginTop: 8,
|
|
837
|
+
paddingTop: 8,
|
|
838
|
+
display: 'flex',
|
|
839
|
+
alignItems: 'center',
|
|
840
|
+
justifyContent: 'space-between',
|
|
841
|
+
gap: 8,
|
|
842
|
+
}}
|
|
843
|
+
>
|
|
844
|
+
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: '.76rem', color: 'var(--muted, #797b8d)', fontWeight: 600 }}>
|
|
845
|
+
<Clock size={13} style={{ opacity: 0.85 }} />
|
|
846
|
+
<span>{loc.time || 'Time:'}</span>
|
|
847
|
+
</div>
|
|
848
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
849
|
+
<TimePicker
|
|
850
|
+
value={timeValue || ''}
|
|
851
|
+
onChange={(t) => onTimeChange?.(t)}
|
|
852
|
+
format={timeFormat}
|
|
853
|
+
showSeconds={showSeconds}
|
|
854
|
+
placeholder={timePlaceholder}
|
|
855
|
+
size={timeProps.size || size || 'md'}
|
|
856
|
+
{...timeProps}
|
|
857
|
+
/>
|
|
858
|
+
<button
|
|
859
|
+
type="button"
|
|
860
|
+
onClick={() => { setOpen(false); setView('day'); }}
|
|
861
|
+
style={{
|
|
862
|
+
padding: '4px 10px',
|
|
863
|
+
borderRadius: 'var(--radxs, 6px)',
|
|
864
|
+
background: 'var(--accent, #f99e2c)',
|
|
865
|
+
color: 'var(--def-btn-text, #ffffff)',
|
|
866
|
+
border: 'none',
|
|
867
|
+
fontSize: '11.5px',
|
|
868
|
+
fontWeight: 600,
|
|
869
|
+
cursor: 'pointer',
|
|
870
|
+
transition: 'opacity 0.15s ease',
|
|
871
|
+
}}
|
|
872
|
+
onMouseEnter={(e) => { e.currentTarget.style.opacity = '0.9'; }}
|
|
873
|
+
onMouseLeave={(e) => { e.currentTarget.style.opacity = '1'; }}
|
|
874
|
+
>
|
|
875
|
+
{loc.ok || 'OK'}
|
|
876
|
+
</button>
|
|
877
|
+
</div>
|
|
878
|
+
</div>
|
|
879
|
+
)}
|
|
880
|
+
|
|
598
881
|
</div>,
|
|
599
882
|
document.body
|
|
600
883
|
)}
|
package/vue/components/Badge.vue
CHANGED
|
@@ -3,11 +3,19 @@ import { computed } from "vue";
|
|
|
3
3
|
|
|
4
4
|
const VARIANTS: Record<string, any> = {
|
|
5
5
|
ok: { bg: "var(--success-bg, rgba(39,174,96,.1))", color: "var(--success, #16a34a)", border: "var(--sd, rgba(39,174,96,.2))", dot: "var(--success, #16a34a)" },
|
|
6
|
+
success: { bg: "var(--success-bg, rgba(39,174,96,.1))", color: "var(--success, #16a34a)", border: "var(--sd, rgba(39,174,96,.2))", dot: "var(--success, #16a34a)" },
|
|
6
7
|
err: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
|
|
8
|
+
danger: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
|
|
9
|
+
error: { bg: "var(--danger-bg, rgba(246,71,71,.1))", color: "var(--danger, #ef4444)", border: "var(--dd, rgba(246,71,71,.2))", dot: "var(--danger, #ef4444)" },
|
|
7
10
|
warn: { bg: "var(--accent-bg, rgba(249,158,44,.1))", color: "var(--accent, #f59e0b)", border: "var(--accent-border, rgba(249,158,44,.25))", dot: "var(--accent, #f59e0b)" },
|
|
11
|
+
warning: { bg: "var(--accent-bg, rgba(249,158,44,.1))", color: "var(--accent, #f59e0b)", border: "var(--accent-border, rgba(249,158,44,.25))", dot: "var(--accent, #f59e0b)" },
|
|
8
12
|
blue: { bg: "var(--blue-bg, rgba(39,130,228,.1))", color: "var(--blue, #2563eb)", border: "var(--infd, rgba(39,130,228,.2))", dot: "var(--blue, #2563eb)" },
|
|
13
|
+
info: { bg: "var(--blue-bg, rgba(39,130,228,.1))", color: "var(--blue, #2563eb)", border: "var(--infd, rgba(39,130,228,.2))", dot: "var(--blue, #2563eb)" },
|
|
9
14
|
teal: { bg: "rgba(18,103,93,.1)", color: "var(--accent, #12675D)", border: "rgba(18,103,93,.25)", dot: "var(--accent, #12675D)" },
|
|
15
|
+
primary: { bg: "rgba(18,103,93,.1)", color: "var(--accent, #12675D)", border: "rgba(18,103,93,.25)", dot: "var(--accent, #12675D)" },
|
|
10
16
|
dim: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
|
|
17
|
+
neutral: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
|
|
18
|
+
secondary: { bg: "var(--grey-1, #f5f5f8)", color: "var(--grey-4, #64748b)", border: "var(--border, #e2e8f0)", dot: "var(--grey-4, #64748b)" },
|
|
11
19
|
ghost: { bg: "transparent", color: "var(--t2, #64748b)", border: "var(--bd, #e2e8f0)", dot: "var(--t3, #94a3b8)" },
|
|
12
20
|
};
|
|
13
21
|
|