@tsed/react-formio 3.0.0-rc.2 → 3.0.0-rc.21

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.
Files changed (114) hide show
  1. package/dist/all.js +2 -0
  2. package/dist/all.js.map +1 -1
  3. package/dist/chunks/_baseGet.js +102 -0
  4. package/dist/chunks/_baseGet.js.map +1 -0
  5. package/dist/chunks/_baseSlice.js +12 -65
  6. package/dist/chunks/_baseSlice.js.map +1 -1
  7. package/dist/chunks/camelCase.js +1 -1
  8. package/dist/chunks/get.js +16 -0
  9. package/dist/chunks/get.js.map +1 -0
  10. package/dist/chunks/index2.js +19746 -22285
  11. package/dist/chunks/index2.js.map +1 -1
  12. package/dist/chunks/moment.js +2535 -0
  13. package/dist/chunks/moment.js.map +1 -0
  14. package/dist/chunks/omit.js +173 -268
  15. package/dist/chunks/omit.js.map +1 -1
  16. package/dist/chunks/toString.js +56 -0
  17. package/dist/chunks/toString.js.map +1 -0
  18. package/dist/interfaces/JSONRecord.d.ts +4 -0
  19. package/dist/interfaces/JSONRecord.js +2 -0
  20. package/dist/interfaces/JSONRecord.js.map +1 -0
  21. package/dist/interfaces/Operation.d.ts +3 -12
  22. package/dist/interfaces/SubmissionType.d.ts +4 -14
  23. package/dist/interfaces/index.d.ts +0 -1
  24. package/dist/molecules/forms/select/Select.interface.d.ts +0 -4
  25. package/dist/molecules/table/Table.d.ts +4 -12
  26. package/dist/molecules/table/Table.js +34 -33
  27. package/dist/molecules/table/Table.js.map +1 -1
  28. package/dist/molecules/table/all.js +2 -0
  29. package/dist/molecules/table/all.js.map +1 -1
  30. package/dist/molecules/table/components/DefaultBooleanCell.d.ts +2 -0
  31. package/dist/molecules/table/components/DefaultBooleanCell.js +12 -0
  32. package/dist/molecules/table/components/DefaultBooleanCell.js.map +1 -0
  33. package/dist/molecules/table/components/DefaultCell.d.ts +2 -5
  34. package/dist/molecules/table/components/DefaultCell.js +8 -6
  35. package/dist/molecules/table/components/DefaultCell.js.map +1 -1
  36. package/dist/molecules/table/components/DefaultCellOperations.d.ts +4 -11
  37. package/dist/molecules/table/components/DefaultCellOperations.js.map +1 -1
  38. package/dist/molecules/table/components/DefaultDateCell.d.ts +2 -0
  39. package/dist/molecules/table/components/DefaultDateCell.js +16 -0
  40. package/dist/molecules/table/components/DefaultDateCell.js.map +1 -0
  41. package/dist/molecules/table/components/DefaultFilter.d.ts +5 -7
  42. package/dist/molecules/table/components/DefaultFilter.js +8 -8
  43. package/dist/molecules/table/components/DefaultFilter.js.map +1 -1
  44. package/dist/molecules/table/components/DefaultOperationButton.d.ts +4 -11
  45. package/dist/molecules/table/components/DefaultOperationButton.js.map +1 -1
  46. package/dist/molecules/table/filters/Filters.d.ts +27 -0
  47. package/dist/molecules/table/filters/Filters.js +2 -0
  48. package/dist/molecules/table/filters/Filters.js.map +1 -0
  49. package/dist/molecules/table/filters/SelectFilter.js +22 -20
  50. package/dist/molecules/table/filters/SelectFilter.js.map +1 -1
  51. package/dist/molecules/table/filters/TextFieldFilter.js +16 -16
  52. package/dist/molecules/table/filters/TextFieldFilter.js.map +1 -1
  53. package/dist/molecules/table/hooks/useTable.d.ts +4 -11
  54. package/dist/molecules/table/hooks/useTable.js +14 -14
  55. package/dist/molecules/table/hooks/useTable.js.map +1 -1
  56. package/dist/molecules/table/hooks/useUniqValues.d.ts +4 -5
  57. package/dist/molecules/table/hooks/useUniqValues.js +23 -7
  58. package/dist/molecules/table/hooks/useUniqValues.js.map +1 -1
  59. package/dist/molecules/table/interfaces/extends.d.ts +3 -0
  60. package/dist/molecules/table/utils/mapFormToColumns.d.ts +5 -1
  61. package/dist/molecules/table/utils/mapFormToColumns.js +53 -26
  62. package/dist/molecules/table/utils/mapFormToColumns.js.map +1 -1
  63. package/dist/organisms/form/Form.d.ts +3 -11
  64. package/dist/organisms/form/Form.js.map +1 -1
  65. package/dist/organisms/form/actions/FormAction.js +5 -5
  66. package/dist/organisms/form/types.d.ts +1 -9
  67. package/dist/organisms/form/useForm.d.ts +4 -11
  68. package/dist/organisms/form/useForm.js.map +1 -1
  69. package/dist/organisms/table/forms/components/FormsCell.js +1 -1
  70. package/dist/organisms/table/submissions/SubmissionsTable.d.ts +4 -11
  71. package/dist/organisms/table/submissions/SubmissionsTable.js +2 -5
  72. package/dist/organisms/table/submissions/SubmissionsTable.js.map +1 -1
  73. package/dist/organisms/views/FormViews.d.ts +4 -11
  74. package/dist/organisms/views/FormViews.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/all.ts +2 -0
  77. package/src/interfaces/JSONRecord.ts +2 -0
  78. package/src/interfaces/Operation.ts +3 -6
  79. package/src/interfaces/SubmissionType.ts +4 -8
  80. package/src/interfaces/index.ts +0 -1
  81. package/src/molecules/forms/select/Select.interface.ts +0 -4
  82. package/src/molecules/table/Table.stories.tsx +101 -66
  83. package/src/molecules/table/Table.tsx +57 -56
  84. package/src/molecules/table/all.ts +2 -0
  85. package/src/molecules/table/components/DefaultBooleanCell.spec.tsx +42 -0
  86. package/src/molecules/table/components/DefaultBooleanCell.tsx +11 -0
  87. package/src/molecules/table/components/DefaultCell.spec.tsx +32 -0
  88. package/src/molecules/table/components/DefaultCell.tsx +8 -9
  89. package/src/molecules/table/components/DefaultCellOperations.tsx +4 -3
  90. package/src/molecules/table/components/DefaultDateCell.spec.tsx +43 -0
  91. package/src/molecules/table/components/DefaultDateCell.tsx +23 -0
  92. package/src/molecules/table/components/DefaultFilter.tsx +10 -7
  93. package/src/molecules/table/components/DefaultOperationButton.tsx +4 -4
  94. package/src/molecules/table/filters/{Filters.d.ts → Filters.ts} +7 -3
  95. package/src/molecules/table/filters/SelectFilter.tsx +5 -3
  96. package/src/molecules/table/filters/TextFieldFilter.tsx +5 -3
  97. package/src/molecules/table/hooks/useTable.tsx +8 -13
  98. package/src/molecules/table/hooks/useUniqValues.spec.tsx +82 -0
  99. package/src/molecules/table/hooks/useUniqValues.tsx +38 -6
  100. package/src/molecules/table/interfaces/extends.ts +3 -0
  101. package/src/molecules/table/utils/mapFormToColumns.spec.tsx +116 -0
  102. package/src/molecules/table/utils/mapFormToColumns.tsx +67 -27
  103. package/src/organisms/form/Form.stories.tsx +7 -2
  104. package/src/organisms/form/Form.tsx +3 -3
  105. package/src/organisms/form/types.ts +1 -6
  106. package/src/organisms/form/useForm.ts +6 -5
  107. package/src/organisms/table/submissions/SubmissionsTable.tsx +5 -10
  108. package/src/organisms/views/FormViews.tsx +6 -9
  109. package/dist/interfaces/QueryOptions.d.ts +0 -23
  110. package/dist/interfaces/QueryOptions.js +0 -2
  111. package/dist/interfaces/QueryOptions.js.map +0 -1
  112. package/dist/molecules/table/filters/Filters.d.js +0 -2
  113. package/dist/molecules/table/filters/Filters.d.js.map +0 -1
  114. package/src/interfaces/QueryOptions.ts +0 -24
