cynx-ui 1.3.13 → 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.
@@ -139,6 +139,12 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
139
139
  };
140
140
 
141
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
+
142
148
  if (typeof value === 'string') {
143
149
  const y = date.getFullYear();
144
150
  const m = pad(date.getMonth() + 1);
@@ -151,7 +157,49 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
151
157
  setView('day');
152
158
  }
153
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
+
154
200
  function nav(dir) {
201
+ if (dir > 0 && isNavNextDisabled()) return;
202
+ if (dir < 0 && isNavPrevDisabled()) return;
155
203
  const c = new Date(cursor);
156
204
  if (view === 'day') { c.setMonth(c.getMonth() + dir); }
157
205
  if (view === 'month') { c.setFullYear(c.getFullYear() + dir); }
@@ -190,6 +238,9 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
190
238
  for (let d = 1; d <= remaining; d++)
191
239
  cells.push({ day: d, cur: false, next: true });
192
240
 
241
+ const minD = toDate(minDate);
242
+ const maxD = toDate(maxDate);
243
+
193
244
  return (
194
245
  <div>
195
246
  {/* Weekday headers */}
@@ -206,11 +257,26 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
206
257
  const date = isPrev
207
258
  ? new Date(y, m - 1, c.day)
208
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;
209
263
  return (
210
- <div key={i} onClick={() => { pick(date); }}
211
- style={{ textAlign: 'center', padding: '6px 2px', fontSize: '.78rem', color: 'var(--muted)', cursor: 'pointer', borderRadius: 'var(--radxs, 6px)', transition: 'background .12s' }}
212
- onMouseEnter={e => e.currentTarget.style.background = 'var(--grey-1)'}
213
- 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
+ >
214
280
  {c.day}
215
281
  </div>
216
282
  );
@@ -220,8 +286,6 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
220
286
  const badge = badges[key];
221
287
  const sel = isSameDay(date, value);
222
288
  const today = isToday(y, m, c.day);
223
- const minD = toDate(minDate);
224
- const maxD = toDate(maxDate);
225
289
  const disMin = minD && date < new Date(new Date(minD).setHours(0,0,0,0));
226
290
  const disMax = maxD && date > new Date(new Date(maxD).setHours(23,59,59,999));
227
291
  const dis = disMin || disMax;
@@ -234,6 +298,7 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
234
298
  color: sel ? 'var(--def-btn-text,#fff)' : dis ? 'var(--border)' : today ? 'var(--accent)' : 'var(--primary-text)',
235
299
  border: today && !sel ? '1.5px solid var(--accent)' : '1.5px solid transparent',
236
300
  transition: 'background .12s, color .12s',
301
+ opacity: dis ? 0.35 : 1,
237
302
  }}
238
303
  onMouseEnter={e => { if (!sel && !dis) e.currentTarget.style.background = 'var(--grey-1)'; }}
239
304
  onMouseLeave={e => { if (!sel && !dis) e.currentTarget.style.background = 'transparent'; }}>
@@ -256,17 +321,44 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
256
321
  // ── Month grid ───────────────────────────────────────────────
257
322
  function renderMonths() {
258
323
  const y = cursor.getFullYear();
324
+ const minD = toDate(minDate);
325
+ const maxD = toDate(maxDate);
326
+
259
327
  return (
260
328
  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4, padding: '4px 0' }}>
