@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/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(options) {
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: options.id,
92
+ id: arg1.id,
47
93
  kind: "custom",
48
- header: options.header,
49
- field: options.field,
50
- render: options.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.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((column) => {
237
- const isSortable = column.kind !== "actions" && (column.kind === "custom" ? false : column.sortable === true);
238
- const field = column.kind === "actions" ? "" : column.kind === "custom" ? column.field : column.field;
239
- return /* @__PURE__ */ jsxRuntime.jsxs(
240
- "th",
241
- {
242
- onClick: isSortable ? () => handleSort(String(field)) : void 0,
243
- role: isSortable ? "button" : void 0,
244
- tabIndex: isSortable ? 0 : void 0,
245
- onKeyDown: isSortable ? (e) => {
246
- if (e.key === "Enter" || e.key === " ") {
247
- e.preventDefault();
248
- handleSort(String(field));
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
- } : void 0,
251
- "aria-sort": isSortable && query.sort?.field === String(field) ? query.sort.direction === "asc" ? "ascending" : "descending" : void 0,
252
- children: [
253
- getHeaderLabel(column),
254
- isSortable && getSortIndicator(String(field))
255
- ]
256
- },
257
- column.id
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) => /* @__PURE__ */ jsxRuntime.jsx("td", { children: renderCell(column, row, isLoading, setConfirmState) }, column.id)) }, key);
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
  ] }),