@dxos/react-ui-table 0.8.4-main.c1de068 → 0.8.4-main.c85a9c8dae

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 (158) hide show
  1. package/dist/lib/browser/chunk-DSR2MRZB.mjs +80 -0
  2. package/dist/lib/browser/chunk-DSR2MRZB.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-PTHMYAAM.mjs +1751 -0
  4. package/dist/lib/browser/chunk-PTHMYAAM.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +1190 -1139
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +31 -38
  9. package/dist/lib/browser/testing/index.mjs.map +3 -3
  10. package/dist/lib/browser/types/index.mjs +3 -3
  11. package/dist/lib/node-esm/chunk-GI72MQSG.mjs +1753 -0
  12. package/dist/lib/node-esm/chunk-GI72MQSG.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-HTZJSULI.mjs +81 -0
  14. package/dist/lib/node-esm/chunk-HTZJSULI.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +1190 -1139
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +31 -38
  19. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/types/index.mjs +3 -3
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +2 -3
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -3
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
  26. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +37 -3
  28. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Relations.stories.d.ts +111 -0
  30. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
  31. package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
  32. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/Table.d.ts +17 -13
  34. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/Table.stories.d.ts +108 -4
  36. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  38. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  39. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +13 -9
  40. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  41. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +11 -18
  42. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  43. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
  44. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  46. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  47. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +5 -5
  48. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  49. package/dist/types/src/components/testing.d.ts +23 -16
  50. package/dist/types/src/components/testing.d.ts.map +1 -1
  51. package/dist/types/src/hooks/index.d.ts +2 -1
  52. package/dist/types/src/hooks/index.d.ts.map +1 -1
  53. package/dist/types/src/hooks/useAddRow.d.ts +6 -6
  54. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  55. package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
  56. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  57. package/dist/types/src/hooks/useTableModel.d.ts +10 -10
  58. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  59. package/dist/types/src/model/modal-controller.d.ts +5 -8
  60. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  61. package/dist/types/src/model/selection-model.d.ts +17 -8
  62. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  63. package/dist/types/src/model/table-model.d.ts +112 -30
  64. package/dist/types/src/model/table-model.d.ts.map +1 -1
  65. package/dist/types/src/model/table-presentation.d.ts +5 -2
  66. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  67. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  68. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  69. package/dist/types/src/testing/useTestTableModel.d.ts +18 -34
  70. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  71. package/dist/types/src/translations.d.ts +4 -4
  72. package/dist/types/src/translations.d.ts.map +1 -1
  73. package/dist/types/src/types/Table.d.ts +31 -0
  74. package/dist/types/src/types/Table.d.ts.map +1 -0
  75. package/dist/types/src/types/index.d.ts +1 -1
  76. package/dist/types/src/types/index.d.ts.map +1 -1
  77. package/dist/types/src/util/dynamic-table.d.ts +14 -12
  78. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  79. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  80. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  81. package/dist/types/src/util/index.d.ts +2 -1
  82. package/dist/types/src/util/index.d.ts.map +1 -1
  83. package/dist/types/src/util/query-ast.d.ts +7 -0
  84. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  85. package/dist/types/src/util/schema.d.ts +1 -1
  86. package/dist/types/src/util/schema.d.ts.map +1 -1
  87. package/dist/types/src/util/sort.d.ts +12 -0
  88. package/dist/types/src/util/sort.d.ts.map +1 -0
  89. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  90. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  91. package/dist/types/tsconfig.tsbuildinfo +1 -1
  92. package/package.json +61 -56
  93. package/src/components/Table/ColumnActionsMenu.tsx +11 -7
  94. package/src/components/Table/ColumnSettings.tsx +9 -10
  95. package/src/components/Table/DynamicTable.stories.tsx +28 -24
  96. package/src/components/Table/DynamicTable.tsx +33 -27
  97. package/src/components/Table/Relations.stories.tsx +240 -0
  98. package/src/components/Table/RowActionsMenu.tsx +26 -17
  99. package/src/components/Table/Table.stories.tsx +146 -116
  100. package/src/components/Table/Table.tsx +353 -297
  101. package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -6
  102. package/src/components/TableCellEditor/FormCellEditor.tsx +134 -40
  103. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +31 -66
  104. package/src/components/TableCellEditor/TableCellEditor.tsx +88 -238
  105. package/src/components/TableCellEditor/index.ts +1 -0
  106. package/src/components/TableToolbar/TableToolbar.tsx +54 -38
  107. package/src/components/testing.ts +39 -13
  108. package/src/hooks/index.ts +2 -1
  109. package/src/hooks/useAddRow.ts +14 -14
  110. package/src/hooks/useProjectionModel.ts +39 -0
  111. package/src/hooks/useTableModel.ts +36 -34
  112. package/src/model/modal-controller.ts +24 -41
  113. package/src/model/selection-model.ts +75 -43
  114. package/src/model/table-model.test.ts +92 -55
  115. package/src/model/table-model.ts +453 -175
  116. package/src/model/table-presentation.test.ts +70 -58
  117. package/src/model/table-presentation.ts +111 -64
  118. package/src/playwright/TableManager.ts +13 -6
  119. package/src/playwright/playwright.config.ts +1 -1
  120. package/src/playwright/smoke.spec.ts +171 -33
  121. package/src/testing/useTestTableModel.ts +46 -35
  122. package/src/translations.ts +3 -3
  123. package/src/types/Table.ts +95 -0
  124. package/src/types/index.ts +1 -1
  125. package/src/util/dynamic-table.test.ts +70 -0
  126. package/src/util/dynamic-table.ts +46 -33
  127. package/src/util/index.ts +2 -1
  128. package/src/util/query-ast.ts +22 -0
  129. package/src/util/schema.ts +4 -4
  130. package/src/util/sort.ts +90 -0
  131. package/src/util/table-buttons.ts +4 -3
  132. package/src/util/table-controls.ts +6 -3
  133. package/dist/lib/browser/chunk-5XLSHLOG.mjs +0 -1751
  134. package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +0 -7
  135. package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
  136. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
  137. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
  138. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
  139. package/dist/lib/node-esm/chunk-Y66YLSTC.mjs +0 -1752
  140. package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +0 -7
  141. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  142. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  143. package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
  144. package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
  145. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  146. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  147. package/dist/types/src/model/table-sorting.d.ts +0 -53
  148. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  149. package/dist/types/src/types/schema.d.ts +0 -8
  150. package/dist/types/src/types/schema.d.ts.map +0 -1
  151. package/dist/types/src/util/table.d.ts +0 -11
  152. package/dist/types/src/util/table.d.ts.map +0 -1
  153. package/src/components/Table/CreateRefPanel.tsx +0 -86
  154. package/src/components/Table/relations.stories.tsx +0 -116
  155. package/src/components/TableCellEditor/extension.ts +0 -61
  156. package/src/model/table-sorting.ts +0 -216
  157. package/src/types/schema.ts +0 -16
  158. package/src/util/table.ts +0 -49
