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

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 (41) 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/paginated-list-data-table.tsx +47 -11
  25. package/src/lib/framework/data-table/data-table-extensions.ts +21 -0
  26. package/src/lib/framework/data-table/data-table-types.ts +25 -0
  27. package/src/lib/framework/extension-api/define-dashboard-extension.ts +11 -0
  28. package/src/lib/framework/extension-api/extension-api-types.ts +35 -0
  29. package/src/lib/framework/form-engine/use-generated-form.tsx +2 -5
  30. package/src/lib/framework/layout-engine/page-block-provider.tsx +6 -0
  31. package/src/lib/framework/layout-engine/page-layout.tsx +43 -33
  32. package/src/lib/framework/page/list-page.tsx +6 -8
  33. package/src/lib/framework/registry/registry-types.ts +4 -2
  34. package/src/lib/hooks/use-page-block.tsx +10 -0
  35. package/src/lib/index.ts +8 -1
  36. package/vite/tests/barrel-exports.spec.ts +13 -9
  37. package/vite/vite-plugin-config.ts +1 -0
  38. package/vite/vite-plugin-dashboard-metadata.ts +1 -9
  39. package/vite/vite-plugin-tailwind-source.ts +65 -0
  40. package/vite/vite-plugin-vendure-dashboard.ts +5 -3
  41. /package/src/lib/components/data-table/{data-table-types.ts → types.ts} +0 -0
@@ -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);
@@ -1,20 +1,21 @@
1
1
  import { CustomFieldsForm } from '@/components/shared/custom-fields-form.js';
2
+ import { NavigationConfirmation } from '@/components/shared/navigation-confirmation.js';
2
3
  import { PermissionGuard } from '@/components/shared/permission-guard.js';
3
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card.js';
4
5
  import { Form } from '@/components/ui/form.js';
5
6
  import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
6
7
  import { usePage } from '@/hooks/use-page.js';
7
8
  import { cn } from '@/lib/utils.js';
8
- import { NavigationConfirmation } from '@/components/shared/navigation-confirmation.js';
9
9
  import { useMediaQuery } from '@uidotdev/usehooks';
10
10
  import React, { ComponentProps } from 'react';
11
11
  import { Control, UseFormReturn } from 'react-hook-form';
12
12
 
13
13
  import { DashboardActionBarItem } from '../extension-api/extension-api-types.js';
14
14
 
15
+ import { PageBlockContext } from '@/framework/layout-engine/page-block-provider.js';
16
+ import { PageContext, PageContextValue } from '@/framework/layout-engine/page-provider.js';
15
17
  import { getDashboardActionBarItems, getDashboardPageBlocks } from './layout-extensions.js';
16
18
  import { LocationWrapper } from './location-wrapper.js';
17
- import { PageContext, PageContextValue } from '@/framework/layout-engine/page-provider.js';
18
19
 
