@undevy-org/tablewright 0.4.0 → 0.5.1
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/dist/es/components/data-table/AppliedStateChips.js +15 -6
- package/dist/es/components/data-table/ColumnFilterPopover.js +42 -29
- package/dist/es/components/data-table/CompoundFilterPopover.js +38 -22
- package/dist/es/components/data-table/DrawerShell.js +2 -0
- package/dist/es/components/data-table/FilterFormPanel.js +36 -18
- package/dist/es/components/data-table/FilterPopoverFooter.js +12 -4
- package/dist/es/components/data-table/TableLayout.js +10 -1
- package/dist/es/components/layout/Sidebar.js +8 -1
- package/dist/es/components/layout/SidebarAccountMenu.js +3 -2
- package/dist/es/components/layout/SidebarUser.js +2 -1
- package/dist/es/lib/collapsed-nav-link-a11y.js +6 -0
- package/dist/es/lib/decorative-avatar-alt.js +6 -0
- package/dist/es/lib/scroll-region-tab-index.js +4 -0
- package/dist/styles.css +3 -0
- package/dist/tablewright.cjs +175 -89
- package/dist/tablewright.css +3 -0
- package/dist/types/components/data-table/AppliedStateChips.d.ts +13 -1
- package/dist/types/components/data-table/ColumnFilterPopover.d.ts +22 -1
- package/dist/types/components/data-table/CompoundFilterPopover.d.ts +19 -1
- package/dist/types/components/data-table/FilterFormPanel.d.ts +17 -1
- package/dist/types/components/data-table/FilterPopoverFooter.d.ts +8 -1
- package/dist/types/components/layout/Sidebar.d.ts +1 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/lib/collapsed-nav-link-a11y.d.ts +2 -0
- package/dist/types/lib/decorative-avatar-alt.d.ts +2 -0
- package/dist/types/lib/scroll-region-tab-index.d.ts +2 -0
- package/package.json +7 -2
|
@@ -27,8 +27,13 @@ function AppliedStateChips({
|
|
|
27
27
|
onClearAll,
|
|
28
28
|
columnFilterStats,
|
|
29
29
|
requestOpenFilterId,
|
|
30
|
-
onRequestOpenHandled
|
|
30
|
+
onRequestOpenHandled,
|
|
31
|
+
labels: labelOverrides,
|
|
32
|
+
columnFilterLabels,
|
|
33
|
+
compoundFilterLabels,
|
|
34
|
+
filterPopoverFooterLabels
|
|
31
35
|
}) {
|
|
36
|
+
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
32
37
|
const hasFilters = filters.length > 0;
|
|
33
38
|
const hasSort = currentSort !== null;
|
|
34
39
|
const hasHiddenColumns = hiddenColumnsCount > 0;
|
|
@@ -39,13 +44,13 @@ function AppliedStateChips({
|
|
|
39
44
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pb-3", children: [
|
|
40
45
|
/* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: onClearAll, className: "shrink-0", children: [
|
|
41
46
|
/* @__PURE__ */ jsx(X, { size: 14 }),
|
|
42
|
-
|
|
47
|
+
labels.clearAll
|
|
43
48
|
] }),
|
|
44
49
|
hasHiddenColumns && /* @__PURE__ */ jsx(
|
|
45
50
|
FilterChip,
|
|
46
51
|
{
|
|
47
|
-
label:
|
|
48
|
-
value:
|
|
52
|
+
label: labels.columns,
|
|
53
|
+
value: labels.hiddenCount(hiddenColumnsCount),
|
|
49
54
|
onRemove: onResetColumns,
|
|
50
55
|
onClick: onOpenColumnsMenu,
|
|
51
56
|
className: "cursor-pointer"
|
|
@@ -68,7 +73,9 @@ function AppliedStateChips({
|
|
|
68
73
|
chipClassName: isEmpty ? "border-dashed" : void 0,
|
|
69
74
|
columnFilterStats,
|
|
70
75
|
requestOpen: filter.columnId === requestOpenFilterId,
|
|
71
|
-
onRequestOpenHandled
|
|
76
|
+
onRequestOpenHandled,
|
|
77
|
+
labels: compoundFilterLabels,
|
|
78
|
+
footerLabels: filterPopoverFooterLabels
|
|
72
79
|
},
|
|
73
80
|
filter.columnId
|
|
74
81
|
);
|
|
@@ -84,7 +91,9 @@ function AppliedStateChips({
|
|
|
84
91
|
chipClassName: isEmpty ? "border-dashed" : void 0,
|
|
85
92
|
stats: columnFilterStats?.[filter.columnId],
|
|
86
93
|
requestOpen: filter.columnId === requestOpenFilterId,
|
|
87
|
-
onRequestOpenHandled
|
|
94
|
+
onRequestOpenHandled,
|
|
95
|
+
labels: columnFilterLabels,
|
|
96
|
+
footerLabels: filterPopoverFooterLabels
|
|
88
97
|
},
|
|
89
98
|
filter.columnId
|
|
90
99
|
);
|
|
@@ -18,6 +18,9 @@ const DEFAULT_LABELS = {
|
|
|
18
18
|
chipTo: "to",
|
|
19
19
|
chipMin: "min",
|
|
20
20
|
chipMax: "max",
|
|
21
|
+
contains: "Contains",
|
|
22
|
+
orBadge: "OR",
|
|
23
|
+
numberInputPlaceholder: "0",
|
|
21
24
|
valuesCount: (n) => `${n} values`,
|
|
22
25
|
selectedCount: (n) => `${n} selected`
|
|
23
26
|
};
|
|
@@ -48,8 +51,11 @@ function ColumnFilterPopover({
|
|
|
48
51
|
chipClassName,
|
|
49
52
|
stats,
|
|
50
53
|
requestOpen,
|
|
51
|
-
onRequestOpenHandled
|
|
54
|
+
onRequestOpenHandled,
|
|
55
|
+
labels: labelOverrides,
|
|
56
|
+
footerLabels
|
|
52
57
|
}) {
|
|
58
|
+
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
53
59
|
const [isOpen, setIsOpen] = useState(false);
|
|
54
60
|
const onRequestOpenHandledRef = useRef(onRequestOpenHandled);
|
|
55
61
|
useEffect(() => {
|
|
@@ -186,7 +192,7 @@ function ColumnFilterPopover({
|
|
|
186
192
|
if (!textStats || textStats.type !== "text") {
|
|
187
193
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
|
|
188
194
|
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
|
|
189
|
-
/* @__PURE__ */ jsx("span", { className: "rounded bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[11px] text-[var(--text-tertiary)] italic", children:
|
|
195
|
+
/* @__PURE__ */ jsx("span", { className: "rounded bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[11px] text-[var(--text-tertiary)] italic", children: labels.noData })
|
|
190
196
|
] });
|
|
191
197
|
}
|
|
192
198
|
const { samples, totalUnique } = textStats;
|
|
@@ -216,8 +222,8 @@ function ColumnFilterPopover({
|
|
|
216
222
|
const filtText = filteredStats?.type === "text" ? filteredStats : null;
|
|
217
223
|
return /* @__PURE__ */ jsxs("div", { className: "p-3 w-64 space-y-3", children: [
|
|
218
224
|
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children: [
|
|
219
|
-
|
|
220
|
-
draftTags.length >= 2 && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[10px] font-semibold text-[var(--tag-blue-text)] bg-[var(--tag-blue-bg)] rounded px-1 py-0.5 uppercase", children:
|
|
225
|
+
labels.contains,
|
|
226
|
+
draftTags.length >= 2 && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[10px] font-semibold text-[var(--tag-blue-text)] bg-[var(--tag-blue-bg)] rounded px-1 py-0.5 uppercase", children: labels.orBadge })
|
|
221
227
|
] }),
|
|
222
228
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1 rounded-md border border-[var(--border-subtle)] bg-[var(--bg-surface)] px-2 py-1.5 focus-within:border-[var(--border-focus)]", children: [
|
|
223
229
|
draftTags.map((tag) => /* @__PURE__ */ jsxs(
|
|
@@ -244,7 +250,7 @@ function ColumnFilterPopover({
|
|
|
244
250
|
{
|
|
245
251
|
autoFocus: true,
|
|
246
252
|
className: "flex-1 min-w-[60px] bg-transparent text-sm text-[var(--text-primary)] outline-none placeholder:text-[var(--text-tertiary)]",
|
|
247
|
-
placeholder: draftTags.length === 0 ?
|
|
253
|
+
placeholder: draftTags.length === 0 ? labels.searchPlaceholder : labels.addValuePlaceholder,
|
|
248
254
|
value: draftInput,
|
|
249
255
|
onChange: (e) => setDraftInput(e.target.value),
|
|
250
256
|
onKeyDown: handleTextKeyDown
|
|
@@ -252,8 +258,8 @@ function ColumnFilterPopover({
|
|
|
252
258
|
)
|
|
253
259
|
] }),
|
|
254
260
|
allText && /* @__PURE__ */ jsxs("div", { className: "border-t border-[var(--border-subtle)] pt-2 space-y-2", children: [
|
|
255
|
-
renderTextRow(
|
|
256
|
-
filteredStats !== null && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderTextRow(
|
|
261
|
+
renderTextRow(labels.statsTextAllPrefix, allText),
|
|
262
|
+
filteredStats !== null && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderTextRow(labels.statsTextFilteredPrefix, filtText) })
|
|
257
263
|
] })
|
|
258
264
|
] });
|
|
259
265
|
}
|
|
@@ -292,7 +298,7 @@ function ColumnFilterPopover({
|
|
|
292
298
|
if (!dateStats || dateStats.type !== "date") {
|
|
293
299
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
|
|
294
300
|
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
|
|
295
|
-
/* @__PURE__ */ jsx("span", { className: "rounded bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[11px] text-[var(--text-tertiary)] italic", children:
|
|
301
|
+
/* @__PURE__ */ jsx("span", { className: "rounded bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[11px] text-[var(--text-tertiary)] italic", children: labels.noData })
|
|
296
302
|
] });
|
|
297
303
|
}
|
|
298
304
|
const { min, max } = dateStats;
|
|
@@ -320,7 +326,7 @@ function ColumnFilterPopover({
|
|
|
320
326
|
className: chipCls(draftDate.from === min),
|
|
321
327
|
onClick: () => setDraftDate((prev) => ({ ...prev, from: min })),
|
|
322
328
|
children: [
|
|
323
|
-
|
|
329
|
+
labels.chipFrom,
|
|
324
330
|
" ",
|
|
325
331
|
formatChipDate(min)
|
|
326
332
|
]
|
|
@@ -333,7 +339,7 @@ function ColumnFilterPopover({
|
|
|
333
339
|
className: chipCls(draftDate.to === max),
|
|
334
340
|
onClick: () => setDraftDate((prev) => ({ ...prev, to: max })),
|
|
335
341
|
children: [
|
|
336
|
-
|
|
342
|
+
labels.chipTo,
|
|
337
343
|
" ",
|
|
338
344
|
formatChipDate(max)
|
|
339
345
|
]
|
|
@@ -345,7 +351,7 @@ function ColumnFilterPopover({
|
|
|
345
351
|
const filtDate = filteredStats?.type === "date" ? filteredStats : null;
|
|
346
352
|
return /* @__PURE__ */ jsxs("div", { className: "p-3 w-64 space-y-4", children: [
|
|
347
353
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
348
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children:
|
|
354
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children: labels.chipFrom }),
|
|
349
355
|
/* @__PURE__ */ jsx(
|
|
350
356
|
Input,
|
|
351
357
|
{
|
|
@@ -356,7 +362,7 @@ function ColumnFilterPopover({
|
|
|
356
362
|
)
|
|
357
363
|
] }),
|
|
358
364
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
359
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children:
|
|
365
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children: labels.chipTo }),
|
|
360
366
|
/* @__PURE__ */ jsx(
|
|
361
367
|
Input,
|
|
362
368
|
{
|
|
@@ -367,8 +373,8 @@ function ColumnFilterPopover({
|
|
|
367
373
|
)
|
|
368
374
|
] }),
|
|
369
375
|
allDate && /* @__PURE__ */ jsxs("div", { className: "border-t border-[var(--border-subtle)] pt-2 space-y-2", children: [
|
|
370
|
-
renderDateRow(
|
|
371
|
-
filteredStats !== null && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderDateRow(
|
|
376
|
+
renderDateRow(labels.statsAllPrefix, allDate),
|
|
377
|
+
filteredStats !== null && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderDateRow(labels.statsFilteredPrefix, filtDate) })
|
|
372
378
|
] })
|
|
373
379
|
] });
|
|
374
380
|
}
|
|
@@ -377,7 +383,7 @@ function ColumnFilterPopover({
|
|
|
377
383
|
if (!numStats || numStats.type !== "number-range") {
|
|
378
384
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
|
|
379
385
|
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--text-tertiary)] whitespace-nowrap min-w-[54px]", children: label }),
|
|
380
|
-
/* @__PURE__ */ jsx("span", { className: "rounded bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[11px] text-[var(--text-tertiary)] italic", children:
|
|
386
|
+
/* @__PURE__ */ jsx("span", { className: "rounded bg-[var(--bg-secondary)] px-1.5 py-0.5 text-[11px] text-[var(--text-tertiary)] italic", children: labels.noData })
|
|
381
387
|
] });
|
|
382
388
|
}
|
|
383
389
|
const { min, max } = numStats;
|
|
@@ -408,7 +414,7 @@ function ColumnFilterPopover({
|
|
|
408
414
|
className: chipCls(draftNumberFrom === String(min)),
|
|
409
415
|
onClick: () => setDraftNumberFrom(String(min)),
|
|
410
416
|
children: [
|
|
411
|
-
|
|
417
|
+
labels.chipMin,
|
|
412
418
|
" ",
|
|
413
419
|
min
|
|
414
420
|
]
|
|
@@ -421,7 +427,7 @@ function ColumnFilterPopover({
|
|
|
421
427
|
className: chipCls(draftNumberTo === String(max)),
|
|
422
428
|
onClick: () => setDraftNumberTo(String(max)),
|
|
423
429
|
children: [
|
|
424
|
-
|
|
430
|
+
labels.chipMax,
|
|
425
431
|
" ",
|
|
426
432
|
max
|
|
427
433
|
]
|
|
@@ -433,13 +439,13 @@ function ColumnFilterPopover({
|
|
|
433
439
|
const filtNum = filteredStats?.type === "number-range" ? filteredStats : null;
|
|
434
440
|
return /* @__PURE__ */ jsxs("div", { className: "p-3 w-64 space-y-4", children: [
|
|
435
441
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
436
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children:
|
|
442
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children: labels.chipMin }),
|
|
437
443
|
/* @__PURE__ */ jsx(
|
|
438
444
|
Input,
|
|
439
445
|
{
|
|
440
446
|
type: "number",
|
|
441
447
|
autoFocus: true,
|
|
442
|
-
placeholder: allNum ? String(allNum.min) :
|
|
448
|
+
placeholder: allNum ? String(allNum.min) : labels.numberInputPlaceholder,
|
|
443
449
|
value: draftNumberFrom,
|
|
444
450
|
onChange: (e) => setDraftNumberFrom(e.target.value),
|
|
445
451
|
onKeyDown: (e) => {
|
|
@@ -452,12 +458,12 @@ function ColumnFilterPopover({
|
|
|
452
458
|
)
|
|
453
459
|
] }),
|
|
454
460
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
455
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children:
|
|
461
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider block", children: labels.chipMax }),
|
|
456
462
|
/* @__PURE__ */ jsx(
|
|
457
463
|
Input,
|
|
458
464
|
{
|
|
459
465
|
type: "number",
|
|
460
|
-
placeholder: allNum ? String(allNum.max) :
|
|
466
|
+
placeholder: allNum ? String(allNum.max) : labels.numberInputPlaceholder,
|
|
461
467
|
value: draftNumberTo,
|
|
462
468
|
onChange: (e) => setDraftNumberTo(e.target.value),
|
|
463
469
|
onKeyDown: (e) => {
|
|
@@ -470,8 +476,8 @@ function ColumnFilterPopover({
|
|
|
470
476
|
)
|
|
471
477
|
] }),
|
|
472
478
|
allNum && /* @__PURE__ */ jsxs("div", { className: "border-t border-[var(--border-subtle)] pt-2 space-y-2", children: [
|
|
473
|
-
renderNumRow(
|
|
474
|
-
filteredStats !== null && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderNumRow(
|
|
479
|
+
renderNumRow(labels.statsAllPrefix, allNum),
|
|
480
|
+
filteredStats !== null && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderNumRow(labels.statsFilteredPrefix, filtNum) })
|
|
475
481
|
] })
|
|
476
482
|
] });
|
|
477
483
|
}
|
|
@@ -488,21 +494,21 @@ function ColumnFilterPopover({
|
|
|
488
494
|
}
|
|
489
495
|
if (value.length === 2) {
|
|
490
496
|
const joined = value.join(", ");
|
|
491
|
-
return joined.length > 24 ?
|
|
497
|
+
return joined.length > 24 ? labels.valuesCount(value.length) : joined;
|
|
492
498
|
}
|
|
493
|
-
return
|
|
499
|
+
return labels.valuesCount(value.length);
|
|
494
500
|
}
|
|
495
501
|
if (config.type === "enum" && Array.isArray(value)) {
|
|
496
502
|
if (value.length === 1) {
|
|
497
503
|
return config.options?.find((o) => o.value === value[0])?.label ?? value[0];
|
|
498
504
|
}
|
|
499
|
-
return
|
|
505
|
+
return labels.selectedCount(value.length);
|
|
500
506
|
}
|
|
501
507
|
if (config.type === "date") {
|
|
502
508
|
const { from, to } = getDateRange(value);
|
|
503
509
|
if (from && to) return `${from} — ${to}`;
|
|
504
|
-
if (from) return `${
|
|
505
|
-
if (to) return `${
|
|
510
|
+
if (from) return `${labels.chipFrom} ${from}`;
|
|
511
|
+
if (to) return `${labels.chipTo} ${to}`;
|
|
506
512
|
}
|
|
507
513
|
if (config.type === "number-range") {
|
|
508
514
|
const { from, to } = getNumberRange(value);
|
|
@@ -531,7 +537,14 @@ function ColumnFilterPopover({
|
|
|
531
537
|
className: "w-auto p-0 [box-shadow:var(--shadow-menu)] border-[var(--border-subtle)] flex flex-col max-h-[min(var(--radix-popover-content-available-height),520px)] overflow-hidden",
|
|
532
538
|
children: [
|
|
533
539
|
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto overscroll-contain", children: renderContent() }),
|
|
534
|
-
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0 bg-[var(--bg-surface)]", children: /* @__PURE__ */ jsx(
|
|
540
|
+
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0 bg-[var(--bg-surface)]", children: /* @__PURE__ */ jsx(
|
|
541
|
+
FilterPopoverFooter,
|
|
542
|
+
{
|
|
543
|
+
onApply: handleApply,
|
|
544
|
+
onClear: handleClear,
|
|
545
|
+
labels: footerLabels
|
|
546
|
+
}
|
|
547
|
+
) })
|
|
535
548
|
]
|
|
536
549
|
}
|
|
537
550
|
)
|
|
@@ -10,10 +10,16 @@ const DEFAULT_LABELS = {
|
|
|
10
10
|
searchPlaceholder: "Search...",
|
|
11
11
|
addPlaceholder: "Add...",
|
|
12
12
|
contains: "Contains",
|
|
13
|
+
orBadge: "OR",
|
|
13
14
|
chipMin: "min",
|
|
14
15
|
chipMax: "max",
|
|
15
16
|
statsAllPrefix: "All:",
|
|
16
|
-
statsFilteredPrefix: "Filt:"
|
|
17
|
+
statsFilteredPrefix: "Filt:",
|
|
18
|
+
numberInputPlaceholder: "0",
|
|
19
|
+
resetSectionTitle: (subLabel) => `Reset ${subLabel}`,
|
|
20
|
+
filtersCount: (n) => `${n} filters`,
|
|
21
|
+
subValuesCount: (n) => `${n} values`,
|
|
22
|
+
subSelectedCount: (n) => `${n} selected`
|
|
17
23
|
};
|
|
18
24
|
function initDraft(sub, compound) {
|
|
19
25
|
const val = compound[sub.key];
|
|
@@ -48,7 +54,7 @@ function draftToValue(draft) {
|
|
|
48
54
|
const all = trimmed.length > 0 && !draft.tags.includes(trimmed) ? [...draft.tags, trimmed] : [...draft.tags];
|
|
49
55
|
return all.length > 0 ? all : void 0;
|
|
50
56
|
}
|
|
51
|
-
function formatSubDisplay(sub, val) {
|
|
57
|
+
function formatSubDisplay(sub, val, labels) {
|
|
52
58
|
if (isFilterValueEmpty(val)) return null;
|
|
53
59
|
if (sub.type === "number-range") {
|
|
54
60
|
const { from, to } = getNumberRange(val);
|
|
@@ -62,14 +68,14 @@ function formatSubDisplay(sub, val) {
|
|
|
62
68
|
const label = sub.options?.find((o) => o.value === val[0])?.label ?? val[0];
|
|
63
69
|
return `${sub.label}: ${label}`;
|
|
64
70
|
}
|
|
65
|
-
return `${sub.label}: ${val.length}
|
|
71
|
+
return `${sub.label}: ${labels.subSelectedCount(val.length)}`;
|
|
66
72
|
}
|
|
67
73
|
if (Array.isArray(val) && val.length > 0) {
|
|
68
74
|
if (val.length === 1) {
|
|
69
75
|
const display = val[0].length > 15 ? `${val[0].slice(0, 15)}…` : val[0];
|
|
70
76
|
return `${sub.label}: ${display}`;
|
|
71
77
|
}
|
|
72
|
-
return `${sub.label}: ${val.length}
|
|
78
|
+
return `${sub.label}: ${labels.subValuesCount(val.length)}`;
|
|
73
79
|
}
|
|
74
80
|
return null;
|
|
75
81
|
}
|
|
@@ -82,8 +88,11 @@ function CompoundFilterPopover({
|
|
|
82
88
|
chipClassName,
|
|
83
89
|
columnFilterStats,
|
|
84
90
|
requestOpen,
|
|
85
|
-
onRequestOpenHandled
|
|
91
|
+
onRequestOpenHandled,
|
|
92
|
+
labels: labelOverrides,
|
|
93
|
+
footerLabels
|
|
86
94
|
}) {
|
|
95
|
+
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
87
96
|
const subFilters = config.subFilters ?? [];
|
|
88
97
|
const compound = getCompoundValue(filter.value);
|
|
89
98
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -159,12 +168,12 @@ function CompoundFilterPopover({
|
|
|
159
168
|
if (isFilterValueEmpty(compound)) return void 0;
|
|
160
169
|
const parts = [];
|
|
161
170
|
for (const sub of subFilters) {
|
|
162
|
-
const display = formatSubDisplay(sub, compound[sub.key]);
|
|
171
|
+
const display = formatSubDisplay(sub, compound[sub.key], labels);
|
|
163
172
|
if (display) parts.push(display);
|
|
164
173
|
}
|
|
165
174
|
if (parts.length === 0) return void 0;
|
|
166
175
|
const joined = parts.join(" & ");
|
|
167
|
-
return joined.length > 30 ?
|
|
176
|
+
return joined.length > 30 ? labels.filtersCount(parts.length) : joined;
|
|
168
177
|
};
|
|
169
178
|
const updateDraft = (key, updater) => {
|
|
170
179
|
setDrafts((prev) => ({ ...prev, [key]: updater(prev[key]) }));
|
|
@@ -187,7 +196,7 @@ function CompoundFilterPopover({
|
|
|
187
196
|
className: chipCls(draft.from === String(min)),
|
|
188
197
|
onClick: () => updateDraft(sub.key, (d) => ({ ...d, from: String(min) })),
|
|
189
198
|
children: [
|
|
190
|
-
|
|
199
|
+
labels.chipMin,
|
|
191
200
|
" ",
|
|
192
201
|
min.toLocaleString()
|
|
193
202
|
]
|
|
@@ -200,7 +209,7 @@ function CompoundFilterPopover({
|
|
|
200
209
|
className: chipCls(draft.to === String(max)),
|
|
201
210
|
onClick: () => updateDraft(sub.key, (d) => ({ ...d, to: String(max) })),
|
|
202
211
|
children: [
|
|
203
|
-
|
|
212
|
+
labels.chipMax,
|
|
204
213
|
" ",
|
|
205
214
|
max.toLocaleString()
|
|
206
215
|
]
|
|
@@ -211,13 +220,13 @@ function CompoundFilterPopover({
|
|
|
211
220
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
212
221
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
213
222
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1", children: [
|
|
214
|
-
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-[0.1em] text-[var(--text-tertiary)] block", children:
|
|
223
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-[0.1em] text-[var(--text-tertiary)] block", children: labels.chipMin }),
|
|
215
224
|
/* @__PURE__ */ jsx(
|
|
216
225
|
Input,
|
|
217
226
|
{
|
|
218
227
|
type: "number",
|
|
219
228
|
autoFocus: subFilters.indexOf(sub) === 0,
|
|
220
|
-
placeholder: allNum ? String(allNum.min) :
|
|
229
|
+
placeholder: allNum ? String(allNum.min) : labels.numberInputPlaceholder,
|
|
221
230
|
value: draft.from,
|
|
222
231
|
onChange: (e) => updateDraft(sub.key, (prev) => ({
|
|
223
232
|
...prev,
|
|
@@ -233,12 +242,12 @@ function CompoundFilterPopover({
|
|
|
233
242
|
)
|
|
234
243
|
] }),
|
|
235
244
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1", children: [
|
|
236
|
-
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-[0.1em] text-[var(--text-tertiary)] block", children:
|
|
245
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-[0.1em] text-[var(--text-tertiary)] block", children: labels.chipMax }),
|
|
237
246
|
/* @__PURE__ */ jsx(
|
|
238
247
|
Input,
|
|
239
248
|
{
|
|
240
249
|
type: "number",
|
|
241
|
-
placeholder: allNum ? String(allNum.max) :
|
|
250
|
+
placeholder: allNum ? String(allNum.max) : labels.numberInputPlaceholder,
|
|
242
251
|
value: draft.to,
|
|
243
252
|
onChange: (e) => updateDraft(sub.key, (prev) => ({
|
|
244
253
|
...prev,
|
|
@@ -255,8 +264,8 @@ function CompoundFilterPopover({
|
|
|
255
264
|
] })
|
|
256
265
|
] }),
|
|
257
266
|
allNum && /* @__PURE__ */ jsxs("div", { className: "border-t border-[var(--border-subtle)] mt-3 pt-2 space-y-2", children: [
|
|
258
|
-
renderNumRow(
|
|
259
|
-
filtNum && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderNumRow(
|
|
267
|
+
renderNumRow(labels.statsAllPrefix, allNum),
|
|
268
|
+
filtNum && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderNumRow(labels.statsFilteredPrefix, filtNum) })
|
|
260
269
|
] })
|
|
261
270
|
] });
|
|
262
271
|
};
|
|
@@ -353,8 +362,8 @@ function CompoundFilterPopover({
|
|
|
353
362
|
};
|
|
354
363
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
355
364
|
/* @__PURE__ */ jsxs("span", { className: "text-[10px] font-semibold uppercase tracking-[0.1em] text-[var(--text-tertiary)] block mb-2", children: [
|
|
356
|
-
|
|
357
|
-
draft.tags.length >= 2 && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[10px] font-semibold text-[var(--tag-blue-text)] bg-[var(--tag-blue-bg)] rounded px-1 py-0.5 uppercase", children:
|
|
365
|
+
labels.contains,
|
|
366
|
+
draft.tags.length >= 2 && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[10px] font-semibold text-[var(--tag-blue-text)] bg-[var(--tag-blue-bg)] rounded px-1 py-0.5 uppercase", children: labels.orBadge })
|
|
358
367
|
] }),
|
|
359
368
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1 rounded-md border border-[var(--border-subtle)] bg-[var(--bg-surface)] px-2 py-1.5 focus-within:border-[var(--border-focus)]", children: [
|
|
360
369
|
draft.tags.map((tag) => /* @__PURE__ */ jsxs(
|
|
@@ -384,7 +393,7 @@ function CompoundFilterPopover({
|
|
|
384
393
|
{
|
|
385
394
|
autoFocus: subFilters.indexOf(sub) === 0,
|
|
386
395
|
className: "flex-1 min-w-[50px] bg-transparent text-sm text-[var(--text-primary)] outline-none placeholder:text-[var(--text-tertiary)]",
|
|
387
|
-
placeholder: draft.tags.length === 0 ?
|
|
396
|
+
placeholder: draft.tags.length === 0 ? labels.searchPlaceholder : labels.addPlaceholder,
|
|
388
397
|
value: draft.input,
|
|
389
398
|
onChange: (e) => updateDraft(sub.key, (prev) => ({ ...prev, input: e.target.value })),
|
|
390
399
|
onKeyDown: handleTextKeyDown
|
|
@@ -392,8 +401,8 @@ function CompoundFilterPopover({
|
|
|
392
401
|
)
|
|
393
402
|
] }),
|
|
394
403
|
allText && /* @__PURE__ */ jsxs("div", { className: "border-t border-[var(--border-subtle)] mt-3 pt-2 space-y-2", children: [
|
|
395
|
-
renderTextRow(
|
|
396
|
-
filtText && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderTextRow(
|
|
404
|
+
renderTextRow(labels.statsAllPrefix, allText),
|
|
405
|
+
filtText && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border-subtle)] pt-1.5", children: renderTextRow(labels.statsFilteredPrefix, filtText) })
|
|
397
406
|
] })
|
|
398
407
|
] });
|
|
399
408
|
};
|
|
@@ -475,7 +484,7 @@ function CompoundFilterPopover({
|
|
|
475
484
|
type: "button",
|
|
476
485
|
className: "text-[11px] text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] transition-colors",
|
|
477
486
|
onClick: () => handleResetSection(sub.key),
|
|
478
|
-
title:
|
|
487
|
+
title: labels.resetSectionTitle(sub.label),
|
|
479
488
|
children: "✕"
|
|
480
489
|
}
|
|
481
490
|
)
|
|
@@ -487,7 +496,14 @@ function CompoundFilterPopover({
|
|
|
487
496
|
},
|
|
488
497
|
sub.key
|
|
489
498
|
)) }),
|
|
490
|
-
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0 bg-[var(--bg-surface)]", children: /* @__PURE__ */ jsx(
|
|
499
|
+
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0 bg-[var(--bg-surface)]", children: /* @__PURE__ */ jsx(
|
|
500
|
+
FilterPopoverFooter,
|
|
501
|
+
{
|
|
502
|
+
onApply: handleApply,
|
|
503
|
+
onClear: handleClearAll,
|
|
504
|
+
labels: footerLabels
|
|
505
|
+
}
|
|
506
|
+
) })
|
|
491
507
|
]
|
|
492
508
|
}
|
|
493
509
|
)
|
|
@@ -3,6 +3,7 @@ import { useState, useRef, useEffect } from "react";
|
|
|
3
3
|
import { Minimize2, Maximize2, X } from "lucide-react";
|
|
4
4
|
import { Button } from "../ui/button.js";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { SCROLL_REGION_TAB_INDEX } from "../../lib/scroll-region-tab-index.js";
|
|
6
7
|
import { useScrollActivity } from "../../hooks/use-scroll-activity.js";
|
|
7
8
|
import { DrawerExpandContext } from "../../context/drawer-expand-context.js";
|
|
8
9
|
function DrawerShell({
|
|
@@ -86,6 +87,7 @@ function DrawerShell({
|
|
|
86
87
|
"div",
|
|
87
88
|
{
|
|
88
89
|
ref: bodyScrollRef,
|
|
90
|
+
tabIndex: SCROLL_REGION_TAB_INDEX,
|
|
89
91
|
className: "scrollbar-auto-hide min-h-0 flex-1 overflow-y-auto px-6 py-6",
|
|
90
92
|
children
|
|
91
93
|
}
|
|
@@ -8,6 +8,21 @@ import { Input } from "../ui/input.js";
|
|
|
8
8
|
import { Combobox } from "../ui/combobox.js";
|
|
9
9
|
import { isFilterValueEmpty, getDateRange, getNumberRange, getCompoundValue } from "./filter-values.js";
|
|
10
10
|
import { hasActiveManagedFilters } from "./managed-filters.js";
|
|
11
|
+
const DEFAULT_LABELS = {
|
|
12
|
+
closeFiltersAriaLabel: "Close filters",
|
|
13
|
+
closeFiltersTitle: "Close filters",
|
|
14
|
+
apply: "Apply",
|
|
15
|
+
clear: "Clear",
|
|
16
|
+
anyPlaceholder: "Any",
|
|
17
|
+
rangeFromPlaceholder: "From",
|
|
18
|
+
rangeToPlaceholder: "To",
|
|
19
|
+
fieldRangeFromAriaLabel: (fieldLabel) => `${fieldLabel} — from`,
|
|
20
|
+
fieldRangeToAriaLabel: (fieldLabel) => `${fieldLabel} — to`,
|
|
21
|
+
compoundSubRangeFromAriaLabel: (subLabel) => `${subLabel} — from`,
|
|
22
|
+
compoundSubRangeToAriaLabel: (subLabel) => `${subLabel} — to`,
|
|
23
|
+
compoundSubRangeFromPlaceholder: (subLabel) => `${subLabel} from`,
|
|
24
|
+
compoundSubRangeToPlaceholder: (subLabel) => `${subLabel} to`
|
|
25
|
+
};
|
|
11
26
|
function deriveInitialState(fields, filters) {
|
|
12
27
|
const byId = new Map(filters.map((f) => [f.columnId, f.value]));
|
|
13
28
|
const next = {};
|
|
@@ -24,8 +39,10 @@ function FilterFormPanel({
|
|
|
24
39
|
className,
|
|
25
40
|
id,
|
|
26
41
|
onClose,
|
|
27
|
-
hiddenSubFilterKeys
|
|
42
|
+
hiddenSubFilterKeys,
|
|
43
|
+
labels: labelOverrides
|
|
28
44
|
}) {
|
|
45
|
+
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
29
46
|
const hiddenSubSet = useMemo(() => new Set(hiddenSubFilterKeys ?? []), [hiddenSubFilterKeys]);
|
|
30
47
|
const renderableFields = useMemo(
|
|
31
48
|
() => fields.filter((id2) => {
|
|
@@ -100,8 +117,8 @@ function FilterFormPanel({
|
|
|
100
117
|
type: "button",
|
|
101
118
|
variant: "ghost",
|
|
102
119
|
size: "icon",
|
|
103
|
-
"aria-label":
|
|
104
|
-
title:
|
|
120
|
+
"aria-label": labels.closeFiltersAriaLabel,
|
|
121
|
+
title: labels.closeFiltersTitle,
|
|
105
122
|
onClick: onClose,
|
|
106
123
|
className: "h-7 w-7",
|
|
107
124
|
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
@@ -118,7 +135,8 @@ function FilterFormPanel({
|
|
|
118
135
|
value: state[fieldId],
|
|
119
136
|
onChange: (v) => setField(fieldId, v),
|
|
120
137
|
fieldId,
|
|
121
|
-
hiddenSubSet
|
|
138
|
+
hiddenSubSet,
|
|
139
|
+
labels
|
|
122
140
|
},
|
|
123
141
|
fieldId
|
|
124
142
|
);
|
|
@@ -132,16 +150,16 @@ function FilterFormPanel({
|
|
|
132
150
|
size: "sm",
|
|
133
151
|
onClick: handleClear,
|
|
134
152
|
disabled: isClearDisabled,
|
|
135
|
-
children:
|
|
153
|
+
children: labels.clear
|
|
136
154
|
}
|
|
137
155
|
),
|
|
138
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", children:
|
|
156
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", children: labels.apply })
|
|
139
157
|
] }) })
|
|
140
158
|
]
|
|
141
159
|
}
|
|
142
160
|
);
|
|
143
161
|
}
|
|
144
|
-
function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
162
|
+
function Field({ config, value, onChange, fieldId, hiddenSubSet, labels }) {
|
|
145
163
|
if (config.type === "text") {
|
|
146
164
|
const text = Array.isArray(value) ? value[0] ?? "" : "";
|
|
147
165
|
return /* @__PURE__ */ jsxs("label", { className: "flex min-w-0 flex-col gap-1.5", children: [
|
|
@@ -170,7 +188,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
170
188
|
value: selected || null,
|
|
171
189
|
onChange: (v) => onChange(v ? [v] : void 0),
|
|
172
190
|
options: config.options ?? [],
|
|
173
|
-
placeholder:
|
|
191
|
+
placeholder: labels.anyPlaceholder,
|
|
174
192
|
triggerAriaLabel: config.label,
|
|
175
193
|
triggerClassName: "h-9 min-w-0"
|
|
176
194
|
}
|
|
@@ -191,7 +209,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
191
209
|
Input,
|
|
192
210
|
{
|
|
193
211
|
type: "date",
|
|
194
|
-
"aria-label":
|
|
212
|
+
"aria-label": labels.fieldRangeFromAriaLabel(config.label),
|
|
195
213
|
value: range.from ?? "",
|
|
196
214
|
onChange: update("from"),
|
|
197
215
|
className: "h-9 min-w-0 flex-1"
|
|
@@ -201,7 +219,7 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
201
219
|
Input,
|
|
202
220
|
{
|
|
203
221
|
type: "date",
|
|
204
|
-
"aria-label":
|
|
222
|
+
"aria-label": labels.fieldRangeToAriaLabel(config.label),
|
|
205
223
|
value: range.to ?? "",
|
|
206
224
|
onChange: update("to"),
|
|
207
225
|
className: "h-9 min-w-0 flex-1"
|
|
@@ -225,10 +243,10 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
225
243
|
Input,
|
|
226
244
|
{
|
|
227
245
|
type: "number",
|
|
228
|
-
"aria-label":
|
|
246
|
+
"aria-label": labels.fieldRangeFromAriaLabel(config.label),
|
|
229
247
|
value: range.from ?? "",
|
|
230
248
|
onChange: update("from"),
|
|
231
|
-
placeholder:
|
|
249
|
+
placeholder: labels.rangeFromPlaceholder,
|
|
232
250
|
className: "h-9 min-w-0 flex-1"
|
|
233
251
|
}
|
|
234
252
|
),
|
|
@@ -236,10 +254,10 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
236
254
|
Input,
|
|
237
255
|
{
|
|
238
256
|
type: "number",
|
|
239
|
-
"aria-label":
|
|
257
|
+
"aria-label": labels.fieldRangeToAriaLabel(config.label),
|
|
240
258
|
value: range.to ?? "",
|
|
241
259
|
onChange: update("to"),
|
|
242
|
-
placeholder:
|
|
260
|
+
placeholder: labels.rangeToPlaceholder,
|
|
243
261
|
className: "h-9 min-w-0 flex-1"
|
|
244
262
|
}
|
|
245
263
|
)
|
|
@@ -319,10 +337,10 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
319
337
|
Input,
|
|
320
338
|
{
|
|
321
339
|
type: "number",
|
|
322
|
-
"aria-label":
|
|
340
|
+
"aria-label": labels.compoundSubRangeFromAriaLabel(sub.label),
|
|
323
341
|
value: range.from ?? "",
|
|
324
342
|
onChange: updateRange("from"),
|
|
325
|
-
placeholder:
|
|
343
|
+
placeholder: labels.compoundSubRangeFromPlaceholder(sub.label),
|
|
326
344
|
className: "h-9 min-w-0 flex-1"
|
|
327
345
|
}
|
|
328
346
|
),
|
|
@@ -330,10 +348,10 @@ function Field({ config, value, onChange, fieldId, hiddenSubSet }) {
|
|
|
330
348
|
Input,
|
|
331
349
|
{
|
|
332
350
|
type: "number",
|
|
333
|
-
"aria-label":
|
|
351
|
+
"aria-label": labels.compoundSubRangeToAriaLabel(sub.label),
|
|
334
352
|
value: range.to ?? "",
|
|
335
353
|
onChange: updateRange("to"),
|
|
336
|
-
placeholder:
|
|
354
|
+
placeholder: labels.compoundSubRangeToPlaceholder(sub.label),
|
|
337
355
|
className: "h-9 min-w-0 flex-1"
|
|
338
356
|
}
|
|
339
357
|
)
|