@@ -0,0 +1,240 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { RegistryContext } from '@effect-atom/atom-react';
6
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
7
+ import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
8
+ import { expect, userEvent, within } from 'storybook/test';
9
+
10
+ import { Obj, Type } from '@dxos/echo';
11
+ import { View } from '@dxos/echo';
12
+ import { invariant } from '@dxos/invariant';
13
+ import { type DxGrid } from '@dxos/lit-grid';
14
+ import '@dxos/lit-ui/dx-tag-picker.pcss';
15
+ import { faker } from '@dxos/random';
16
+ import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
17
+ import { useAsyncEffect } from '@dxos/react-ui';
18
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
19
+ import { translations as formTranslations } from '@dxos/react-ui-form';
20
+ import { ViewModel } from '@dxos/schema';
21
+ import { type ValueGenerator, createAsyncGenerator } from '@dxos/schema/testing';
22
+ import { withRegistry } from '@dxos/storybook-utils';
23
+ import { Organization, Person } from '@dxos/types';
24
+
25
+ import { useProjectionModel, useTableModel } from '../../hooks';
26
+ import { type TableFeatures, TablePresentation, type TableRow } from '../../model';
27
+ import { translations } from '../../translations';
28
+ import { Table } from '../../types';
29
+
30
+ import { Table as TableComponent } from './Table';
31
+
32
+ faker.seed(1);
33
+ const generator: ValueGenerator = faker as any;
34
+
35
+ // TODO(burdon): Many-to-many relations.
36
+ // TODO(burdon): Mutable and immutable views.
37
+ // TODO(burdon): Reconcile schemas types and utils (see API PR).
38
+ // TODO(burdon): Base type for T (with id); see ECHO API PR?
39
+ const useTestModel = <S extends Type.AnyObj>(schema: S, count: number) => {
40
+ const registry = useContext(RegistryContext);
41
+ const { space } = useClientStory();
42
+ const [object, setObject] = useState<Table.Table>();
43
+
44
+ const features = useMemo<TableFeatures>(
45
+ () => ({ schemaEditable: false, dataEditable: true, selection: { enabled: false } }),
46
+ [],
47
+ );
48
+
49
+ useAsyncEffect(async () => {
50
+ if (!space) {
51
+ return;
52
+ }
53
+
54
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename: Type.getTypename(schema) });
55
+ const object = Table.make({ view, jsonSchema });
56
+ setObject(object);
57
+ space.db.add(object);
58
+ }, [space, schema]);
59
+
60
+ const projection = useProjectionModel(schema, object, registry);
61
+ const model = useTableModel<TableRow>({ object, projection, db: space?.db, features });
62
+
63
+ useEffect(() => {
64
+ if (!model || !space) {
65
+ return;
66
+ }
67
+
68
+ const objectGenerator = createAsyncGenerator(generator, schema, { db: space?.db, force: true });
69
+ void objectGenerator.createObjects(count).then((objects) => {
70
+ model.setRows(objects);
71
+ });
72
+ }, [model, space]);
73
+
74
+ const presentation = useMemo(() => {
75
+ if (!model) {
76
+ return;
77
+ }
78
+
79
+ return new TablePresentation(registry, model);
80
+ }, [registry, model]);
81
+
82
+ return { model, presentation };
83
+ };
84
+
85
+ const DefaultStory = () => {
86
+ const { model: orgModel, presentation: orgPresentation } = useTestModel(Organization.Organization, 50);
87
+ const { model: contactModel, presentation: contactPresentation } = useTestModel(Person.Person, 50);
88
+ const { space } = useClientStory();
89
+
90
+ const handleCreate = useCallback(
91
+ (schema: Type.AnyEntity, values: any) => {
92
+ invariant(Type.isObjectSchema(schema));
93
+ invariant(space);
94
+ return space.db.add(Obj.make(schema, values));
95
+ },
96
+ [space],
97
+ );
98
+
99
+ return (
100
+ <div className='w-full h-full grid grid-cols-2 divide-x divide-separator'>
101
+ <TableComponent.Root>
102
+ <TableComponent.Main
103
+ model={orgModel}
104
+ schema={Organization.Organization}
105
+ presentation={orgPresentation}
106
+ onCreate={handleCreate}
107
+ ignoreAttention
108
+ testId='relations-0'
109
+ />
110
+ </TableComponent.Root>
111
+ <TableComponent.Root>
112
+ <TableComponent.Main
113
+ model={contactModel}
114
+ schema={Person.Person}
115
+ presentation={contactPresentation}
116
+ onCreate={handleCreate}
117
+ ignoreAttention
118
+ testId='relations-1'
119
+ />
120
+ </TableComponent.Root>
121
+ </div>
122
+ );
123
+ };
124
+
125
+ const meta = {
126
+ title: 'ui/react-ui-table/Relations',
127
+ render: DefaultStory,
128
+ decorators: [
129
+ withTheme(),
130
+ withRegistry,
131
+ // TODO(thure): Shouldn't `layout: 'fullscreen'` below make this unnecessary?
132
+ withLayout({ classNames: 'fixed inset-0' }),
133
+ withClientProvider({
134
+ types: [View.View, Organization.Organization, Person.Person, Table.Table],
135
+ createIdentity: true,
136
+ createSpace: true,
137
+ }),
138
+ ],
139
+ parameters: {
140
+ layout: 'fullscreen',
141
+ controls: { disable: true },
142
+ translations: [...translations, ...formTranslations],
143
+ },
144
+ } satisfies Meta<typeof DefaultStory>;
145
+
146
+ export default meta;
147
+
148
+ type Story = StoryObj<typeof meta>;
149
+
150
+ export const Default: Story = {
151
+ play: async ({ canvasElement }: any) => {
152
+ const canvas = within(canvasElement);
153
+ const body = within(document.body);
154
+
155
+ // Get all dx-grid elements (should have 2 - one for each table)
156
+ const firstGrid = await canvas.findByTestId('relations-0', undefined, { timeout: 30_000 });
157
+ const secondGrid = await canvas.findByTestId('relations-1', undefined, { timeout: 30_000 });
158
+
159
+ // Focus on the second table (Person/contacts table)
160
+ await expect(secondGrid).toBeVisible();
161
+
162
+ // Scroll to the relations column (column 4) - this mimics the scrollToColumn call
163
+ (secondGrid.closest('dx-grid') as DxGrid).scrollToColumn(4);
164
+
165
+ // Find and click the target cell (first row, relations column)
166
+ const targetCell = await within(secondGrid).findByTestId('grid.4.0', undefined, { timeout: 10_000 });
167
+ await expect(targetCell).toBeVisible();
168
+
169
+ // Click to focus the cell
170
+ await userEvent.click(targetCell as HTMLElement);
171
+ await expect(targetCell).toHaveFocus();
172
+ await userEvent.keyboard('{Enter}');
173
+
174
+ // Look for the combobox that should appear in edit mode
175
+ const combobox = await body.findByRole('combobox', undefined, { timeout: 5000 });
176
+ await userEvent.click(combobox);
177
+
178
+ const searchField = await body.findByPlaceholderText('Search…');
179
+ await userEvent.click(searchField);
180
+
181
+ // Get the first organization name from the first table to search for
182
+ const orgCell = await within(firstGrid).findByTestId('grid.0.0', undefined, { timeout: 10_000 });
183
+
184
+ const orgName = (orgCell.querySelector('.dx-grid__cell__content') as HTMLElement).textContent;
185
+ // Type the first 4 characters to search
186
+ await userEvent.type(searchField, orgName.substring(0, 4));
187
+
188
+ // Look for an option to select
189
+ const option = await body.findAllByRole('option');
190
+ await expect(option[0]).toBeVisible();
191
+
192
+ // Press Enter to select
193
+ await userEvent.keyboard('{Enter}');
194
+
195
+ // Look for and click save button
196
+ const saveButton = await body.findByTestId('save-button');
197
+ await userEvent.click(saveButton);
198
+
199
+ // Verify the relation was set (cell should now contain the org name).
200
+ const updatedCell = within(secondGrid).getByTestId('grid.4.0');
201
+ await expect(updatedCell).toHaveTextContent(orgName.substring(0, 4));
202
+
203
+ // Test object creation (new relations) - equivalent to "new relations work as expected" test
204
+ // Find a different cell to test object creation (second row, relations column)
205
+ const newTargetCell = await within(secondGrid).findByTestId('grid.4.1', undefined, { timeout: 10_000 });
206
+ await expect(newTargetCell).toBeTruthy();
207
+
208
+ // Click to focus the cell
209
+ await userEvent.click(newTargetCell as Element);
210
+
211
+ await userEvent.keyboard('{Enter}');
212
+
213
+ // Look for the combobox that should appear in edit mode
214
+ const newCombobox = await body.findByRole('combobox');
215
+ await userEvent.click(newCombobox);
216
+
217
+ const newSearchField = await body.findByPlaceholderText('Search…');
218
+ await userEvent.click(newSearchField);
219
+
220
+ // Type a new object name (this will create a new object)
221
+ const newOrgName = 'Salieri LLC';
222
+ await userEvent.type(newSearchField, newOrgName);
223
+
224
+ // Wait for the create option to appear (debounce is 200ms, allow time for render)
225
+ const createOption = await body.findByRole('option', undefined, { timeout: 500 });
226
+ await expect(createOption).toBeVisible();
227
+
228
+ // Press Enter to select/create
229
+ await userEvent.keyboard('{Enter}');
230
+
231
+ // Look for and click save button
232
+ const createReferencedObjectForm = await body.findByTestId('create-referenced-object-form');
233
+ const saveObjectButton = await within(createReferencedObjectForm).findByTestId('save-button');
234
+ await userEvent.click(saveObjectButton);
235
+
236
+ // Verify the new object was created and relation was set.
237
+ const updatedNewCell = within(secondGrid).getByTestId('grid.4.1');
238
+ await expect(updatedNewCell).toHaveTextContent(newOrgName);
239
+ },
240
+ } as any;
@@ -2,19 +2,20 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { useAtomValue } from '@effect-atom/atom-react';
5
6
  import React from 'react';
