cynx-ui 1.2.31 → 1.2.33
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.
|
@@ -18,13 +18,23 @@ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', '
|
|
|
18
18
|
|
|
19
19
|
function pad(n) { return String(n).padStart(2, '0'); }
|
|
20
20
|
function toKey(y, m, d) { return `${y}-${pad(m+1)}-${pad(d)}`; }
|
|
21
|
-
function
|
|
21
|
+
function toDate(val) {
|
|
22
|
+
if (!val) return null;
|
|
23
|
+
if (val instanceof Date) return isNaN(val.getTime()) ? null : val;
|
|
24
|
+
const d = new Date(val);
|
|
25
|
+
return isNaN(d.getTime()) ? null : d;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isSameDay(a, b) {
|
|
29
|
+
const da = toDate(a), db = toDate(b);
|
|
30
|
+
return da && db && da.getFullYear()===db.getFullYear() && da.getMonth()===db.getMonth() && da.getDate()===db.getDate();
|
|
31
|
+
}
|
|
22
32
|
function isToday(y, m, d) { const t = new Date(); return t.getFullYear()===y && t.getMonth()===m && t.getDate()===d; }
|
|
23
33
|
|
|
24
34
|
export function DatePicker({ value, onChange, placeholder = 'Select date', badges = {}, minDate, maxDate, disabled, style }) {
|
|
25
35
|
const [open, setOpen] = useState(false);
|
|
26
36
|
const [view, setView] = useState('day'); // 'day' | 'month' | 'year'
|
|
27
|
-
const [cursor, setCursor] = useState(() =>
|
|
37
|
+
const [cursor, setCursor] = useState(() => toDate(value) || new Date());
|
|
28
38
|
const [pos, setPos] = useState({ top: 0, left: 0, width: 0 });
|
|
29
39
|
const ref = useRef(null);
|
|
30
40
|
const btnRef = useRef(null);
|
|
@@ -43,7 +53,10 @@ export function DatePicker({ value, onChange, placeholder = 'Select date', badge
|
|
|
43
53
|
}, [open]);
|
|
44
54
|
|
|
45
55
|
// Sync cursor when value changes externally
|
|
46
|
-
useEffect(() => {
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const d = toDate(value);
|
|
58
|
+
if (d) setCursor(d);
|
|
59
|
+
}, [value]);
|
|
47
60
|
|
|
48
61
|
// Compute button position when dropdown opens
|
|
49
62
|
useEffect(() => {
|
|
@@ -61,7 +74,14 @@ export function DatePicker({ value, onChange, placeholder = 'Select date', badge
|
|
|
61
74
|
}, [open]);
|
|
62
75
|
|
|
63
76
|
function pick(date) {
|
|
64
|
-
|
|
77
|
+
if (typeof value === 'string') {
|
|
78
|
+
const y = date.getFullYear();
|
|
79
|
+
const m = pad(date.getMonth() + 1);
|
|
80
|
+
const d = pad(date.getDate());
|
|
81
|
+
onChange?.(`${y}-${m}-${d}`);
|
|
82
|
+
} else {
|
|
83
|
+
onChange?.(date);
|
|
84
|
+
}
|
|
65
85
|
setOpen(false);
|
|
66
86
|
setView('day');
|
|
67
87
|
}
|
|
@@ -78,9 +98,10 @@ export function DatePicker({ value, onChange, placeholder = 'Select date', badge
|
|
|
78
98
|
setView(v => v === 'day' ? 'month' : v === 'month' ? 'year' : 'year');
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
101
|
+
const valDate = toDate(value);
|
|
102
|
+
const formatted = valDate
|
|
103
|
+
? `${MONTHS[valDate.getMonth()]} ${valDate.getDate()}, ${valDate.getFullYear()}`
|
|
104
|
+
: (typeof value === 'string' ? value : null);
|
|
84
105
|
|
|
85
106
|
// ── Day grid ─────────────────────────────────────────────────
|
|
86
107
|
function renderDays() {
|
|
@@ -14,7 +14,16 @@ export function SearchableDropdown({ value, onChange, options = [], placeholder
|
|
|
14
14
|
const selected = options.find(o => o.value === value);
|
|
15
15
|
|
|
16
16
|
const filtered = query
|
|
17
|
-
? options.filter(o =>
|
|
17
|
+
? options.filter(o => {
|
|
18
|
+
const q = query.toLowerCase().trim();
|
|
19
|
+
for (const [key, val] of Object.entries(o)) {
|
|
20
|
+
if (key === 'displayLabel') continue;
|
|
21
|
+
if (val != null && typeof val !== 'function' && typeof val !== 'object') {
|
|
22
|
+
if (String(val).toLowerCase().includes(q)) return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
})
|
|
18
27
|
: options;
|
|
19
28
|
|
|
20
29
|
useEffect(() => {
|
|
@@ -68,7 +77,7 @@ export function SearchableDropdown({ value, onChange, options = [], placeholder
|
|
|
68
77
|
onMouseEnter={e => { if (!open) e.currentTarget.style.borderColor = 'var(--accent)'; }}
|
|
69
78
|
onMouseLeave={e => { if (!open) e.currentTarget.style.borderColor = 'var(--border)'; }}>
|
|
70
79
|
<span style={{ flex: 1, textAlign: 'left', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
71
|
-
{selected
|
|
80
|
+
{selected ? (selected.displayLabel || selected.label) : placeholder}
|
|
72
81
|
</span>
|
|
73
82
|
<ChevronDown size={13} style={{ color: 'var(--muted)', flexShrink: 0 }} />
|
|
74
83
|
</button>
|
|
@@ -105,7 +114,7 @@ export function SearchableDropdown({ value, onChange, options = [], placeholder
|
|
|
105
114
|
}}
|
|
106
115
|
onMouseEnter={e => { if (o.value !== value) e.currentTarget.style.background = 'var(--grey-1)'; }}
|
|
107
116
|
onMouseLeave={e => { if (o.value !== value) e.currentTarget.style.background = 'transparent'; }}>
|
|
108
|
-
<span>{o.label}</span>
|
|
117
|
+
<span>{o.displayLabel || o.label}</span>
|
|
109
118
|
{o.value === value && <Check size={12} />}
|
|
110
119
|
</div>
|
|
111
120
|
))}
|
package/components/Select.jsx
CHANGED
|
@@ -66,8 +66,16 @@ export default function Select({
|
|
|
66
66
|
|
|
67
67
|
const filtered = useMemo(() => {
|
|
68
68
|
if (!query) return options;
|
|
69
|
-
const q = query.toLowerCase();
|
|
70
|
-
return options.filter(o =>
|
|
69
|
+
const q = query.toLowerCase().trim();
|
|
70
|
+
return options.filter(o => {
|
|
71
|
+
for (const [key, val] of Object.entries(o)) {
|
|
72
|
+
if (key === 'displayLabel') continue;
|
|
73
|
+
if (val != null && typeof val !== 'function' && typeof val !== 'object') {
|
|
74
|
+
if (String(val).toLowerCase().includes(q)) return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
});
|
|
71
79
|
}, [options, query]);
|
|
72
80
|
|
|
73
81
|
function handleOpen() {
|
|
@@ -101,7 +109,7 @@ export default function Select({
|
|
|
101
109
|
|
|
102
110
|
function getLabel(optVal) {
|
|
103
111
|
const opt = options.find(o => o.value === optVal);
|
|
104
|
-
return opt ? opt.label : optVal;
|
|
112
|
+
return opt ? (opt.displayLabel || opt.label) : optVal;
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
const selectedValues = multi ? (Array.isArray(value) ? value : []) : (value ? [value] : []);
|
|
@@ -286,7 +294,7 @@ export default function Select({
|
|
|
286
294
|
</span>
|
|
287
295
|
)}
|
|
288
296
|
<span style={{ flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
289
|
-
{opt.label}
|
|
297
|
+
{opt.displayLabel || opt.label}
|
|
290
298
|
</span>
|
|
291
299
|
{!multi && isSelected(opt.value) && (
|
|
292
300
|
<Check size={13} style={{ flexShrink: 0, color: 'var(--accent)' }} />
|
package/components/Toast.jsx
CHANGED
|
@@ -77,7 +77,7 @@ export function Toast({ toasts, remove }) {
|
|
|
77
77
|
<div id="toast-root">
|
|
78
78
|
{toasts.map(t => (
|
|
79
79
|
<div key={t.id} className={`toast ${t.type}`} onClick={() => remove(t.id)}>
|
|
80
|
-
{t.type === 'ok'
|
|
80
|
+
{t.type === 'ok' || t.type === 'success'
|
|
81
81
|
? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--success)" strokeWidth="2.5" strokeLinecap="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
82
82
|
: <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--danger)" strokeWidth="2.5" strokeLinecap="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
|
|
83
83
|
}
|