@rowakit/table 0.1.0 → 0.2.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/README.md +296 -1
- package/dist/index.cjs +289 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +144 -6
- package/dist/index.d.ts +144 -6
- package/dist/index.js +289 -33
- package/dist/index.js.map +1 -1
- package/package.json +5 -10
- package/src/styles/table.css +104 -0
package/dist/index.cjs
CHANGED
|
@@ -11,7 +11,10 @@ function text(field, options) {
|
|
|
11
11
|
field,
|
|
12
12
|
header: options?.header,
|
|
13
13
|
sortable: options?.sortable ?? false,
|
|
14
|
-
format: options?.format
|
|
14
|
+
format: options?.format,
|
|
15
|
+
width: options?.width,
|
|
16
|
+
align: options?.align,
|
|
17
|
+
truncate: options?.truncate
|
|
15
18
|
};
|
|
16
19
|
}
|
|
17
20
|
function date(field, options) {
|
|
@@ -21,7 +24,10 @@ function date(field, options) {
|
|
|
21
24
|
field,
|
|
22
25
|
header: options?.header,
|
|
23
26
|
sortable: options?.sortable ?? false,
|
|
24
|
-
format: options?.format
|
|
27
|
+
format: options?.format,
|
|
28
|
+
width: options?.width,
|
|
29
|
+
align: options?.align,
|
|
30
|
+
truncate: options?.truncate
|
|
25
31
|
};
|
|
26
32
|
}
|
|
27
33
|
function boolean(field, options) {
|
|
@@ -31,7 +37,36 @@ function boolean(field, options) {
|
|
|
31
37
|
field,
|
|
32
38
|
header: options?.header,
|
|
33
39
|
sortable: options?.sortable ?? false,
|
|
34
|
-
format: options?.format
|
|
40
|
+
format: options?.format,
|
|
41
|
+
width: options?.width,
|
|
42
|
+
align: options?.align,
|
|
43
|
+
truncate: options?.truncate
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function badge(field, options) {
|
|
47
|
+
return {
|
|
48
|
+
id: field,
|
|
49
|
+
kind: "badge",
|
|
50
|
+
field,
|
|
51
|
+
header: options?.header,
|
|
52
|
+
sortable: options?.sortable ?? false,
|
|
53
|
+
map: options?.map,
|
|
54
|
+
width: options?.width,
|
|
55
|
+
align: options?.align,
|
|
56
|
+
truncate: options?.truncate
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function number(field, options) {
|
|
60
|
+
return {
|
|
61
|
+
id: field,
|
|
62
|
+
kind: "number",
|
|
63
|
+
field,
|
|
64
|
+
header: options?.header,
|
|
65
|
+
sortable: options?.sortable ?? false,
|
|
66
|
+
format: options?.format,
|
|
67
|
+
width: options?.width,
|
|
68
|
+
align: options?.align,
|
|
69
|
+
truncate: options?.truncate
|
|
35
70
|
};
|
|
36
71
|
}
|
|
37
72
|
function actions(actions2) {
|
|
@@ -41,19 +76,32 @@ function actions(actions2) {
|
|
|
41
76
|
actions: actions2
|
|
42
77
|
};
|
|
43
78
|
}
|
|
44
|
-
function custom(
|
|
79
|
+
function custom(arg1, arg2) {
|
|
80
|
+
if (typeof arg1 === "string") {
|
|
81
|
+
if (typeof arg2 !== "function") {
|
|
82
|
+
throw new Error("col.custom(field, render): render must be a function");
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
id: arg1,
|
|
86
|
+
kind: "custom",
|
|
87
|
+
field: arg1,
|
|
88
|
+
render: arg2
|
|
89
|
+
};
|
|
90
|
+
}
|
|
45
91
|
return {
|
|
46
|
-
id:
|
|
92
|
+
id: arg1.id,
|
|
47
93
|
kind: "custom",
|
|
48
|
-
header:
|
|
49
|
-
field:
|
|
50
|
-
render:
|
|
94
|
+
header: arg1.header,
|
|
95
|
+
field: arg1.field,
|
|
96
|
+
render: arg1.render
|
|
51
97
|
};
|
|
52
98
|
}
|
|
53
99
|
var col = {
|
|
54
100
|
text,
|
|
55
101
|
date,
|
|
56
102
|
boolean,
|
|
103
|
+
badge,
|
|
104
|
+
number,
|
|
57
105
|
actions,
|
|
58
106
|
custom
|
|
59
107
|
};
|
|
@@ -101,6 +149,25 @@ function renderCell(column, row, isLoading, setConfirmState) {
|
|
|
101
149
|
}
|
|
102
150
|
return value ? "Yes" : "No";
|
|
103
151
|
}
|
|
152
|
+
case "badge": {
|
|
153
|
+
const value = row[column.field];
|
|
154
|
+
const valueStr = String(value ?? "");
|
|
155
|
+
const mapped = column.map?.[valueStr];
|
|
156
|
+
const label = mapped?.label ?? valueStr;
|
|
157
|
+
const tone = mapped?.tone ?? "neutral";
|
|
158
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `rowakit-badge rowakit-badge-${tone}`, children: label });
|
|
159
|
+
}
|
|
160
|
+
case "number": {
|
|
161
|
+
const value = row[column.field];
|
|
162
|
+
const numValue = Number(value ?? 0);
|
|
163
|
+
if (column.format) {
|
|
164
|
+
if (typeof column.format === "function") {
|
|
165
|
+
return column.format(numValue, row);
|
|
166
|
+
}
|
|
167
|
+
return new Intl.NumberFormat(void 0, column.format).format(numValue);
|
|
168
|
+
}
|
|
169
|
+
return numValue.toLocaleString();
|
|
170
|
+
}
|
|
104
171
|
case "actions": {
|
|
105
172
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rowakit-table-actions", children: column.actions.map((action) => {
|
|
106
173
|
const isDisabled = isLoading || action.disabled === true || typeof action.disabled === "function" && action.disabled(row);
|
|
@@ -145,7 +212,8 @@ function RowaKitTable({
|
|
|
145
212
|
defaultPageSize = 20,
|
|
146
213
|
pageSizeOptions = [10, 20, 50],
|
|
147
214
|
rowKey,
|
|
148
|
-
className = ""
|
|
215
|
+
className = "",
|
|
216
|
+
enableFilters = false
|
|
149
217
|
}) {
|
|
150
218
|
const [dataState, setDataState] = react.useState({
|
|
151
219
|
state: "idle",
|
|
@@ -156,8 +224,27 @@ function RowaKitTable({
|
|
|
156
224
|
page: 1,
|
|
157
225
|
pageSize: defaultPageSize
|
|
158
226
|
});
|
|
227
|
+
const [filters, setFilters] = react.useState({});
|
|
159
228
|
const [confirmState, setConfirmState] = react.useState(null);
|
|
160
229
|
const requestIdRef = react.useRef(0);
|
|
230
|
+
react.useEffect(() => {
|
|
231
|
+
if (!enableFilters) return;
|
|
232
|
+
const activeFilters = {};
|
|
233
|
+
let hasFilters = false;
|
|
234
|
+
for (const [field, value] of Object.entries(filters)) {
|
|
235
|
+
if (value !== void 0) {
|
|
236
|
+
activeFilters[field] = value;
|
|
237
|
+
hasFilters = true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const filtersToSend = hasFilters ? activeFilters : void 0;
|
|
241
|
+
setQuery((prev) => ({
|
|
242
|
+
...prev,
|
|
243
|
+
filters: filtersToSend,
|
|
244
|
+
page: 1
|
|
245
|
+
// Reset page to 1 when filters change
|
|
246
|
+
}));
|
|
247
|
+
}, [filters, enableFilters]);
|
|
161
248
|
react.useEffect(() => {
|
|
162
249
|
const currentRequestId = ++requestIdRef.current;
|
|
163
250
|
setDataState((prev) => ({ ...prev, state: "loading" }));
|
|
@@ -225,38 +312,190 @@ function RowaKitTable({
|
|
|
225
312
|
}
|
|
226
313
|
return query.sort.direction === "asc" ? " \u2191" : " \u2193";
|
|
227
314
|
};
|
|
315
|
+
const handleFilterChange = (field, value) => {
|
|
316
|
+
setFilters((prev) => ({
|
|
317
|
+
...prev,
|
|
318
|
+
[field]: value
|
|
319
|
+
}));
|
|
320
|
+
};
|
|
321
|
+
const handleClearFilter = (field) => {
|
|
322
|
+
setFilters((prev) => {
|
|
323
|
+
const newFilters = { ...prev };
|
|
324
|
+
delete newFilters[field];
|
|
325
|
+
return newFilters;
|
|
326
|
+
});
|
|
327
|
+
};
|
|
328
|
+
const handleClearAllFilters = () => {
|
|
329
|
+
setFilters({});
|
|
330
|
+
};
|
|
228
331
|
const isLoading = dataState.state === "loading";
|
|
229
332
|
const isError = dataState.state === "error";
|
|
230
333
|
const isEmpty = dataState.state === "empty";
|
|
231
334
|
const totalPages = Math.ceil(dataState.total / query.pageSize);
|
|
232
335
|
const canGoPrevious = query.page > 1 && !isLoading;
|
|
233
336
|
const canGoNext = query.page < totalPages && !isLoading;
|
|
337
|
+
const hasActiveFilters = enableFilters && Object.values(filters).some((v) => v !== void 0);
|
|
234
338
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `rowakit-table${className ? ` ${className}` : ""}`, children: [
|
|
339
|
+
hasActiveFilters && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rowakit-table-filter-controls", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
340
|
+
"button",
|
|
341
|
+
{
|
|
342
|
+
onClick: handleClearAllFilters,
|
|
343
|
+
className: "rowakit-button rowakit-button-secondary",
|
|
344
|
+
type: "button",
|
|
345
|
+
children: "Clear all filters"
|
|
346
|
+
}
|
|
347
|
+
) }),
|
|
235
348
|
/* @__PURE__ */ jsxRuntime.jsxs("table", { children: [
|
|
236
|
-
/* @__PURE__ */ jsxRuntime.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
e.
|
|
248
|
-
|
|
349
|
+
/* @__PURE__ */ jsxRuntime.jsxs("thead", { children: [
|
|
350
|
+
/* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((column) => {
|
|
351
|
+
const isSortable = column.kind !== "actions" && (column.kind === "custom" ? false : column.sortable === true);
|
|
352
|
+
const field = column.kind === "actions" ? "" : column.kind === "custom" ? column.field : column.field;
|
|
353
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
354
|
+
"th",
|
|
355
|
+
{
|
|
356
|
+
onClick: isSortable ? () => handleSort(String(field)) : void 0,
|
|
357
|
+
role: isSortable ? "button" : void 0,
|
|
358
|
+
tabIndex: isSortable ? 0 : void 0,
|
|
359
|
+
onKeyDown: isSortable ? (e) => {
|
|
360
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
361
|
+
e.preventDefault();
|
|
362
|
+
handleSort(String(field));
|
|
363
|
+
}
|
|
364
|
+
} : void 0,
|
|
365
|
+
"aria-sort": isSortable && query.sort?.field === String(field) ? query.sort.direction === "asc" ? "ascending" : "descending" : void 0,
|
|
366
|
+
style: {
|
|
367
|
+
width: column.width ? `${column.width}px` : void 0,
|
|
368
|
+
textAlign: column.align
|
|
369
|
+
},
|
|
370
|
+
className: column.truncate ? "rowakit-cell-truncate" : void 0,
|
|
371
|
+
children: [
|
|
372
|
+
getHeaderLabel(column),
|
|
373
|
+
isSortable && getSortIndicator(String(field))
|
|
374
|
+
]
|
|
375
|
+
},
|
|
376
|
+
column.id
|
|
377
|
+
);
|
|
378
|
+
}) }),
|
|
379
|
+
enableFilters && /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "rowakit-table-filter-row", children: columns.map((column) => {
|
|
380
|
+
const field = column.kind === "actions" || column.kind === "custom" ? "" : String(column.field);
|
|
381
|
+
const canFilter = field && column.kind !== "actions";
|
|
382
|
+
if (!canFilter) {
|
|
383
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", {}, column.id);
|
|
384
|
+
}
|
|
385
|
+
const filterValue = filters[field];
|
|
386
|
+
if (column.kind === "badge") {
|
|
387
|
+
const options = column.map ? Object.keys(column.map) : [];
|
|
388
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
389
|
+
"select",
|
|
390
|
+
{
|
|
391
|
+
className: "rowakit-filter-select",
|
|
392
|
+
value: filterValue?.op === "equals" ? String(filterValue.value ?? "") : "",
|
|
393
|
+
onChange: (e) => {
|
|
394
|
+
const value = e.target.value;
|
|
395
|
+
if (value === "") {
|
|
396
|
+
handleClearFilter(field);
|
|
397
|
+
} else {
|
|
398
|
+
handleFilterChange(field, { op: "equals", value });
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
children: [
|
|
402
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All" }),
|
|
403
|
+
options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt, children: opt }, opt))
|
|
404
|
+
]
|
|
249
405
|
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
406
|
+
) }, column.id);
|
|
407
|
+
}
|
|
408
|
+
if (column.kind === "boolean") {
|
|
409
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
410
|
+
"select",
|
|
411
|
+
{
|
|
412
|
+
className: "rowakit-filter-select",
|
|
413
|
+
value: filterValue?.op === "equals" && typeof filterValue.value === "boolean" ? String(filterValue.value) : "",
|
|
414
|
+
onChange: (e) => {
|
|
415
|
+
const value = e.target.value;
|
|
416
|
+
if (value === "") {
|
|
417
|
+
handleClearFilter(field);
|
|
418
|
+
} else {
|
|
419
|
+
handleFilterChange(field, { op: "equals", value: value === "true" });
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
children: [
|
|
423
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All" }),
|
|
424
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "true", children: "True" }),
|
|
425
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "false", children: "False" })
|
|
426
|
+
]
|
|
427
|
+
}
|
|
428
|
+
) }, column.id);
|
|
429
|
+
}
|
|
430
|
+
if (column.kind === "date") {
|
|
431
|
+
const fromValue = filterValue?.op === "range" ? filterValue.value.from ?? "" : "";
|
|
432
|
+
const toValue = filterValue?.op === "range" ? filterValue.value.to ?? "" : "";
|
|
433
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rowakit-filter-date-range", children: [
|
|
434
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
435
|
+
"input",
|
|
436
|
+
{
|
|
437
|
+
type: "date",
|
|
438
|
+
className: "rowakit-filter-input",
|
|
439
|
+
placeholder: "From",
|
|
440
|
+
value: fromValue,
|
|
441
|
+
onChange: (e) => {
|
|
442
|
+
const from = e.target.value || void 0;
|
|
443
|
+
const to = toValue || void 0;
|
|
444
|
+
if (!from && !to) {
|
|
445
|
+
handleClearFilter(field);
|
|
446
|
+
} else {
|
|
447
|
+
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
),
|
|
452
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
453
|
+
"input",
|
|
454
|
+
{
|
|
455
|
+
type: "date",
|
|
456
|
+
className: "rowakit-filter-input",
|
|
457
|
+
placeholder: "To",
|
|
458
|
+
value: toValue,
|
|
459
|
+
onChange: (e) => {
|
|
460
|
+
const to = e.target.value || void 0;
|
|
461
|
+
const from = fromValue || void 0;
|
|
462
|
+
if (!from && !to) {
|
|
463
|
+
handleClearFilter(field);
|
|
464
|
+
} else {
|
|
465
|
+
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
] }) }, column.id);
|
|
471
|
+
}
|
|
472
|
+
const isNumberColumn = column.kind === "number";
|
|
473
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
474
|
+
"input",
|
|
475
|
+
{
|
|
476
|
+
type: isNumberColumn ? "number" : "text",
|
|
477
|
+
className: "rowakit-filter-input",
|
|
478
|
+
placeholder: `Filter ${getHeaderLabel(column)}...`,
|
|
479
|
+
value: filterValue?.op === "contains" ? filterValue.value : filterValue?.op === "equals" && typeof filterValue.value === "string" ? filterValue.value : filterValue?.op === "equals" && typeof filterValue.value === "number" ? String(filterValue.value) : "",
|
|
480
|
+
onChange: (e) => {
|
|
481
|
+
const rawValue = e.target.value;
|
|
482
|
+
if (rawValue === "") {
|
|
483
|
+
handleClearFilter(field);
|
|
484
|
+
} else if (isNumberColumn) {
|
|
485
|
+
const numValue = Number(rawValue);
|
|
486
|
+
if (!isNaN(numValue)) {
|
|
487
|
+
handleFilterChange(field, { op: "equals", value: rawValue });
|
|
488
|
+
} else {
|
|
489
|
+
handleClearFilter(field);
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
handleFilterChange(field, { op: "contains", value: rawValue });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
) }, column.id);
|
|
497
|
+
}) })
|
|
498
|
+
] }),
|
|
260
499
|
/* @__PURE__ */ jsxRuntime.jsxs("tbody", { children: [
|
|
261
500
|
isLoading && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsxs("td", { colSpan: columns.length, className: "rowakit-table-loading", children: [
|
|
262
501
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rowakit-table-loading-spinner" }),
|
|
@@ -277,7 +516,24 @@ function RowaKitTable({
|
|
|
277
516
|
isEmpty && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: columns.length, className: "rowakit-table-empty", children: "No data" }) }),
|
|
278
517
|
dataState.state === "success" && dataState.items.map((row) => {
|
|
279
518
|
const key = getRowKey(row, rowKey);
|
|
280
|
-
return /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((column) =>
|
|
519
|
+
return /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((column) => {
|
|
520
|
+
const cellClass = [
|
|
521
|
+
column.kind === "number" ? "rowakit-cell-number" : "",
|
|
522
|
+
column.truncate ? "rowakit-cell-truncate" : ""
|
|
523
|
+
].filter(Boolean).join(" ") || void 0;
|
|
524
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
525
|
+
"td",
|
|
526
|
+
{
|
|
527
|
+
className: cellClass,
|
|
528
|
+
style: {
|
|
529
|
+
width: column.width ? `${column.width}px` : void 0,
|
|
530
|
+
textAlign: column.align || (column.kind === "number" ? "right" : void 0)
|
|
531
|
+
},
|
|
532
|
+
children: renderCell(column, row, isLoading, setConfirmState)
|
|
533
|
+
},
|
|
534
|
+
column.id
|
|
535
|
+
);
|
|
536
|
+
}) }, key);
|
|
281
537
|
})
|
|
282
538
|
] })
|
|
283
539
|
] }),
|