6
7
 
7
- import { DropdownMenu, useTranslation } from '@dxos/react-ui';
8
+ import { DropdownMenu, toLocalizedString, useTranslation } from '@dxos/react-ui';
8
9
 
9
- import { type TableModel, type ModalController } from '../../model';
10
+ import { type ModalController, type TableModel } from '../../model';
10
11
  import { translationKey } from '../../translations';
11
12
 
12
13
  type RowActionsMenuProps = { model: TableModel; modals: ModalController };
13
14
 
14
15
  export const RowActionsMenu = ({ model, modals }: RowActionsMenuProps) => {
15
16
  const { t } = useTranslation(translationKey);
16
- const hasSelection = model.selection.hasSelection.value;
17
- const state = modals.state.value;
17
+ const hasSelection = model.selection.hasSelection;
18
+ const state = useAtomValue(modals.state);
18
19
  if (state?.type !== 'row') {
19
20
  return null;
20
21
  }
@@ -23,24 +24,32 @@ export const RowActionsMenu = ({ model, modals }: RowActionsMenuProps) => {
23
24
  <DropdownMenu.VirtualTrigger virtualRef={modals.trigger} />
24
25
  <DropdownMenu.Content>
25
26
  <DropdownMenu.Viewport>
27
+ {/* Custom actions */}
28
+ {model.rowActions?.length > 0 && (
29
+ <>
30
+ <DropdownMenu.Group>
31
+ {model.rowActions?.map((action) => (
32
+ <DropdownMenu.Item
33
+ key={action.id}
34
+ data-testid={`row-action-${action.id}`}
35
+ onClick={() => {
36
+ modals.close();
37
+ model.handleRowAction(action.id, state.rowIndex);
38
+ }}
39
+ >
40
+ {toLocalizedString(action.label, t)}
41
+ </DropdownMenu.Item>
42
+ ))}
43
+ </DropdownMenu.Group>
44
+ <DropdownMenu.Separator />
45
+ </>
46
+ )}
47
+ {/* Default actions */}
26
48
  {model.features.dataEditable !== false && (
27
49
  <DropdownMenu.Item data-testid='row-menu-delete' onClick={() => model.deleteRow(state.rowIndex)}>
28
50
  {t(hasSelection ? 'bulk delete row label' : 'delete row label')}
29
51
  </DropdownMenu.Item>
30
52
  )}
31
- {/* Custom actions */}
32
- {model.rowActions?.map((action) => (
33
- <DropdownMenu.Item
34
- key={action.id}
35
- data-testid={`row-action-${action.id}`}
36
- onClick={() => {
37
- modals.close();
38
- model.handleRowAction(action.id, state.rowIndex);
39
- }}
40
- >
41
- {t(action.translationKey)}
42
- </DropdownMenu.Item>
43
- ))}
44
53
  </DropdownMenu.Viewport>
45
54
  <DropdownMenu.Arrow />
46
55
  </DropdownMenu.Content>