261
329
  {MONTHS.map((name, i) => {
262
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
+
263
337
  return (
264
- <div key={i} onClick={() => { const c = new Date(cursor); c.setMonth(i); setCursor(c); setView('day'); }}
265
- style={{ textAlign: 'center', padding: '10px 4px', borderRadius: 'var(--radxs, 6px)', fontSize: '.82rem', fontWeight: 600, cursor: 'pointer',
266
- background: sel ? 'var(--accent)' : 'transparent', color: sel ? 'var(--def-btn-text,#fff)' : 'var(--primary-text)',
267
- transition: 'background .12s', }}
268
- onMouseEnter={e => { if (!sel) e.currentTarget.style.background = 'var(--grey-1)'; }}
269
- 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
+ >
270
362
  {name}
271
363
  </div>
272
364
  );
@@ -279,17 +371,44 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
279
371
  function renderYears() {
280
372
  const base = Math.floor(cursor.getFullYear() / 12) * 12;
281
373
  const years = Array.from({ length: 12 }, (_, i) => base + i);
374
+ const minD = toDate(minDate);
375
+ const maxD = toDate(maxDate);
376
+
282
377
  return (
283
378
  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4, padding: '4px 0' }}>
284
379
  {years.map(yr => {
285
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
+
286
387
  return (
287
- <div key={yr} onClick={() => { const c = new Date(cursor); c.setFullYear(yr); setCursor(c); setView('month'); }}
288
- style={{ textAlign: 'center', padding: '10px 4px', borderRadius: 'var(--radxs, 6px)', fontSize: '.82rem', fontWeight: 600, cursor: 'pointer',
289
- background: sel ? 'var(--accent)' : 'transparent', color: sel ? 'var(--def-btn-text,#fff)' : 'var(--primary-text)',
290
- transition: 'background .12s', }}
291
- onMouseEnter={e => { if (!sel) e.currentTarget.style.background = 'var(--grey-1)'; }}
292
- 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
+ >
293
412
  {yr}
294
413
  </div>
295
414
  );
@@ -304,47 +423,71 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
304
423
  ? `${cursor.getFullYear()}`
305
424
  : `${Math.floor(cursor.getFullYear()/12)*12} – ${Math.floor(cursor.getFullYear()/12)*12+11}`;
306
425
 
307
- const renderPrevBtn = () => (
308
- <button
309
- type="button"
310
- disabled={disabled}
311
- onClick={(e) => { e.stopPropagation(); changeDay(-1); }}
312
- style={{
313
- display: 'flex', alignItems: 'center', justifyContent: 'center',
314
- width: 32, height: 32, borderRadius: 'var(--rads)',
315
- border: '1px solid var(--border)', background: 'var(--surface)',
316
- cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.5 : 1,
317
- color: 'var(--muted)', transition: 'all .15s', boxShadow: 'var(--shadow)',
318
- flexShrink: 0
319
- }}
320
- title="Previous day (-1)"
321
- onMouseEnter={e => { if (!disabled) { e.currentTarget.style.borderColor = 'var(--accent)'; e.currentTarget.style.color = 'var(--accent)'; } }}
322
- onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--muted)'; }}
323
- >
324
- <ChevronLeft size={15} />
325
- </button>
326
- );
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
+ };
327
434
 
328
- const renderNextBtn = () => (
329
- <button
330
- type="button"
331
- disabled={disabled}
332
- onClick={(e) => { e.stopPropagation(); changeDay(1); }}
333
- style={{
334
- display: 'flex', alignItems: 'center', justifyContent: 'center',
335
- width: 32, height: 32, borderRadius: 'var(--rads)',
336
- border: '1px solid var(--border)', background: 'var(--surface)',
337
- cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.5 : 1,
338
- color: 'var(--muted)', transition: 'all .15s', boxShadow: 'var(--shadow)',
339
- flexShrink: 0
340
- }}
341
- title="Next day (+1)"
342
- onMouseEnter={e => { if (!disabled) { e.currentTarget.style.borderColor = 'var(--accent)'; e.currentTarget.style.color = 'var(--accent)'; } }}
343
- onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--muted)'; }}
344
- >
345
- <ChevronRight size={15} />
346
- </button>
347
- );
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
+ };
348
491
 
349
492
  const renderTriggerBtn = () => (
350
493
  <button ref={btnRef} type="button" onClick={toggleOpen} style={{
@@ -408,9 +551,20 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
408
551
  }}>
409
552
  {/* Calendar header */}
410
553
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
411
- <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' }}
412
- onMouseEnter={e => e.currentTarget.style.background = 'var(--grey-1)'}
413
- 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
+ >
414
568
  <ChevronLeft size={15} />
415
569
  </button>
416
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)' }}
@@ -418,9 +572,20 @@ export function DatePicker({ value, onChange, label, placeholder = 'Select date'
418
572
  onMouseLeave={e => e.currentTarget.style.background = 'none'}>
419
573
  {headerLabel}
420
574
  </button>
421
- <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' }}
422
- onMouseEnter={e => e.currentTarget.style.background = 'var(--grey-1)'}
423
- 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
+ >
424
589
  <ChevronRight size={15} />
425
590
  </button>
426
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.13",
3
+ "version": "1.3.14",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js"