cynx-ui 1.3.12 → 1.3.14

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.
@@ -56,8 +56,10 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
56
56
  const current = toDate(value) || new Date();
57
57
  const next = new Date(current.getFullYear(), current.getMonth(), current.getDate() + delta);
58
58
 
59
- if (minDate && next < new Date(new Date(minDate).setHours(0, 0, 0, 0))) return;
60
- if (maxDate && next > new Date(new Date(maxDate).setHours(23, 59, 59, 999))) return;
59
+ const minD = toDate(minDate);
60
+ const maxD = toDate(maxDate);
61
+ if (minD && next < new Date(new Date(minD).setHours(0, 0, 0, 0))) return;
62
+ if (maxD && next > new Date(new Date(maxD).setHours(23, 59, 59, 999))) return;
61
63
 
62
64
  if (typeof value === 'string') {
63
65
  const y = next.getFullYear();
@@ -137,6 +139,12 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
137
139
  };
138
140
 
139
141
  function pick(date) {
142
+ if (!date) return;
143
+ const minD = toDate(minDate);
144
+ const maxD = toDate(maxDate);
145
+ if (minD && date < new Date(new Date(minD).setHours(0, 0, 0, 0))) return;
146
+ if (maxD && date > new Date(new Date(maxD).setHours(23, 59, 59, 999))) return;
147
+
140
148
  if (typeof value === 'string') {
141
149
  const y = date.getFullYear();
142
150
  const m = pad(date.getMonth() + 1);
@@ -149,7 +157,49 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
149
157
  setView('day');
150
158
  }
151
159
 
160
+ const isNavNextDisabled = () => {
161
+ const maxD = toDate(maxDate);
162
+ if (!maxD) return false;
163
+ const y = cursor.getFullYear(), m = cursor.getMonth();
164
+ if (view === 'day') {
165
+ const nextMonthStart = new Date(y, m + 1, 1);
166
+ return nextMonthStart > new Date(new Date(maxD).setHours(23, 59, 59, 999));
167
+ }
168
+ if (view === 'month') {
169
+ const nextYearStart = new Date(y + 1, 0, 1);
170
+ return nextYearStart > new Date(new Date(maxD).setHours(23, 59, 59, 999));
171
+ }
172
+ if (view === 'year') {
173
+ const base = Math.floor(y / 12) * 12;
174
+ const nextBlockStart = new Date(base + 12, 0, 1);
175
+ return nextBlockStart > new Date(new Date(maxD).setHours(23, 59, 59, 999));
176
+ }
177
+ return false;
178
+ };
179
+
180
+ const isNavPrevDisabled = () => {
181
+ const minD = toDate(minDate);
182
+ if (!minD) return false;
183
+ const y = cursor.getFullYear(), m = cursor.getMonth();
184
+ if (view === 'day') {
185
+ const prevMonthEnd = new Date(y, m, 0, 23, 59, 59, 999);
186
+ return prevMonthEnd < new Date(new Date(minD).setHours(0, 0, 0, 0));
187
+ }
188
+ if (view === 'month') {
189
+ const prevYearEnd = new Date(y - 1, 11, 31, 23, 59, 59, 999);
190
+ return prevYearEnd < new Date(new Date(minD).setHours(0, 0, 0, 0));
191
+ }
192
+ if (view === 'year') {
193
+ const base = Math.floor(y / 12) * 12;
194
+ const prevBlockEnd = new Date(base - 1, 11, 31, 23, 59, 59, 999);
195
+ return prevBlockEnd < new Date(new Date(minD).setHours(0, 0, 0, 0));
196
+ }
197
+ return false;
198
+ };
199
+
152
200
  function nav(dir) {
201
+ if (dir > 0 && isNavNextDisabled()) return;
202
+ if (dir < 0 && isNavPrevDisabled()) return;
153
203
  const c = new Date(cursor);
154
204
  if (view === 'day') { c.setMonth(c.getMonth() + dir); }
155
205
  if (view === 'month') { c.setFullYear(c.getFullYear() + dir); }
@@ -188,6 +238,9 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
188
238
  for (let d = 1; d <= remaining; d++)
189
239
  cells.push({ day: d, cur: false, next: true });
190
240
 
241
+ const minD = toDate(minDate);
242
+ const maxD = toDate(maxDate);
243
+
191
244
  return (
192
245
  <div>
193
246
  {/* Weekday headers */}
@@ -204,11 +257,26 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
204
257
  const date = isPrev
205
258
  ? new Date(y, m - 1, c.day)
206
259
  : new Date(y, m + 1, c.day);
260
+ const disMin = minD && date < new Date(new Date(minD).setHours(0,0,0,0));
261
+ const disMax = maxD && date > new Date(new Date(maxD).setHours(23,59,59,999));
262
+ const dis = disMin || disMax;
207
263
  return (
208
- <div key={i} onClick={() => { pick(date); }}
209
- style={{ textAlign: 'center', padding: '6px 2px', fontSize: '.78rem', color: 'var(--muted)', cursor: 'pointer', borderRadius: 'var(--radxs, 6px)', transition: 'background .12s' }}
210
- onMouseEnter={e => e.currentTarget.style.background = 'var(--grey-1)'}
211
- onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
264
+ <div
265
+ key={i}
266
+ onClick={() => { if (!dis) pick(date); }}
267
+ style={{
268
+ textAlign: 'center',
269
+ padding: '6px 2px',
270
+ fontSize: '.78rem',
271
+ color: dis ? 'var(--border, #e5e7eb)' : 'var(--muted)',
272
+ cursor: dis ? 'not-allowed' : 'pointer',
273
+ borderRadius: 'var(--radxs, 6px)',
274
+ transition: 'background .12s',
275
+ opacity: dis ? 0.35 : 0.7,
276
+ }}
277
+ onMouseEnter={e => { if (!dis) e.currentTarget.style.background = 'var(--grey-1)'; }}
278
+ onMouseLeave={e => { if (!dis) e.currentTarget.style.background = 'transparent'; }}
279
+ >
212
280
  {c.day}
213
281
  </div>
214
282
  );
@@ -218,8 +286,8 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
218
286
  const badge = badges[key];
219
287
  const sel = isSameDay(date, value);
220
288
  const today = isToday(y, m, c.day);
221
- const disMin = minDate && date < new Date(minDate.setHours(0,0,0,0));
222
- const disMax = maxDate && date > new Date(maxDate.setHours(23,59,59,999));
289
+ const disMin = minD && date < new Date(new Date(minD).setHours(0,0,0,0));
290
+ const disMax = maxD && date > new Date(new Date(maxD).setHours(23,59,59,999));
223
291
  const dis = disMin || disMax;
224
292
  return (
225
293
  <div key={i} onClick={() => !dis && pick(date)} style={{
@@ -230,6 +298,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
230
298
  color: sel ? 'var(--def-btn-text,#fff)' : dis ? 'var(--border)' : today ? 'var(--accent)' : 'var(--primary-text)',
231
299
  border: today && !sel ? '1.5px solid var(--accent)' : '1.5px solid transparent',
232
300
  transition: 'background .12s, color .12s',
301
+ opacity: dis ? 0.35 : 1,
233
302
  }}
234
303
  onMouseEnter={e => { if (!sel && !dis) e.currentTarget.style.background = 'var(--grey-1)'; }}
235
304
  onMouseLeave={e => { if (!sel && !dis) e.currentTarget.style.background = 'transparent'; }}>
@@ -252,17 +321,44 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
252
321
  // ── Month grid ───────────────────────────────────────────────
253
322
  function renderMonths() {
254
323
  const y = cursor.getFullYear();
324
+ const minD = toDate(minDate);
325
+ const maxD = toDate(maxDate);
326
+
255
327
  return (
256
328
  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4, padding: '4px 0' }}>
257
329
  {MONTHS.map((name, i) => {
258
330
  const sel = value && value.getFullYear() === y && value.getMonth() === i;
331
+ const monthStart = new Date(y, i, 1);
332
+ const monthEnd = new Date(y, i + 1, 0, 23, 59, 59, 999);
333
+ const disMin = minD && monthEnd < new Date(new Date(minD).setHours(0, 0, 0, 0));
334
+ const disMax = maxD && monthStart > new Date(new Date(maxD).setHours(23, 59, 59, 999));
335
+ const dis = disMin || disMax;
336
+
259
337
  return (
260
- <div key={i} onClick={() => { const c = new Date(cursor); c.setMonth(i); setCursor(c); setView('day'); }}
261
- style={{ textAlign: 'center', padding: '10px 4px', borderRadius: 'var(--radxs, 6px)', fontSize: '.82rem', fontWeight: 600, cursor: 'pointer',
262
- background: sel ? 'var(--accent)' : 'transparent', color: sel ? 'var(--def-btn-text,#fff)' : 'var(--primary-text)',
263
- transition: 'background .12s', }}
264
- onMouseEnter={e => { if (!sel) e.currentTarget.style.background = 'var(--grey-1)'; }}
265
- onMouseLeave={e => { if (!sel) e.currentTarget.style.background = 'transparent'; }}>
338
+ <div
339
+ key={i}
340
+ onClick={() => {
341
+ if (dis) return;
342
+ const c = new Date(cursor);
343
+ c.setMonth(i);
344
+ setCursor(c);
345
+ setView('day');
346
+ }}
347
+ style={{
348
+ textAlign: 'center',
349
+ padding: '10px 4px',
350
+ borderRadius: 'var(--radxs, 6px)',
351
+ fontSize: '.82rem',
352
+ fontWeight: 600,
353
+ cursor: dis ? 'not-allowed' : 'pointer',
354
+ opacity: dis ? 0.35 : 1,
355
+ background: sel ? 'var(--accent)' : 'transparent',
356
+ color: sel ? 'var(--def-btn-text,#fff)' : dis ? 'var(--border)' : 'var(--primary-text)',
357
+ transition: 'background .12s',
358
+ }}
359
+ onMouseEnter={e => { if (!sel && !dis) e.currentTarget.style.background = 'var(--grey-1)'; }}
360
+ onMouseLeave={e => { if (!sel && !dis) e.currentTarget.style.background = 'transparent'; }}
361
+ >
266
362
  {name}
267
363
  </div>
268
364
  );
@@ -275,17 +371,44 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
275
371
  function renderYears() {
276
372
  const base = Math.floor(cursor.getFullYear() / 12) * 12;
277
373
  const years = Array.from({ length: 12 }, (_, i) => base + i);
374
+ const minD = toDate(minDate);
375
+ const maxD = toDate(maxDate);
376
+
278
377
  return (
279
378
  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4, padding: '4px 0' }}>
280
379
  {years.map(yr => {
281
380
  const sel = value && value.getFullYear() === yr;
381
+ const yrStart = new Date(yr, 0, 1);
382
+ const yrEnd = new Date(yr, 11, 31, 23, 59, 59, 999);
383
+ const disMin = minD && yrEnd < new Date(new Date(minD).setHours(0, 0, 0, 0));
384
+ const disMax = maxD && yrStart > new Date(new Date(maxD).setHours(23, 59, 59, 999));
385
+ const dis = disMin || disMax;
386
+
282
387
  return (
283
- <div key={yr} onClick={() => { const c = new Date(cursor); c.setFullYear(yr); setCursor(c); setView('month'); }}
284
- style={{ textAlign: 'center', padding: '10px 4px', borderRadius: 'var(--radxs, 6px)', fontSize: '.82rem', fontWeight: 600, cursor: 'pointer',
285
- background: sel ? 'var(--accent)' : 'transparent', color: sel ? 'var(--def-btn-text,#fff)' : 'var(--primary-text)',
286
- transition: 'background .12s', }}
287
- onMouseEnter={e => { if (!sel) e.currentTarget.style.background = 'var(--grey-1)'; }}
288
- onMouseLeave={e => { if (!sel) e.currentTarget.style.background = 'transparent'; }}>
388
+ <div
389
+ key={yr}
390
+ onClick={() => {
391
+ if (dis) return;
392
+ const c = new Date(cursor);
393
+ c.setFullYear(yr);
394
+ setCursor(c);
395
+ setView('month');
396
+ }}
397
+ style={{
398
+ textAlign: 'center',
399
+ padding: '10px 4px',
400
+ borderRadius: 'var(--radxs, 6px)',
401
+ fontSize: '.82rem',
402
+ fontWeight: 600,
403
+ cursor: dis ? 'not-allowed' : 'pointer',
404
+ opacity: dis ? 0.35 : 1,
405
+ background: sel ? 'var(--accent)' : 'transparent',
406
+ color: sel ? 'var(--def-btn-text,#fff)' : dis ? 'var(--border)' : 'var(--primary-text)',
407
+ transition: 'background .12s',
408
+ }}
409
+ onMouseEnter={e => { if (!sel && !dis) e.currentTarget.style.background = 'var(--grey-1)'; }}
410
+ onMouseLeave={e => { if (!sel && !dis) e.currentTarget.style.background = 'transparent'; }}
411
+ >
289
412
  {yr}
290
413
  </div>
291
414
  );
@@ -300,47 +423,71 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
300
423
  ? `${cursor.getFullYear()}`
301
424
  : `${Math.floor(cursor.getFullYear()/12)*12} – ${Math.floor(cursor.getFullYear()/12)*12+11}`;
302
425
 
303
- const renderPrevBtn = () => (
304
- <button
305
- type="button"
306
- disabled={disabled}
307
- onClick={(e) => { e.stopPropagation(); changeDay(-1); }}
308
- style={{
309
- display: 'flex', alignItems: 'center', justifyContent: 'center',
310
- width: 32, height: 32, borderRadius: 'var(--rads)',
311
- border: '1px solid var(--border)', background: 'var(--surface)',
312
- cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.5 : 1,
313
- color: 'var(--muted)', transition: 'all .15s', boxShadow: 'var(--shadow)',
314
- flexShrink: 0
315
- }}
316
- title="Previous day (-1)"
317
- onMouseEnter={e => { if (!disabled) { e.currentTarget.style.borderColor = 'var(--accent)'; e.currentTarget.style.color = 'var(--accent)'; } }}
318
- onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--muted)'; }}
319
- >
320
- <ChevronLeft size={15} />
321
- </button>
322
- );
426
+ const isNextDisabled = () => {
427
+ if (disabled) return true;
428
+ const maxD = toDate(maxDate);
429
+ if (!maxD) return false;
430
+ const current = toDate(value) || new Date();
431
+ const next = new Date(current.getFullYear(), current.getMonth(), current.getDate() + 1);
432
+ return next > new Date(new Date(maxD).setHours(23, 59, 59, 999));
433
+ };
323
434
 
324
- const renderNextBtn = () => (
325
- <button
326
- type="button"
327
- disabled={disabled}
328
- onClick={(e) => { e.stopPropagation(); changeDay(1); }}
329
- style={{
330
- display: 'flex', alignItems: 'center', justifyContent: 'center',
331
- width: 32, height: 32, borderRadius: 'var(--rads)',
332
- border: '1px solid var(--border)', background: 'var(--surface)',
333
- cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.5 : 1,
334
- color: 'var(--muted)', transition: 'all .15s', boxShadow: 'var(--shadow)',
335
- flexShrink: 0
336
- }}
337
- title="Next day (+1)"
338
- onMouseEnter={e => { if (!disabled) { e.currentTarget.style.borderColor = 'var(--accent)'; e.currentTarget.style.color = 'var(--accent)'; } }}
339
- onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--muted)'; }}
340
- >
341
- <ChevronRight size={15} />
342
- </button>
343
- );
435
+ const isPrevDisabled = () => {
436
+ if (disabled) return true;
437
+ const minD = toDate(minDate);
438
+ if (!minD) return false;
439
+ const current = toDate(value) || new Date();
440
+ const prev = new Date(current.getFullYear(), current.getMonth(), current.getDate() - 1);
441
+ return prev < new Date(new Date(minD).setHours(0, 0, 0, 0));
442
+ };
443
+
444
+ const renderPrevBtn = () => {
445
+ const dis = isPrevDisabled();
446
+ return (
447
+ <button
448
+ type="button"
449
+ disabled={dis}
450
+ onClick={(e) => { e.stopPropagation(); changeDay(-1); }}
451
+ style={{
452
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
453
+ width: 32, height: 32, borderRadius: 'var(--rads)',
454
+ border: '1px solid var(--border)', background: 'var(--surface)',
455
+ cursor: dis ? 'not-allowed' : 'pointer', opacity: dis ? 0.35 : 1,
456
+ color: 'var(--muted)', transition: 'all .15s', boxShadow: 'var(--shadow)',
457
+ flexShrink: 0
458
+ }}
459
+ title="Previous day (-1)"
460
+ onMouseEnter={e => { if (!dis) { e.currentTarget.style.borderColor = 'var(--accent)'; e.currentTarget.style.color = 'var(--accent)'; } }}
461
+ onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--muted)'; }}
462
+ >
463
+ <ChevronLeft size={15} />
464
+ </button>
465
+ );
466
+ };
467
+
468
+ const renderNextBtn = () => {
469
+ const dis = isNextDisabled();
470
+ return (
471
+ <button
472
+ type="button"
473
+ disabled={dis}
474
+ onClick={(e) => { e.stopPropagation(); changeDay(1); }}
475
+ style={{
476
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
477
+ width: 32, height: 32, borderRadius: 'var(--rads)',
478
+ border: '1px solid var(--border)', background: 'var(--surface)',
479
+ cursor: dis ? 'not-allowed' : 'pointer', opacity: dis ? 0.35 : 1,
480
+ color: 'var(--muted)', transition: 'all .15s', boxShadow: 'var(--shadow)',
481
+ flexShrink: 0
482
+ }}
483
+ title="Next day (+1)"
484
+ onMouseEnter={e => { if (!dis) { e.currentTarget.style.borderColor = 'var(--accent)'; e.currentTarget.style.color = 'var(--accent)'; } }}
485
+ onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--muted)'; }}
486
+ >
487
+ <ChevronRight size={15} />
488
+ </button>
489
+ );
490
+ };
344
491
 
345
492
  const renderTriggerBtn = () => (
346
493
  <button ref={btnRef} type="button" onClick={toggleOpen} style={{
@@ -404,9 +551,20 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
404
551
  }}>
405
552
  {/* Calendar header */}
406
553
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
407
- <button type="button" onClick={() => nav(-1)} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 4, borderRadius: 'var(--radxs, 6px)', color: 'var(--muted)', display: 'flex', alignItems: 'center' }}
408
- onMouseEnter={e => e.currentTarget.style.background = 'var(--grey-1)'}
409
- onMouseLeave={e => e.currentTarget.style.background = 'none'}>
554
+ <button
555
+ type="button"
556
+ disabled={isNavPrevDisabled()}
557
+ onClick={() => nav(-1)}
558
+ style={{
559
+ background: 'none', border: 'none',
560
+ cursor: isNavPrevDisabled() ? 'not-allowed' : 'pointer',
561
+ opacity: isNavPrevDisabled() ? 0.35 : 1,
562
+ padding: 4, borderRadius: 'var(--radxs, 6px)',
563
+ color: 'var(--muted)', display: 'flex', alignItems: 'center'
564
+ }}
565
+ onMouseEnter={e => { if (!isNavPrevDisabled()) e.currentTarget.style.background = 'var(--grey-1)'; }}
566
+ onMouseLeave={e => e.currentTarget.style.background = 'none'}
567
+ >
410
568
  <ChevronLeft size={15} />
411
569
  </button>
412
570
  <button type="button" onClick={cycleView} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: '.82rem', fontWeight: 700, color: 'var(--primary-text)', padding: '4px 8px', borderRadius: 'var(--radxs, 6px)', fontFamily: 'var(--sans)' }}
@@ -414,9 +572,20 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
414
572
  onMouseLeave={e => e.currentTarget.style.background = 'none'}>
415
573
  {headerLabel}
416
574
  </button>
417
- <button type="button" onClick={() => nav(1)} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 4, borderRadius: 'var(--radxs, 6px)', color: 'var(--muted)', display: 'flex', alignItems: 'center' }}
418
- onMouseEnter={e => e.currentTarget.style.background = 'var(--grey-1)'}
419
- onMouseLeave={e => e.currentTarget.style.background = 'none'}>
575
+ <button
576
+ type="button"
577
+ disabled={isNavNextDisabled()}
578
+ onClick={() => nav(1)}
579
+ style={{
580
+ background: 'none', border: 'none',
581
+ cursor: isNavNextDisabled() ? 'not-allowed' : 'pointer',
582
+ opacity: isNavNextDisabled() ? 0.35 : 1,
583
+ padding: 4, borderRadius: 'var(--radxs, 6px)',
584
+ color: 'var(--muted)', display: 'flex', alignItems: 'center'
585
+ }}
586
+ onMouseEnter={e => { if (!isNavNextDisabled()) e.currentTarget.style.background = 'var(--grey-1)'; }}
587
+ onMouseLeave={e => e.currentTarget.style.background = 'none'}
588
+ >
420
589
  <ChevronRight size={15} />
421
590
  </button>
422
591
  </div>
@@ -123,11 +123,12 @@ export default function TabBar({ tabs, active, onChange, capitalize = false, pad
123
123
  >
124
124
  {tabs.map(t => {
125
125
  const hasItems = Array.isArray(t.items) && t.items.length > 0;
126
- const activeSubItem = hasItems ? t.items.find(item => item.id === active) : null;
127
- const isActive = active === t.id || !!activeSubItem;
126
+ const activeSubItem = hasItems ? t.items.find(item => String(item.id) === String(active)) : null;
127
+ const isActive = (active != null && t.id != null && String(active) === String(t.id)) || !!activeSubItem;
128
128
  const isOpen = openDropdownId === t.id;
129
129
 
130
130
  const handleTabClick = () => {
131
+ if (t.disabled) return;
131
132
  if (hasItems) {
132
133
  setOpenDropdownId(isOpen ? null : t.id);
133
134
  } else {
@@ -142,6 +143,7 @@ export default function TabBar({ tabs, active, onChange, capitalize = false, pad
142
143
  type="button"
143
144
  data-tabid={t.id}
144
145
  data-active={isActive}
146
+ disabled={t.disabled}
145
147
  onClick={handleTabClick}
146
148
  style={{
147
149
  display: 'flex', alignItems: 'center', gap: 5,
@@ -150,10 +152,11 @@ export default function TabBar({ tabs, active, onChange, capitalize = false, pad
150
152
  border: 'none',
151
153
  borderRadius: 'var(--radxs, 6px)',
152
154
  background: isActive ? 'var(--surface,#fff)' : isOpen ? 'var(--grey-2, #e5e7eb)' : 'transparent',
153
- boxShadow: isActive ? 'var(--shadow, 0 1px 3px rgba(0,0,0,.08))' : 'none',
155
+ boxShadow: isActive ? '0 1px 3px rgba(0,0,0,.1), 0 1px 2px rgba(0,0,0,.06)' : 'none',
154
156
  fontSize: '.75rem', fontWeight: isActive ? 600 : 500,
155
157
  color: isActive ? 'var(--primary-text, #0f111c)' : 'var(--muted, #9b9daa)',
156
- cursor: 'pointer',
158
+ cursor: t.disabled ? 'not-allowed' : 'pointer',
159
+ opacity: t.disabled ? 0.38 : 1,
157
160
  transition: 'all .18s ease',
158
161
  fontFamily: 'inherit',
159
162
  whiteSpace: 'nowrap',
@@ -266,11 +269,12 @@ export default function TabBar({ tabs, active, onChange, capitalize = false, pad
266
269
 
267
270
  {tabs.map(t => {
268
271
  const hasItems = Array.isArray(t.items) && t.items.length > 0;
269
- const activeSubItem = hasItems ? t.items.find(item => item.id === active) : null;
270
- const isActive = active === t.id || !!activeSubItem;
272
+ const activeSubItem = hasItems ? t.items.find(item => String(item.id) === String(active)) : null;
273
+ const isActive = (active != null && t.id != null && String(active) === String(t.id)) || !!activeSubItem;
271
274
  const isOpen = openDropdownId === t.id;
272
275
 
273
276
  const handleTabClick = () => {
277
+ if (t.disabled) return;
274
278
  if (hasItems) {
275
279
  setOpenDropdownId(isOpen ? null : t.id);
276
280
  } else {
@@ -285,6 +289,7 @@ export default function TabBar({ tabs, active, onChange, capitalize = false, pad
285
289
  type="button"
286
290
  data-tabid={t.id}
287
291
  data-active={isActive}
292
+ disabled={t.disabled}
288
293
  onClick={handleTabClick}
289
294
  style={{
290
295
  display:'flex', alignItems:'center', gap:6,
@@ -292,7 +297,8 @@ export default function TabBar({ tabs, active, onChange, capitalize = false, pad
292
297
  border:'none', background:'none',
293
298
  fontSize:'.82rem', fontWeight:500,
294
299
  color: isActive ? 'var(--accent)' : 'var(--muted)',
295
- cursor:'pointer',
300
+ cursor: t.disabled ? 'not-allowed' : 'pointer',
301
+ opacity: t.disabled ? 0.38 : 1,
296
302
  transition:'color .18s',
297
303
  marginBottom:'-1px',
298
304
  position:'relative', zIndex:1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cynx-ui",
3
- "version": "1.3.12",
3
+ "version": "1.3.14",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js"