@@ -1,17 +1,14 @@
1
- import React from "react";
2
-
3
1
  import type { OperationButtonProps } from "../molecules/table/components/DefaultOperationButton.js";
4
- import type { JSON } from "./SubmissionType.js";
2
+ import type { JSONRecord } from "./JSONRecord.js";
5
3
 
6
4
  export type CellMetadata = Record<string, unknown>;
7
-
8
- export type PermissionsResolver<Data extends { [key: string]: JSON } = { [key: string]: JSON }> = (
5
+ export type PermissionsResolver<Data extends object = JSONRecord> = (
9
6
  data: Data,
10
7
  metadata: CellMetadata,
11
8
  operation: Operation<Data>
12
9
  ) => void;
13
10
 
14
- export interface Operation<Data extends { [key: string]: JSON } = { [key: string]: JSON }> {
11
+ export interface Operation<Data extends object = JSONRecord> {
15
12
  /**
16
13
  * Action identifier
17
14
  */
@@ -1,21 +1,17 @@
1
1
  import type { Component, Submission } from "@formio/core";
2
2
 
3
- export type JSON = unknown | string | number | boolean | null | undefined | JSON[] | { [key: string]: JSON };
3
+ import type { JSONRecord } from "./JSONRecord.js";
4
4
 
5
- export type SubmissionType<Data extends { [key: string]: JSON } = { [key: string]: JSON }> = Omit<Partial<Submission>, "data"> & {
5
+ export type SubmissionType<Data extends object = JSONRecord> = Omit<Partial<Submission>, "data"> & {
6
6
  data: Data;
7
7
  };
8
8
 
9
- export interface ChangedSubmission<
10
- Data extends { [key: string]: JSON } = {
11
- [key: string]: JSON;
12
- }
13
- > extends SubmissionType<Data> {
9
+ export interface ChangedSubmission<Data extends object = JSONRecord> extends SubmissionType<Data> {
14
10
  data: Data;
15
11
  changed?: {
16
12
  component: Component;
17
13
  instance: any;
18
14
  value: unknown;
19
- } & Record<string, JSON>;
15
+ } & JSONRecord;
20
16
  isValid: boolean;
21
17
  }
@@ -5,6 +5,5 @@ export * from "./FormBuilderOptions";
5
5
  export * from "./FormOptions";
6
6
  export * from "./FormType";
7
7
  export * from "./Operation";
8
- export * from "./QueryOptions";
9
8
  export * from "./RoleType";
10
9
  export * from "./SubmissionType";
@@ -28,10 +28,6 @@ export interface SelectProps<Data = string> extends FormControlProps<Data, Selec
28
28
  searchEnabled?: boolean;
29
29
  customProperties?: Record<string, any>;
30
30
  options: (SelectOptionBaseProps<Data> | Omit<SelectOptionProps<Data>, "value">)[];
31
- /**
32
- * @deprecated
33
- */
34
- choices?: (SelectOptionBaseProps<Data> | Omit<SelectOptionProps<Data>, "value">)[];
35
31
  }
36
32
 
37
33
  export interface SelectSingle<Data = string> extends SelectProps<Data> {
@@ -74,7 +74,7 @@ type Story = StoryObj<typeof Table<ProductSubmission>>;
74
74
  export const Usage: Story = {
75
75
  args: {
76
76
  data: formSubmissions as unknown as ProductSubmission[],
77
- columns: mapFormToColumns(FormType as any),
77
+ columns: mapFormToColumns({ form: FormType as any }),
78
78
  operations: [
79
79
  {
80
80
  title: "Edit",
@@ -172,7 +172,10 @@ export const Usage: Story = {
172
172
  return expect(canvas.queryByText("MPEC")).not.toBeInTheDocument();
173
173
  });
174
174
 
175
- await expect(canvas.getByText("La Caravelle")).toBeInTheDocument();
175
+ const tableBody = canvasElement.querySelector("tbody");
176
+
177
+ await expect(tableBody).toBeInTheDocument();
178
+ await expect(within(tableBody as HTMLElement).getByText("La Caravelle")).toBeInTheDocument();
176
179
 
177
180
  await delay(300);
178
181
 
@@ -183,17 +186,20 @@ export const Usage: Story = {
183
186
  export const WithFilters: Story = {
184
187
  args: {
185
188
  data: formSubmissions as unknown as ProductSubmission[],
186
- columns: mapFormToColumns(FormType as any, [
187
- {
188
- accessorKey: "data.id",
189
- meta: {
190
- filter: {
191
- variant: "select",
192
- layout: "react"
189
+ columns: mapFormToColumns({
190
+ form: FormType as any,
191
+ columns: [
192
+ {
193
+ accessorKey: "data.id",
194
+ meta: {
195
+ filter: {
196
+ variant: "select",
197
+ layout: "react"
198
+ }
193
199
  }
194
200
  }
195
- }
196
- ]),
201
+ ]
202
+ }),
197
203
  operations: [
198
204
  {
199
205
  title: "Edit",
@@ -217,70 +223,99 @@ export const WithFilters: Story = {
217
223
  }
218
224
  };
219
225
 
220
- export const WithCustomCell: Story = {
226
+ export const WithPaginationOptions: Story = {
221
227
  args: {
222
228
  data: formSubmissions as unknown as ProductSubmission[],
223
- columns: mapFormToColumns<ProductSubmission>(FormType as any, [
229
+ columns: mapFormToColumns({ form: FormType as any }),
230
+ operations: [
224
231
  {
225
- accessorKey: "data.id",
226
- meta: {
227
- filter: {
228
- variant: "select",
229
- layout: "react"
232
+ title: "Edit",
233
+ action: "edit",
234
+ alias: "row",
235
+ path: "/resources/:resourceId/submissions/:submissionId",
236
+ icon: "edit"
237
+ }
238
+ ],
239
+ i18n: {},
240
+ enablePagination: true,
241
+ pageSizes: [5, 10, 25],
242
+ rowCount: formSubmissions.length,
243
+ initialState: {
244
+ pagination: {
245
+ pageIndex: 0,
246
+ pageSize: 5
247
+ }
248
+ }
249
+ }
250
+ };
251
+
252
+ export const WithCustomCell: Story = {
253
+ args: {
254
+ data: formSubmissions as unknown as ProductSubmission[],
255
+ columns: mapFormToColumns<ProductSubmission>({
256
+ form: FormType as any,
257
+ columns: [
258
+ {
259
+ accessorKey: "data.id",
260
+ meta: {
261
+ filter: {
262
+ variant: "select",
263
+ layout: "react"
264
+ },
265
+ cellProps: {
266
+ colSpan: 2
267
+ }
230
268
  },
231
- cellProps: {
232
- colSpan: 2
269
+ cell: (context) => {
270
+ return (
271
+ <div className='flex space-x-4 align-items-center'>
272
+ <div className='max-w-[80px]'>
273
+ <img className='max-w-[80px] rounded-md' src={context.row.original.data.image} alt={context.row.original.data.label} />
274
+ </div>
275
+ <div>
276
+ <div className='text-lg text-primary'>{context.row.original.data.label}</div>
277
+ <div className='text-xs'>{context.getValue()}</div>
278
+ </div>
279
+ </div>
280
+ );
233
281
  }
234
282
  },
235
- cell: (context) => {
236
- return (
237
- <div className='flex space-x-4 align-items-center'>
238
- <div className='max-w-[80px]'>
239
- <img className='max-w-[80px] rounded-md' src={context.row.original.data.image} alt={context.row.original.data.label} />
240
- </div>
241
- <div>
242
- <div className='text-lg text-primary'>{context.row.original.data.label}</div>
243
- <div className='text-xs'>{context.getValue()}</div>
244
- </div>
245
- </div>
246
- );
247
- }
248
- },
249
- {
250
- accessorKey: "data.label",
251
- meta: {
252
- hidden: true
253
- }
254
- },
255
- {
256
- accessorKey: "data.description",
257
- meta: {
258
- filter: {
259
- variant: "text",
260
- disableDatalist: true
283
+ {
284
+ accessorKey: "data.label",
285
+ meta: {
286
+ hidden: true
261
287
  }
262
- }
263
- },
264
- {
265
- accessorKey: "data.price",
266
- cell: (context) => {
267
- const value = context.getValue();
268
-
269
- if (value === undefined) {
270
- return "-";
288
+ },
289
+ {
290
+ accessorKey: "data.description",
291
+ meta: {
292
+ filter: {
293
+ variant: "text",
294
+ disableDatalist: true
295
+ }
296
+ }
297
+ },
298
+ {
299
+ accessorKey: "data.price",
300
+ cell: (context) => {
301
+ const value = context.getValue();
302
+
303
+ if (value === undefined) {
304
+ return "-";
305
+ }
306
+
307
+ return (
308
+ <div className='text-right'>
309
+ {Intl.NumberFormat("fr-FR", {
310
+ style: "currency",
311
+ currency: context.row.original.data.currency
312
+ }).format(context.getValue())}
313
+ </div>
314
+ );
271
315
  }
272
-
273
- return (
274
- <div className='text-right'>
275
- {Intl.NumberFormat("fr-FR", {
276
- style: "currency",
277
- currency: context.row.original.data.currency
278
- }).format(context.getValue())}
279
- </div>
280
- );
281
316
  }
282
- }
283
- ]),
317
+ ]
318
+ }),
284
319
  operations: [
285
320
  {
286
321
  title: "Edit",
@@ -2,24 +2,23 @@ import { flexRender } from "@tanstack/react-table";
2
2
  import cx from "classnames";
3
3
  import { PropsWithChildren } from "react";
4
4
 
5
- import type { JSON } from "../../interfaces/index.js";
5
+ import type { JSONRecord } from "../../interfaces/JSONRecord.js";
6
6
  import { getComponent } from "../../registries/components";
7
7
  import type { Pagination as DefaultPagination } from "../pagination/Pagination";
8
8
  import type { DefaultCellFooter } from "./components/DefaultCellFooter";
9
9
  import type { DefaultCellHeader } from "./components/DefaultCellHeader";
10
10
  import { useTable, UseTableProps } from "./hooks/useTable";
11
11
 
12
- export interface TableProps<Data extends { [key: string]: JSON } = { [key: string]: JSON }> extends UseTableProps<Data> {
12
+ export interface TableProps<Data extends object = JSONRecord> extends UseTableProps<Data> {
13
13
  className?: string;
14
-
15
14
  enableFooter?: boolean;
16
-
17
- pageSizes?: number[];
15
+ enablePagination?: boolean;
18
16
  }
19
17
 
20
- export function Table<Data extends { [key: string]: JSON } = { [key: string]: JSON }>({
18
+ export function Table<Data extends object = JSONRecord>({
21
19
  className,
22
20
  enableFooter,
21
+ enablePagination = true,
23
22
  children,
24
23
  ...props
25
24
  }: PropsWithChildren<TableProps<Data>>) {
@@ -31,58 +30,60 @@ export function Table<Data extends { [key: string]: JSON } = { [key: string]: JS
31
30
  const { pagination } = tableInstance.getState();
32
31
 
33
32
  return (
34
- <div className={cx("table-group table-responsive", className)}>
35
- <table className='table table-striped table-hover'>
36
- <thead>
37
- {tableInstance.getHeaderGroups().map((headerGroup) => (
38
- <tr key={headerGroup.id}>
39
- {headerGroup.headers.map((header) => {
40
- const sort = header.column.getIsSorted();
41
- return (
42
- <th
43
- data-testid={`head-cell-${header.id}`}
44
- key={header.id}
45
- aria-sort={sort ? (sort === "asc" ? "ascending" : "descending") : "none"}
46
- >
47
- {header.isPlaceholder ? null : <CellHeader header={header} i18n={i18n} />}
48
- </th>
49
- );
50
- })}
51
- </tr>
52
- ))}
53
- </thead>
54
- <tbody>
55
- {tableInstance.getRowModel().rows.map((row) => {
56
- return (
57
- <tr key={row.id} data-testid={`body-row-${row.id}`}>
58
- {row
59
- .getVisibleCells()
60
- .filter((cell) => !cell.column.columnDef.meta?.hidden)
61
- .map((cell) => {
62
- return (
63
- <td {...cell.column.columnDef?.meta?.cellProps} key={cell.id} data-testid={`body-cell-${cell.id}`}>
64
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
65
- </td>
66
- );
67
- })}
68
- </tr>
69
- );
70
- })}
71
- </tbody>
72
- {enableFooter && (
73
- <tfoot>
74
- {tableInstance.getFooterGroups().map((footerGroup) => (
75
- <tr key={footerGroup.id}>
76
- {footerGroup.headers.map((header) => (
77
- <th key={header.id}>{header.isPlaceholder ? null : <CellFooter header={header} i18n={i18n} />}</th>
78
- ))}
33
+ <div className={cx("table-group", className)}>
34
+ <div className='table-group-body'>
35
+ <table className='table table-striped table-hover'>
36
+ <thead>
37
+ {tableInstance.getHeaderGroups().map((headerGroup) => (
38
+ <tr key={headerGroup.id}>
39
+ {headerGroup.headers.map((header) => {
40
+ const sort = header.column.getIsSorted();
41
+ return (
42
+ <th
43
+ data-testid={`head-cell-${header.id}`}
44
+ key={header.id}
45
+ aria-sort={sort ? (sort === "asc" ? "ascending" : "descending") : "none"}
46
+ >
47
+ {header.isPlaceholder ? null : <CellHeader header={header} i18n={i18n} />}
48
+ </th>
49
+ );
50
+ })}
79
51
  </tr>
80
52
  ))}
81
- </tfoot>
82
- )}
83
- </table>
84
- <div className={"overflow-hidden flex flex-wrap"}>
85
- {props.data.length && pagination ? (
53
+ </thead>
54
+ <tbody>
55
+ {tableInstance.getRowModel().rows.map((row) => {
56
+ return (
57
+ <tr key={row.id} data-testid={`body-row-${row.id}`}>
58
+ {row
59
+ .getVisibleCells()
60
+ .filter((cell) => !cell.column.columnDef.meta?.hidden)
61
+ .map((cell) => {
62
+ return (
63
+ <td {...cell.column.columnDef?.meta?.cellProps} key={cell.id} data-testid={`body-cell-${cell.id}`}>
64
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
65
+ </td>
66
+ );
67
+ })}
68
+ </tr>
69
+ );
70
+ })}
71
+ </tbody>
72
+ {enableFooter && (
73
+ <tfoot>
74
+ {tableInstance.getFooterGroups().map((footerGroup) => (
75
+ <tr key={footerGroup.id}>
76
+ {footerGroup.headers.map((header) => (
77
+ <th key={header.id}>{header.isPlaceholder ? null : <CellFooter header={header} i18n={i18n} />}</th>
78
+ ))}
79
+ </tr>
80
+ ))}
81
+ </tfoot>
82
+ )}
83
+ </table>
84
+ </div>
85
+ <div className='table-group-footer'>
86
+ {props.data.length && pagination && enablePagination ? (
86
87
  <Pagination
87
88
  className={"flex-1"}
88
89
  canNextPage={tableInstance.getCanNextPage()}
@@ -1,6 +1,8 @@
1
1
  import "./components/DefaultFilter";
2
2
  import "./components/DefaultArrowSort";
3
3
  import "./components/DefaultCell";
4
+ import "./components/DefaultBooleanCell";
5
+ import "./components/DefaultDateCell";
4
6
  import "./components/DefaultCellOperations";
5
7
  import "./components/DefaultOperationButton";
6
8
  import "./components/DefaultCellHeader";
@@ -0,0 +1,42 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { DefaultCellBoolean } from "./DefaultBooleanCell";
4
+
5
+ function createCellContext(value: boolean, labels?: Record<string, string>) {
6
+ return {
7
+ getValue: () => value,
8
+ column: {
9
+ columnDef: {
10
+ meta: {
11
+ labels
12
+ }
13
+ }
14
+ }
15
+ } as any;
16
+ }
17
+
18
+ describe("DefaultCellBoolean", () => {
19
+ it("should render the default yes label for true values", () => {
20
+ render(<DefaultCellBoolean {...createCellContext(true)} />);
21
+
22
+ expect(screen.getByText("Yes", { selector: "span" })).toBeInTheDocument();
23
+ });
24
+
25
+ it("should render the default no label for false values", () => {
26
+ render(<DefaultCellBoolean {...createCellContext(false)} />);
27
+
28
+ expect(screen.getByText("No", { selector: "span" })).toBeInTheDocument();
29
+ });
30
+
31
+ it("should render custom labels from column metadata", () => {
32
+ render(<DefaultCellBoolean {...createCellContext(true, { yes: "Enabled", no: "Disabled" })} />);
33
+
34
+ expect(screen.getByText("Enabled", { selector: "span" })).toBeInTheDocument();
35
+ });
36
+
37
+ it("should render the custom false label from column metadata", () => {
38
+ render(<DefaultCellBoolean {...createCellContext(false, { yes: "Enabled", no: "Disabled" })} />);
39
+
40
+ expect(screen.getByText("Disabled", { selector: "span" })).toBeInTheDocument();
41
+ });
42
+ });
@@ -0,0 +1,11 @@
1
+ import { CellContext } from "@tanstack/react-table";
2
+
3
+ import { registerComponent } from "../../../registries/components";
4
+
5
+ export function DefaultCellBoolean<Data extends object>({ getValue, column: { columnDef } }: CellContext<Data, boolean>) {
6
+ const value = getValue();
7
+ return <span>{String(value ? columnDef.meta?.labels?.["yes"] || "Yes" : columnDef.meta?.labels?.["no"] || "No")}</span>;
8
+ }
9
+
10
+ registerComponent("Cell.boolean", DefaultCellBoolean);
11
+ registerComponent("Cell.checkbox", DefaultCellBoolean);
@@ -0,0 +1,32 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { DefaultCell } from "./DefaultCell";
4
+
5
+ function createCellContext(value: unknown, rendered: unknown) {
6
+ return {
7
+ getValue: () => value,
8
+ renderValue: () => rendered
9
+ } as any;
10
+ }
11
+
12
+ describe("DefaultCell", () => {
13
+ it("should render an empty span when value is undefined", () => {
14
+ render(<DefaultCell {...createCellContext(undefined, undefined)} />);
15
+
16
+ expect(screen.getByText("", { selector: "span" })).toBeInTheDocument();
17
+ expect(screen.queryByText(/.+/)).not.toBeInTheDocument();
18
+ });
19
+
20
+ it("should render html when rendered value differs from raw value", () => {
21
+ render(<DefaultCell {...createCellContext("hello", "<strong>hello</strong>")} />);
22
+
23
+ expect(screen.getByText("hello", { selector: "strong" })).toBeInTheDocument();
24
+ });
25
+
26
+ it("should render text when rendered value matches raw value", () => {
27
+ render(<DefaultCell {...createCellContext("hello", "hello")} />);
28
+
29
+ expect(screen.getByText("hello", { selector: "span" })).toBeInTheDocument();
30
+ expect(screen.queryByText("hello", { selector: "strong" })).not.toBeInTheDocument();
31
+ });
32
+ });
@@ -1,22 +1,21 @@
1
- import { registerComponent } from "../../../registries/components";
1
+ import type { CellContext } from "@tanstack/react-table";
2
2
 
3
- export interface DefaultCellProps<Data = any> {
4
- value: Data;
5
- render?: (value: Data) => any;
6
- }
3
+ import { registerComponent } from "../../../registries/components";
7
4
 
8
- export function DefaultCell<Data = any>({ value, render = (f: any) => f }: DefaultCellProps<Data>): JSX.Element {
5
+ export function DefaultCell<Data = any>({ getValue, renderValue }: CellContext<Data, any>): JSX.Element {
6
+ const value = getValue();
9
7
  if (value === undefined) {
10
8
  return <span></span>;
11
9
  }
12
10
 
13
- const rendered = render(value);
11
+ const rendered = renderValue();
14
12
 
15
- if (value !== rendered) {
16
- return <div dangerouslySetInnerHTML={{ __html: rendered }} />;
13
+ if (rendered != null && value !== rendered) {
14
+ return <div dangerouslySetInnerHTML={{ __html: String(rendered) }} />;
17
15
  }
18
16
 
19
17
  return <span>{String(value)}</span>;
20
18
  }
21
19
 
22
20
  registerComponent("Cell", DefaultCell);
21
+ registerComponent("Cell.string", DefaultCell);
@@ -1,10 +1,11 @@
1
1
  import type { CellContext } from "@tanstack/react-table";
2
2
 
3
- import type { CellMetadata, JSON, Operation } from "../../../interfaces";
3
+ import type { CellMetadata, Operation } from "../../../interfaces";
4
+ import type { JSONRecord } from "../../../interfaces/JSONRecord.js";
4
5
  import { getComponent, registerComponent } from "../../../registries/components";
5
6
  import type { DefaultOperationButton } from "./DefaultOperationButton";
6
7
 
7
- export interface DefaultCellOperationsProps<Data extends { [key: string]: JSON } = { [key: string]: JSON }> {
8
+ export interface DefaultCellOperationsProps<Data extends object = JSONRecord> {
8
9
  info: CellContext<Data, unknown>;
9
10
  operations: Operation<Data>[];
10
11
  metadata?: CellMetadata;
@@ -12,7 +13,7 @@ export interface DefaultCellOperationsProps<Data extends { [key: string]: JSON }
12
13
  onClick?: (data: any, operation: Operation<Data>) => void;
13
14
  }
14
15
 
15
- export function DefaultCellOperations<Data extends { [key: string]: JSON } = { [key: string]: JSON }>({
16
+ export function DefaultCellOperations<Data extends object = JSONRecord>({
16
17
  info,
17
18
  metadata,
18
19
  operations,
@@ -0,0 +1,43 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { DefaultDateCell } from "./DefaultDateCell";
4
+
5
+ function createCellContext(value: string | undefined, format?: string) {
6
+ return {
7
+ getValue: () => value,
8
+ column: {
9
+ columnDef: {
10
+ meta: {
11
+ format
12
+ }
13
+ }
14
+ }
15
+ } as any;
16
+ }
17
+
18
+ describe("DefaultDateCell", () => {
19
+ it("should render an empty span when value is undefined", () => {
20
+ render(<DefaultDateCell {...createCellContext(undefined)} />);
21
+
22
+ expect(screen.getByText("", { selector: "span" })).toBeInTheDocument();
23
+ expect(screen.queryByText(/.+/)).not.toBeInTheDocument();
24
+ });
25
+
26
+ it("should render the formatted date with the default format", () => {
27
+ render(<DefaultDateCell {...createCellContext("2026-03-12T10:30:00.000Z")} />);
28
+
29
+ expect(screen.getByText("03/12/2026", { selector: "span" })).toBeInTheDocument();
30
+ });
31
+
32
+ it("should render the formatted date with a custom format", () => {
33
+ render(<DefaultDateCell {...createCellContext("2026-03-12T10:30:00.000Z", "YYYY-MM-DD HH:mm")} />);
34
+
35
+ expect(screen.getByText("2026-03-12 10:30", { selector: "span" })).toBeInTheDocument();
36
+ });
37
+
38
+ it("should fallback to the raw value when the date is invalid", () => {
39
+ render(<DefaultDateCell {...createCellContext("not-a-date")} />);
40
+
41
+ expect(screen.getByText("not-a-date", { selector: "span" })).toBeInTheDocument();
42
+ });
43
+ });
@@ -0,0 +1,23 @@
1
+ import { CellContext } from "@tanstack/react-table";
2
+ import moment from "moment";
3
+
4
+ import { registerComponent } from "../../../registries/components";
5
+
6
+ export function DefaultDateCell<Data extends object>({ getValue, column: { columnDef } }: CellContext<Data, string>) {
7
+ const value = getValue();
8
+
9
+ if (!value) {
10
+ return <span />;
11
+ }
12
+
13
+ const date = moment.parseZone(value, moment.ISO_8601, true);
14
+
15
+ if (!date.isValid()) {
16
+ return <span>{String(value)}</span>;
17
+ }
18
+
19
+ return <span>{date.format(columnDef.meta?.format || "L")}</span>;
20
+ }
21
+
22
+ registerComponent("Cell.date", DefaultDateCell);
23
+ registerComponent("Cell.datetime", DefaultDateCell);