19
20
  export interface PageProps extends ComponentProps<'div'> {
20
21
  pageId?: string;
@@ -45,9 +46,7 @@ export function Page({ children, pageId, entity, form, submitHandler, ...props }
45
46
  const childArray = React.Children.toArray(children);
46
47
 
47
48
  const pageTitle = childArray.find(child => React.isValidElement(child) && child.type === PageTitle);
48
- const pageActionBar = childArray.find(
49
- child => isOfType(child, PageActionBar),
50
- );
49
+ const pageActionBar = childArray.find(child => isOfType(child, PageActionBar));
51
50
 
52
51
  const pageContent = childArray.filter(
53
52
  child => !isOfType(child, PageTitle) && !isOfType(child, PageActionBar),
@@ -73,7 +72,13 @@ export function Page({ children, pageId, entity, form, submitHandler, ...props }
73
72
  );
74
73
  }
75
74
 
76
- function PageContent({ pageHeader, pageContent, form, submitHandler, ...props }: {
75
+ function PageContent({
76
+ pageHeader,
77
+ pageContent,
78
+ form,
79
+ submitHandler,
80
+ ...props
81
+ }: {
77
82
  pageHeader: React.ReactNode;
78
83
  pageContent: React.ReactNode;
79
84
  form?: UseFormReturn<any>;
@@ -94,9 +99,14 @@ function PageContent({ pageHeader, pageContent, form, submitHandler, ...props }:
94
99
  );
95
100
  }
96
101
 
97
- export function PageContentWithOptionalForm({ form, pageHeader, pageContent, submitHandler }: {
102
+ export function PageContentWithOptionalForm({
103
+ form,
104
+ pageHeader,
105
+ pageContent,
106
+ submitHandler,
107
+ }: {
98
108
  form?: UseFormReturn<any>;
99
- pageHeader: React.ReactNode
109
+ pageHeader: React.ReactNode;
100
110
  pageContent: React.ReactNode;
101
111
  submitHandler?: any;
102
112
  }) {
@@ -261,12 +271,8 @@ export function PageTitle({ children }: { children: React.ReactNode }) {
261
271
  export function PageActionBar({ children }: { children: React.ReactNode }) {
262
272
  let childArray = React.Children.toArray(children);
263
273
 
264
- const leftContent = childArray.filter(
265
- child => isOfType(child, PageActionBarLeft),
266
- );
267
- const rightContent = childArray.filter(
268
- child => isOfType(child, PageActionBarRight),
269
- );
274
+ const leftContent = childArray.filter(child => isOfType(child, PageActionBarLeft));
275
+ const rightContent = childArray.filter(child => isOfType(child, PageActionBarRight));
270
276
 
271
277
  return (
272
278
  <div className={cn('flex gap-2', leftContent.length > 0 ? 'justify-between' : 'justify-end')}>
@@ -348,18 +354,20 @@ export type PageBlockProps = {
348
354
  * @docsWeight 0
349
355
  * @since 3.3.0
350
356
  */
351
- export function PageBlock({ children, title, description, className, blockId }: PageBlockProps) {
357
+ export function PageBlock({ children, title, description, className, blockId, column }: PageBlockProps) {
352
358
  return (
353
359
  <LocationWrapper blockId={blockId}>
354
- <Card className={cn('w-full', className)}>
355
- {title || description ? (
356
- <CardHeader>
357
- {title && <CardTitle>{title}</CardTitle>}
358
- {description && <CardDescription>{description}</CardDescription>}
359
- </CardHeader>
360
- ) : null}
361
- <CardContent className={cn(!title ? 'pt-6' : '')}>{children}</CardContent>
362
- </Card>
360
+ <PageBlockContext.Provider value={{ blockId, title, description, column }}>
361
+ <Card className={cn('w-full', className)}>
362
+ {title || description ? (
363
+ <CardHeader>
364
+ {title && <CardTitle>{title}</CardTitle>}
365
+ {description && <CardDescription>{description}</CardDescription>}
366
+ </CardHeader>
367
+ ) : null}
368
+ <CardContent className={cn(!title ? 'pt-6' : '')}>{children}</CardContent>
369
+ </Card>
370
+ </PageBlockContext.Provider>
363
371
  </LocationWrapper>
364
372
  );
365
373
  }
@@ -376,13 +384,15 @@ export function PageBlock({ children, title, description, className, blockId }:
376
384
  * @since 3.3.0
377
385
  */
378
386
  export function FullWidthPageBlock({
379
- children,
380
- className,
381
- blockId,
382
- }: Pick<PageBlockProps, 'children' | 'className' | 'blockId'>) {
387
+ children,
388
+ className,
389
+ blockId,
390
+ }: Pick<PageBlockProps, 'children' | 'className' | 'blockId'>) {
383
391
  return (
384
392
  <LocationWrapper blockId={blockId}>
385
- <div className={cn('w-full', className)}>{children}</div>
393
+ <PageBlockContext.Provider value={{ blockId, column: 'main' }}>
394
+ <div className={cn('w-full', className)}>{children}</div>
395
+ </PageBlockContext.Provider>
386
396
  </LocationWrapper>
387
397
  );
388
398
  }
@@ -398,10 +408,10 @@ export function FullWidthPageBlock({
398
408
  * @since 3.3.0
399
409
  */
400
410
  export function CustomFieldsPageBlock({
401
- column,
402
- entityType,
403
- control,
404
- }: {
411
+ column,
412
+ entityType,
413
+ control,
414
+ }: {
405
415
  column: 'main' | 'side';
406
416
  entityType: string;
407
417
  control: Control<any, any>;
@@ -3,12 +3,13 @@ import {
3
3
  CustomFieldKeysOfItem,
4
4
  CustomizeColumnConfig,
5
5
  FacetedFilterConfig,
6
+ ListQueryFields,
6
7
  ListQueryOptionsShape,
7
8
  ListQueryShape,
8
- ListQueryFields,
9
9
  PaginatedListDataTable,
10
10
  RowAction,
11
11
  } from '@/components/shared/paginated-list-data-table.js';
12
+ import { BulkAction } from '@/framework/data-table/data-table-types.js';
12
13
  import { useUserSettings } from '@/hooks/use-user-settings.js';
13
14
  import { TypedDocumentNode } from '@graphql-typed-document-node/core';
14
15
  import { AnyRoute, AnyRouter, useNavigate } from '@tanstack/react-router';
@@ -16,13 +17,7 @@ import { ColumnFiltersState, SortingState, Table } from '@tanstack/react-table';
16
17
  import { TableOptions } from '@tanstack/table-core';
17
18
 
18
19
  import { addCustomFields } from '../document-introspection/add-custom-fields.js';
19
- import {
20
- FullWidthPageBlock,
21
- Page,
22
- PageActionBar,
23
- PageLayout,
24
- PageTitle,
25
- } from '../layout-engine/page-layout.js';
20
+ import { FullWidthPageBlock, Page, PageActionBar, PageLayout, PageTitle } from '../layout-engine/page-layout.js';
26
21
 
27
22
  /**
28
23
  * @description
@@ -57,6 +52,7 @@ export interface ListPageProps<
57
52
  rowActions?: RowAction<ListQueryFields<T>>[];
58
53
  transformData?: (data: any[]) => any[];
59
54
  setTableOptions?: (table: TableOptions<any>) => TableOptions<any>;
55
+ bulkActions?: BulkAction[];
60
56
  }
61
57
 
62
58
  /**
@@ -93,6 +89,7 @@ export function ListPage<
93
89
  rowActions,
94
90
  transformData,
95
91
  setTableOptions,
92
+ bulkActions,
96
93
  }: ListPageProps<T, U, V, AC>) {
97
94
  const route = typeof routeOrFn === 'function' ? routeOrFn() : routeOrFn;
98
95
  const routeSearch = route.useSearch();
@@ -191,6 +188,7 @@ export function ListPage<
191
188
  }}
192
189
  facetedFilters={facetedFilters}
193
190
  rowActions={rowActions}
191
+ bulkActions={bulkActions}
194
192
  setTableOptions={setTableOptions}
195
193
  transformData={transformData}
196
194
  />
@@ -1,5 +1,8 @@
1
+ import React from 'react';
2
+
1
3
  import { DashboardAlertDefinition } from '../alert/types.js';
2
4
  import { DashboardWidgetDefinition } from '../dashboard-widget/types.js';
5
+ import { BulkAction } from '../data-table/data-table-types.js';
3
6
  import {
4
7
  DashboardActionBarItem,
5
8
  DashboardPageBlockDefinition,
@@ -16,6 +19,5 @@ export interface GlobalRegistryContents {
16
19
  dashboardWidgetRegistry: Map<string, DashboardWidgetDefinition>;
17
20
  dashboardAlertRegistry: Map<string, DashboardAlertDefinition>;
18
21
  customFormComponents: Map<string, React.FunctionComponent<CustomFormComponentInputProps>>;
22
+ bulkActionsRegistry: Map<string, BulkAction[]>;
19
23
  }
20
-
21
- export type GlobalRegistryKey = keyof GlobalRegistryContents;
@@ -0,0 +1,10 @@
1
+ import { PageBlockContext } from '@/framework/layout-engine/page-block-provider.js';
2
+ import { useContext } from 'react';
3
+
4
+ export function usePageBlock() {
5
+ const pageBlock = useContext(PageBlockContext);
6
+ if (!pageBlock) {
7
+ throw new Error('PageBlockProvider not found');
8
+ }
9
+ return pageBlock;
10
+ }
package/src/lib/index.ts CHANGED
@@ -11,12 +11,13 @@ export * from './components/data-input/facet-value-input.js';
11
11
  export * from './components/data-input/money-input.js';
12
12
  export * from './components/data-input/richt-text-input.js';
13
13
  export * from './components/data-table/add-filter-menu.js';
14
+ export * from './components/data-table/data-table-bulk-action-item.js';
15
+ export * from './components/data-table/data-table-bulk-actions.js';
14
16
  export * from './components/data-table/data-table-column-header.js';
15
17
  export * from './components/data-table/data-table-faceted-filter.js';
16
18
  export * from './components/data-table/data-table-filter-badge.js';
17
19
  export * from './components/data-table/data-table-filter-dialog.js';
18
20
  export * from './components/data-table/data-table-pagination.js';
19
- export * from './components/data-table/data-table-types.js';
20
21
  export * from './components/data-table/data-table-view-options.js';
21
22
  export * from './components/data-table/data-table.js';
22
23
  export * from './components/data-table/filters/data-table-boolean-filter.js';
@@ -26,6 +27,7 @@ export * from './components/data-table/filters/data-table-number-filter.js';
26
27
  export * from './components/data-table/filters/data-table-string-filter.js';
27
28
  export * from './components/data-table/human-readable-operator.js';
28
29
  export * from './components/data-table/refresh-button.js';
30
+ export * from './components/data-table/types.js';
29
31
  export * from './components/layout/app-layout.js';
30
32
  export * from './components/layout/app-sidebar.js';
31
33
  export * from './components/layout/channel-switcher.js';
@@ -137,6 +139,8 @@ export * from './framework/dashboard-widget/orders-summary/index.js';
137
139
  export * from './framework/dashboard-widget/orders-summary/order-summary-widget.graphql.js';
138
140
  export * from './framework/dashboard-widget/types.js';
139
141
  export * from './framework/dashboard-widget/widget-extensions.js';
142
+ export * from './framework/data-table/data-table-extensions.js';
143
+ export * from './framework/data-table/data-table-types.js';
140
144
  export * from './framework/defaults.js';
141
145
  export * from './framework/document-introspection/add-custom-fields.js';
142
146
  export * from './framework/document-introspection/get-document-structure.js';
@@ -150,6 +154,7 @@ export * from './framework/form-engine/form-schema-tools.js';
150
154
  export * from './framework/form-engine/use-generated-form.js';
151
155
  export * from './framework/layout-engine/layout-extensions.js';
152
156
  export * from './framework/layout-engine/location-wrapper.js';
157
+ export * from './framework/layout-engine/page-block-provider.js';
153
158
  export * from './framework/layout-engine/page-layout.js';
154
159
  export * from './framework/layout-engine/page-provider.js';
155
160
  export * from './framework/nav-menu/nav-menu-extensions.js';
@@ -164,12 +169,14 @@ export * from './framework/registry/global-registry.js';
164
169
  export * from './framework/registry/registry-types.js';
165
170
  export * from './graphql/api.js';
166
171
  export * from './graphql/fragments.js';
172
+ export * from './graphql/graphql.js';
167
173
  export * from './hooks/use-auth.js';
168
174
  export * from './hooks/use-channel.js';
169
175
  export * from './hooks/use-custom-field-config.js';
170
176
  export * from './hooks/use-grouped-permissions.js';
171
177
  export * from './hooks/use-local-format.js';
172
178
  export * from './hooks/use-mobile.js';
179
+ export * from './hooks/use-page-block.js';
173
180
  export * from './hooks/use-page.js';
174
181
  export * from './hooks/use-permissions.js';
175
182
  export * from './hooks/use-server-config.js';
@@ -4,14 +4,18 @@ import { describe, expect, it } from 'vitest';
4
4
  import { loadVendureConfig } from '../utils/config-loader.js';
5
5
 
6
6
  describe('detecting plugins in barrel exports', () => {
7
- it('should detect plugins in barrel exports', async () => {
8
- const result = await loadVendureConfig({
9
- tempDir: join(__dirname, './__temp'),
10
- vendureConfigPath: join(__dirname, 'barrel-exports', 'vendure-config.ts'),
11
- });
7
+ it(
8
+ 'should detect plugins in barrel exports',
9
+ async () => {
10
+ const result = await loadVendureConfig({
11
+ tempDir: join(__dirname, './__temp'),
12
+ vendureConfigPath: join(__dirname, 'barrel-exports', 'vendure-config.ts'),
13
+ });
12
14
 
13
- expect(result.pluginInfo).toHaveLength(1);
14
- expect(result.pluginInfo[0].name).toBe('MyPlugin');
15
- expect(result.pluginInfo[0].dashboardEntryPath).toBe('./dashboard/index.tsx');
16
- });
15
+ expect(result.pluginInfo).toHaveLength(1);
16
+ expect(result.pluginInfo[0].name).toBe('MyPlugin');
17
+ expect(result.pluginInfo[0].dashboardEntryPath).toBe('./dashboard/index.tsx');
18
+ },
19
+ { timeout: 10_000 },
20
+ );
17
21
  });
@@ -63,6 +63,7 @@ export function viteConfigPlugin({ packageRoot }: { packageRoot: string }): Plug
63
63
  ...(config.optimizeDeps?.include || []),
64
64
  '@/components > recharts',
65
65
  '@/components > react-dropzone',
66
+ '@vendure/common/lib/generated-types',
66
67
  ],
67
68
  };
68
69
  return config;
@@ -12,7 +12,7 @@ const resolvedVirtualModuleId = `\0${virtualModuleId}`;
12
12
  * generates an import statement for each one, wrapped up in a `runDashboardExtensions()`
13
13
  * function which can then be imported and executed in the Dashboard app.
14
14
  */
15
- export function dashboardMetadataPlugin(options: { rootDir: string }): Plugin {
15
+ export function dashboardMetadataPlugin(): Plugin {
16
16
  let configLoaderApi: ConfigLoaderApi;
17
17
  let loadVendureConfigResult: LoadVendureConfigResult;
18
18
  return {
@@ -52,11 +52,3 @@ export function dashboardMetadataPlugin(options: { rootDir: string }): Plugin {
52
52
  },
53
53
  };
54
54
  }
55
-
56
- /**
57
- * Converts an import path to a normalized path relative to the rootDir.
58
- */
59
- function normalizeImportPath(rootDir: string, importPath: string): string {
60
- const relativePath = path.relative(rootDir, importPath).replace(/\\/g, '/');
61
- return relativePath.replace(/\.tsx?$/, '.js');
62
- }