@vendure/dashboard 3.3.5-master-202506250727 → 3.3.5-master-202506251318

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 (42) hide show
  1. package/dist/plugin/tests/barrel-exports.spec.js +1 -1
  2. package/dist/plugin/vite-plugin-config.js +1 -0
  3. package/dist/plugin/vite-plugin-dashboard-metadata.d.ts +1 -3
  4. package/dist/plugin/vite-plugin-dashboard-metadata.js +1 -8
  5. package/dist/plugin/vite-plugin-tailwind-source.d.ts +7 -0
  6. package/dist/plugin/vite-plugin-tailwind-source.js +49 -0
  7. package/dist/plugin/vite-plugin-vendure-dashboard.js +3 -1
  8. package/package.json +4 -4
  9. package/src/app/routes/_authenticated/_products/components/assign-facet-values-dialog.tsx +98 -0
  10. package/src/app/routes/_authenticated/_products/components/assign-to-channel-dialog.tsx +126 -0
  11. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +268 -0
  12. package/src/app/routes/_authenticated/_products/products.graphql.ts +64 -0
  13. package/src/app/routes/_authenticated/_products/products.tsx +31 -2
  14. package/src/app/routes/_authenticated/_products/products_.$id.tsx +3 -1
  15. package/src/app/styles.css +3 -0
  16. package/src/lib/components/data-table/data-table-bulk-action-item.tsx +101 -0
  17. package/src/lib/components/data-table/data-table-bulk-actions.tsx +89 -0
  18. package/src/lib/components/data-table/data-table-filter-badge.tsx +16 -8
  19. package/src/lib/components/data-table/data-table-filter-dialog.tsx +4 -4
  20. package/src/lib/components/data-table/data-table-pagination.tsx +2 -2
  21. package/src/lib/components/data-table/data-table.tsx +50 -31
  22. package/src/lib/components/data-table/human-readable-operator.tsx +3 -3
  23. package/src/lib/components/shared/assigned-facet-values.tsx +1 -5
  24. package/src/lib/components/shared/custom-fields-form.tsx +141 -67
  25. package/src/lib/components/shared/paginated-list-data-table.tsx +47 -11
  26. package/src/lib/framework/data-table/data-table-extensions.ts +21 -0
  27. package/src/lib/framework/data-table/data-table-types.ts +25 -0
  28. package/src/lib/framework/extension-api/define-dashboard-extension.ts +11 -0
  29. package/src/lib/framework/extension-api/extension-api-types.ts +35 -0
  30. package/src/lib/framework/form-engine/use-generated-form.tsx +2 -5
  31. package/src/lib/framework/layout-engine/page-block-provider.tsx +6 -0
  32. package/src/lib/framework/layout-engine/page-layout.tsx +43 -33
  33. package/src/lib/framework/page/list-page.tsx +6 -8
  34. package/src/lib/framework/registry/registry-types.ts +4 -2
  35. package/src/lib/hooks/use-page-block.tsx +10 -0
  36. package/src/lib/index.ts +8 -1
  37. package/vite/tests/barrel-exports.spec.ts +13 -9
  38. package/vite/vite-plugin-config.ts +1 -0
  39. package/vite/vite-plugin-dashboard-metadata.ts +1 -9
  40. package/vite/vite-plugin-tailwind-source.ts +65 -0
  41. package/vite/vite-plugin-vendure-dashboard.ts +5 -3
  42. /package/src/lib/components/data-table/{data-table-types.ts → types.ts} +0 -0
@@ -7,12 +7,15 @@ import {
7
7
  FormMessage,
8
8
  } from '@/components/ui/form.js';
9
9
  import { Input } from '@/components/ui/input.js';
10
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs.js';
10
11
  import { CustomFormComponent } from '@/framework/form-engine/custom-form-component.js';
11
12
  import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
12
13
  import { useUserSettings } from '@/hooks/use-user-settings.js';
14
+ import { useLingui } from '@/lib/trans.js';
13
15
  import { customFieldConfigFragment } from '@/providers/server-config.js';
14
16
  import { CustomFieldType } from '@vendure/common/lib/shared-types';
15
17
  import { ResultOf } from 'gql.tada';
18
+ import React, { useMemo } from 'react';
16
19
  import { Control, ControllerRenderProps } from 'react-hook-form';
17
20
  import { Switch } from '../ui/switch.js';
18
21
  import { TranslatableFormField } from './translatable-form-field.js';
