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.
- package/components/DatePicker.jsx +235 -66
- package/components/TabBar.jsx +13 -7
- package/package.json +1 -1
|
@@ -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
|
-
|
|
60
|
-
|
|
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
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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 =
|
|
222
|
-
const disMax =
|
|
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
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
|
408
|
-
|
|
409
|
-
|
|
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
|
|
418
|
-
|
|
419
|
-
|
|
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>
|
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,
|