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.
- package/components/DatePicker.jsx +229 -64
- package/components/TabBar.jsx +13 -7
- package/package.json +1 -1
|
@@ -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
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
|
412
|
-
|
|
413
|
-
|
|
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
|
|
422
|
-
|
|
423
|
-
|
|
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>
|
package/components/TabBar.jsx
CHANGED
|
@@ -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 ? '
|
|
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,
|