@@ -29,6 +32,7 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Custom
29
32
  const {
30
33
  settings: { displayLanguage },
31
34
  } = useUserSettings();
35
+ const { i18n } = useLingui();
32
36
 
33
37
  const getTranslation = (input: Array<{ languageCode: string; value: string }> | null | undefined) => {
34
38
  return input?.find(t => t.languageCode === displayLanguage)?.value;
@@ -42,18 +46,80 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Custom
42
46
  : `customFields.${fieldDefName}`;
43
47
  };
44
48
 
49
+ // Group custom fields by tabs
50
+ const groupedFields = useMemo(() => {
51
+ if (!customFields) return [];
52
+
53
+ const tabMap = new Map<string, CustomFieldConfig[]>();
54
+ const defaultTabName = '__default_tab__';
55
+
56
+ for (const field of customFields) {
57
+ const tabName = field.ui?.tab ?? defaultTabName;
58
+ if (tabMap.has(tabName)) {
59
+ tabMap.get(tabName)?.push(field);
60
+ } else {
61
+ tabMap.set(tabName, [field]);
62
+ }
63
+ }
64
+
65
+ return Array.from(tabMap.entries())
66
+ .sort((a, b) => (a[0] === defaultTabName ? -1 : 1))
67
+ .map(([tabName, customFields]) => ({
68
+ tabName: tabName === defaultTabName ? 'general' : tabName,
69
+ customFields,
70
+ }));
71
+ }, [customFields]);
72
+
73
+ // Check if we should show tabs (more than one tab or at least one field has a tab)
74
+ const shouldShowTabs = useMemo(() => {
75
+ if (!customFields) return false;
76
+ const hasTabbedFields = customFields.some(field => field.ui?.tab);
77
+ return hasTabbedFields || groupedFields.length > 1;
78
+ }, [customFields, groupedFields.length]);
79
+
80
+ if (!shouldShowTabs) {
81
+ // Single tab view - use the original grid layout
82
+ return (
83
+ <div className="grid grid-cols-2 gap-4">
84
+ {customFields?.map(fieldDef => (
85
+ <CustomFieldItem
86
+ key={fieldDef.name}
87
+ fieldDef={fieldDef}
88
+ control={control}
89
+ fieldName={getFieldName(fieldDef.name)}
90
+ getTranslation={getTranslation}
91
+ />
92
+ ))}
93
+ </div>
94
+ );
95
+ }
96
+
97
+ // Tabbed view
45
98
  return (
46
- <div className="grid grid-cols-2 gap-4">
47
- {customFields?.map(fieldDef => (
48
- <CustomFieldItem
49
- key={fieldDef.name}
50
- fieldDef={fieldDef}
51
- control={control}
52
- fieldName={getFieldName(fieldDef.name)}
53
- getTranslation={getTranslation}
54
- />
99
+ <Tabs defaultValue={groupedFields[0]?.tabName} className="w-full">
100
+ <TabsList>
101
+ {groupedFields.map(group => (
102
+ <TabsTrigger key={group.tabName} value={group.tabName}>
103
+ {group.tabName === 'general' ? i18n.t('General') : group.tabName}
104
+ </TabsTrigger>
105
+ ))}
106
+ </TabsList>
107
+ {groupedFields.map(group => (
108
+ <TabsContent key={group.tabName} value={group.tabName} className="mt-4">
109
+ <div className="grid grid-cols-2 gap-4">
110
+ {group.customFields.map(fieldDef => (
111
+ <CustomFieldItem
112
+ key={fieldDef.name}
113
+ fieldDef={fieldDef}
114
+ control={control}
115
+ fieldName={getFieldName(fieldDef.name)}
116
+ getTranslation={getTranslation}
117
+ />
118
+ ))}
119
+ </div>
120
+ </TabsContent>
55
121
  ))}
56
- </div>
122
+ </Tabs>
57
123
  );
58
124
  }
59
125
 
@@ -69,83 +135,91 @@ interface CustomFieldItemProps {
69
135
  function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: CustomFieldItemProps) {
70
136
  const hasCustomFormComponent = fieldDef.ui && fieldDef.ui.component;
71
137
  const isLocaleField = fieldDef.type === 'localeString' || fieldDef.type === 'localeText';
138
+ const shouldBeFullWidth = fieldDef.ui?.fullWidth === true;
139
+ const containerClassName = shouldBeFullWidth ? 'col-span-2' : '';
72
140
 
73
141
  // For locale fields, always use TranslatableFormField regardless of custom components
74
142
  if (isLocaleField) {
75
143
  return (
76
- <TranslatableFormField
77
- control={control}
78
- name={fieldName}
79
- render={({ field, ...props }) => (
80
- <FormItem>
81
- <FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
82
- <FormControl>
83
- {hasCustomFormComponent ? (
84
- <CustomFormComponent
85
- fieldDef={fieldDef}
86
- fieldProps={{
87
- ...props,
88
- field: {
89
- ...field,
90
- disabled: fieldDef.readonly ?? false,
91
- },
92
- }}
93
- />
94
- ) : (
95
- <FormInputForType fieldDef={fieldDef} field={field} />
96
- )}
97
- </FormControl>
98
- <FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
99
- <FormMessage />
100
- </FormItem>
101
- )}
102
- />
144
+ <div className={containerClassName}>
145
+ <TranslatableFormField
146
+ control={control}
147
+ name={fieldName}
148
+ render={({ field, ...props }) => (
149
+ <FormItem>
150
+ <FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
151
+ <FormControl>
152
+ {hasCustomFormComponent ? (
153
+ <CustomFormComponent
154
+ fieldDef={fieldDef}
155
+ fieldProps={{
156
+ ...props,
157
+ field: {
158
+ ...field,
159
+ disabled: fieldDef.readonly ?? false,
160
+ },
161
+ }}
162
+ />
163
+ ) : (
164
+ <FormInputForType fieldDef={fieldDef} field={field} />
165
+ )}
166
+ </FormControl>
167
+ <FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
168
+ <FormMessage />
169
+ </FormItem>
170
+ )}
171
+ />
172
+ </div>
103
173
  );
104
174
  }
105
175
 
106
176
  // For non-locale fields with custom components
107
177
  if (hasCustomFormComponent) {
108
178
  return (
179
+ <div className={containerClassName}>
180
+ <FormField
181
+ control={control}
182
+ name={fieldName}
183
+ render={fieldProps => (
184
+ <CustomFieldFormItem
185
+ fieldDef={fieldDef}
186
+ getTranslation={getTranslation}
187
+ fieldName={fieldProps.field.name}
188
+ >
189
+ <CustomFormComponent
190
+ fieldDef={fieldDef}
191
+ fieldProps={{
192
+ ...fieldProps,
193
+ field: {
194
+ ...fieldProps.field,
195
+ disabled: fieldDef.readonly ?? false,
196
+ },
197
+ }}
198
+ />
199
+ </CustomFieldFormItem>
200
+ )}
201
+ />
202
+ </div>
203
+ );
204
+ }
205
+
206
+ // For regular fields without custom components
207
+ return (
208
+ <div className={containerClassName}>
109
209
  <FormField
110
210
  control={control}
111
211
  name={fieldName}
112
- render={fieldProps => (
212
+ render={({ field }) => (
113
213
  <CustomFieldFormItem
114
214
  fieldDef={fieldDef}
115
215
  getTranslation={getTranslation}
116
- fieldName={fieldProps.field.name}
216
+ fieldName={field.name}
117
217
  >
118
- <CustomFormComponent
119
- fieldDef={fieldDef}
120
- fieldProps={{
121
- ...fieldProps,
122
- field: {
123
- ...fieldProps.field,
124
- disabled: fieldDef.readonly ?? false,
125
- },
126
- }}
127
- />
218
+ <FormInputForType fieldDef={fieldDef} field={field} />
128
219
  </CustomFieldFormItem>
129
220
  )}
130
221
  />
131
- );
132
- }
133
-
134
- // For regular fields without custom components
135
- return (
136
- <FormField
137
- control={control}
138
- name={fieldName}
139
- render={({ field }) => (
140
- <CustomFieldFormItem
141
- fieldDef={fieldDef}
142
- getTranslation={getTranslation}
143
- fieldName={field.name}
144
- >
145
- <FormInputForType fieldDef={fieldDef} field={field} />
146
- </CustomFieldFormItem>
147
- )}
148
- />
222
+ </div>
149
223
  );
150
224
  }
151
225
 
@@ -7,15 +7,9 @@ import {
7
7
  } from '@/framework/document-introspection/get-document-structure.js';
8
8
  import { useListQueryFields } from '@/framework/document-introspection/hooks.js';
9
9
  import { api } from '@/graphql/api.js';
10
- import { useMutation, useQueryClient } from '@tanstack/react-query';
10
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
11
11
  import { useDebounce } from '@uidotdev/usehooks';
12
12
 
13
- import {
14
- DropdownMenu,
15
- DropdownMenuContent,
16
- DropdownMenuItem,
17
- DropdownMenuTrigger,
18
- } from '@/components/ui/dropdown-menu.js';
19
13
  import {
20
14
  AlertDialog,
21
15
  AlertDialogAction,
@@ -27,11 +21,17 @@ import {
27
21
  AlertDialogTitle,
28
22
  AlertDialogTrigger,
29
23
  } from '@/components/ui/alert-dialog.js';
24
+ import {
25
+ DropdownMenu,
26
+ DropdownMenuContent,
27
+ DropdownMenuItem,
28
+ DropdownMenuTrigger,
29
+ } from '@/components/ui/dropdown-menu.js';
30
30
  import { DisplayComponent } from '@/framework/component-registry/dynamic-component.js';
31
+ import { BulkAction } from '@/framework/data-table/data-table-types.js';
31
32
  import { ResultOf } from '@/graphql/graphql.js';
32
33
  import { Trans, useLingui } from '@/lib/trans.js';
33
34
  import { TypedDocumentNode } from '@graphql-typed-document-node/core';
34
- import { useQuery } from '@tanstack/react-query';
35
35
  import {
36
36
  ColumnFiltersState,
37
37
  ColumnSort,
@@ -44,6 +44,7 @@ import { EllipsisIcon, TrashIcon } from 'lucide-react';
44
44
  import React, { useMemo } from 'react';
45
45
  import { toast } from 'sonner';
46
46
  import { Button } from '../ui/button.js';
47
+ import { Checkbox } from '../ui/checkbox.js';
47
48
 
48
49
  // Type that identifies a paginated list structure (has items array and totalItems)
49
50
  type IsPaginatedList<T> = T extends { items: any[]; totalItems: number } ? true : false;
@@ -227,6 +228,7 @@ export interface PaginatedListDataTableProps<
227
228
  onColumnVisibilityChange?: (table: Table<any>, columnVisibility: VisibilityState) => void;
228
229
  facetedFilters?: FacetedFilterConfig<T>;
229
230
  rowActions?: RowAction<PaginatedListItemFields<T>>[];
231
+ bulkActions?: BulkAction[];
230
232
  disableViewOptions?: boolean;
231
233
  transformData?: (data: PaginatedListItemFields<T>[]) => PaginatedListItemFields<T>[];
232
234
  setTableOptions?: (table: TableOptions<any>) => TableOptions<any>;
@@ -265,6 +267,7 @@ export function PaginatedListDataTable<
265
267
  onColumnVisibilityChange,
266
268
  facetedFilters,
267
269
  rowActions,
270
+ bulkActions,
268
271
  disableViewOptions,
269
272
  setTableOptions,
270
273
  transformData,
@@ -309,6 +312,7 @@ export function PaginatedListDataTable<
309
312
  function refetchPaginatedList() {
310
313
  queryClient.invalidateQueries({ queryKey });
311
314
  }
315
+
312
316
  registerRefresher?.(refetchPaginatedList);
313
317
 
314
318
  const { data } = useQuery({
@@ -427,7 +431,10 @@ export function PaginatedListDataTable<
427
431
  // existing order
428
432
  const orderedColumns = finalColumns
429
433
  .filter(column => column.id && defaultColumnOrder.includes(column.id as any))
430
- .sort((a, b) => defaultColumnOrder.indexOf(a.id as any) - defaultColumnOrder.indexOf(b.id as any));
434
+ .sort(
435
+ (a, b) =>
436
+ defaultColumnOrder.indexOf(a.id as any) - defaultColumnOrder.indexOf(b.id as any),
437
+ );
431
438
  const remainingColumns = finalColumns.filter(
432
439
  column => !column.id || !defaultColumnOrder.includes(column.id as any),
433
440
  );
@@ -441,6 +448,31 @@ export function PaginatedListDataTable<
441
448
  }
442
449
  }
443
450
 
451
+ // Add the row selection column
452
+ finalColumns.unshift({
453
+ id: 'selection',
454
+ accessorKey: 'selection',
455
+ header: ({ table }) => (
456
+ <Checkbox
457
+ className="mx-1"
458
+ checked={table.getIsAllRowsSelected()}
459
+ onCheckedChange={checked =>
460
+ table.toggleAllRowsSelected(checked === 'indeterminate' ? undefined : checked)
461
+ }
462
+ />
463
+ ),
464
+ enableColumnFilter: false,
465
+ cell: ({ row }) => {
466
+ return (
467
+ <Checkbox
468
+ className="mx-1"
469
+ checked={row.getIsSelected()}
470
+ onCheckedChange={row.getToggleSelectedHandler()}
471
+ />
472
+ );
473
+ },
474
+ });
475
+
444
476
  return { columns: finalColumns, customFieldColumnNames };
445
477
  }, [fields, customizeColumns, rowActions]);
446
478
 
@@ -465,6 +497,7 @@ export function PaginatedListDataTable<
465
497
  defaultColumnVisibility={columnVisibility}
466
498
  facetedFilters={facetedFilters}
467
499
  disableViewOptions={disableViewOptions}
500
+ bulkActions={bulkActions}
468
501
  setTableOptions={setTableOptions}
469
502
  onRefresh={refetchPaginatedList}
470
503
  />
@@ -536,7 +569,7 @@ function DeleteMutationRowAction({
536
569
  return (
537
570
  <AlertDialog>
538
571
  <AlertDialogTrigger asChild>
539
- <DropdownMenuItem onSelect={(e) => e.preventDefault()}>
572
+ <DropdownMenuItem onSelect={e => e.preventDefault()}>
540
573
  <div className="flex items-center gap-2 text-destructive">
541
574
  <TrashIcon className="w-4 h-4 text-destructive" />
542
575
  <Trans>Delete</Trans>
@@ -549,7 +582,9 @@ function DeleteMutationRowAction({
549
582
  <Trans>Confirm deletion</Trans>
550
583
  </AlertDialogTitle>
551
584
  <AlertDialogDescription>
552
- <Trans>Are you sure you want to delete this item? This action cannot be undone.</Trans>
585
+ <Trans>
586
+ Are you sure you want to delete this item? This action cannot be undone.
587
+ </Trans>
553
588
  </AlertDialogDescription>
554
589
  </AlertDialogHeader>
555
590
  <AlertDialogFooter>
@@ -567,6 +602,7 @@ function DeleteMutationRowAction({
567
602
  </AlertDialog>
568
603
  );
569
604
  }
605
+
570
606
  /**
571
607
  * Returns the default column visibility configuration.
572
608
  */
@@ -0,0 +1,21 @@
1
+ import { BulkAction } from '@/framework/data-table/data-table-types.js';
2
+
3
+ import { globalRegistry } from '../registry/global-registry.js';
4
+
5
+ globalRegistry.register('bulkActionsRegistry', new Map<string, BulkAction[]>());
6
+
7
+ export function getBulkActions(pageId: string, blockId = 'list-table'): BulkAction[] {
8
+ const key = createKey(pageId, blockId);
9
+ return globalRegistry.get('bulkActionsRegistry').get(key) || [];
10
+ }
11
+
12
+ export function addBulkAction(pageId: string, blockId: string | undefined, action: BulkAction) {
13
+ const bulkActionsRegistry = globalRegistry.get('bulkActionsRegistry');
14
+ const key = createKey(pageId, blockId);
15
+ const existingActions = bulkActionsRegistry.get(key) || [];
16
+ bulkActionsRegistry.set(key, [...existingActions, action]);
17
+ }
18
+
19
+ function createKey(pageId: string, blockId: string | undefined): string {
20
+ return `${pageId}__${blockId ?? 'list-table'}`;
21
+ }
@@ -0,0 +1,25 @@
1
+ import { Table } from '@tanstack/react-table';
2
+
3
+ export type BulkActionContext<Item extends { id: string } & Record<string, any>> = {
4
+ selection: Item[];
5
+ table: Table<Item>;
6
+ };
7
+
8
+ export type BulkActionComponent<Item extends { id: string } & Record<string, any>> = React.FunctionComponent<
9
+ BulkActionContext<Item>
10
+ >;
11
+
12
+ /**
13
+ * @description
14
+ * **Status: Developer Preview**
15
+ *
16
+ * A bulk action is a component that will be rendered in the bulk actions dropdown.
17
+ *
18
+ * @docsCategory components
19
+ * @docsPage DataTableBulkActions
20
+ * @since 3.4.0
21
+ */
22
+ export type BulkAction = {
23
+ order?: number;
24
+ component: BulkActionComponent<any>;
25
+ };
@@ -1,3 +1,5 @@
1
+ import { addBulkAction } from '@/framework/data-table/data-table-extensions.js';
2
+
1
3
  import { registerDashboardWidget } from '../dashboard-widget/widget-extensions.js';
2
4
  import { addCustomFormComponent } from '../form-engine/custom-form-component-extensions.js';
3
5
  import {
@@ -82,6 +84,15 @@ export function defineDashboardExtension(extension: DashboardExtension) {
82
84
  addCustomFormComponent(component);
83
85
  }
84
86
  }
87
+ if (extension.dataTables) {
88
+ for (const dataTable of extension.dataTables) {
89
+ if (dataTable.bulkActions?.length) {
90
+ for (const action of dataTable.bulkActions) {
91
+ addBulkAction(dataTable.pageId, dataTable.blockId, action);
92
+ }
93
+ }
94
+ }
95
+ }
85
96
  const callbacks = globalRegistry.get('extensionSourceChangeCallbacks');
86
97
  if (callbacks.size) {
87
98
  for (const callback of callbacks) {
@@ -5,6 +5,7 @@ import type React from 'react';
5
5
 
6
6
  import { DashboardAlertDefinition } from '../alert/types.js';
7
7
  import { DashboardWidgetDefinition } from '../dashboard-widget/types.js';
8
+ import { BulkAction } from '../data-table/data-table-types.js';
8
9
  import { CustomFormComponentInputProps } from '../form-engine/custom-form-component.js';
9
10
  import { NavMenuItem } from '../nav-menu/nav-menu-extensions.js';
10
11
 
@@ -109,6 +110,35 @@ export interface DashboardPageBlockDefinition {
109
110
  requiresPermission?: string | string[];
110
111
  }
111
112
 
113
+ /**
114
+ * @description
115
+ * **Status: Developer Preview**
116
+ *
117
+ * This allows you to customize aspects of existing data tables in the dashboard.
118
+ *
119
+ * @docsCategory extensions
120
+ * @since 3.4.0
121
+ */
122
+ export interface DashboardDataTableDefinition {
123
+ /**
124
+ * @description
125
+ * The ID of the page where the data table is located, e.g. `'product-list'`, `'order-list'`.
126
+ */
127
+ pageId: string;
128
+ /**
129
+ * @description
130
+ * The ID of the data table block. Defaults to `'list-table'`, which is the default blockId
131
+ * for the standard list pages. However, some other pages may use a different blockId,
132
+ * such as `'product-variants-table'` on the `'product-detail'` page.
133
+ */
134
+ blockId?: string;
135
+ /**
136
+ * @description
137
+ * An array of additional bulk actions that will be available on the data table.
138
+ */
139
+ bulkActions?: BulkAction[];
140
+ }
141
+
112
142
  /**
113
143
  * @description
114
144
  * **Status: Developer Preview**
@@ -155,4 +185,9 @@ export interface DashboardExtension {
155
185
  * Allows you to define custom form components for custom fields in the dashboard.
156
186
  */
157
187
  customFormComponents?: DashboardCustomFormComponent[];
188
+ /**
189
+ * @description
190
+ * Allows you to customize aspects of existing data tables in the dashboard.
191
+ */
192
+ dataTables?: DashboardDataTableDefinition[];
158
193
  }
@@ -1,8 +1,5 @@
1
1
  import { getOperationVariablesFields } from '@/framework/document-introspection/get-document-structure.js';
2
- import {
3
- createFormSchemaFromFields,
4
- getDefaultValuesFromFields,
5
- } from '@/framework/form-engine/form-schema-tools.js';
2
+ import { createFormSchemaFromFields, getDefaultValuesFromFields } from '@/framework/form-engine/form-schema-tools.js';
6
3
  import { useChannel } from '@/hooks/use-channel.js';
7
4
  import { useServerConfig } from '@/hooks/use-server-config.js';
8
5
  import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
@@ -57,7 +54,7 @@ export function useGeneratedForm<
57
54
  },
58
55
  mode: 'onChange',
59
56
  defaultValues,
60
- values: processedEntity ? processedEntity : defaultValues,
57
+ values: processedEntity ? setValues(processedEntity) : defaultValues,
61
58
  });
62
59
  let submitHandler = (event: FormEvent) => {
63
60
  event.preventDefault();
@@ -0,0 +1,6 @@
1
+ import { PageBlockProps } from '@/framework/layout-engine/page-layout.js';
2
+ import { createContext } from 'react';
3
+
4
+ export type PageBlockContextValue = Pick<PageBlockProps, 'blockId' | 'column' | 'title' | 'description'>;
5
+
6
+ export const PageBlockContext = createContext<PageBlockContextValue | undefined>(undefined);