Browse Source

docs: Add docs to ui extensions types & guides

Michael Bromley 2 years ago
parent
commit
10fab25388
100 changed files with 2892 additions and 1132 deletions
  1. 1 1
      docs/docs/guides/core-concepts/auth/index.md
  2. 147 122
      docs/docs/guides/extending-the-admin-ui/add-actions-to-pages/index.md
  3. 23 26
      docs/docs/guides/extending-the-admin-ui/adding-ui-translations/index.md
  4. 61 62
      docs/docs/guides/extending-the-admin-ui/admin-ui-theming-branding/index.md
  5. 0 0
      docs/docs/guides/extending-the-admin-ui/bulk-actions/bulk-actions-screenshot.webp
  6. 78 0
      docs/docs/guides/extending-the-admin-ui/bulk-actions/index.md
  7. 148 39
      docs/docs/guides/extending-the-admin-ui/custom-detail-components/index.md
  8. 163 89
      docs/docs/guides/extending-the-admin-ui/custom-form-inputs/index.md
  9. 30 24
      docs/docs/guides/extending-the-admin-ui/custom-timeline-components/index.md
  10. 82 108
      docs/docs/guides/extending-the-admin-ui/dashboard-widgets/index.md
  11. 552 0
      docs/docs/guides/extending-the-admin-ui/getting-started/index.md
  12. BIN
      docs/docs/guides/extending-the-admin-ui/getting-started/provider-extension-points.webp
  13. 0 0
      docs/docs/guides/extending-the-admin-ui/getting-started/ui-extensions-greeter.webp
  14. 0 144
      docs/docs/guides/extending-the-admin-ui/introduction.md
  15. 0 111
      docs/docs/guides/extending-the-admin-ui/modifying-navigation-items/index.md
  16. 74 0
      docs/docs/guides/extending-the-admin-ui/nav-menu/index.md
  17. 0 0
      docs/docs/guides/extending-the-admin-ui/nav-menu/ui-extensions-navbar.webp
  18. 28 0
      docs/docs/guides/extending-the-admin-ui/page-tabs/index.md
  19. 0 0
      docs/docs/guides/extending-the-admin-ui/page-tabs/ui-extensions-tabs.webp
  20. 0 125
      docs/docs/guides/extending-the-admin-ui/using-angular/index.md
  21. 38 70
      docs/docs/guides/extending-the-admin-ui/using-other-frameworks/index.md
  22. 51 0
      docs/docs/reference/admin-ui-api/action-bar/action-bar-context.md
  23. 11 4
      docs/docs/reference/admin-ui-api/action-bar/action-bar-item.md
  24. 1 1
      docs/docs/reference/admin-ui-api/action-bar/action-bar-location-id.md
  25. 9 16
      docs/docs/reference/admin-ui-api/action-bar/add-action-bar-item.md
  26. 1 1
      docs/docs/reference/admin-ui-api/action-bar/page-location-id.md
  27. 20 0
      docs/docs/reference/admin-ui-api/action-bar/router-link-definition.md
  28. 25 29
      docs/docs/reference/admin-ui-api/bulk-actions/register-bulk-action.md
  29. 2 2
      docs/docs/reference/admin-ui-api/components/asset-picker-dialog-component.md
  30. 1 1
      docs/docs/reference/admin-ui-api/components/currency-input-component.md
  31. 40 4
      docs/docs/reference/admin-ui-api/components/data-table2component.md
  32. 1 1
      docs/docs/reference/admin-ui-api/components/facet-value-selector-component.md
  33. 1 1
      docs/docs/reference/admin-ui-api/components/product-variant-selector-component.md
  34. 1 1
      docs/docs/reference/admin-ui-api/components/zone-selector-component.md
  35. 6 0
      docs/docs/reference/admin-ui-api/custom-detail-components/custom-detail-component-config.md
  36. 1 1
      docs/docs/reference/admin-ui-api/custom-detail-components/custom-detail-component-location-id.md
  37. 47 1
      docs/docs/reference/admin-ui-api/custom-detail-components/register-custom-detail-component.md
  38. 57 1
      docs/docs/reference/admin-ui-api/custom-history-entry-components/register-history-entry-component.md
  39. 4 4
      docs/docs/reference/admin-ui-api/custom-input-components/default-inputs.md
  40. 1 1
      docs/docs/reference/admin-ui-api/custom-input-components/form-input-component.md
  41. 14 13
      docs/docs/reference/admin-ui-api/custom-input-components/register-form-input-component.md
  42. 36 0
      docs/docs/reference/admin-ui-api/custom-table-components/custom-column-component.md
  43. 52 0
      docs/docs/reference/admin-ui-api/custom-table-components/data-table-component-config.md
  44. 14 0
      docs/docs/reference/admin-ui-api/custom-table-components/index.md
  45. 59 0
      docs/docs/reference/admin-ui-api/custom-table-components/register-data-table-component.md
  46. 64 0
      docs/docs/reference/admin-ui-api/dashboard-widgets/dashboard-widget-config.md
  47. 14 0
      docs/docs/reference/admin-ui-api/dashboard-widgets/index.md
  48. 31 0
      docs/docs/reference/admin-ui-api/dashboard-widgets/register-dashboard-widget.md
  49. 26 0
      docs/docs/reference/admin-ui-api/dashboard-widgets/set-dashboard-widget-layout.md
  50. 20 0
      docs/docs/reference/admin-ui-api/dashboard-widgets/widget-layout-definition.md
  51. 1 1
      docs/docs/reference/admin-ui-api/directives/if-multichannel-directive.md
  52. 1 1
      docs/docs/reference/admin-ui-api/directives/if-permissions-directive.md
  53. 1 1
      docs/docs/reference/admin-ui-api/list-detail-views/base-detail-component.md
  54. 8 12
      docs/docs/reference/admin-ui-api/nav-menu/add-nav-menu-item.md
  55. 9 13
      docs/docs/reference/admin-ui-api/nav-menu/add-nav-menu-section.md
  56. 2 2
      docs/docs/reference/admin-ui-api/nav-menu/nav-menu-item.md
  57. 1 1
      docs/docs/reference/admin-ui-api/nav-menu/nav-menu-section.md
  58. 1 1
      docs/docs/reference/admin-ui-api/nav-menu/navigation-types.md
  59. 1 1
      docs/docs/reference/admin-ui-api/pipes/has-permission-pipe.md
  60. 1 1
      docs/docs/reference/admin-ui-api/pipes/locale-currency-name-pipe.md
  61. 1 1
      docs/docs/reference/admin-ui-api/pipes/locale-currency-pipe.md
  62. 1 1
      docs/docs/reference/admin-ui-api/pipes/locale-date-pipe.md
  63. 1 1
      docs/docs/reference/admin-ui-api/pipes/locale-language-name-pipe.md
  64. 1 1
      docs/docs/reference/admin-ui-api/pipes/locale-region-name-pipe.md
  65. 14 0
      docs/docs/reference/admin-ui-api/react-extensions/index.md
  66. 45 0
      docs/docs/reference/admin-ui-api/react-extensions/react-custom-detail-component-config.md
  67. 52 0
      docs/docs/reference/admin-ui-api/react-extensions/react-data-table-component-config.md
  68. 27 0
      docs/docs/reference/admin-ui-api/react-extensions/register-react-custom-detail-component.md
  69. 59 0
      docs/docs/reference/admin-ui-api/react-extensions/register-react-data-table-component.md
  70. 30 0
      docs/docs/reference/admin-ui-api/react-extensions/register-react-form-input-component.md
  71. 22 0
      docs/docs/reference/admin-ui-api/react-extensions/register-react-route-component-options.md
  72. 26 0
      docs/docs/reference/admin-ui-api/react-extensions/register-react-route-component.md
  73. 14 0
      docs/docs/reference/admin-ui-api/react-hooks/index.md
  74. 43 0
      docs/docs/reference/admin-ui-api/react-hooks/use-detail-component-data.md
  75. 40 0
      docs/docs/reference/admin-ui-api/react-hooks/use-form-control.md
  76. 43 0
      docs/docs/reference/admin-ui-api/react-hooks/use-injector.md
  77. 66 0
      docs/docs/reference/admin-ui-api/react-hooks/use-mutation.md
  78. 40 0
      docs/docs/reference/admin-ui-api/react-hooks/use-page-metadata.md
  79. 59 0
      docs/docs/reference/admin-ui-api/react-hooks/use-query.md
  80. 14 0
      docs/docs/reference/admin-ui-api/routes/index.md
  81. 30 0
      docs/docs/reference/admin-ui-api/routes/register-route-component-options.md
  82. 61 0
      docs/docs/reference/admin-ui-api/routes/register-route-component.md
  83. 3 3
      docs/docs/reference/admin-ui-api/services/data-service.md
  84. 14 0
      docs/docs/reference/admin-ui-api/services/index.md
  85. 5 5
      docs/docs/reference/admin-ui-api/services/modal-service.md
  86. 2 2
      docs/docs/reference/admin-ui-api/services/notification-service.md
  87. 11 12
      docs/docs/reference/admin-ui-api/tabs/register-page-tab.md
  88. 62 45
      docs/docs/reference/admin-ui-api/ui-devkit/admin-ui-extension.md
  89. 1 1
      docs/docs/reference/admin-ui-api/ui-devkit/compile-ui-extensions.md
  90. 22 1
      docs/docs/reference/admin-ui-api/ui-devkit/ui-extension-compiler-options.md
  91. 1 1
      docs/docs/reference/admin-ui-api/ui-devkit/ui-extension-compiler-process-argument.md
  92. 7 7
      docs/docs/reference/core-plugins/elasticsearch-plugin/elasticsearch-options.md
  93. 1 1
      docs/docs/reference/core-plugins/email-plugin/custom-template-loader.md
  94. 4 4
      docs/docs/reference/core-plugins/email-plugin/email-plugin-types.md
  95. 1 1
      docs/docs/reference/core-plugins/harden-plugin/default-vendure-complexity-estimator.md
  96. 2 2
      docs/docs/reference/core-plugins/harden-plugin/index.md
  97. 3 3
      docs/docs/reference/core-plugins/payments-plugin/stripe-plugin.md
  98. 1 1
      docs/docs/reference/typescript-api/assets/asset-options.md
  99. 1 1
      docs/docs/reference/typescript-api/auth/auth-options.md
  100. 1 1
      docs/docs/reference/typescript-api/auth/cookie-options.md

+ 1 - 1
docs/docs/guides/core-concepts/auth/index.md

@@ -99,7 +99,7 @@ In addition to the built-in `NativeAuthenticationStrategy`, it is possible to de
 
 Custom authentication strategies are set via the [`VendureConfig.authOptions` object](/reference/typescript-api/auth/auth-options/#shopauthenticationstrategy):
 
-```ts title="vendure-config.ts"
+```ts title="src/vendure-config.ts"
 import { VendureConfig, NativeAuthenticationStrategy } from '@vendure/core';
 
 import { FacebookAuthenticationStrategy } from './plugins/authentication/facebook-authentication-strategy';

+ 147 - 122
docs/docs/guides/extending-the-admin-ui/add-actions-to-pages/index.md

@@ -1,158 +1,183 @@
 ---
-title: 'Add Actions To Pages'
+title: 'Page ActionBar Buttons'
 weight: 5
 ---
 
-# Add Actions To Pages
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
 
+The `ActionBar` is the horizontal area at the top of each list or detail page, which contains the main buttons for that page. This guide explains how to add new buttons to the ActionBar.
 
-## Adding ActionBar buttons
+For example, consider an "order invoice" extension that allows you to print invoices for orders. In this case, you can add a "print invoice" button to the ActionBar. This is done using the [addActionBarItem function](/reference/admin-ui-api/action-bar/add-action-bar-item/).
 
-It may not always make sense to navigate to your extension view from the main nav menu. For example, an "order invoice" extension that allows you to print invoices for orders. In this case, you can add new buttons to the "ActionBar", which is the horizontal section at the top of each screen containing the primary actions for that view. This is done using the [addActionBarItem function](/reference/admin-ui-api/action-bar/add-action-bar-item/).
+## ActionBar Example
 
-### ActionBar Example
+```ts title="src/plugins/invoice/ui/providers.ts"
+import { addActionBarItem } from '@vendure/admin-ui/core';
 
-```ts
-import { NgModule } from '@angular/core';
-import { SharedModule, addActionBarItem } from '@vendure/admin-ui/core';
-
-@NgModule({
-  imports: [SharedModule],
-  providers: [
+export default [
     addActionBarItem({
-      id: 'print-invoice',
-      label: 'Print invoice',
-      locationId: 'order-detail',
-      routerLink: route => {
-          const id = route.snapshot.params.id;
-          return ['./extensions/order-invoices', id];
-      },
-      requiresPermission: 'ReadOrder',
+        id: 'print-invoice',
+        label: 'Print invoice',
+        locationId: 'order-detail',
+        routerLink: route => {
+            const id = route.snapshot.params.id;
+            return ['./extensions/order-invoices', id];
+        },
+        requiresPermission: 'ReadOrder',
     }),
-  ],
-})
-export class SharedExtensionModule {}
+];
 ```
 
 ![./ui-extensions-actionbar.webp](./ui-extensions-actionbar.webp)
 
-In each list or detail view in the app, the ActionBar has a unique `locationId` which is how the app knows in which view to place your button. The complete list of available locations into which you can add new ActionBar can be found in the [ActionBarLocationId docs](/reference/admin-ui-api/action-bar/action-bar-location-id/).
+In each list or detail view in the app, the ActionBar has a unique `locationId` which is how the app knows in which view to place your button. The complete list of available locations into which you can add new ActionBar can be found in the [PageLocationId docs](/reference/admin-ui-api/action-bar/page-location-id/). You can also press `ctrl + u` when in development mode to see the location of all UI extension points.
+
+## Handling button clicks
+
+There are two ways to handle the click event of an ActionBar button:
+
+1. Use the `routerLink` property to navigate to a new route when the button is clicked.
+2. Use the `onClick` property to execute a function when the button is clicked.
+
+### Using routerLink
+
+The `routerLink` property allows you to specify a route to navigate to when the button is clicked. The route can be a constant value, or it can be a function which receives the current route as well as a [`context` object](/reference/admin-ui-api/action-bar/action-bar-context) as arguments.
+
+
+<Tabs>
+<TabItem value="routerLink constant" label="routerLink constant" default>
+
+```ts title="src/plugins/invoice/ui/providers.ts"
+import { addActionBarItem } from '@vendure/admin-ui/core';
 
-### Adding onClick Actions to ActionBar buttons
+export default [
+    addActionBarItem({
+        id: 'print-invoice',
+        label: 'Print invoice',
+        locationId: 'order-detail',
+        // highlight-start
+        // The route can be a constant value...
+        routerLink: ['./extensions/order-invoices'],
+        // highlight-end
+    }),
+];
+```
+
+</TabItem>
+<TabItem value="routerLink function" label="routerLink function">
+
+```ts title="src/plugins/invoice/ui/providers.ts"
+import { addActionBarItem } from '@vendure/admin-ui/core';
+
+export default [
+    addActionBarItem({
+        id: 'print-invoice',
+        label: 'Print invoice',
+        locationId: 'order-detail',
+        // highlight-start
+        // The route can be a function
+        routerLink: (route) => {
+            const id = route.snapshot.params.id;
+            return ['./extensions/order-invoices', id];
+        },
+        // highlight-end
+    }),
+];
+```
+
+</TabItem>
+</Tabs>
+
+### Using onClick
 
 The onClick property of the addActionBarItem function allows you to define a function that will be executed when the ActionBar button is clicked. This function receives two arguments: the click event and the current context.
 
-The context object provides access to the DataService, which allows you to perform GraphQL queries and mutations, and the current route, which can be used to get parameters from the URL.
+The context object provides access to commonly-used services, which allows you to perform GraphQL queries and mutations, and the current route, which can be used to get parameters from the URL.
 
 Here's an example of how to use the onClick property to perform a GraphQL mutation when the ActionBar button is clicked:
 
-```ts
-addActionBarItem({
-    id: 'myButtonId',
-    label: 'My Button Label',
-    locationId: 'order-detail',
-    onClick: async (event, context) => {
-        const mutation = gql`
-            mutation MyMutation($orderId: ID!) {
-                myMutation(orderId: $orderId)
-            }
-        `;
+```ts title="src/plugins/invoice/ui/providers.ts"
+import gql from 'graphql-tag';
+import { firstValueFrom } from 'rxjs';
+import { addActionBarItem } from '@vendure/admin-ui/core';
 
-        try {
-            const orderId = context.route.snapshot.params.id;
-            const mutationResult = await firstValueFrom(
-                context.dataService.mutate(mutation, { orderId })
-            );
-            return mutationResult;
-        } catch (error) {
-            console.error('Error executing mutation:', error);
-        }
-    },
-    requiresPermission: 'ReadOrder',
-}),
+const mutation = gql`
+    mutation MyMutation($orderId: ID!) {
+        myMutation(orderId: $orderId)
+    }`;
+
+export default [
+    addActionBarItem({
+        id: 'myButtonId',
+        label: 'My Button Label',
+        locationId: 'order-detail',
+        // highlight-start
+        onClick: async (event, context) => {
+            try {
+                const orderId = context.route.snapshot.params.id;
+                await firstValueFrom(
+                    context.dataService.mutate(mutation, { orderId })
+                );
+            } catch (error) {
+                context.notificationService
+                    .error('Error executing mutation: ' + error.message);
+            }
+        },
+        // highlight-end
+    }),
+];
 ```
 
 In this example, clicking the ActionBar button triggers a GraphQL mutation. The `context.dataService` is utilized to execute the mutation. It can also be employed to retrieve additional information about the current order if needed. The `context.route` is used to extract the ID of the current order from the URL.
 
 The utility function `firstValueFrom` from the RxJS library is used in this example to convert the Observable returned by `context.dataService.mutate(...)` into a Promise. This conversion allows the use of the `await` keyword to pause execution until the Observable emits its first value or completes.
 
-## Adding Bulk Actions
-
-Certain list views in the Admin UI support bulk actions. There are a default set of bulk actions that are defined by the Admin UI itself (e.g. delete, assign to channels), but using the `@vendure/ui-devit` package
-you are also able to define your own bulk actions.
-
-![./bulk-actions-screenshot.webp](./bulk-actions-screenshot.webp)
-
-Use cases for bulk actions include things like:
-
-- Sending multiple products to a 3rd-party localization service
-- Exporting selected products to csv
-- Bulk-updating custom field data
-
-### Bulk Action Example
-
-A bulk action must be provided to a [ui-extension shared module](/guides/extending-the-admin-ui/introduction/#lazy-vs-shared-modules) using the [`registerBulkAction` function](/reference/admin-ui-api/bulk-actions/register-bulk-action/)
-
-```ts
-import { NgModule } from '@angular/core';
-import { ModalService, registerBulkAction, SharedModule } from '@vendure/admin-ui/core';
-
-@NgModule({
-  imports: [SharedModule],
-  providers: [
-    ProductDataTranslationService,
-
-    // Here is where we define our bulk action
-    // for sending the selected products to a 3rd-party
-    // translation API
-    registerBulkAction({
-      // This tells the Admin UI that this bulk action should be made
-      // available on the product list view.
-      location: 'product-list',
-      label: 'Send to translation service',
-      icon: 'language',
-      // Here is the logic that is executed when the bulk action menu item
-      // is clicked.
-      onClick: ({ injector, selection }) => {
-        const modalService = injector.get(ModalService);
-        const translationService = injector.get(ProductDataTranslationService);
-        modalService
-          .dialog({
-            title: `Send ${selection.length} products for translation?`,
-            buttons: [
-              { type: 'secondary', label: 'cancel' },
-              { type: 'primary', label: 'send', returnValue: true },
-            ],
-          })
-          .subscribe(response => {
-            if (response) {
-              translationService.sendForTranslation(selection.map(item => item.productId));
-            }
-          });
-      },
+## Setting visibility & disabled state
+
+Use the `buttonState` property (added in v2.1) to control the visibility and disabled state of the button. This property is a function which receives the current context as an argument and returns an Observable of the button state:
+
+```ts title="src/plugins/invoice/ui/providers.ts"
+import { map, switchMap } from 'rxjs/operators';
+import { addActionBarItem } from '@vendure/admin-ui/core'; 
+
+export default [
+    addActionBarItem({
+        id: 'print-invoice',
+        label: 'Print invoice',
+        locationId: 'order-detail',
+        buttonState: context => {
+            return context.route.data.pipe(
+                // For any of the detail pages, we can get an observable stream
+                // of the entity with the following "switchMap" function:
+                switchMap(data => data.detail.entity),
+                map((order: any) => {
+                    return {
+                        disabled: order.state === 'AddingItems',
+                        visible: true,
+                    };
+                }),
+            );
+        },
     }),
-  ],
-})
-export class MyUiExtensionModule {}
+];
 ```
 
-### Conditionally displaying bulk actions
-
-Sometimes a bulk action only makes sense in certain circumstances. For example, the "assign to channel" action only makes sense when your server has multiple channels set up.
+## Restricting access by permissions
 
-We can conditionally control the display of a bulk action with the `isVisible` function, which should return a Promise resolving to a boolean:
+You can use the `requiresPermission` property to restrict access to the button by permission. This property accepts a single permission string or an array of permission strings. If the current user does not have the required permission, the button will not be visible.
 
-```ts
-import { registerBulkAction, DataService } from '@vendure/admin-ui/core';
+```ts title="src/plugins/invoice/ui/providers.ts"
+import { addActionBarItem } from '@vendure/admin-ui/core';
 
-registerBulkAction({
-  location: 'product-list',
-  label: 'Assign to channel',
-  // Only display this action if there are multiple channels
-  isVisible: ({ injector }) => injector.get(DataService).client
-    .userStatus()
-    .mapSingle(({ userStatus }) => 1 < userStatus.channels.length)
-    .toPromise(),
-  // ...
-});
+export default [
+    addActionBarItem({
+        id: 'print-invoice',
+        label: 'Print invoice',
+        locationId: 'order-detail',
+        routerLink: ['./extensions/order-invoices'],
+        // highlight-next-line
+        requiresPermission: 'CreateInvoice',
+    }),
+];
 ```

+ 23 - 26
docs/docs/guides/extending-the-admin-ui/adding-ui-translations/index.md

@@ -2,8 +2,6 @@
 title: 'Adding UI Translations'
 ---
 
-# Adding Admin UI Translations
-
 The Vendure Admin UI is fully localizable, allowing you to:
 
 * create custom translations for your UI extensions
@@ -18,7 +16,7 @@ The Admin UI uses the [Messageformat](https://messageformat.github.io/messagefor
 
 Here is an excerpt from the `en.json` file that ships with the Admin UI:
 
-```JSON
+```JSON title="en.json"
 {
   "admin": {
     "create-new-administrator": "Create new administrator"
@@ -42,7 +40,7 @@ That is, the `{ ... }` represent variables that are passed from the application
 
 ## Adding a new language
 
-The Admin UI ships with language files only for English and Spanish as of version 0.11.0, but allows you to add support for any other language without the need to modify the package internals.
+The Admin UI ships with built-in support for many languages, but allows you to add support for any other language without the need to modify the package internals.
 
 1. **Create your translation file**
 
@@ -57,38 +55,37 @@ The Admin UI ships with language files only for English and Spanish as of versio
     ```text
     /src
     ├─ vendure-config.ts
-    ─ translations/
-        ├─ de.json
+    ─ translations/
+        └─ ms.json
     ```
     
     And the config code to register the translation file:
     
-    ```ts
-    // vendure-config.ts
+    ```ts title="src.vendure-config.ts"
     import path from 'path';
     import { VendureConfig } from '@vendure/core';
     import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
     import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
     
     export const config: VendureConfig = {
-      // ...
-      plugins: [
-        AdminUiPlugin.init({
-          port: 3002,
-          app: compileUiExtensions({
-            outputPath: path.join(__dirname, '../admin-ui'),
-            extensions: [{
-               translations: {
-                 de: path.join(__dirname, 'translations/de.json'),
-               }
-             }],
-          }),
-          adminUiConfig:{
-            defaultLanguage: LanguageCode.de,
-            availableLanguages: [LanguageCode.en, LanguageCode.de],
-          }
-        }),
-      ],
+        // ...
+        plugins: [
+            AdminUiPlugin.init({
+                port: 3002,
+                app: compileUiExtensions({
+                    outputPath: path.join(__dirname, '../admin-ui'),
+                    extensions: [{
+                        translations: {
+                            ms: path.join(__dirname, 'translations/ms.json'),
+                        }
+                    }],
+                }),
+                adminUiConfig:{
+                    defaultLanguage: LanguageCode.ms,
+                    availableLanguages: [LanguageCode.ms, LanguageCode.en],
+                }
+            }),
+        ],
     };
     ```
 

+ 61 - 62
docs/docs/guides/extending-the-admin-ui/admin-ui-theming-branding/index.md

@@ -2,64 +2,65 @@
 title: 'Admin UI Theming & Branding'
 ---
 
-# Admin UI Theming & Branding
-
 The Vendure Admin UI can be themed to your company's style and branding.
     
 ## AdminUiPlugin branding settings
 
 The `AdminUiPlugin` allows you to specify your "brand" name, and allows you to control whether to display the Vendure name and version in the UI. Specifying a brand name will also set it as the title of the Admin UI in the browser.
 
-```ts
-// vendure-config.ts
-import path from 'path';
+```ts title="src/vendure-config.ts"
 import { VendureConfig } from '@vendure/core';
 import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
 
 export const config: VendureConfig = {
-  // ...
-  plugins: [
-    AdminUiPlugin.init({
-      adminUiConfig:{
-        brand: 'My Store',
-        hideVendureBranding: false,
-        hideVersion: false,
-      }
-    }),
-  ],
+    // ...
+    plugins: [
+        AdminUiPlugin.init({
+            // ...
+            adminUiConfig:{
+                brand: 'My Store',
+                hideVendureBranding: false,
+                hideVersion: false,
+            }
+        }),
+    ],
 };
 ```
 
+:::note
+For the simple level of branding shown above, the `@vendure/ui-devkit` package is not required.
+:::
+
 ## Specifying custom logos
 
 You can replace the Vendure logos and favicon with your own brand logo:
 
 1. Install `@vendure/ui-devkit`
 2. Configure the AdminUiPlugin to compile a custom build featuring your logos:
-    ```ts
+    ```ts title="src/vendure-config.ts"
     import path from 'path';
     import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
     import { VendureConfig } from '@vendure/core';
     import { compileUiExtensions, setBranding } from '@vendure/ui-devkit/compiler';
     
     export const config: VendureConfig = {
-      // ...
-      plugins: [
-        AdminUiPlugin.init({
-          app: compileUiExtensions({
-            outputPath: path.join(__dirname, '../admin-ui'),
-            extensions: [
-              setBranding({
-                // The small logo appears in the top left of the screen  
-                smallLogoPath: path.join(__dirname, 'images/my-logo-sm.png'),
-                // The large logo is used on the login page  
-                largeLogoPath: path.join(__dirname, 'images/my-logo-lg.png'),
-                faviconPath: path.join(__dirname, 'images/my-favicon.ico'),
-              }),
-            ],
-          }),
-        }),
-      ],
+        // ...
+        plugins: [
+            AdminUiPlugin.init({
+                app: compileUiExtensions({
+                    outputPath: path.join(__dirname, '../admin-ui'),
+                    extensions: [
+                        setBranding({
+                            // The small logo appears in the top left of the screen  
+                            smallLogoPath: path.join(__dirname, 'images/my-logo-sm.png'),
+                            // The large logo is used on the login page  
+                            largeLogoPath: path.join(__dirname, 'images/my-logo-lg.png'),
+                            faviconPath: path.join(__dirname, 'images/my-favicon.ico'),
+                        }),
+                    ],
+                }),
+            }),
+        ],
     }
     ```
 
@@ -69,8 +70,7 @@ Much of the visual styling of the Admin UI can be customized by providing your o
 
 1. Install `@vendure/ui-devkit`
 2. Create a custom stylesheet which overrides one or more of the CSS custom properties used in the Admin UI:
-    ```css
-    /* my-theme.scss */
+    ```css title="my-theme.scss"
     :root {
       --clr-link-active-color: hsl(110, 65%, 57%);
       --clr-link-color: hsl(110, 65%, 57%);
@@ -80,54 +80,53 @@ Much of the visual styling of the Admin UI can be customized by providing your o
     ```
    To get an idea of which custom properties are available for theming, take a look at the source of the [Default theme](https://github.com/vendure-ecommerce/vendure/tree/master/packages/admin-ui/src/lib/static/styles/theme/default.scss) and the [Dark theme](https://github.com/vendure-ecommerce/vendure/tree/master/packages/admin-ui/src/lib/static/styles/theme/dark.scss)
 3. Set this as a globalStyles extension:   
-    ```ts
+    ```ts title="src/vendure-config.ts"
     import path from 'path';
     import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
     import { VendureConfig } from '@vendure/core';
     import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
     
     export const config: VendureConfig = {
-      // ...
-      plugins: [
-        AdminUiPlugin.init({
-          app: compileUiExtensions({
-            outputPath: path.join(__dirname, '../admin-ui'),
-            extensions: [{
-              globalStyles: path.join(__dirname, 'my-theme.scss')
-            }],
-          }),
-        }),
-      ],
+        // ...
+        plugins: [
+            AdminUiPlugin.init({
+                app: compileUiExtensions({
+                    outputPath: path.join(__dirname, '../admin-ui'),
+                    extensions: [{
+                        globalStyles: path.join(__dirname, 'my-theme.scss')
+                    }],
+                }),
+            }),
+        ],
     }
     ```
 
-Some customizable styles in [Clarity](https://clarity.design/), Admin UI's Design System framework, are controlled by Sass variables, which can be found on the [project's GitHub page](https://github.com/vmware-clarity/ng-clarity/blob/689a572344149aea90df1676eae04479795754f3/projects/angular/src/utils/_variables.clarity.scss). Similar to above, you can also provide your own values, which will override defaults set by the framework. Here's an example which changes the [height of the main header](https://github.com/vmware-clarity/ng-clarity/blob/689a572344149aea90df1676eae04479795754f3/projects/angular/src/layout/main-container/_variables.header.scss#L10):
+Some customizable styles in [Clarity](https://clarity.design/), the Admin UI's underlying UI framework, are controlled by Sass variables, which can be found on the [project's GitHub page](https://github.com/vmware-clarity/ng-clarity/blob/689a572344149aea90df1676eae04479795754f3/projects/angular/src/utils/_variables.clarity.scss). Similar to above, you can also provide your own values, which will override defaults set by the framework. Here's an example which changes the [height of the main header](https://github.com/vmware-clarity/ng-clarity/blob/689a572344149aea90df1676eae04479795754f3/projects/angular/src/layout/main-container/_variables.header.scss#L10):
 
 1. Install `@vendure/ui-devkit` if not already installed
 2. Create a custom stylesheet which overrides the target variable(s):
-    ```css
-    /* my-variables.scss */
+    ```css title="my-variables.scss"
     $clr-header-height: 4rem;
     ```
 3. Set this as a sassVariableOverrides extension:
-    ```ts
+    ```ts title="src/vendure-config.ts"
     import path from 'path';
     import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
     import { VendureConfig } from '@vendure/core';
     import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
 
     export const config: VendureConfig = {
-      // ...
-      plugins: [
-        AdminUiPlugin.init({
-          app: compileUiExtensions({
-            outputPath: path.join(__dirname, 'admin-ui'),
-            extensions: [{
-              sassVariableOverrides: path.join(__dirname, 'my-variables.scss')
-            }],
-          }),
-        }),
-      ],
+        // ...
+        plugins: [
+            AdminUiPlugin.init({
+                app: compileUiExtensions({
+                    outputPath: path.join(__dirname, 'admin-ui'),
+                    extensions: [{
+                        sassVariableOverrides: path.join(__dirname, 'my-variables.scss')
+                    }],
+                }),
+            }),
+        ],
     }
     ```
 

+ 0 - 0
docs/docs/guides/extending-the-admin-ui/add-actions-to-pages/bulk-actions-screenshot.webp → docs/docs/guides/extending-the-admin-ui/bulk-actions/bulk-actions-screenshot.webp


+ 78 - 0
docs/docs/guides/extending-the-admin-ui/bulk-actions/index.md

@@ -0,0 +1,78 @@
+---
+title: 'Bulk Actions for List Views'
+---
+
+List views in the Admin UI support bulk actions, which are performed on any selected items in the list. There are a default set of bulk actions that are defined by the Admin UI itself (e.g. delete, assign to channels), but using the `@vendure/ui-devit` package you are also able to define your own bulk actions.
+
+![./bulk-actions-screenshot.webp](./bulk-actions-screenshot.webp)
+
+Use cases for bulk actions include things like:
+
+- Sending multiple products to a 3rd-party localization service
+- Exporting selected products to csv
+- Bulk-updating custom field data
+
+### Bulk Action Example
+
+Bulk actions are declared using the [`registerBulkAction` function](/reference/admin-ui-api/bulk-actions/register-bulk-action/)
+
+```ts title="src/plugins/translation/ui/providers.ts"
+import { ModalService, registerBulkAction } from '@vendure/admin-ui/core';
+import { ProductDataTranslationService } from './product-data-translation.service';
+
+export default [
+    ProductDataTranslationService,
+    // Here is where we define our bulk action
+    // for sending the selected products to a 3rd-party
+    // translation API
+    registerBulkAction({
+        // This tells the Admin UI that this bulk action should be made
+        // available on the product list view.
+        location: 'product-list',
+        label: 'Send to translation service',
+        icon: 'language',
+        // Here is the logic that is executed when the bulk action menu item
+        // is clicked.
+        onClick: ({injector, selection}) => {
+            const modalService = injector.get(ModalService);
+            const translationService = injector.get(ProductDataTranslationService);
+            modalService
+                .dialog({
+                    title: `Send ${selection.length} products for translation?`,
+                    buttons: [
+                        {type: 'secondary', label: 'cancel'},
+                        {type: 'primary', label: 'send', returnValue: true},
+                    ],
+                })
+                .subscribe(response => {
+                    if (response) {
+                        translationService.sendForTranslation(selection.map(item => item.productId));
+                    }
+                });
+        },
+    }),
+];
+```
+
+### Conditionally displaying bulk actions
+
+Sometimes a bulk action only makes sense in certain circumstances. For example, the "assign to channel" action only makes sense when your server has multiple channels set up.
+
+We can conditionally control the display of a bulk action with the `isVisible` function, which should return a Promise resolving to a boolean:
+
+```ts title="src/plugins/my-plugin/ui/providers.ts"
+import { registerBulkAction, DataService } from '@vendure/admin-ui/core';
+
+export default [
+    registerBulkAction({
+        location: 'product-list',
+        label: 'Assign to channel',
+        // Only display this action if there are multiple channels
+        isVisible: ({ injector }) => injector.get(DataService).client
+            .userStatus()
+            .mapSingle(({ userStatus }) => 1 < userStatus.channels.length)
+            .toPromise(),
+        // ...
+    }),
+];
+```

+ 148 - 39
docs/docs/guides/extending-the-admin-ui/custom-detail-components/index.md

@@ -3,62 +3,171 @@ title: 'Custom Detail Components'
 weight: 6
 ---
 
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
 # Custom Detail Components
 
-Most of the detail views can be extended with custom Angular components using the [registerCustomDetailComponent function](/reference/admin-ui-api/custom-detail-components/register-custom-detail-component/).
+Detail views can be extended with custom Angular or React components using the [`registerCustomDetailComponent`](/reference/admin-ui-api/custom-detail-components/register-custom-detail-component/) and [`registerReactCustomDetailComponent`](/reference/admin-ui-api/react-extensions/register-react-custom-detail-component) functions.
 
 Any components registered in this way will appear below the main detail form.
 
-Let's imagine that your project has an external content management system (CMS) which is used to store additional details about products. You might want to display some of this information in the product detail page.
+:::info
+The valid locations for embedding custom detail components can be found in the [CustomDetailComponentLocationId docs](/reference/admin-ui-api/custom-detail-components/custom-detail-component-location-id).
+:::
+
+Let's imagine that your project has an external content management system (CMS) which is used to store additional details about products. You might want to display some of this information in the product detail page. We will demonstrate the same component in both Angular and React.
 
-```ts
-import { NgModule, Component, OnInit } from '@angular/core';
+## Angular
+
+```ts title="src/plugins/cms/ui/components/product-info/product-info.component.ts"
+import { Component, OnInit } from '@angular/core';
 import { switchMap } from 'rxjs';
 import { FormGroup } from '@angular/forms';
-import { CustomFieldConfig } from '@vendure/common/lib/generated-types';
-import {
-    DataService,
-    SharedModule,
-    CustomDetailComponent,
-    registerCustomDetailComponent,
-    GetProductWithVariants
-} from '@vendure/admin-ui/core';
+import { DataService, CustomDetailComponent, SharedModule } from '@vendure/admin-ui/core';
+import { CmsDataService } from '../../providers/cms-data.service';
 
 @Component({
-  template: `
-    {{ extraInfo$ | async | json }}
-  `,
+    template: `
+        <vdr-card title="CMS Info">
+            <pre>{{ extraInfo$ | async | json }}</pre>
+        </vdr-card>`,
+    standalone: true,
+    providers: [CmsDataService],
+    imports: [SharedModule],
 })
 export class ProductInfoComponent implements CustomDetailComponent, OnInit {
-  // These two properties are provided by Vendure and will vary
-  // depending on the particular detail page you are embedding this
-  // component into.
-  entity$: Observable<GetProductWithVariants.Product>
-  detailForm: FormGroup;
-  
-  extraInfo$: Observable<any>;
-  
-  constructor(private cmsDataService: CmsDataService) {}
-  
-  ngOnInit() {
-    this.extraInfo$ = this.entity$.pipe(
-      switchMap(entity => this.cmsDataService.getDataFor(entity.id))
-    );
-  }
+    // These two properties are provided by Vendure and will vary
+    // depending on the particular detail page you are embedding this
+    // component into. In this case, it will be a "product" entity.
+    entity$: Observable<any>
+    detailForm: FormGroup;
+
+    extraInfo$: Observable<any>;
+
+    constructor(private cmsDataService: CmsDataService) {
+    }
+
+    ngOnInit() {
+        this.extraInfo$ = this.entity$.pipe(
+            switchMap(entity => this.cmsDataService.getDataFor(entity.id))
+        );
+    }
 }
+```
+
+```ts title="src/plugins/cms/ui/providers.ts"
+import { registerCustomDetailComponent } from '@vendure/admin-ui/core';
+import { ProductInfoComponent } from './components/product-info/product-info.component';
 
-@NgModule({
-  imports: [SharedModule],
-  declarations: [ProductInfoComponent],
-  providers: [
+export default [
     registerCustomDetailComponent({
-      locationId: 'product-detail',
-      component: ProductInfoComponent,
+        locationId: 'product-detail',
+        component: ProductInfoComponent,
     }),
-  ]
+];
+```
+
+## React
+
+When using React, we can use the [`useDetailComponentData` hook](/reference/admin-ui-api/react-hooks/use-detail-component-data) to access the same data (the entity and the form) as the Angular example above.
+
+```tsx title="src/plugins/cms/ui/components/ProductInfo.tsx"
+import React, { useEffect, useState } from 'react';
+import { Card, useDetailComponentData, useInjector } from '@vendure/admin-ui/react';
+import { CmsDataService } from '../providers/cms-data.service';
+
+export function ProductInfo() {
+    // The "entity" will vary depending on which detail page this component
+    // is embedded in. In this case, it will be a "product" entity.
+    const { entity, detailForm } = useDetailComponentData();
+    const cmsDataService = useInjector(CmsDataService);
+    const [extraInfo, setExtraInfo] = useState<any>();
+    
+    useEffect(() => {
+        const subscription = cmsDataService.getDataFor(entity.id).subscribe(data => {
+            setExtraInfo(data);
+        });
+        return () => subscription.unsubscribe();
+    }, [entity.id]);
+    
+    return (
+        <Card title="CMS Info">
+            <pre>{JSON.stringify(extraInfo, null, 2)}</pre>
+        </Card>
+    );
+}
+```
+
+```ts title="src/plugins/cms/ui/providers.ts"
+import { registerReactCustomDetailComponent } from '@vendure/admin-ui/react';
+import { ProductInfo } from './components/ProductInfo';
+
+export default [
+    registerReactCustomDetailComponent({
+        locationId: 'product-detail',
+        component: ProductInfo,
+    }),
+];
+```
+
+## Manipulating the detail form
+
+The `detailForm` property is an instance of the Angular [FormGroup](https://angular.io/api/forms/FormGroup) which can be used to manipulate the form fields, set the validity of the form, mark the form as dirty etc. For example, we could add a button which updates the `description` field of the product:
+
+<Tabs>
+<TabItem value="Angular" label="Angular" default>
+
+```ts title="src/plugins/cms/ui/components/product-info/product-info.component.ts"
+import { Component, OnInit } from '@angular/core';
+import { FormGroup } from '@angular/forms';
+import { CustomDetailComponent } from '@vendure/admin-ui/core';
+
+@Component({
+    template: `<button class="button secondary" (click)="updateDescription()">Update description</button>`,
+    standalone: true,
 })
-export class SharedExtensionModule {}
+export class ProductInfoComponent implements CustomDetailComponent, OnInit {
+    entity$: Observable<any>
+    detailForm: FormGroup;
+    
+    // highlight-start
+    updateDescription() {
+        const descriptionControl = this.detailForm.get('description');
+        if (descriptionControl) {
+            descriptionControl.setValue('New description');
+            descriptionControl.markAsDirty();
+        }        
+    }
+    // highlight-end
+}
 ```
 
-The valid locations for embedding custom detail components can be found in the [CustomDetailComponentLocationId docs](/reference/admin-ui-api/custom-detail-components/custom-detail-component-location-id).
+</TabItem>
+<TabItem value="React" label="React">
+
+```tsx title="src/plugins/cms/ui/components/ProductInfo.tsx"
+import React from 'react';
+import { Card, useDetailComponentData } from '@vendure/admin-ui/react';
+
+export function ProductInfo() {
+    const { detailForm } = useDetailComponentData();
+
+    // highlight-start
+    const updateDescription = () => {
+        const descriptionControl = detailForm.get('description');
+        if (descriptionControl) {
+            descriptionControl.setValue('New description');
+            descriptionControl.markAsDirty();
+        }
+    };
+    // highlight-end
+
+    return (
+        <button className="button secondary" onClick={updateDescription}>Update description</button>
+    );
+}
+```
 
+</TabItem>
+</Tabs>

+ 163 - 89
docs/docs/guides/extending-the-admin-ui/custom-form-inputs/index.md

@@ -1,21 +1,26 @@
 ---
 title: 'Custom Form Inputs'
-weight: 5
 ---
 
-# Custom Form Inputs
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
 
-Another way to extend the Admin UI app is to define custom form input components for manipulating any [Custom Fields](/guides/developer-guide/custom-fields/) you have defined on your entities as well as [configurable args](/reference/typescript-api/configurable-operation-def/config-args/) used by custom [Configurable Operations](/guides/developer-guide/strategies-configurable-operations/#configurable-operations).
+You can define custom Angular or React components which can be used to render [Custom Fields](/guides/developer-guide/custom-fields/) you have defined on your entities as well as [configurable args](/reference/typescript-api/configurable-operation-def/config-args/) used by custom [Configurable Operations](/guides/developer-guide/strategies-configurable-operations/#configurable-operations).
 
 ## For Custom Fields
 
 Let's say you define a custom "intensity" field on the Product entity:
 
 ```ts title="src/vendure-config.ts"
-customFields: {
-    Product: [
-        {name: 'intensity', type: 'int', min: 0, max: 100, defaultValue: 0},
-    ],
+import { VendureConfig } from '@vendure/core';
+
+export const config: VendureConfig = {
+    // ...
+    customFields: {
+        Product: [
+            { name: 'intensity', type: 'int', min: 0, max: 100, defaultValue: 0 },
+        ],
+    },
 }
 ```
 
@@ -23,12 +28,21 @@ By default, the "intensity" field will be displayed as a number input:
 
 ![./ui-extensions-custom-field-default.webp](./ui-extensions-custom-field-default.webp)
 
-But let's say we want to display a range slider instead. Here's how we can do this using our shared extension module combined with the [registerFormInputComponent function](/reference/admin-ui-api/custom-input-components/register-form-input-component/):
+But let's say we want to display a **range slider** instead.
+
+### 1. Define a component
 
-```ts title="src/ui-extensions/slider-form-input.component.ts"
-import { NgModule, Component } from '@angular/core';
+First we need to define a new Angular or React component to render the slider:
+
+<Tabs>
+<TabItem value="Angular" label="Angular" default>
+
+Angular components will have the `readonly`, `config` and `formControl` properties populated automatically.
+
+```ts title="src/plugins/common/ui/components/slider-form-input/slider-form-input.component.ts"
+import { Component } from '@angular/core';
 import { FormControl } from '@angular/forms';
-import { IntCustomFieldConfig, SharedModule, FormInputComponent, registerFormInputComponent } from '@vendure/admin-ui/core';
+import { IntCustomFieldConfig, SharedModule, FormInputComponent } from '@vendure/admin-ui/core';
 
 @Component({
     template: `
@@ -39,43 +53,114 @@ import { IntCustomFieldConfig, SharedModule, FormInputComponent, registerFormInp
           [formControl]="formControl" />
       {{ formControl.value }}
   `,
+    standalone: true,
+    imports: [SharedModule],
 })
-export class SliderControl implements FormInputComponent<IntCustomFieldConfig> {
+export class SliderControlComponent implements FormInputComponent<IntCustomFieldConfig> {
     readonly: boolean;
     config: IntCustomFieldConfig;
     formControl: FormControl;
 }
+```
 
-@NgModule({
-    imports: [SharedModule],
-    declarations: [SliderControl],
-    providers: [
-        registerFormInputComponent('slider-form-input', SliderControl),
-    ]
-})
-export class SharedExtensionModule {
-}
+</TabItem>
+<TabItem value="React" label="React">
+
+React components can use the [`useFormControl`](/reference/admin-ui-api/react-hooks/use-form-control) hook to access the form control and set its value. The 
+component will also receive `config` and `readonly` data as props. 
+
+```tsx title="src/plugins/common/ui/components/SliderFormInput.tsx"
+import React from 'react';
+import { useFormControl, ReactFormInputOptions, useInjector } from '@vendure/admin-ui/react';
+
+export function SliderFormInput({ readonly, config }: ReactFormInputOptions) {
+    const { value, setFormValue } = useFormControl();
+    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        const val = +e.target.value;
+        setFormValue(val);
+    };
+    return (
+        <>
+            <input
+                type="range"
+                readOnly={readonly}
+                min={config.min || 0}
+                max={config.max || 100}
+                value={value}
+                onChange={handleChange}
+            />
+            {value}
+        </>
+    );
+};
+```
+
+</TabItem>
+</Tabs>
+
+### 2. Register the component
+
+Next we will register this component in our `providers.ts` file and give it a unique ID, `'slider-form-input'`:
+
+<Tabs>
+<TabItem value="Angular" label="Angular" default>
+
+```ts title="src/plugins/common/ui/providers.ts"
+import { registerFormInputComponent } from '@vendure/admin-ui/core';
+import { SliderControlComponent } from './components/slider-form-input/slider-form-input.component';
+
+export default [
+    registerFormInputComponent('slider-form-input', SliderControlComponent),
+];
+```
+
+</TabItem>
+<TabItem value="React" label="React">
+
+```ts title="src/plugins/common/ui/providers.ts"
+import { registerReactFormInputComponent } from '@vendure/admin-ui/react';
+import { SliderControl } from './components/SliderFormInput';
+
+export default [
+    registerReactFormInputComponent('slider-form-input', SliderFormInput),
+];
 ```
 
-The `SharedExtensionModule` is then passed to the `compileUiExtensions()` function as described in the [UI Extensions With Angular guide](/guides/extending-the-admin-ui/using-angular/#4-pass-the-extension-to-the-compileuiextensions-function):
+</TabItem>
+</Tabs>
+
+### 3. Register the providers
+
+The `providers.ts` is then passed to the `compileUiExtensions()` function as described in the [UI Extensions Getting Started guide](/guides/extending-the-admin-ui/getting-started/):
 
 ```ts title="src/vendure-config.ts"
-AdminUiPlugin.init({
-    port: 5001,
-    app: compileUiExtensions({
-        outputPath: path.join(__dirname, '../admin-ui'),
-        extensions: [{
-            extensionPath: path.join(__dirname, 'ui-extensions'),
-            ngModules: [{
-                type: 'shared',
-                ngModuleFileName: 'shared.module.ts',
-                ngModuleName: 'SharedExtensionModule',
-            }],
-        }],
-    }),
-})
+import * as path from 'path';
+import { VendureConfig } from '@vendure/core';
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        AdminUiPlugin.init({
+            port: 3302,
+            app: compileUiExtensions({
+                outputPath: path.join(__dirname, '../admin-ui'),
+                extensions: [{
+                    id: 'common',
+                    // highlight-start
+                    extensionPath: path.join(__dirname, 'plugins/common/ui'),
+                    providers: ['providers.ts'],
+                    // highlight-end
+                }],
+            }),
+        }),
+    ],
+};
 ```
 
+### 4. Update the custom field config
+
 Once registered, this new slider input can be used in our custom field config:
 
 ```ts title="src/vendure-config.ts"
@@ -89,6 +174,7 @@ customFields: {
     ],
 }
 ```
+
 As we can see, adding the `ui` property to the custom field config allows us to specify our custom slider component.
 The component id _'slider-form-input'_ **must match** the string passed as the first argument to `registerFormInputComponent()`.
 
@@ -101,78 +187,66 @@ Re-compiling the Admin UI will result in our SliderControl now being used for th
 
 ![./ui-extensions-custom-field-slider.webp](./ui-extensions-custom-field-slider.webp)
 
-To recap the steps involved:
-
-1. Create an Angular Component which implements the `FormInputComponent` interface.
-2. Add this component to your shared extension module's `declarations` array.
-3. Use `registerFormInputComponent()` to register your component for the given entity & custom field name.
-4. Specify this component's ID in your custom field config.
-
-### Custom Field Controls for Relations
+## Custom Field Controls for Relations
 
 If you have a custom field of the `relation` type (which allows you to relate entities with one another), you can also define custom field controls for them. The basic mechanism is exactly the same as with primitive custom field types (i.e. `string`, `int` etc.), but there are a couple of important points to know:
 
 1. The value of the `formControl` will be the _related entity object_ rather than an id. The Admin UI will internally take care of converting the entity object into an ID when performing the create/update mutation.
 2. Your control will most likely need to fetch data in order to display a list of selections for the user.
 
-Here is a simple example taken from the [real-world-vendure](https://github.com/vendure-ecommerce/real-world-vendure/blob/master/src/plugins/reviews/ui/components/featured-review-selector/featured-review-selector.component.ts) repo:
+Here's an example of a custom field control for a `relation` field which relates a Product to a custom `ProductReview` entity:
 
 ```ts title="src/plugins/reviews/ui/components/relation-review-input/relation-review-input.component.ts"
-import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { FormControl } from '@angular/forms';
 import { ActivatedRoute } from '@angular/router';
 import { RelationCustomFieldConfig } from '@vendure/common/lib/generated-types';
-import { CustomFieldControl, DataService } from '@vendure/admin-ui/core';
+import { CustomFieldControl, DataService, SharedModule } from '@vendure/admin-ui/core';
 import { Observable } from 'rxjs';
 import { switchMap } from 'rxjs/operators';
 
 import { GET_REVIEWS_FOR_PRODUCT } from '../product-reviews-list/product-reviews-list.graphql';
-import { GetReviewForProduct, ProductReviewFragment } from '../../generated-types';
 
 @Component({
-  selector: 'relation-review-input',
-  template: `
-    <div *ngIf="formControl.value as review">
-      <vdr-chip>{{ review.rating }} / 5</vdr-chip>
-      {{ review.summary }}
-      <a [routerLink]="['/extensions', 'product-reviews', review.id]">
-        <clr-icon shape="link"></clr-icon>
-      </a>
-    </div>
-    <select appendTo="body" [formControl]="formControl">
-      <option [ngValue]="null">Select a review...</option>
-      <option *ngFor="let item of reviews$ | async" [ngValue]="item">
-        <b>{{ item.summary }}</b>
-        {{ item.rating }} / 5
-      </option>
-    </select>
-  `,
-  changeDetection: ChangeDetectionStrategy.OnPush,
+    selector: 'relation-review-input',
+    template: `
+        <div *ngIf="formControl.value as review">
+            <vdr-chip>{{ review.rating }} / 5</vdr-chip>
+            {{ review.summary }}
+            <a [routerLink]="['/extensions', 'product-reviews', review.id]">
+                <clr-icon shape="link"></clr-icon>
+            </a>
+        </div>
+        <select [formControl]="formControl">
+            <option [ngValue]="null">Select a review...</option>
+            <option *ngFor="let item of reviews$ | async" [ngValue]="item">
+                <b>{{ item.summary }}</b>
+                {{ item.rating }} / 5
+            </option>
+        </select>
+    `,
+    standalone: true,
+    imports: [SharedModule],
 })
 export class RelationReviewInputComponent implements OnInit, FormInputComponent<RelationCustomFieldConfig> {
-  readonly: boolean;
-  formControl: FormControl;
-  config: RelationCustomFieldConfig;
-
-  reviews$: Observable<ProductReviewFragment[]>;
-
-  constructor(private dataService: DataService, private route: ActivatedRoute) {}
-
-  ngOnInit() {
-    this.reviews$ = this.route.data.pipe(
-      switchMap(data => data.entity),
-      switchMap((product: any) => {
-        return this.dataService
-          .query<GetReviewForProduct.Query, GetReviewForProduct.Variables>(
-            GET_REVIEWS_FOR_PRODUCT,
-            {
-              productId: product.id,
-            },
-          )
-          .mapSingle(({ product }) => product?.reviews.items ?? []);
-      }),
-    );
-  }
+    readonly: boolean;
+    formControl: FormControl;
+    config: RelationCustomFieldConfig;
+
+    reviews$: Observable<any[]>;
+
+    constructor(private dataService: DataService, private route: ActivatedRoute) {}
+
+    ngOnInit() {
+        this.reviews$ = this.route.data.pipe(
+            switchMap(data => data.entity),
+            switchMap((product: any) => {
+                return this.dataService
+                    .query(GET_REVIEWS_FOR_PRODUCT, { productId: product.id })
+                    .mapSingle(({ product }) => product?.reviews.items ?? []);
+            }),
+        );
+    }
 }
 ```
 

+ 30 - 24
docs/docs/guides/extending-the-admin-ui/custom-timeline-components/index.md

@@ -1,21 +1,22 @@
 ---
 title: 'Custom History Timeline Components'
-weight: 6
 ---
 
-# Custom History Timeline Components
-
 The Order & Customer detail pages feature a timeline of history entries. Since v1.9.0 it is possible to define custom history entry types - see the [HistoryService docs](/reference/typescript-api/services/history-service/) for an example.
 
 You can also define a custom Angular component to render any timeline entry using the [registerHistoryEntryComponent function](/reference/admin-ui-api/custom-history-entry-components/register-history-entry-component/).
 
 ![./timeline-entry.webp](./timeline-entry.webp)
 
+:::note
+Currently it is only possible to define new tabs using Angular components.
+:::
+
 Following the example used in the HistoryService docs, we can define a component to render the tax ID verification
 entry in our Customer timeline:
 
 ```ts title="src/plugins/tax-id/ui/components/tax-id-history-entry/tax-id-history-entry.component.ts"
-import { Component, NgModule } from '@angular/core';
+import { Component } from '@angular/core';
 import {
     CustomerFragment,
     CustomerHistoryEntryComponent,
@@ -23,19 +24,22 @@ import {
     SharedModule,
     TimelineDisplayType,
     TimelineHistoryEntry,
+    SharedModule,
 } from '@vendure/admin-ui/core';
 
 @Component({
     selector: 'tax-id-verification-component',
     template: `
-    <div *ngIf="entry.data.valid">
-      Tax ID <strong>{{ entry.data.taxId }}</strong> was verified
-      <vdr-history-entry-detail *ngIf="entry.data">
-        <vdr-object-tree [value]="entry.data"></vdr-object-tree>
-      </vdr-history-entry-detail>
-    </div>
-    <div *ngIf="entry.data.valid">Tax ID {{ entry.data.taxId }} could not be verified</div>
-  `,
+        <div *ngIf="entry.data.valid">
+            Tax ID <strong>{{ entry.data.taxId }}</strong> was verified
+            <vdr-history-entry-detail *ngIf="entry.data">
+                <vdr-object-tree [value]="entry.data"></vdr-object-tree>
+            </vdr-history-entry-detail>
+        </div>
+        <div *ngIf="entry.data.valid">Tax ID {{ entry.data.taxId }} could not be verified</div>
+    `,
+    standalone: true,
+    imports: [SharedModule],
 })
 class TaxIdHistoryEntryComponent implements CustomerHistoryEntryComponent {
     entry: TimelineHistoryEntry;
@@ -57,19 +61,21 @@ class TaxIdHistoryEntryComponent implements CustomerHistoryEntryComponent {
         return entry.data.valid ? 'check-circle' : 'exclamation-circle';
     }
 }
+```
 
-@NgModule({
-    imports: [SharedModule],
-    declarations: [TaxIdHistoryEntryComponent],
-    providers: [
-        registerHistoryEntryComponent({
-            type: 'CUSTOMER_TAX_ID_VERIFICATION',
-            component: TaxIdHistoryEntryComponent,
-        }),
-    ]
-})
-export class SharedExtensionModule {
-}
+We can then register this component in the `providers.ts` file:
+
+```ts title="src/plugins/tax-id/ui/providers.ts"
+import { registerHistoryEntryComponent } from '@vendure/admin-ui/core';
+import { TaxIdHistoryEntryComponent } from './components/tax-id-history-entry/tax-id-history-entry.component';
+
+export default [
+    registerHistoryEntryComponent({
+        type: 'CUSTOMER_TAX_ID_VERIFICATION',
+        component: TaxIdHistoryEntryComponent,
+    }),
+];
 ```
 
+Then we need to add the `providers.ts` file to the `uiExtensions` array as described in the [UI Extensions Getting Started guide](/guides/extending-the-admin-ui/getting-started/).
 

+ 82 - 108
docs/docs/guides/extending-the-admin-ui/dashboard-widgets/index.md

@@ -2,14 +2,16 @@
 title: 'Dashboard Widgets'
 ---
 
-# Dashboard Widgets
-
 Dashboard widgets are components which can be added to the Admin UI dashboard. These widgets are useful for displaying information which is commonly required by administrations, such as sales summaries, lists of incomplete orders, notifications, etc.
 
 The Admin UI comes with a handful of widgets, and you can also create your own widgets.
 
 ![Dashboard widgets](./dashboard-widgets.webp)
 
+:::note
+Currently it is only possible to define new widgets using Angular components.
+:::
+
 ## Example: Reviews Widget
 
 In this example we will use a hypothetical reviews plugin, which allows customers to write product reviews. These reviews then get approved by an Administrator before being displayed in the storefront.
@@ -21,55 +23,49 @@ To notify administrators about new reviews that need approval, we'll create a da
 A dashboard widget is an Angular component. This example features a simplified UI, just to illustrate the overall structure:
 
 ```ts title="src/plugins/reviews/ui/components/reviews-widget/reviews-widget.component.ts"
-import { Component, NgModule, OnInit } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { DataService, SharedModule } from '@vendure/admin-ui/core';
 import { Observable } from 'rxjs';
 
 @Component({
-  selector: 'reviews-widget',
-  template: `
-    <ul>
-      <li *ngFor="let review of pendingReviews$ | async">
-        <a [routerLink]="['/extensions', 'product-reviews', review.id]">{{ review.summary }}</a>
-        <span class="rating">{{ review.rating }} / 5</span>
-      </li>
-    </ul>
-  `,
+    selector: 'reviews-widget',
+    template: `
+        <ul>
+            <li *ngFor="let review of pendingReviews$ | async">
+                <a [routerLink]="['/extensions', 'product-reviews', review.id]">{{ review.summary }}</a>
+                <span class="rating">{{ review.rating }} / 5</span>
+            </li>
+        </ul>
+    `,
+    standalone: true,
+    imports: [SharedModule],
 })
 export class ReviewsWidgetComponent implements OnInit {
-  pendingReviews$: Observable<GetAllReviews.Items[]>;
-
-  constructor(private dataService: DataService) {}
-
-  ngOnInit() {
-    this.pendingReviews$ = this.dataService.query(gql`
-      query GetAllReviews($options: ProductReviewListOptions) {
-        productReviews(options: $options) {
-          items {
-            id
-            createdAt
-            authorName
-            summary
-            rating
-          }
-        }
-      }`, {
-        options: {
-          filter: {
-              state: { eq: 'new' },
-          },
-          take: 10,
-        },
-      })
-      .mapStream(data => data.productReviews.items);
-  }
+    pendingReviews$: Observable<any[]>;
+
+    constructor(private dataService: DataService) {}
+
+    ngOnInit() {
+        this.pendingReviews$ = this.dataService.query(gql`
+            query GetAllReviews($options: ProductReviewListOptions) {
+                productReviews(options: $options) {
+                    items {
+                        id
+                        createdAt
+                        authorName
+                        summary
+                        rating
+                    }
+                }
+            }`, {
+                options: {
+                    filter: { state: { eq: 'new' } },
+                    take: 10,
+                },
+            })
+            .mapStream(data => data.productReviews.items);
+    }
 }
-
-@NgModule({
-    imports: [SharedModule],
-    declarations: [ReviewsWidgetComponent],
-})
-export class ReviewsWidgetModule {}
 ```
 
 :::note
@@ -78,32 +74,24 @@ We also need to define an `NgModule` for this component. This is because we will
 
 ### Register the widget
 
-Our widget now needs to be registered as part of a [shared module](/guides/extending-the-admin-ui/introduction#lazy-vs-shared-modules):
+Our widget now needs to be registered in our [providers file](/guides/extending-the-admin-ui/getting-started/#providers):
 
-```ts title="src/plugins/reviews/ui/shared-ui-extension.module.ts"
-import { NgModule } from '@angular/core';
+```ts title="src/plugins/reviews/ui/providers.ts"
 import { registerDashboardWidget } from '@vendure/admin-ui/core';
-import { reviewPermission } from '../constants';
-
-@NgModule({
-    imports: [],
-    declarations: [],
-    providers: [
-        // highlight-start
-        registerDashboardWidget('reviews', {
-            title: 'Latest reviews',
-            supportedWidths: [4, 6, 8, 12],
-            requiresPermissions: [reviewPermission.Read],
-            loadComponent: () =>
-                import('./reviews-widget/reviews-widget.component').then(
-                    m => m.ReviewsWidgetComponent,
-                ),
-        }),
-        // highlight-end
-    ],
-})
-export class MySharedUiExtensionModule {
-}
+
+export default [
+    // highlight-start
+    registerDashboardWidget('reviews', {
+        title: 'Latest reviews',
+        supportedWidths: [4, 6, 8, 12],
+        requiresPermissions: ['ReadReview'],
+        loadComponent: () =>
+            import('./reviews-widget/reviews-widget.component').then(
+                m => m.ReviewsWidgetComponent,
+            ),
+    }),
+    // highlight-end
+];
 ```
 
 * **`title`** This is the title of the widget that will be displayed in the widget header.
@@ -117,29 +105,22 @@ Once registered, the reviews widget will be available to select by administrator
 
 While administrators can customize which widgets they want to display on the dashboard, and the layout of those widgets, you can also set a default layout:
 
-```ts title="src/plugins/reviews/ui/shared-ui-extension.module.ts"
-import { NgModule } from '@angular/core';
+```ts title="src/plugins/reviews/ui/providers.ts"
 import { registerDashboardWidget, setDashboardWidgetLayout } from '@vendure/admin-ui/core';
-import { reviewPermission } from '../constants';
-
-@NgModule({
-    imports: [],
-    declarations: [],
-    providers: [
-        registerDashboardWidget('reviews', {
-            // omitted for brevity
-        }),
-        // highlight-start
-        setDashboardWidgetLayout([
-            {id: 'welcome', width: 12},
-            {id: 'orderSummary', width: 4},
-            {id: 'latestOrders', width: 8},
-            {id: 'reviews', width: 6},
-        ]),
-        // highlight-end
-    ],
-})
-export class MySharedUiExtensionModule {}
+
+export default [
+    registerDashboardWidget('reviews', {
+        // omitted for brevity
+    }),
+    // highlight-start
+    setDashboardWidgetLayout([
+        { id: 'welcome', width: 12 },
+        { id: 'orderSummary', width: 4 },
+        { id: 'latestOrders', width: 8 },
+        { id: 'reviews', width: 6 },
+    ]),
+    // highlight-end
+];
 ```
 
 This defines the order of widgets with their default widths. The actual layout in terms of rows and columns will be calculated at run-time based on what will fit on each row.
@@ -150,26 +131,19 @@ The Admin UI comes with a set of default widgets, such as the order summary and
 
 Sometimes you may wish to alter the permissions settings of the default widgets to better control which of your Administrators is able to access it.
 
-For example, the "order summary" widget has a default permission requirement of "ReadOrder". If you want to limit the availability to e.g. the SuperAdmin role, you can do so
-by overriding the definition like this:
+For example, the "order summary" widget has a default permission requirement of "ReadOrder". If you want to limit the availability to e.g. the SuperAdmin role, you can do so by overriding the definition like this:
 
-```ts title="src/plugins/reviews/ui/shared-ui-extension.module.ts"
-import { NgModule } from '@angular/core';
+```ts title="src/plugins/reviews/ui/providers.ts"
 import { registerDashboardWidget } from '@vendure/admin-ui/core';
 import { OrderSummaryWidgetComponent } from '@vendure/admin-ui/dashboard';
 
-@NgModule({
-    imports: [],
-    declarations: [],
-    providers: [
-        // highlight-start
-        registerDashboardWidget('orderSummary', {
-            title: 'dashboard.orders-summary',
-            loadComponent: () => OrderSummaryWidgetComponent,
-            requiresPermissions: ['SuperAdmin'],
-        }),
-        // highlight-end
-    ],
-})
-export class MySharedUiExtensionModule {}
+export default [
+    // highlight-start
+    registerDashboardWidget('orderSummary', {
+        title: 'dashboard.orders-summary',
+        loadComponent: () => OrderSummaryWidgetComponent,
+        requiresPermissions: ['SuperAdmin'],
+    }),
+    // highlight-end
+];
 ```

+ 552 - 0
docs/docs/guides/extending-the-admin-ui/getting-started/index.md

@@ -0,0 +1,552 @@
+---
+title: 'Getting Started'
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+When creating a plugin, you may wish to extend the Admin UI in order to expose a graphical interface to the plugin's functionality, or to add new functionality to the Admin UI itself. The UI can be extended with custom components written in [Angular](https://angular.io/) or [React](https://react.dev/).
+
+:::note
+The APIs described in this section were introduced in Vendure v2.1.0. For the legacy APIs, see the [Legacy API section](#legacy-api--v210).
+:::
+
+UI extensions fall into two categories:
+
+-   **Providers**: these are used to add new functionality to the Admin UI, such as adding buttons to pages, adding new nav menu items, or defining custom form inputs. They would typically be defined in a file named `providers.ts`.
+-   **Routes**: these are used to define new pages in the Admin UI, such as a new page for managing a custom entity. They would typically be defined in a file named `routes.ts`.
+
+To extend the Admin UI, install the [`@vendure/ui-devkit` package](https://www.npmjs.com/package/@vendure/ui-devkit) as a dev dependency:
+
+```bash
+yarn add --save-dev @vendure/ui-devkit
+
+# or
+
+npm install --save-dev @vendure/ui-devkit
+```
+
+You can then create the following folder structure to hold your UI extensions:
+
+```
+src
+├── vendure-config.ts
+└── plugins
+    └── my-plugin
+        └── ui
+            ├── routes.ts
+            └── providers.ts
+```
+
+Let's add a simple UI extension that adds a new button to the "order list" page. We'll leave the routes file empty for now.
+
+```ts title="src/plugins/my-plugin/ui/providers.ts"
+import { addActionBarItem } from '@vendure/admin-ui/core';
+
+export default [
+    addActionBarItem({
+        id: 'test-button',
+        label: 'Test Button',
+        locationId: 'order-list',
+    }),
+];
+```
+
+You can then use the `compileUiExtensions` function to compile your UI extensions and add them to the Admin UI app bundle.
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+// highlight-next-line
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+import * as path from 'path';
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        AdminUiPlugin.init({
+            port: 3002,
+            // highlight-start
+            app: compileUiExtensions({
+                outputPath: path.join(__dirname, '../admin-ui'),
+                extensions: [
+                    {
+                        id: 'test-extension',
+                        extensionPath: path.join(__dirname, 'plugins/my-plugin/ui'),
+                        providers: ['providers.ts'],
+                    },
+                ],
+                devMode: true,
+            }),
+            // highlight-end
+            adminUiConfig: {
+                apiPort: 3000,
+            },
+        }),
+    ],
+};
+```
+
+Now when you start the server, the following will happen:
+
+1. A new folder called `admin-ui` will be created in the root of your project (as specified by the `outputPath` option). This is a temporary directory (it should not be added to version control) which will contain the source files of your custom Admin UI app.
+2. During bootstrap, the `compileUiExtensions` function will be called, which will compile the Admin UI app and serve it in development mode. The dev server will be listening on port `4200` but this port will also be proxied to port `3000` (as specified by `apiOptions.port`). This step can take up to a minute or two, depending on the speed of your machine.
+
+:::caution
+**Note:** the TypeScript source files of your UI extensions **must not** be compiled by your regular TypeScript build task. This is because they will instead be compiled by the Angular compiler when you run `compileUiExtensions()`.
+
+You can exclude them in your main `tsconfig.json` by adding a line to the "exclude" array:
+
+```json title="tsconfig.json"
+{
+    "exclude": [
+        "node_modules",
+        "migration.ts",
+        // highlight-start
+        "src/plugins/**/ui/*",
+        "admin-ui"
+        // highlight-end
+    ]
+}
+```
+
+:::
+
+:::info
+**How It Works:** The Admin UI is an Angular application, and to generate a custom UI including your extensions, it is internally using the powerful [Angular CLI](https://angular.io/cli) to compile the app into an optimized bundle, including code-splitting and lazy-loading any routes which you define.
+:::
+
+## Providers
+
+Your `providers.ts` file exports an array of objects known as "providers" in Angular terminology. These providers are passed to the application on startup to configure new functionality. With providers you can:
+
+-   Add new buttons to the action bar of existing pages (the top bar containing the primary actions for a page) using [`addActionBarItem`](/reference/admin-ui-api/action-bar/add-action-bar-item).
+-   Add new menu items to the left-hand navigation menu using [`addNavMenuItem`](/reference/admin-ui-api/nav-menu/add-nav-menu-item) and [`addNavMenuSection`](/reference/admin-ui-api/nav-menu/add-nav-menu-section).
+-   Define bulk actions for list views using [`registerBulkAction`](/reference/admin-ui-api/bulk-actions/register-bulk-action).
+-   Define arbitrary components to be rendered in a detail view page using [`registerCustomDetailComponent`](/reference/admin-ui-api/custom-detail-components/register-custom-detail-component) or [`registerReactCustomDetailComponent`](/reference/admin-ui-api/react-extensions/register-react-custom-detail-component)
+-   Add custom widgets to the dashboard using [`registerDashboardWidget`](/reference/admin-ui-api/dashboard-widgets/register-dashboard-widget)
+-   Define custom components for rendering data table cells using [`registerDataTableComponent`](/reference/admin-ui-api/custom-table-components/register-data-table-component) or [`registerReactDataTableComponent`](/reference/admin-ui-api/react-extensions/register-react-data-table-component)
+-   Define custom form input components for custom fields and configurable operation arguments using [`registerFormInputComponent`](/reference/admin-ui-api/custom-input-components/register-form-input-component) or [`registerReactFormInputComponent`](/reference/admin-ui-api/react-extensions/register-react-form-input-component)
+-   Define custom components to render customer/order history timeline entries using [`registerHistoryEntryComponent`](/reference/admin-ui-api/custom-history-entry-components/register-history-entry-component)
+
+:::info
+When running the Admin UI in dev mode, you can use the `ctrl + u` keyboard shortcut to see the location of all UI extension points.
+
+Clicking on an extension point will display a code snippet which you can copy and paste into your `providers.ts` file.
+
+![Provider extension points](./provider-extension-points.webp)
+:::
+
+In addition to the specialized UI extension providers listed above, the providers array can also contain any kind of Angular providers which you want to use inside your custom logic. For example, we can define a custom service, add it to the providers array and then consume it from within another provider:
+
+```ts title="src/plugins/my-plugin/ui/providers.ts"
+import { Injectable } from '@angular/core';
+import { addActionBarItem } from '@vendure/admin-ui/core';
+
+// highlight-start
+@Injectable()
+class MyService {
+    greet() { return 'Hello!'; }
+}
+// highlight-end 
+
+export default [
+    MyService,
+    addActionBarItem({
+        id: 'print-invoice',
+        label: 'Print invoice',
+        locationId: 'order-detail',
+        onClick: (event, context) => {
+            // highlight-start
+            const myService = context.injector.get(MyService);
+            console.log(myService.greet());
+            // logs "Hello!"
+            // highlight-end
+        },
+    }),
+];
+```
+
+
+## Routes
+
+Your `routes.ts` file exports an array of objects which define new routes in the Admin UI. For example, imagine you have created a plugin which implements a simple content management system. You can define a route for the list of articles, and another for the detail view of an article.
+
+<Tabs>
+<TabItem value="Angular" label="Angular" default>
+
+Using [`registerRouteComponent`](/reference/admin-ui-api/routes/register-route-component) you can define a new route based on an Angular component. Here's a simple example:
+
+```ts title="src/plugins/greeter/ui/routes.ts"
+import { registerRouteComponent, SharedModule } from '@vendure/admin-ui/core';
+import { Component } from '@angular/core';
+
+@Component({
+    selector: 'greeter',
+    template: ` <vdr-page-block>
+        <h2>{{ greeting }}</h2>
+    </vdr-page-block>`,
+    standalone: true,
+    imports: [SharedModule],
+})
+export class GreeterComponent {
+    greeting = 'Hello!';
+}
+
+export default [
+    registerRouteComponent({
+        component: GreeterComponent,
+        path: '',
+        title: 'Greeter Page',
+        breadcrumb: 'Greeter',
+    }),
+];
+```
+
+</TabItem>
+<TabItem value="React" label="React">
+
+Here's the equivalent example using React and [`registerReactRouteComponent`](/reference/admin-ui-api/react-extensions/register-react-route-component):
+
+```ts title="src/plugins/greeter/ui/routes.ts"
+import { registerReactRouteComponent } from '@vendure/admin-ui/react';
+import React from 'react';
+
+function Greeter() {
+    const greeting = 'Hello!';
+    return (
+        <div className="page-block">
+            <h2>{greeting}</h2>
+        </div>
+    );
+}
+
+export default [
+    registerReactRouteComponent({
+        component: Greeter,
+        path: '',
+        title: 'Greeter Page',
+        breadcrumb: 'Greeter',
+    }),
+];
+```
+
+</TabItem>
+</Tabs>
+
+:::note
+The `<vdr-page-block>` (Angular) and `<div className="page-block">` (React) is a wrapper that sets the layout and max width of your component to match the rest of the Admin UI. You should usually wrap your component in this element.
+:::
+
+Now we need to add this routes file to our extension definition:
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+import * as path from 'path';
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        AdminUiPlugin.init({
+            port: 3002,
+            app: compileUiExtensions({
+                outputPath: path.join(__dirname, '../admin-ui'),
+                extensions: [
+                    {
+                        id: 'greeter',
+                        extensionPath: path.join(__dirname, 'plugins/greeter/ui'),
+                        // highlight-start
+                        routes: [{ route: 'greet', filePath: 'routes.ts' }],
+                        // highlight-end
+                    },
+                ],
+            }),
+        }),
+    ],
+};
+```
+
+Note that by specifying `route: 'greet'`, we are "mounting" the routes at the `/extensions/greet` path.
+
+Now go to the Admin UI app in your browser and log in. You should now be able to manually enter the URL `http://localhost:3000/admin/extensions/greet` and you should see the component with the "Hello!" header:
+
+![./ui-extensions-greeter.webp](./ui-extensions-greeter.webp)
+
+## Dev vs Prod mode
+
+When you are developing your Admin UI extension, you can set the `devMode` option to `true` which will compile the Admin UI app in development mode, and recompile and auto-refresh the browser on any changes to your extension source files.
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+import * as path from 'path';
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        AdminUiPlugin.init({
+            port: 3002,
+            app: compileUiExtensions({
+                outputPath: path.join(__dirname, '../admin-ui'),
+                extensions: [
+                    {
+                        // ...
+                    },
+                ],
+                devMode: true,
+            }),
+        }),
+    ],
+};
+```
+
+## Compiling as a deployment step
+
+Although the examples so far all use the `compileUiExtensions` function in conjunction with the AdminUiPlugin, it is also possible to use it on its own:
+
+```ts title="src/compile-admin-ui.ts"
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+import * as path from 'path';
+
+compileUiExtensions({
+    outputPath: path.join(__dirname, '../admin-ui'),
+    extensions: [
+        /* ... */
+    ],
+})
+    .compile?.()
+    .then(() => {
+        process.exit(0);
+    });
+```
+
+This can then be run from the command line:
+
+```bash
+yarn ts-node compile-admin-ui.ts
+```
+
+Once complete, the production-ready app bundle will be output to `admin-ui/dist`. This method is suitable for a production setup, so that the Admin UI can be compiled ahead-of-time as part of your deployment process. This ensures that your Vendure server starts up as quickly as possible. In this case, you can pass the path of the compiled app to the AdminUiPlugin:
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import * as path from 'path';
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        AdminUiPlugin.init({
+            port: 3002,
+            app: {
+                path: path.join(__dirname, '../admin-ui/dist'),
+            },
+        }),
+    ],
+};
+```
+
+:::info
+To compile the angular app ahead of time (for production) and copy the dist folder to Vendure's output dist folder, include the following commands in your packages.json scripts:
+
+```json
+{
+    "scripts": {
+        "copy": "npx copyfiles -u 1 'src/__admin-ui/dist/**/*' dist",
+        "build": "tsc && yarn copy",
+        "build:admin": "rimraf admin-ui && npx ts-node src/compile-admin-ui.ts"
+    }
+}
+```
+
+"build:admin" will remove the admin-ui folder and run the compileUiExtensions function to generate the admin-ui Angular app.
+Make sure to install copyfiles before running the "copy" command:
+
+```bash
+yarn install copyfiles
+```
+
+:::
+
+## Using other frameworks
+
+While the Admin UI natively supports extensions written with Angular or React, it is still possible to create extensions using other front-end frameworks such as Vue or Solid. Note that creating extensions in this way is much more limited, with only the ability to define new routes, and limited access to internal services such as data fetching and notifications. See [UI extensions in other frameworks](/guides/extending-the-admin-ui/using-other-frameworks/).
+
+## Legacy API < v2.1.0
+
+Prior to Vendure v2.1.0, the API for extending the Admin UI was more verbose and less flexible (React components were not supported at all, for instance). This API is still supported, but from v2.1 is marked as deprecated and will be removed in a future major version. 
+
+This section describes the legacy API.
+
+### Lazy vs Shared Modules
+
+Angular uses the concept of modules ([NgModules](https://angular.io/guide/ngmodules)) for organizing related code. These modules can be lazily loaded, which means that the code is not loaded when the app starts, but only later once that code is required. This keeps the main bundle small and improves performance.
+
+When creating your UI extensions, you can set your module to be either `lazy` or `shared`. Shared modules are loaded _eagerly_, i.e. their code is bundled up with the main app and loaded as soon as the app loads.
+
+As a rule, modules defining new routes should be lazily loaded (so that the code is only loaded once that route is activated), and modules defining [new navigations items](/guides/extending-the-admin-ui/modifying-navigation-items/) and [custom form input](/guides/extending-the-admin-ui/custom-form-inputs/) should be set to `shared`.
+
+:::info
+"lazy" modules are equivalent to the new "routes" API, and "shared" modules are equivalent to the new "providers" API. In fact, behind the scenes,
+the new APIs are automatically creating these modules for you.
+:::
+
+### Example lazy module
+
+Here's a very simple Angular component which displays a greeting:
+
+```ts title="src/plugins/greeter/ui/components/greeter/greeter.component.ts"
+import { Component } from '@angular/core';
+
+@Component({
+    selector: 'greeter',
+    template: `<vdr-page-block><h1>{{ greeting }}</h1></vdr-page-block>`,
+})
+export class GreeterComponent {
+    greeting = 'Hello!';
+}
+```
+
+Next we need to declare an Angular module to house the component:
+
+```ts title="src/plugins/greeter/ui/greeter.module.ts"
+import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { SharedModule } from '@vendure/admin-ui/core';
+import { GreeterComponent } from './greeter.component';
+
+@NgModule({
+    imports: [
+        SharedModule,
+        RouterModule.forChild([{
+            path: '',
+            pathMatch: 'full',
+            component: GreeterComponent,
+            data: { breadcrumb: 'Greeter' },
+        }]),
+    ],
+    declarations: [GreeterComponent],
+})
+export class GreeterModule {}
+```
+
+:::note
+The `SharedModule` should, in general, always be imported by your extension modules. It provides the basic Angular
+directives and other common functionality that any extension would require.
+:::
+
+Now we need to tell the `compileUiExtensions` function where to find the extension, and which file contains the NgModule itself (since a non-trivial UI extension will likely contain multiple files).
+
+```ts title="src/vendure-config.ts"
+import path from 'path';
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import { VendureConfig } from '@vendure/core';
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        AdminUiPlugin.init({
+            port: 3002,
+            app: compileUiExtensions({
+                outputPath: path.join(__dirname, '../admin-ui'),
+                extensions: [{
+                    extensionPath: path.join(__dirname, 'plugins/greeter/ui'),
+                    // highlight-start
+                    ngModules: [{
+                        type: 'lazy',
+                        route: 'greet',
+                        ngModuleFileName: 'greeter.module.ts',
+                        ngModuleName: 'GreeterModule',
+                    }],
+                    // highlight-end
+                }],
+            }),
+        }),
+    ],
+};
+```
+
+### Example shared module
+
+Here's an example of the legacy API for defining a shared module:
+
+```ts title="src/plugins/invoices/ui/invoice-shared.module.ts"
+import { NgModule } from '@angular/core';
+import { SharedModule, addActionBarItem } from '@vendure/admin-ui/core';
+
+@NgModule({
+    imports: [SharedModule],
+    providers: [
+        addActionBarItem({
+            id: 'print-invoice',
+            label: 'Print invoice',
+            locationId: 'order-detail',
+            routerLink: route => {
+                const id = route.snapshot.params.id;
+                return ['./extensions/order-invoices', id];
+            },
+            requiresPermission: 'ReadOrder',
+        }),
+    ],
+})
+export class InvoiceSharedModule {}
+```
+
+```ts title="src/vendure-config.ts"
+import path from 'path';
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import { VendureConfig } from '@vendure/core';
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        AdminUiPlugin.init({
+            port: 3002,
+            app: compileUiExtensions({
+                outputPath: path.join(__dirname, '../admin-ui'),
+                extensions: [{
+                    extensionPath: path.join(__dirname, 'plugins/invoices/ui'),
+                    // highlight-start
+                    ngModules: [{
+                        type: 'shared',
+                        ngModuleFileName: 'invoice-shared.module.ts',
+                        ngModuleName: 'InvoiceSharedModule',
+                    }],
+                    // highlight-end
+                }],
+            }),
+        }),
+    ],
+};
+```
+
+### Migrating to the new API
+
+If you have existing UI extensions written using the legacy API, you can migrate them to the new API as follows:
+
+1. Convert all components to be [standalone components](https://angular.io/guide/standalone-components). Standalone components were introduced in recent versions of Angular and allow components to be defined without the need for a module. To convert an existing component, you need to set `standalone: true` and add an `imports` array containing any components, directives or pipes you are using in that component. Typically you can import `SharedModule` to get access to all the common Angular directives and pipes, as well as the shared Admin UI components.
+  ```ts
+  import { Component } from '@angular/core';
+  // highlight-next-line
+  import { SharedModule } from '@vendure/admin-ui/core';
+  
+  @Component({
+      selector: 'greeter',
+      template: `<vdr-page-block><h1>{{ greeting }}</h1></vdr-page-block>`,
+      // highlight-start
+      standalone: true,
+      imports: [SharedModule],
+      // highlight-end
+  })
+  export class GreeterComponent {
+      greeting = 'Hello!';
+  }
+  ```
+2. Remove any `NgModule` files, and replace lazy modules with `routes.ts`, and shared modules with `providers.ts` (see above).
+
+

BIN
docs/docs/guides/extending-the-admin-ui/getting-started/provider-extension-points.webp


+ 0 - 0
docs/docs/guides/extending-the-admin-ui/using-angular/ui-extensions-greeter.webp → docs/docs/guides/extending-the-admin-ui/getting-started/ui-extensions-greeter.webp


+ 0 - 144
docs/docs/guides/extending-the-admin-ui/introduction.md

@@ -1,144 +0,0 @@
----
-title: 'Introduction'
----
-
-# Extending the Admin UI: Introduction
-
-When creating a plugin, you may wish to extend the Admin UI in order to expose a graphical interface to the plugin's functionality.
-
-This is possible by defining [AdminUiExtensions](/reference/admin-ui-api/ui-devkit/admin-ui-extension/).
-
-:::info
-For a complete working example of a Vendure plugin that extends the Admin UI, see the [real-world-vendure Reviews plugin](https://github.com/vendure-ecommerce/real-world-vendure/tree/master/src/plugins/reviews)
-:::
-
-## How It Works
-
-A UI extension is an [Angular module](https://angular.io/guide/ngmodules) that gets compiled into the Admin UI application bundle by the [`compileUiExtensions`](/reference/admin-ui-api/ui-devkit/compile-ui-extensions) function exported by the `@vendure/ui-devkit` package. Internally, the ui-devkit package makes use of the Angular CLI to compile an optimized set of JavaScript bundles containing your extensions.
-
-## Use Your Favourite Framework
-
-The Vendure Admin UI is built with Angular, and writing UI extensions in Angular is seamless and powerful. But if you are not familiar with Angular, that's no problem! You can write UI extensions using **React**, **Vue**, or **any other** web technology of choice!
-
-* [UI extensions in Angular](/guides/extending-the-admin-ui/using-angular/)
-* [UI extensions in other frameworks](/guides/extending-the-admin-ui/using-other-frameworks/)
-
-## Lazy vs Shared Modules
-
-Angular uses the concept of modules ([NgModules](https://angular.io/guide/ngmodules)) for organizing related code. These modules can be lazily loaded, which means that the code is not loaded when the app starts, but only later once that code is required. This keeps the main bundle small and improves performance.
-
-When creating your UI extensions, you can set your module to be either `lazy` or `shared`. Shared modules are loaded _eagerly_, i.e. their code is bundled up with the main app and loaded as soon as the app loads.
-
-As a rule, modules defining new routes should be lazily loaded (so that the code is only loaded once that route is activated), and modules defining [new navigations items](/guides/extending-the-admin-ui/modifying-navigation-items/) and [custom form input](/guides/extending-the-admin-ui/custom-form-inputs/) should be set to `shared`.
-
-## Dev mode
-
-When you are developing your Admin UI extension, you can set the `devMode` option to `true` which will compile the Admin UI app in development mode, and recompile and auto-refresh the browser on any changes to your extension source files.
-
-```ts title="src/vendure-config.ts"
-import { VendureConfig } from '@vendure/core';
-import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
-import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
-import * as path from 'path';
-
-export const config: VendureConfig = {
-    // ...
-    plugins: [
-        AdminUiPlugin.init({
-            port: 3002,
-            app: compileUiExtensions({
-                outputPath: path.join(__dirname, '../admin-ui'),
-                extensions: [{
-                    // ...
-                }],
-                devMode: true,
-            }),
-        }),
-    ],
-};
-```
-
-## Compiling as a deployment step
-
-Although the examples so far all use the `compileUiExtensions` function in conjunction with the AdminUiPlugin, it is also possible to use it on its own:
-
-```ts title="src/compile-admin-ui.ts"
-import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
-import * as path from 'path';
-
-compileUiExtensions({
-    outputPath: path.join(__dirname, '../admin-ui'),
-    extensions: [/* ... */],
-}).compile?.().then(() => {
-    process.exit(0);
-});
-
-```
-
-This can then be run from the command line:
-
-```bash
-yarn ts-node compile-admin-ui.ts
-```
-
-Once complete, the production-ready app bundle will be output to `admin-ui/dist`. This method is suitable for a production setup, so that the Admin UI can be compiled ahead-of-time as part of your deployment process. This ensures that your Vendure server starts up as quickly as possible. In this case, you can pass the path of the compiled app to the AdminUiPlugin:
-
-```ts title="src/vendure-config.ts"
-import { VendureConfig } from '@vendure/core';
-import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
-import * as path from 'path';
-
-export const config: VendureConfig = {
-    // ...
-    plugins: [
-        AdminUiPlugin.init({
-            port: 3002,
-            app: {
-                path: path.join(__dirname, '../admin-ui/dist'),
-            },
-        }),
-    ],
-};
-```
-
-:::caution
-**Note:** the TypeScript source files of your UI extensions **must not** be compiled by your regular TypeScript build task. This is because they will instead be compiled by the Angular compiler when you run `compileUiExtensions()`. You can exclude them in your main `tsconfig.json` by adding a line to the "exclude" array:
-```json
-{
-  "exclude": [
-    "src/plugins/**/ui/*"
-  ]
-}
-```
-:::
-
-:::caution
-**Also note:** When you use compileUiExtensions to compile the Angular App, a new directory will be created to host the compiled Admin-UI app. The name and location of the app is specified by the "outputPath" which is set in the compileUiExtensions options object. Make sure to **exlcude** the admin-ui directory from typescript's transpilation since code there is already transpiled.
-```json
-{
-  "exclude": [
-    "node_modules",
-    "migration.ts",
-    "admin-ui" // <-- add this
-  ],
-}
-```
-:::
-
-:::info
-To compile the angular app ahead of time (for production) and copy the dist folder to Vendure's output dist folder, include the following commands in your packages.json scripts:
-```json
-{
-  "scripts": {
-    "copy": "npx copyfiles -u 1 'src/__admin-ui/dist/**/*' dist",
-    "build": "tsc && yarn copy",
-    "build:admin": "rimraf admin-ui && npx ts-node src/compile-admin-ui.ts",
-  }
-}
-```
-"build:admin" will remove the admin-ui folder and run the compileUiExtensions function to generate the admin-ui Angular app.
-Make sure to install copyfiles before running the "copy" command:
-```bash
-yarn install copyfiles
-```
-:::

+ 0 - 111
docs/docs/guides/extending-the-admin-ui/modifying-navigation-items/index.md

@@ -1,111 +0,0 @@
----
-title: 'Modify Navigation Items'
-weight: 5
----
-
-# Adding Navigation Items
-
-## Extending the NavMenu
-
-Once you have defined some custom routes in a lazy extension module, you need some way for the administrator to access them. For this you will use the [addNavMenuItem](/reference/admin-ui-api/nav-menu/add-nav-menu-item/) and [addNavMenuSection](/reference/admin-ui-api/nav-menu/add-nav-menu-section) functions.
-
-Let's add a new section to the Admin UI main nav bar containing a link to the "greeter" module from the [Using Angular](/guides/extending-the-admin-ui/using-angular/) example:
-
-```ts title="src/plugins/greeter/ui/greeter-shared.module.ts"
-import { NgModule } from '@angular/core';
-import { SharedModule, addNavMenuSection } from '@vendure/admin-ui/core';
-
-@NgModule({
-    imports: [SharedModule],
-    providers: [
-        addNavMenuSection({
-                id: 'greeter',
-                label: 'My Extensions',
-                items: [{
-                    id: 'greeter',
-                    label: 'Greeter',
-                    routerLink: ['/extensions/greet'],
-                    // Icon can be any of https://clarity.design/icons
-                    icon: 'cursor-hand-open',
-                }],
-            },
-            // Add this section before the "settings" section
-            'settings'),
-    ]
-})
-export class GreeterSharedModule {}
-```
-
-Now we must also register this new module with the compiler:
-
-```ts title="src/vendure-config.ts"
-import path from 'path';
-import { VendureConfig } from '@vendure/core';
-import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
-import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
-
-export const config: VendureConfig = {
-    // ...
-    plugins: [
-        AdminUiPlugin.init({
-            port: 3002,
-            app: compileUiExtensions({
-                outputPath: path.join(__dirname, '../admin-ui'),
-                extensions: [{
-                    extensionPath: path.join(__dirname, 'plugins/greeter/ui'),
-                    ngModules: [
-                        {
-                            type: 'lazy',
-                            route: 'greet',
-                            ngModuleFileName: 'greeter.module.ts',
-                            ngModuleName: 'GreeterModule',
-                        },
-                        {
-                            type: 'shared',
-                            ngModuleFileName: 'greeter-shared.module.ts',
-                            ngModuleName: 'GreeterSharedModule',
-                        }
-                    ],
-                }],
-            }),
-        }),
-    ],
-};
-```
-
-Running the server will compile our new shared module into the app, and the result should look like this:
-
-![./ui-extensions-navbar.webp](./ui-extensions-navbar.webp)
-
-## Overriding existing nav items
-
-It is also possible to override one of the default (built-in) nav menu sections or items. This can be useful for example if you wish to provide a completely different implementation of the product list view.
-
-This is done by setting the `id` property to that of an existing nav menu section or item.
-
-## Adding page tabs
-
-You can add your own tabs to any of the Admin UI's list or detail pages using the [registerPageTab](/reference/admin-ui-api/tabs/register-page-tab/) function. For example, to add a new tab to the product detail page for displaying product reviews:
-
-```ts title="src/plugins/reviews/ui/shared-ui-extension.module.ts"
-import { NgModule } from '@angular/core';
-import { SharedModule, registerPageTab } from '@vendure/admin-ui/core';
-
-import { ReviewListComponent } from './components/review-list/review-list.component';
-
-@NgModule({
-    imports: [SharedModule],
-    providers: [
-        registerPageTab({
-            location: 'product-detail',
-            tab: 'Reviews',
-            route: 'reviews',
-            tabIcon: 'star',
-            component: ReviewListComponent,
-        }),
-    ]
-})
-export class ReviewsSharedModule {}
-```
-
-![./ui-extensions-tabs.webp](./ui-extensions-tabs.webp)

+ 74 - 0
docs/docs/guides/extending-the-admin-ui/nav-menu/index.md

@@ -0,0 +1,74 @@
+---
+title: 'Modify the Nav Menu'
+weight: 5
+---
+
+The Nav Menu is the main navigation for the Admin UI, located on the left-hand side when in desktop mode. It is used to provide top-level
+access to routes in the app, and can be extended and modified by UI extensions.
+
+## Extending the NavMenu
+
+Once you have defined some custom routes, you need some way for the administrator to access them. For this you will use the [addNavMenuItem](/reference/admin-ui-api/nav-menu/add-nav-menu-item/) and [addNavMenuSection](/reference/admin-ui-api/nav-menu/add-nav-menu-section) functions.
+
+Let's add a new section to the Admin UI main nav bar containing a link to the "greeter" module from the [Getting Started guide](/guides/extending-the-admin-ui/getting-started/#routes) example:
+
+```ts title="src/plugins/greeter/ui/providers.ts"
+import { addNavMenuSection } from '@vendure/admin-ui/core';
+
+export default [
+    addNavMenuSection({
+        id: 'greeter',
+        label: 'My Extensions',
+        items: [{
+            id: 'greeter',
+            label: 'Greeter',
+            routerLink: ['/extensions/greet'],
+            // Icon can be any of https://core.clarity.design/foundation/icons/shapes/
+            icon: 'cursor-hand-open',
+        }],
+    },
+    // Add this section before the "settings" section
+    'settings'),
+];
+```
+
+Now we must also register these providers with the compiler:
+
+```ts title="src/vendure-config.ts"
+import path from 'path';
+import { VendureConfig } from '@vendure/core';
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        AdminUiPlugin.init({
+            port: 3002,
+            app: compileUiExtensions({
+                outputPath: path.join(__dirname, '../admin-ui'),
+                extensions: [
+                    {
+                        id: 'greeter',
+                        extensionPath: path.join(__dirname, 'plugins/greeter/ui'),
+                        routes: [{ route: 'greet', filePath: 'routes.ts' }],
+                        // highlight-start
+                        providers: ['providers.ts']
+                        // highlight-end
+                    },
+                ],
+            }),
+        }),
+    ],
+};
+```
+
+Running the server will compile our new shared module into the app, and the result should look like this:
+
+![./ui-extensions-navbar.webp](./ui-extensions-navbar.webp)
+
+## Overriding existing nav items
+
+It is also possible to override one of the default (built-in) nav menu sections or items. This can be useful for example if you wish to provide a completely different implementation of the product list view.
+
+This is done by setting the `id` property to that of an existing nav menu section or item.

+ 0 - 0
docs/docs/guides/extending-the-admin-ui/modifying-navigation-items/ui-extensions-navbar.webp → docs/docs/guides/extending-the-admin-ui/nav-menu/ui-extensions-navbar.webp


+ 28 - 0
docs/docs/guides/extending-the-admin-ui/page-tabs/index.md

@@ -0,0 +1,28 @@
+---
+title: 'Page Tabs'
+weight: 5
+---
+
+You can add your own tabs to any of the Admin UI's list or detail pages using the [registerPageTab](/reference/admin-ui-api/tabs/register-page-tab/) function. For example, to add a new tab to the product detail page for displaying product reviews:
+
+```ts title="src/plugins/reviews/ui/providers.ts"
+import { registerPageTab } from '@vendure/admin-ui/core';
+
+import { ReviewListComponent } from './components/review-list/review-list.component';
+
+export default [
+    registerPageTab({
+        location: 'product-detail',
+        tab: 'Reviews',
+        route: 'reviews',
+        tabIcon: 'star',
+        component: ReviewListComponent,
+    }),
+];
+```
+
+![./ui-extensions-tabs.webp](./ui-extensions-tabs.webp)
+
+:::note
+Currently it is only possible to define new tabs using Angular components.
+:::

+ 0 - 0
docs/docs/guides/extending-the-admin-ui/modifying-navigation-items/ui-extensions-tabs.webp → docs/docs/guides/extending-the-admin-ui/page-tabs/ui-extensions-tabs.webp


+ 0 - 125
docs/docs/guides/extending-the-admin-ui/using-angular/index.md

@@ -1,125 +0,0 @@
----
-title: 'Using Angular'
-weight: 0
----
-
-# UI Extensions with Angular
-
-Writing your UI extensions with Angular results in the best-optimized and most seamless UI extensions, since you can re-use shared components exported by the `@vendure/admin-ui/core` library, and the Angular framework itself is already present in the app.
-
-:::note
-An understanding of [Angular](https://angular.io/) is necessary for successfully working with Angular-based UI extensions. Try [Angular's "Getting Started" guide](https://angular.io/start) to learn more.
-:::
-
-## 1. Install `@vendure/ui-devkit`
-
-To create UI extensions, you'll need to install the `@vendure/ui-devkit` package. This package contains a compiler for building your customized version of the Admin UI, as well as the Angular dependencies you'll need to create your extensions.
-
-```bash
-yarn add @vendure/ui-devkit
-
-# or
-
-npm install @vendure/ui-devkit
-```
-
-## 2. Create a simple component
-
-Here's a very simple Angular component which displays a greeting:
-
-```ts title="src/plugins/greeter/ui/components/greeter/greeter.component.ts"
-import { Component } from '@angular/core';
-
-@Component({
-    selector: 'greeter',
-    template: `<vdr-page-block><h1>{{ greeting }}</h1></vdr-page-block>`,
-})
-export class GreeterComponent {
-    greeting = 'Hello!';
-}
-```
-
-The `<vdr-page-block>` is just a wrapper that sets the layout and max width of your component to match the rest of the Admin UI.
-
-## 3. Create the Angular module
-
-Next we need to declare an Angular module to house the component:
-
-```ts title="src/plugins/greeter/ui/greeter.module.ts"
-import { NgModule } from '@angular/core';
-import { RouterModule } from '@angular/router';
-import { SharedModule } from '@vendure/admin-ui/core';
-import { GreeterComponent } from './greeter.component';
-
-@NgModule({
-    imports: [
-        SharedModule,
-        RouterModule.forChild([{
-            path: '',
-            pathMatch: 'full',
-            component: GreeterComponent,
-            data: {breadcrumb: 'Greeter'},
-        }]),
-    ],
-    declarations: [GreeterComponent],
-})
-export class GreeterModule {}
-```
-
-:::note
-The `SharedModule` should, in general, always be imported by your extension modules. It provides the basic Angular
-directives and other common functionality that any extension would require.
-:::
-
-## 4. Pass the extension to the `compileUiExtensions` function
-
-Now we need to tell the `compileUiExtensions` function where to find the extension, and which file contains the NgModule itself (since a non-trivial UI extension will likely contain multiple files).
-
-```ts title="src/vendure-config.ts"
-import path from 'path';
-import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
-import { VendureConfig } from '@vendure/core';
-import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
-
-export const config: VendureConfig = {
-    // ...
-    plugins: [
-        AdminUiPlugin.init({
-            port: 5001,
-            app: compileUiExtensions({
-                outputPath: path.join(__dirname, '../admin-ui'),
-                extensions: [{
-                    extensionPath: path.join(__dirname, 'ui-extensions'),
-                    ngModules: [{
-                        type: 'lazy',
-                        route: 'greet',
-                        ngModuleFileName: 'greeter.module.ts',
-                        ngModuleName: 'GreeterModule',
-                    }],
-                }],
-            }),
-        }),
-    ],
-};
-```
-
-## 5. Start the server to compile
-
-The `compileUiExtensions()` function returns a `compile()` function which will be invoked by the AdminUiPlugin upon server bootstrap. During this compilation process, a new directory will be generated at `/admin-ui` (as specified by the `outputPath` option) which will contains the un-compiled sources of your new Admin UI app.
-
-Next, these source files will be run through the Angular compiler, the output of which will be visible in the console.
-
-Now go to the Admin UI app in your browser and log in. You should now be able to manually enter the URL `http://localhost:3000/admin/extensions/greet` and you should see the component with the "Hello!" header:
-
-![./ui-extensions-greeter.webp](./ui-extensions-greeter.webp)
-
-:::caution
-**Note:** the TypeScript source files of your UI extensions **must not** be compiled by your regular TypeScript build task. This is because they will instead be compiled by the Angular compiler when you run `compileUiExtensions()`. You can exclude them in your main `tsconfig.json` by adding a line to the "exclude" array:
-```json
-{
-  "exclude": [
-    "src/plugins/**/ui/*"
-  ]
-}
-```
-:::

+ 38 - 70
docs/docs/guides/extending-the-admin-ui/using-other-frameworks/index.md

@@ -3,12 +3,12 @@ title: 'Using Other Frameworks'
 weight: 1
 ---
 
-# UI Extensions With Other Frameworks
+From version 2.1.0, Admin UI extensions can be written in either Angular or React. Prior to v2.1.0, only Angular was natively supported. 
 
-Although the Admin UI is an Angular app, it is possible to create UI extensions using any web technology - React, Vue, plain JavaScript, etc.
+It is, however, possible to extend the Admin UI using other frameworks such as Vue, Svelte, Solid etc. Note that the extension experience is much more limited than with Angular or React, but depending on your needs it may be sufficient.
 
 :::info
-For working examples of a UI extensions built with **Vue** and **React**, see the [real-world-vendure ui extensions](https://github.com/vendure-ecommerce/real-world-vendure/tree/master/src/ui-extensions)
+For working examples of a UI extensions built with **Vue**, see the [real-world-vendure ui extensions](https://github.com/vendure-ecommerce/real-world-vendure/tree/master/src/ui-extensions)
 :::
 
 There is still a small amount of Angular "glue code" needed to let the compiler know how to integrate your extension, so let's take a look at how this is done.
@@ -30,53 +30,42 @@ npm install @vendure/ui-devkit
 In this example, we will work with the following folder structure, and use Create React App our example.
 
 ```text
-/src
-├─ui-extension/
-     ├─ modules/
-     |      ├─ react-extension.module.ts
-     ├─ react-app/
-            ├─ (create react app directory)
+src
+└─plugins
+  └─ my-plugin
+    └─ ui
+      ├─ routes.ts
+      └─ vue-app
+        └─ (directory created by `vue create`, for example)
 ```
 
 ## 3. Create an extension module
 
 Here's the Angular code needed to tell the compiler where to find your extension:
 
-```ts title="src/ui-extension/modules/react-extension.module.ts"
-import { NgModule } from '@angular/core';
-import { RouterModule } from '@angular/router';
+```ts title="src/plugins/my-plugin/ui/routes.ts"
 import { hostExternalFrame } from '@vendure/admin-ui/core';
 
-@NgModule({
-    imports: [
-        RouterModule.forChild([
-            hostExternalFrame({
-                path: '',
-
-                // You can also use parameters which allow the app
-                // to have dynamic routing, e.g.
-                // path: ':slug'
-                // Then you can use the getActivatedRoute() function from the
-                // UiDevkitClient in order to access the value of the "slug"
-                // parameter.
-
-                breadcrumbLabel: 'React App',
-                // This is the URL to the compiled React app index.
-                // The next step will explain the "assets/react-app" path.
-                extensionUrl: './assets/react-app/index.html',
-                openInNewTab: false,
-            }),
-        ]),
-    ],
-})
-export class ReactUiExtensionModule {}
+export default [
+    hostExternalFrame({
+        path: '',
+
+        // You can also use parameters which allow the app
+        // to have dynamic routing, e.g.
+        // path: ':slug'
+        // Then you can use the getActivatedRoute() function from the
+        // UiDevkitClient in order to access the value of the "slug"
+        // parameter.
+
+        breadcrumbLabel: 'Vue App',
+        // This is the URL to the compiled React app index.
+        // The next step will explain the "assets/react-app" path.
+        extensionUrl: './assets/vue-app/index.html',
+        openInNewTab: false,
+    })
+];
 ```
 
-:::note
-If you are using **Create React App**, you should additionally update your package.json file to include the [homepage property](https://create-react-app.dev/docs/deployment/#building-for-relative-paths) so that it works when run from the admin ui assets directory:
-`"homepage": "/admin/assets/react-app/"`
-:::
-
 ## 4. Define the AdminUiExtension config
 
 Next we will define an [AdminUiExtension](/reference/admin-ui-api/ui-devkit/admin-ui-extension/) object which is passed to the `compileUiExtensions()` function in your Vendure config:
@@ -97,28 +86,14 @@ export const config: VendureConfig = {
                 outputPath: path.join(__dirname, '../admin-ui'),
                 extensions: [{
                     // Points to the path containing our Angular "glue code" module
-                    extensionPath: path.join(__dirname, 'ui-extension/modules'),
-                    ngModules: [
-                        {
-                            // We want to lazy-load our extension...
-                            type: 'lazy',
-                            // ...when the `/admin/extensions/react-ui`
-                            // route is activated
-                            route: 'react-ui',
-                            // The filename of the extension module
-                            // relative to the `extensionPath` above
-                            ngModuleFileName: 'react-extension.module.ts',
-                            // The name of the extension module class exported
-                            // from the module file.
-                            ngModuleName: 'ReactUiExtensionModule',
-                        },
-                    ],
+                    extensionPath: path.join(__dirname, 'plugins/my-plugin/ui'),
+                    routes: [{ route: 'vue-ui', filePath: 'routes.ts' }],
                     staticAssets: [
-                        // This is where we tell the compiler to copy the compiled React app
+                        // This is where we tell the compiler to copy the compiled Vue app
                         // artifacts over to the Admin UI's `/static` directory. In this case we
-                        // also rename "build" to "react-app". This is why the `extensionUrl`
-                        // in the module config points to './assets/react-app/index.html'.
-                        {path: path.join(__dirname, 'ui-extension/react-app/build'), rename: 'react-app'},
+                        // also rename "build" to "vue-app". This is why the `extensionUrl`
+                        // in the module config points to './assets/vue-app/index.html'.
+                        {path: path.join(__dirname, 'plugins/my-plugin/ui/vue-app/dist'), rename: 'vue-app'},
                     ],
                 }],
                 devMode: true,
@@ -130,7 +105,7 @@ export const config: VendureConfig = {
 
 ## 5. Build your extension
 
-To ensure things are working we can now build our React app by running `yarn build` in the `react-app` directory. This will build and output the app artifacts to the `react-app/build` directory - the one we pointed to in the `staticAssets` array above.
+To ensure things are working we can now build our Vue app by running `yarn build` in the `vue-app` directory. This will build and output the app artifacts to the `vue-app/build` directory - the one we pointed to in the `staticAssets` array above.
 
 Once build, we can start the Vendure server.
 
@@ -138,23 +113,16 @@ The `compileUiExtensions()` function returns a `compile()` function which will b
 
 Next, these source files will be run through the Angular compiler, the output of which will be visible in the console.
 
-{{< alert "warning" >}}
-**Note:** The first time the compiler is run, an additional step ([compatibility compiler](https://v12.angular.io/guide/ivy#ivy-and-libraries)) is run to make sure all dependencies work with the latest version of Angular. This step can take up to a few minutes.
-{{< /alert >}}
-
-Now go to the Admin UI app in your browser and log in. You should now be able to manually enter the URL `http://localhost:3000/admin/extensions/react-ui` and you should see the default Create React App demo page:
-
-![./ui-extensions-cra.jpg](./ui-extensions-cra.jpg)
+Now go to the Admin UI app in your browser and log in. You should now be able to manually enter the URL `http://localhost:3000/admin/extensions/vue-ui` and you should see the Vue app rendered in the Admin UI.
 
 ## Integrate with the Admin UI
 
 ### Styling
 The `@vendure/admin-ui` package (which will be installed alongside the ui-devkit) provides a stylesheet to allow your extension to fit visually with the rest of the Admin UI.
 
-If you have a build step (as in our Create React App example), you can import it into your app like this:
+If you have a build step, you can import it into your app like this:
 
 ```ts
-// src/ui-extension/react-app/src/App.tsx
 import '@vendure/admin-ui/static/theme.min.css';
 ```
 

+ 51 - 0
docs/docs/reference/admin-ui-api/action-bar/action-bar-context.md

@@ -0,0 +1,51 @@
+---
+title: "ActionBarContext"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## ActionBarContext
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="78" packageName="@vendure/admin-ui" />
+
+Providers available to the onClick handler of an <a href='/reference/admin-ui-api/action-bar/action-bar-item#actionbaritem'>ActionBarItem</a> or <a href='/reference/admin-ui-api/nav-menu/nav-menu-item#navmenuitem'>NavMenuItem</a>.
+
+```ts title="Signature"
+interface ActionBarContext {
+    route: ActivatedRoute;
+    injector: Injector;
+    dataService: DataService;
+    notificationService: NotificationService;
+}
+```
+
+<div className="members-wrapper">
+
+### route
+
+<MemberInfo kind="property" type={`ActivatedRoute`}   />
+
+
+### injector
+
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/common/injector#injector'>Injector</a>`}   />
+
+
+### dataService
+
+<MemberInfo kind="property" type={`<a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>`}   />
+
+
+### notificationService
+
+<MemberInfo kind="property" type={`<a href='/reference/admin-ui-api/services/notification-service#notificationservice'>NotificationService</a>`}   />
+
+
+
+
+</div>

+ 11 - 4
docs/docs/reference/admin-ui-api/action-bar/action-bar-item.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## ActionBarItem
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="89" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="96" packageName="@vendure/admin-ui" />
 
 A button in the ActionBar area at the top of one of the list or detail views.
 
@@ -21,7 +21,8 @@ interface ActionBarItem {
     label: string;
     locationId: ActionBarLocationId;
     disabled?: Observable<boolean>;
-    onClick?: (event: MouseEvent, context: OnClickContext) => void;
+    buttonState?: (context: ActionBarContext) => Observable<ActionBarButtonState>;
+    onClick?: (event: MouseEvent, context: ActionBarContext) => void;
     routerLink?: RouterLinkDefinition;
     buttonColor?: 'primary' | 'success' | 'warning';
     buttonStyle?: 'solid' | 'outline' | 'link';
@@ -51,15 +52,21 @@ interface ActionBarItem {
 
 <MemberInfo kind="property" type={`Observable&#60;boolean&#62;`}   />
 
+Deprecated since v2.1.0 - use `buttonState` instead.
+### buttonState
 
+<MemberInfo kind="property" type={`(context: <a href='/reference/admin-ui-api/action-bar/action-bar-context#actionbarcontext'>ActionBarContext</a>) =&#62; Observable&#60;ActionBarButtonState&#62;`}  since="2.1.0"  />
+
+A function which returns an observable of the button state, allowing you to
+dynamically enable/disable or show/hide the button.
 ### onClick
 
-<MemberInfo kind="property" type={`(event: MouseEvent, context: <a href='/reference/admin-ui-api/action-bar/on-click-context#onclickcontext'>OnClickContext</a>) =&#62; void`}   />
+<MemberInfo kind="property" type={`(event: MouseEvent, context: <a href='/reference/admin-ui-api/action-bar/action-bar-context#actionbarcontext'>ActionBarContext</a>) =&#62; void`}   />
 
 
 ### routerLink
 
-<MemberInfo kind="property" type={`RouterLinkDefinition`}   />
+<MemberInfo kind="property" type={`<a href='/reference/admin-ui-api/action-bar/router-link-definition#routerlinkdefinition'>RouterLinkDefinition</a>`}   />
 
 
 ### buttonColor

+ 1 - 1
docs/docs/reference/admin-ui-api/action-bar/action-bar-location-id.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## ActionBarLocationId
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/component-registry-types.ts" sourceLine="103" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/component-registry-types.ts" sourceLine="104" packageName="@vendure/admin-ui" />
 
 The valid locationIds for registering action bar items.
 

+ 9 - 16
docs/docs/reference/admin-ui-api/action-bar/add-action-bar-item.md

@@ -11,29 +11,22 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## addActionBarItem
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder.service.ts" sourceLine="120" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/add-action-bar-item.ts" sourceLine="23" packageName="@vendure/admin-ui" />
 
 Adds a button to the ActionBar at the top right of each list or detail view. The locationId can
-be determined by inspecting the DOM and finding the `<vdr-action-bar>` element and its
-`data-location-id` attribute.
-
-This should be used in the NgModule `providers` array of your ui extension module.
+be determined by pressing `ctrl + u` when running the Admin UI in dev mode.
 
 *Example*
 
-```ts
-@NgModule({
-  imports: [SharedModule],
-  providers: [
+```ts title="providers.ts"
+export default [
     addActionBarItem({
-     id: 'print-invoice'
-     label: 'Print Invoice',
-     locationId: 'order-detail',
-     routerLink: ['/extensions/invoicing'],
+        id: 'print-invoice',
+        label: 'Print Invoice',
+        locationId: 'order-detail',
+        routerLink: ['/extensions/invoicing'],
     }),
-  ],
-})
-export class MyUiExtensionModule {}
+];
 ```
 
 ```ts title="Signature"

+ 1 - 1
docs/docs/reference/admin-ui-api/action-bar/page-location-id.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## PageLocationId
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/component-registry-types.ts" sourceLine="51" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/component-registry-types.ts" sourceLine="52" packageName="@vendure/admin-ui" />
 
 The valid locationIds for registering action bar items or tabs.
 

+ 20 - 0
docs/docs/reference/admin-ui-api/action-bar/router-link-definition.md

@@ -0,0 +1,20 @@
+---
+title: "RouterLinkDefinition"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## RouterLinkDefinition
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="128" packageName="@vendure/admin-ui" />
+
+A function which returns the router link for an <a href='/reference/admin-ui-api/action-bar/action-bar-item#actionbaritem'>ActionBarItem</a> or <a href='/reference/admin-ui-api/nav-menu/nav-menu-item#navmenuitem'>NavMenuItem</a>.
+
+```ts title="Signature"
+type RouterLinkDefinition = ((route: ActivatedRoute, context: ActionBarContext) => any[]) | any[]
+```

+ 25 - 29
docs/docs/reference/admin-ui-api/bulk-actions/register-bulk-action.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## registerBulkAction
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/bulk-action-registry/register-bulk-action.ts" sourceLine="56" packageName="@vendure/admin-ui" since="1.8.0" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/register-bulk-action.ts" sourceLine="52" packageName="@vendure/admin-ui" since="1.8.0" />
 
 Registers a custom <a href='/reference/admin-ui-api/bulk-actions/bulk-action#bulkaction'>BulkAction</a> which can be invoked from the bulk action menu
 of any supported list view.
@@ -25,39 +25,35 @@ translation via a custom service which integrates with the translation service's
 
 *Example*
 
-```ts
-import { NgModule } from '@angular/core';
+```ts title="providers.ts"
 import { ModalService, registerBulkAction, SharedModule } from '@vendure/admin-ui/core';
+import { ProductDataTranslationService } from './product-data-translation.service';
 
-@NgModule({
-  imports: [SharedModule],
-  providers: [
+export default [
     ProductDataTranslationService,
     registerBulkAction({
-      location: 'product-list',
-      label: 'Send to translation service',
-      icon: 'language',
-      onClick: ({ injector, selection }) => {
-        const modalService = injector.get(ModalService);
-        const translationService = injector.get(ProductDataTranslationService);
-        modalService
-          .dialog({
-            title: `Send ${selection.length} products for translation?`,
-            buttons: [
-              { type: 'secondary', label: 'cancel' },
-              { type: 'primary', label: 'send', returnValue: true },
-            ],
-          })
-          .subscribe(response => {
-            if (response) {
-              translationService.sendForTranslation(selection.map(item => item.productId));
-            }
-          });
-      },
+        location: 'product-list',
+        label: 'Send to translation service',
+        icon: 'language',
+        onClick: ({ injector, selection }) => {
+            const modalService = injector.get(ModalService);
+            const translationService = injector.get(ProductDataTranslationService);
+            modalService
+                .dialog({
+                    title: `Send ${selection.length} products for translation?`,
+                    buttons: [
+                        { type: 'secondary', label: 'cancel' },
+                        { type: 'primary', label: 'send', returnValue: true },
+                    ],
+                })
+                .subscribe(response => {
+                    if (response) {
+                        translationService.sendForTranslation(selection.map(item => item.productId));
+                    }
+                });
+        },
     }),
-  ],
-})
-export class MyUiExtensionModule {}
+];
 ```
 
 ```ts title="Signature"

+ 2 - 2
docs/docs/reference/admin-ui-api/components/asset-picker-dialog-component.md

@@ -58,7 +58,7 @@ class AssetPickerDialogComponent implements OnInit, AfterViewInit, OnDestroy, Di
     createAssets(files: File[]) => ;
 }
 ```
-* Implements: <code>OnInit</code>, <code>AfterViewInit</code>, <code>OnDestroy</code>, <code><a href='/reference/admin-ui-api/providers/modal-service#dialog'>Dialog</a>&#60;<a href='/reference/typescript-api/entities/asset#asset'>Asset</a>[]&#62;</code>
+* Implements: <code>OnInit</code>, <code>AfterViewInit</code>, <code>OnDestroy</code>, <code><a href='/reference/admin-ui-api/services/modal-service#dialog'>Dialog</a>&#60;<a href='/reference/typescript-api/entities/asset#asset'>Asset</a>[]&#62;</code>
 
 
 
@@ -116,7 +116,7 @@ class AssetPickerDialogComponent implements OnInit, AfterViewInit, OnDestroy, Di
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, notificationService: <a href='/reference/admin-ui-api/providers/notification-service#notificationservice'>NotificationService</a>) => AssetPickerDialogComponent`}   />
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, notificationService: <a href='/reference/admin-ui-api/services/notification-service#notificationservice'>NotificationService</a>) => AssetPickerDialogComponent`}   />
 
 
 ### ngOnInit

+ 1 - 1
docs/docs/reference/admin-ui-api/components/currency-input-component.md

@@ -113,7 +113,7 @@ class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnChanges,
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, changeDetectorRef: ChangeDetectorRef) => CurrencyInputComponent`}   />
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, changeDetectorRef: ChangeDetectorRef) => CurrencyInputComponent`}   />
 
 
 ### ngOnInit

+ 40 - 4
docs/docs/reference/admin-ui-api/components/data-table2component.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## DataTable2Component
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts" sourceLine="92" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts" sourceLine="101" packageName="@vendure/admin-ui" />
 
 A table for displaying PaginatedList results. It is designed to be used inside components which
 extend the <a href='/reference/admin-ui-api/list-detail-views/base-list-component#baselistcomponent'>BaseListComponent</a> or <a href='/reference/admin-ui-api/list-detail-views/typed-base-list-component#typedbaselistcomponent'>TypedBaseListComponent</a> class.
@@ -74,7 +74,7 @@ extend the <a href='/reference/admin-ui-api/list-detail-views/base-list-componen
 
 ```ts title="Signature"
 class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
-    @Input() id: string;
+    @Input() id: DataTableLocationId;
     @Input() items: T[];
     @Input() itemsPerPage: number;
     @Input() currentPage: number;
@@ -91,12 +91,18 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
     @ContentChild(BulkActionMenuComponent) bulkActionMenuComponent: BulkActionMenuComponent;
     @ContentChild('vdrDt2CustomSearch') customSearchTemplate: TemplateRef<any>;
     @ContentChildren(TemplateRef) templateRefs: QueryList<TemplateRef<any>>;
+    injector = inject(Injector);
+    route = inject(ActivatedRoute);
+    filterPresetService = inject(FilterPresetService);
+    dataTableCustomComponentService = inject(DataTableCustomComponentService);
+    protected customComponents = new Map<string, { config: DataTableComponentConfig; injector: Injector }>();
     rowTemplate: TemplateRef<any>;
     currentStart: number;
     currentEnd: number;
     disableSelect = false;
     showSearchFilterRow = false;
     protected uiLanguage$: Observable<LanguageCode>;
+    protected destroy$ = new Subject<void>();
     constructor(changeDetectorRef: ChangeDetectorRef, localStorageService: LocalStorageService, dataService: DataService)
     selectionManager: void
     allColumns: void
@@ -122,7 +128,7 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
 
 ### id
 
-<MemberInfo kind="property" type={`string`}   />
+<MemberInfo kind="property" type={`DataTableLocationId`}   />
 
 
 ### items
@@ -200,6 +206,31 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
 <MemberInfo kind="property" type={`QueryList&#60;TemplateRef&#60;any&#62;&#62;`}   />
 
 
+### injector
+
+<MemberInfo kind="property" type={``}   />
+
+
+### route
+
+<MemberInfo kind="property" type={``}   />
+
+
+### filterPresetService
+
+<MemberInfo kind="property" type={``}   />
+
+
+### dataTableCustomComponentService
+
+<MemberInfo kind="property" type={``}   />
+
+
+### customComponents
+
+<MemberInfo kind="property" type={``}   />
+
+
 ### rowTemplate
 
 <MemberInfo kind="property" type={`TemplateRef&#60;any&#62;`}   />
@@ -230,9 +261,14 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
 <MemberInfo kind="property" type={`Observable&#60;<a href='/reference/typescript-api/common/language-code#languagecode'>LanguageCode</a>&#62;`}   />
 
 
+### destroy$
+
+<MemberInfo kind="property" type={``}   />
+
+
 ### constructor
 
-<MemberInfo kind="method" type={`(changeDetectorRef: ChangeDetectorRef, localStorageService: LocalStorageService, dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>) => DataTable2Component`}   />
+<MemberInfo kind="method" type={`(changeDetectorRef: ChangeDetectorRef, localStorageService: LocalStorageService, dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => DataTable2Component`}   />
 
 
 ### selectionManager

+ 1 - 1
docs/docs/reference/admin-ui-api/components/facet-value-selector-component.md

@@ -121,7 +121,7 @@ class FacetValueSelectorComponent implements OnInit, OnDestroy, ControlValueAcce
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, changeDetectorRef: ChangeDetectorRef) => FacetValueSelectorComponent`}   />
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, changeDetectorRef: ChangeDetectorRef) => FacetValueSelectorComponent`}   />
 
 
 ### ngOnInit

+ 1 - 1
docs/docs/reference/admin-ui-api/components/product-variant-selector-component.md

@@ -61,7 +61,7 @@ class ProductVariantSelectorComponent implements OnInit {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>) => ProductVariantSelectorComponent`}   />
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => ProductVariantSelectorComponent`}   />
 
 
 ### ngOnInit

+ 1 - 1
docs/docs/reference/admin-ui-api/components/zone-selector-component.md

@@ -90,7 +90,7 @@ class ZoneSelectorComponent implements ControlValueAccessor {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, changeDetectorRef: ChangeDetectorRef) => ZoneSelectorComponent`}   />
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, changeDetectorRef: ChangeDetectorRef) => ZoneSelectorComponent`}   />
 
 
 ### onChange

+ 6 - 0
docs/docs/reference/admin-ui-api/custom-detail-components/custom-detail-component-config.md

@@ -19,6 +19,7 @@ Configures a <a href='/reference/admin-ui-api/custom-detail-components/custom-de
 interface CustomDetailComponentConfig {
     locationId: CustomDetailComponentLocationId;
     component: Type<CustomDetailComponent>;
+    providers?: Provider[];
 }
 ```
 
@@ -34,6 +35,11 @@ interface CustomDetailComponentConfig {
 <MemberInfo kind="property" type={`Type&#60;<a href='/reference/admin-ui-api/custom-detail-components/custom-detail-component#customdetailcomponent'>CustomDetailComponent</a>&#62;`}   />
 
 
+### providers
+
+<MemberInfo kind="property" type={`Provider[]`}   />
+
+
 
 
 </div>

+ 1 - 1
docs/docs/reference/admin-ui-api/custom-detail-components/custom-detail-component-location-id.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## CustomDetailComponentLocationId
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/component-registry-types.ts" sourceLine="111" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/component-registry-types.ts" sourceLine="112" packageName="@vendure/admin-ui" />
 
 The valid locations for embedding a <a href='/reference/admin-ui-api/custom-detail-components/custom-detail-component#customdetailcomponent'>CustomDetailComponent</a>.
 

+ 47 - 1
docs/docs/reference/admin-ui-api/custom-detail-components/register-custom-detail-component.md

@@ -11,11 +11,57 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## registerCustomDetailComponent
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/custom-detail-component/custom-detail-component.service.ts" sourceLine="12" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/register-custom-detail-component.ts" sourceLine="57" packageName="@vendure/admin-ui" />
 
 Registers a <a href='/reference/admin-ui-api/custom-detail-components/custom-detail-component#customdetailcomponent'>CustomDetailComponent</a> to be placed in a given location. This allows you
 to embed any type of custom Angular component in the entity detail pages of the Admin UI.
 
+*Example*
+
+```ts
+import { Component, OnInit } from '@angular/core';
+import { switchMap } from 'rxjs';
+import { FormGroup } from '@angular/forms';
+import { CustomFieldConfig } from '@vendure/common/lib/generated-types';
+import {
+    DataService,
+    SharedModule,
+    CustomDetailComponent,
+    registerCustomDetailComponent,
+    GetProductWithVariants
+} from '@vendure/admin-ui/core';
+
+@Component({
+    template: `{{ extraInfo$ | async | json }}`,
+    standalone: true,
+    imports: [SharedModule],
+})
+export class ProductInfoComponent implements CustomDetailComponent, OnInit {
+    // These two properties are provided by Vendure and will vary
+    // depending on the particular detail page you are embedding this
+    // component into.
+    entity$: Observable<GetProductWithVariants.Product>
+    detailForm: FormGroup;
+
+    extraInfo$: Observable<any>;
+
+    constructor(private cmsDataService: CmsDataService) {}
+
+    ngOnInit() {
+        this.extraInfo$ = this.entity$.pipe(
+            switchMap(entity => this.cmsDataService.getDataFor(entity.id))
+        );
+    }
+}
+
+export default [
+    registerCustomDetailComponent({
+        locationId: 'product-detail',
+        component: ProductInfoComponent,
+    }),
+];
+```
+
 ```ts title="Signature"
 function registerCustomDetailComponent(config: CustomDetailComponentConfig): Provider
 ```

+ 57 - 1
docs/docs/reference/admin-ui-api/custom-history-entry-components/register-history-entry-component.md

@@ -11,11 +11,67 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## registerHistoryEntryComponent
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/custom-history-entry-component/history-entry-component.service.ts" sourceLine="13" packageName="@vendure/admin-ui" since="1.9.0" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/register-history-entry-component.ts" sourceLine="68" packageName="@vendure/admin-ui" since="1.9.0" />
 
 Registers a <a href='/reference/admin-ui-api/custom-history-entry-components/history-entry-component#historyentrycomponent'>HistoryEntryComponent</a> for displaying history entries in the Order/Customer
 history timeline.
 
+*Example*
+
+```ts
+import { Component } from '@angular/core';
+import {
+    CustomerFragment,
+    CustomerHistoryEntryComponent,
+    registerHistoryEntryComponent,
+    SharedModule,
+    TimelineDisplayType,
+    TimelineHistoryEntry,
+} from '@vendure/admin-ui/core';
+
+@Component({
+    selector: 'tax-id-verification-component',
+    template: `
+        <div *ngIf="entry.data.valid">
+          Tax ID <strong>{{ entry.data.taxId }}</strong> was verified
+          <vdr-history-entry-detail *ngIf="entry.data">
+            <vdr-object-tree [value]="entry.data"></vdr-object-tree>
+          </vdr-history-entry-detail>
+        </div>
+        <div *ngIf="entry.data.valid">Tax ID {{ entry.data.taxId }} could not be verified</div>
+    `,
+    standalone: true,
+    imports: [SharedModule],
+})
+class TaxIdHistoryEntryComponent implements CustomerHistoryEntryComponent {
+    entry: TimelineHistoryEntry;
+    customer: CustomerFragment;
+
+    getDisplayType(entry: TimelineHistoryEntry): TimelineDisplayType {
+        return entry.data.valid ? 'success' : 'error';
+    }
+
+    getName(entry: TimelineHistoryEntry): string {
+        return 'Tax ID Verification Plugin';
+    }
+
+    isFeatured(entry: TimelineHistoryEntry): boolean {
+        return true;
+    }
+
+    getIconShape(entry: TimelineHistoryEntry) {
+        return entry.data.valid ? 'check-circle' : 'exclamation-circle';
+    }
+}
+
+export default [
+    registerHistoryEntryComponent({
+        type: 'CUSTOMER_TAX_ID_VERIFICATION',
+        component: TaxIdHistoryEntryComponent,
+    }),
+];
+```
+
 ```ts title="Signature"
 function registerHistoryEntryComponent(config: HistoryEntryConfig): Provider
 ```

+ 4 - 4
docs/docs/reference/admin-ui-api/custom-input-components/default-inputs.md

@@ -264,7 +264,7 @@ class CurrencyFormInputComponent implements FormInputComponent {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>) => CurrencyFormInputComponent`}   />
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => CurrencyFormInputComponent`}   />
 
 
 
@@ -325,7 +325,7 @@ class CustomerGroupFormInputComponent implements FormInputComponent, OnInit {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>) => CustomerGroupFormInputComponent`}   />
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => CustomerGroupFormInputComponent`}   />
 
 
 ### ngOnInit
@@ -655,7 +655,7 @@ class ProductSelectorFormInputComponent implements FormInputComponent, OnInit {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>) => ProductSelectorFormInputComponent`}   />
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => ProductSelectorFormInputComponent`}   />
 
 
 ### ngOnInit
@@ -828,7 +828,7 @@ class SelectFormInputComponent implements FormInputComponent, OnInit {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>) => SelectFormInputComponent`}   />
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => SelectFormInputComponent`}   />
 
 
 ### ngOnInit

+ 1 - 1
docs/docs/reference/admin-ui-api/custom-input-components/form-input-component.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## FormInputComponent
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/component-registry-types.ts" sourceLine="10" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/component-registry-types.ts" sourceLine="11" packageName="@vendure/admin-ui" />
 
 This interface should be implemented by any component being used as a custom input. For example,
 inputs for custom fields, or for configurable arguments.

+ 14 - 13
docs/docs/reference/admin-ui-api/custom-input-components/register-form-input-component.md

@@ -11,37 +11,38 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## registerFormInputComponent
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/register-dynamic-input-components.ts" sourceLine="96" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/register-form-input-component.ts" sourceLine="53" packageName="@vendure/admin-ui" />
 
 Registers a custom FormInputComponent which can be used to control the argument inputs
-of a <a href='/reference/typescript-api/configurable-operation-def/#configurableoperationdef'>ConfigurableOperationDef</a> (e.g. CollectionFilter, ShippingMethod etc) or for
+of a <a href='/reference/typescript-api/configurable-operation-def/#configurableoperationdef'>ConfigurableOperationDef</a> (e.g. CollectionFilter, ShippingMethod etc.) or for
 a custom field.
 
 *Example*
 
-```ts
-@NgModule({
-  imports: [SharedModule],
-  declarations: [MyCustomFieldControl],
-  providers: [
-      registerFormInputComponent('my-custom-input', MyCustomFieldControl),
-  ],
-})
-export class MyUiExtensionModule {}
+```ts title="providers.ts"
+import { registerFormInputComponent } from '@vendure/admin-ui/core';
+
+export default [
+    // highlight-next-line
+    registerFormInputComponent('my-custom-input', MyCustomFieldControl),
+];
 ```
 
 This input component can then be used in a custom field:
 
 *Example*
 
-```ts
-const config = {
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+
+const config: VendureConfig = {
   // ...
   customFields: {
     ProductVariant: [
       {
         name: 'rrp',
         type: 'int',
+        // highlight-next-line
         ui: { component: 'my-custom-input' },
       },
     ]

+ 36 - 0
docs/docs/reference/admin-ui-api/custom-table-components/custom-column-component.md

@@ -0,0 +1,36 @@
+---
+title: "CustomColumnComponent"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## CustomColumnComponent
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table-custom-component.service.ts" sourceLine="43" packageName="@vendure/admin-ui" />
+
+Components which are to be used to render custom cells in a data table should implement this interface.
+
+The `rowItem` property is the data object for the row, e.g. the `Product` object if used
+in the `product-list` table.
+
+```ts title="Signature"
+interface CustomColumnComponent {
+    rowItem: any;
+}
+```
+
+<div className="members-wrapper">
+
+### rowItem
+
+<MemberInfo kind="property" type={`any`}   />
+
+
+
+
+</div>

+ 52 - 0
docs/docs/reference/admin-ui-api/custom-table-components/data-table-component-config.md

@@ -0,0 +1,52 @@
+---
+title: "DataTableComponentConfig"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## DataTableComponentConfig
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table-custom-component.service.ts" sourceLine="53" packageName="@vendure/admin-ui" />
+
+Configures a <a href='/reference/admin-ui-api/custom-detail-components/custom-detail-component#customdetailcomponent'>CustomDetailComponent</a> to be placed in the given location.
+
+```ts title="Signature"
+interface DataTableComponentConfig {
+    tableId: DataTableLocationId;
+    columnId: DataTableColumnId;
+    component: Type<CustomColumnComponent>;
+    providers?: Provider[];
+}
+```
+
+<div className="members-wrapper">
+
+### tableId
+
+<MemberInfo kind="property" type={`DataTableLocationId`}   />
+
+The location in the UI where the custom component should be placed.
+### columnId
+
+<MemberInfo kind="property" type={`DataTableColumnId`}   />
+
+The column in the table where the custom component should be placed.
+### component
+
+<MemberInfo kind="property" type={`Type&#60;<a href='/reference/admin-ui-api/custom-table-components/custom-column-component#customcolumncomponent'>CustomColumnComponent</a>&#62;`}   />
+
+The component to render in the table cell. This component should implement the
+<a href='/reference/admin-ui-api/custom-table-components/custom-column-component#customcolumncomponent'>CustomColumnComponent</a> interface.
+### providers
+
+<MemberInfo kind="property" type={`Provider[]`}   />
+
+
+
+
+</div>

+ 14 - 0
docs/docs/reference/admin-ui-api/custom-table-components/index.md

@@ -0,0 +1,14 @@
+---
+title: "Custom Table Components"
+isDefaultIndex: true
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+import DocCardList from '@theme/DocCardList';
+
+<DocCardList />

+ 59 - 0
docs/docs/reference/admin-ui-api/custom-table-components/register-data-table-component.md

@@ -0,0 +1,59 @@
+---
+title: "RegisterDataTableComponent"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## registerDataTableComponent
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/register-data-table-component.ts" sourceLine="45" packageName="@vendure/admin-ui" />
+
+Allows you to override the default component used to render the data of a particular column in a DataTable.
+The component should implement the {@link CustomDataTableColumnComponent} interface. The tableId and columnId can
+be determined by pressing `ctrl + u` when running the Admin UI in dev mode.
+
+*Example*
+
+```ts title="components/custom-table.component.ts"
+import { Component, Input } from '@angular/core';
+import { CustomColumnComponent } from '@vendure/admin-ui/core';
+
+@Component({
+    selector: 'custom-slug-component',
+    template: `
+        <a [href]="'https://example.com/products/' + rowItem.slug" target="_blank">{{ rowItem.slug }}</a>
+    `,
+    standalone: true,
+})
+export class CustomTableComponent implements CustomColumnComponent {
+    @Input() rowItem: any;
+}
+```
+
+```ts title="providers.ts"
+import { registerDataTableComponent } from '@vendure/admin-ui/core';
+import { CustomTableComponent } from './components/custom-table.component';
+
+export default [
+    registerDataTableComponent({
+        component: CustomTableComponent,
+        tableId: 'product-list',
+        columnId: 'slug',
+    }),
+];
+```
+
+```ts title="Signature"
+function registerDataTableComponent(config: DataTableComponentConfig): void
+```
+Parameters
+
+### config
+
+<MemberInfo kind="parameter" type={`<a href='/reference/admin-ui-api/custom-table-components/data-table-component-config#datatablecomponentconfig'>DataTableComponentConfig</a>`} />
+

+ 64 - 0
docs/docs/reference/admin-ui-api/dashboard-widgets/dashboard-widget-config.md

@@ -0,0 +1,64 @@
+---
+title: "DashboardWidgetConfig"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## DashboardWidgetConfig
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/dashboard-widget/dashboard-widget-types.ts" sourceLine="11" packageName="@vendure/admin-ui" />
+
+A configuration object for a dashboard widget.
+
+```ts title="Signature"
+interface DashboardWidgetConfig {
+    loadComponent: () => Promise<Type<any>> | Type<any>;
+    title?: string;
+    supportedWidths?: DashboardWidgetWidth[];
+    requiresPermissions?: string[];
+}
+```
+
+<div className="members-wrapper">
+
+### loadComponent
+
+<MemberInfo kind="property" type={`() =&#62; Promise&#60;Type&#60;any&#62;&#62; | Type&#60;any&#62;`}   />
+
+Used to specify the widget component. Supports both eager- and lazy-loading.
+
+*Example*
+
+```ts
+// eager-loading
+loadComponent: () => MyWidgetComponent,
+
+// lazy-loading
+loadComponent: () => import('./path-to/widget.component').then(m => m.MyWidgetComponent),
+```
+### title
+
+<MemberInfo kind="property" type={`string`}   />
+
+The title of the widget. Can be a translation token as it will get passed
+through the `translate` pipe.
+### supportedWidths
+
+<MemberInfo kind="property" type={`DashboardWidgetWidth[]`}   />
+
+The supported widths of the widget, in terms of a Bootstrap-style 12-column grid.
+If omitted, then it is assumed the widget supports all widths.
+### requiresPermissions
+
+<MemberInfo kind="property" type={`string[]`}   />
+
+If set, the widget will only be displayed if the current user has all the
+specified permissions.
+
+
+</div>

+ 14 - 0
docs/docs/reference/admin-ui-api/dashboard-widgets/index.md

@@ -0,0 +1,14 @@
+---
+title: "Dashboard Widgets"
+isDefaultIndex: true
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+import DocCardList from '@theme/DocCardList';
+
+<DocCardList />

+ 31 - 0
docs/docs/reference/admin-ui-api/dashboard-widgets/register-dashboard-widget.md

@@ -0,0 +1,31 @@
+---
+title: "RegisterDashboardWidget"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## registerDashboardWidget
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/register-dashboard-widget.ts" sourceLine="16" packageName="@vendure/admin-ui" />
+
+Registers a dashboard widget. Once registered, the widget can be set as part of the default
+(using <a href='/reference/admin-ui-api/dashboard-widgets/set-dashboard-widget-layout#setdashboardwidgetlayout'>setDashboardWidgetLayout</a>).
+
+```ts title="Signature"
+function registerDashboardWidget(id: string, config: DashboardWidgetConfig): FactoryProvider
+```
+Parameters
+
+### id
+
+<MemberInfo kind="parameter" type={`string`} />
+
+### config
+
+<MemberInfo kind="parameter" type={`<a href='/reference/admin-ui-api/dashboard-widgets/dashboard-widget-config#dashboardwidgetconfig'>DashboardWidgetConfig</a>`} />
+

+ 26 - 0
docs/docs/reference/admin-ui-api/dashboard-widgets/set-dashboard-widget-layout.md

@@ -0,0 +1,26 @@
+---
+title: "SetDashboardWidgetLayout"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## setDashboardWidgetLayout
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/register-dashboard-widget.ts" sourceLine="33" packageName="@vendure/admin-ui" />
+
+Sets the default widget layout for the Admin UI dashboard.
+
+```ts title="Signature"
+function setDashboardWidgetLayout(layoutDef: WidgetLayoutDefinition): FactoryProvider
+```
+Parameters
+
+### layoutDef
+
+<MemberInfo kind="parameter" type={`<a href='/reference/admin-ui-api/dashboard-widgets/widget-layout-definition#widgetlayoutdefinition'>WidgetLayoutDefinition</a>`} />
+

+ 20 - 0
docs/docs/reference/admin-ui-api/dashboard-widgets/widget-layout-definition.md

@@ -0,0 +1,20 @@
+---
+title: "WidgetLayoutDefinition"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## WidgetLayoutDefinition
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/dashboard-widget/dashboard-widget-types.ts" sourceLine="51" packageName="@vendure/admin-ui" />
+
+A configuration object for the default dashboard widget layout.
+
+```ts title="Signature"
+type WidgetLayoutDefinition = Array<{ id: string; width: DashboardWidgetWidth }>
+```

+ 1 - 1
docs/docs/reference/admin-ui-api/directives/if-multichannel-directive.md

@@ -37,7 +37,7 @@ class IfMultichannelDirective extends IfDirectiveBase<[]> {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(_viewContainer: ViewContainerRef, templateRef: TemplateRef&#60;any&#62;, dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>) => IfMultichannelDirective`}   />
+<MemberInfo kind="method" type={`(_viewContainer: ViewContainerRef, templateRef: TemplateRef&#60;any&#62;, dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => IfMultichannelDirective`}   />
 
 
 

+ 1 - 1
docs/docs/reference/admin-ui-api/directives/if-permissions-directive.md

@@ -39,7 +39,7 @@ class IfPermissionsDirective extends IfDirectiveBase<Array<Permission[] | null>>
 
 ### constructor
 
-<MemberInfo kind="method" type={`(_viewContainer: ViewContainerRef, templateRef: TemplateRef&#60;any&#62;, dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, changeDetectorRef: ChangeDetectorRef) => IfPermissionsDirective`}   />
+<MemberInfo kind="method" type={`(_viewContainer: ViewContainerRef, templateRef: TemplateRef&#60;any&#62;, dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, changeDetectorRef: ChangeDetectorRef) => IfPermissionsDirective`}   />
 
 
 

+ 1 - 1
docs/docs/reference/admin-ui-api/list-detail-views/base-detail-component.md

@@ -119,7 +119,7 @@ class BaseDetailComponent<Entity extends { id: string; updatedAt?: string }> imp
 
 ### constructor
 
-<MemberInfo kind="method" type={`(route: ActivatedRoute, router: Router, serverConfigService: ServerConfigService, dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>) => BaseDetailComponent`}   />
+<MemberInfo kind="method" type={`(route: ActivatedRoute, router: Router, serverConfigService: ServerConfigService, dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => BaseDetailComponent`}   />
 
 
 ### init

+ 8 - 12
docs/docs/reference/admin-ui-api/nav-menu/add-nav-menu-item.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## addNavMenuItem
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder.service.ts" sourceLine="84" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/add-nav-menu-item.ts" sourceLine="64" packageName="@vendure/admin-ui" />
 
 Add a menu item to an existing section specified by `sectionId`. The id of the section
 can be found by inspecting the DOM and finding the `data-section-id` attribute.
@@ -23,20 +23,16 @@ This should be used in the NgModule `providers` array of your ui extension modul
 
 *Example*
 
-```ts
-@NgModule({
-  imports: [SharedModule],
-  providers: [
+```ts title="providers.ts"
+export default [
     addNavMenuItem({
-      id: 'reviews',
-      label: 'Product Reviews',
-      routerLink: ['/extensions/reviews'],
-      icon: 'star',
+        id: 'reviews',
+        label: 'Product Reviews',
+        routerLink: ['/extensions/reviews'],
+        icon: 'star',
     },
     'marketing'),
-  ],
-})
-export class MyUiExtensionModule {}
+];
 ``
 
 ```ts title="Signature"

+ 9 - 13
docs/docs/reference/admin-ui-api/nav-menu/add-nav-menu-section.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## addNavMenuSection
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder.service.ts" sourceLine="44" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/add-nav-menu-item.ts" sourceLine="28" packageName="@vendure/admin-ui" />
 
 Add a section to the main nav menu. Providing the `before` argument will
 move the section before any existing section with the specified id. If
@@ -21,21 +21,17 @@ This should be used in the NgModule `providers` array of your ui extension modul
 
 *Example*
 
-```ts
-@NgModule({
-  imports: [SharedModule],
-  providers: [
+```ts title="providers.ts"
+export default [
     addNavMenuSection({
-      id: 'reports',
-      label: 'Reports',
-      items: [{
-          // ...
-      }],
+        id: 'reports',
+        label: 'Reports',
+        items: [{
+            // ...
+        }],
     },
     'settings'),
-  ],
-})
-export class MyUiExtensionModule {}
+];
 ```
 
 ```ts title="Signature"

+ 2 - 2
docs/docs/reference/admin-ui-api/nav-menu/nav-menu-item.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## NavMenuItem
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="36" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="37" packageName="@vendure/admin-ui" />
 
 A NavMenuItem is a menu item in the main (left-hand side) nav
 bar.
@@ -42,7 +42,7 @@ interface NavMenuItem {
 
 ### routerLink
 
-<MemberInfo kind="property" type={`RouterLinkDefinition`}   />
+<MemberInfo kind="property" type={`<a href='/reference/admin-ui-api/action-bar/router-link-definition#routerlinkdefinition'>RouterLinkDefinition</a>`}   />
 
 
 ### onClick

+ 1 - 1
docs/docs/reference/admin-ui-api/nav-menu/nav-menu-section.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## NavMenuSection
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="56" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="57" packageName="@vendure/admin-ui" />
 
 A NavMenuSection is a grouping of links in the main
 (left-hand side) nav bar.

+ 1 - 1
docs/docs/reference/admin-ui-api/nav-menu/navigation-types.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## NavMenuBadge
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="18" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="19" packageName="@vendure/admin-ui" />
 
 A color-coded notification badge which will be displayed by the
 NavMenuItem's icon.

+ 1 - 1
docs/docs/reference/admin-ui-api/pipes/has-permission-pipe.md

@@ -37,7 +37,7 @@ class HasPermissionPipe implements PipeTransform, OnDestroy {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, changeDetectorRef: ChangeDetectorRef) => HasPermissionPipe`}   />
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, changeDetectorRef: ChangeDetectorRef) => HasPermissionPipe`}   />
 
 
 ### transform

+ 1 - 1
docs/docs/reference/admin-ui-api/pipes/locale-currency-name-pipe.md

@@ -38,7 +38,7 @@ class LocaleCurrencyNamePipe extends LocaleBasePipe implements PipeTransform {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService?: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, changeDetectorRef?: ChangeDetectorRef) => LocaleCurrencyNamePipe`}   />
+<MemberInfo kind="method" type={`(dataService?: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, changeDetectorRef?: ChangeDetectorRef) => LocaleCurrencyNamePipe`}   />
 
 
 ### transform

+ 1 - 1
docs/docs/reference/admin-ui-api/pipes/locale-currency-pipe.md

@@ -39,7 +39,7 @@ class LocaleCurrencyPipe extends LocaleBasePipe implements PipeTransform {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService?: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, changeDetectorRef?: ChangeDetectorRef) => LocaleCurrencyPipe`}   />
+<MemberInfo kind="method" type={`(dataService?: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, changeDetectorRef?: ChangeDetectorRef) => LocaleCurrencyPipe`}   />
 
 
 ### transform

+ 1 - 1
docs/docs/reference/admin-ui-api/pipes/locale-date-pipe.md

@@ -39,7 +39,7 @@ class LocaleDatePipe extends LocaleBasePipe implements PipeTransform {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService?: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, changeDetectorRef?: ChangeDetectorRef) => LocaleDatePipe`}   />
+<MemberInfo kind="method" type={`(dataService?: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, changeDetectorRef?: ChangeDetectorRef) => LocaleDatePipe`}   />
 
 
 ### transform

+ 1 - 1
docs/docs/reference/admin-ui-api/pipes/locale-language-name-pipe.md

@@ -38,7 +38,7 @@ class LocaleLanguageNamePipe extends LocaleBasePipe implements PipeTransform {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService?: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, changeDetectorRef?: ChangeDetectorRef) => LocaleLanguageNamePipe`}   />
+<MemberInfo kind="method" type={`(dataService?: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, changeDetectorRef?: ChangeDetectorRef) => LocaleLanguageNamePipe`}   />
 
 
 ### transform

+ 1 - 1
docs/docs/reference/admin-ui-api/pipes/locale-region-name-pipe.md

@@ -38,7 +38,7 @@ class LocaleRegionNamePipe extends LocaleBasePipe implements PipeTransform {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(dataService?: <a href='/reference/admin-ui-api/providers/data-service#dataservice'>DataService</a>, changeDetectorRef?: ChangeDetectorRef) => LocaleRegionNamePipe`}   />
+<MemberInfo kind="method" type={`(dataService?: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>, changeDetectorRef?: ChangeDetectorRef) => LocaleRegionNamePipe`}   />
 
 
 ### transform

+ 14 - 0
docs/docs/reference/admin-ui-api/react-extensions/index.md

@@ -0,0 +1,14 @@
+---
+title: "React Extensions"
+isDefaultIndex: true
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+import DocCardList from '@theme/DocCardList';
+
+<DocCardList />

+ 45 - 0
docs/docs/reference/admin-ui-api/react-extensions/react-custom-detail-component-config.md

@@ -0,0 +1,45 @@
+---
+title: "ReactCustomDetailComponentConfig"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## ReactCustomDetailComponentConfig
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/register-react-custom-detail-component.ts" sourceLine="15" packageName="@vendure/admin-ui" />
+
+Configures a React-based component to be placed in a detail page in the given location.
+
+```ts title="Signature"
+interface ReactCustomDetailComponentConfig {
+    locationId: CustomDetailComponentLocationId;
+    component: ElementType;
+    props?: Record<string, any>;
+}
+```
+
+<div className="members-wrapper">
+
+### locationId
+
+<MemberInfo kind="property" type={`<a href='/reference/admin-ui-api/custom-detail-components/custom-detail-component-location-id#customdetailcomponentlocationid'>CustomDetailComponentLocationId</a>`}   />
+
+The id of the detail page location in which to place the component.
+### component
+
+<MemberInfo kind="property" type={`ElementType`}   />
+
+The React component to render.
+### props
+
+<MemberInfo kind="property" type={`Record&#60;string, any&#62;`}   />
+
+Optional props to pass to the React component.
+
+
+</div>

+ 52 - 0
docs/docs/reference/admin-ui-api/react-extensions/react-data-table-component-config.md

@@ -0,0 +1,52 @@
+---
+title: "ReactDataTableComponentConfig"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## ReactDataTableComponentConfig
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/register-react-data-table-component.ts" sourceLine="19" packageName="@vendure/admin-ui" />
+
+Configures a <a href='/reference/admin-ui-api/custom-detail-components/custom-detail-component#customdetailcomponent'>CustomDetailComponent</a> to be placed in the given location.
+
+```ts title="Signature"
+interface ReactDataTableComponentConfig {
+    tableId: DataTableLocationId;
+    columnId: DataTableColumnId;
+    component: ElementType;
+    props?: Record<string, any>;
+}
+```
+
+<div className="members-wrapper">
+
+### tableId
+
+<MemberInfo kind="property" type={`DataTableLocationId`}   />
+
+The location in the UI where the custom component should be placed.
+### columnId
+
+<MemberInfo kind="property" type={`DataTableColumnId`}   />
+
+The column in the table where the custom component should be placed.
+### component
+
+<MemberInfo kind="property" type={`ElementType`}   />
+
+The component to render in the table cell. This component will receive the `rowItem` prop
+which is the data object for the row, e.g. the `Product` object if used in the `product-list` table.
+### props
+
+<MemberInfo kind="property" type={`Record&#60;string, any&#62;`}   />
+
+Optional props to pass to the React component.
+
+
+</div>

+ 27 - 0
docs/docs/reference/admin-ui-api/react-extensions/register-react-custom-detail-component.md

@@ -0,0 +1,27 @@
+---
+title: "RegisterReactCustomDetailComponent"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## registerReactCustomDetailComponent
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/register-react-custom-detail-component.ts" sourceLine="40" packageName="@vendure/admin-ui" />
+
+Registers a React component to be rendered in a detail page in the given location.
+Components used as custom detail components can make use of the <a href='/reference/admin-ui-api/react-hooks/use-detail-component-data#usedetailcomponentdata'>useDetailComponentData</a> hook.
+
+```ts title="Signature"
+function registerReactCustomDetailComponent(config: ReactCustomDetailComponentConfig): void
+```
+Parameters
+
+### config
+
+<MemberInfo kind="parameter" type={`<a href='/reference/admin-ui-api/react-extensions/react-custom-detail-component-config#reactcustomdetailcomponentconfig'>ReactCustomDetailComponentConfig</a>`} />
+

+ 59 - 0
docs/docs/reference/admin-ui-api/react-extensions/register-react-data-table-component.md

@@ -0,0 +1,59 @@
+---
+title: "RegisterReactDataTableComponent"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## registerReactDataTableComponent
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/register-react-data-table-component.ts" sourceLine="90" packageName="@vendure/admin-ui" />
+
+Registers a React component to be rendered in a data table in the given location.
+The component will receive the `rowItem` prop which is the data object for the row,
+e.g. the `Product` object if used in the `product-list` table.
+
+*Example*
+
+```ts title="components/SlugWithLink.tsx"
+import { ReactDataTableComponentProps } from '@vendure/admin-ui/react';
+import React from 'react';
+
+export function SlugWithLink({ rowItem }: ReactDataTableComponentProps) {
+    return (
+        <a href={`https://example.com/products/${rowItem.slug}`} target="_blank">
+            {rowItem.slug}
+        </a>
+    );
+}
+```
+
+```ts title="providers.ts"
+import { registerReactDataTableComponent } from '@vendure/admin-ui/react';
+import { SlugWithLink } from './components/SlugWithLink';
+
+export default [
+    registerReactDataTableComponent({
+        component: SlugWithLink,
+        tableId: 'product-list',
+        columnId: 'slug',
+        props: {
+        foo: 'bar',
+        },
+    }),
+];
+```
+
+```ts title="Signature"
+function registerReactDataTableComponent(config: ReactDataTableComponentConfig): void
+```
+Parameters
+
+### config
+
+<MemberInfo kind="parameter" type={`<a href='/reference/admin-ui-api/react-extensions/react-data-table-component-config#reactdatatablecomponentconfig'>ReactDataTableComponentConfig</a>`} />
+

+ 30 - 0
docs/docs/reference/admin-ui-api/react-extensions/register-react-form-input-component.md

@@ -0,0 +1,30 @@
+---
+title: "RegisterReactFormInputComponent"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## registerReactFormInputComponent
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/register-react-form-input-component.ts" sourceLine="15" packageName="@vendure/admin-ui" />
+
+Registers a React component to be used as a <a href='/reference/admin-ui-api/custom-input-components/form-input-component#forminputcomponent'>FormInputComponent</a>.
+
+```ts title="Signature"
+function registerReactFormInputComponent(id: string, component: ElementType): FactoryProvider
+```
+Parameters
+
+### id
+
+<MemberInfo kind="parameter" type={`string`} />
+
+### component
+
+<MemberInfo kind="parameter" type={`ElementType`} />
+

+ 22 - 0
docs/docs/reference/admin-ui-api/react-extensions/register-react-route-component-options.md

@@ -0,0 +1,22 @@
+---
+title: "RegisterReactRouteComponentOptions"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## RegisterReactRouteComponentOptions
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/register-react-route-component.ts" sourceLine="15" packageName="@vendure/admin-ui" />
+
+Configuration for a React-based route component.
+
+```ts title="Signature"
+type RegisterReactRouteComponentOptions<Entity extends { id: string; updatedAt?: string }, T extends DocumentNode | TypedDocumentNode<any, { id: string }>, Field extends keyof ResultOf<T>, R extends Field> = RegisterRouteComponentOptions<ElementType, Entity, T, Field, R> & {
+    props?: Record<string, any>;
+}
+```

+ 26 - 0
docs/docs/reference/admin-ui-api/react-extensions/register-react-route-component.md

@@ -0,0 +1,26 @@
+---
+title: "RegisterReactRouteComponent"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## registerReactRouteComponent
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/register-react-route-component.ts" sourceLine="30" packageName="@vendure/admin-ui" />
+
+Registers a React component to be used as a route component.
+
+```ts title="Signature"
+function registerReactRouteComponent<Entity extends { id: string; updatedAt?: string }, T extends DocumentNode | TypedDocumentNode<any, { id: string }>, Field extends keyof ResultOf<T>, R extends Field>(options: RegisterReactRouteComponentOptions<Entity, T, Field, R>): Route
+```
+Parameters
+
+### options
+
+<MemberInfo kind="parameter" type={`<a href='/reference/admin-ui-api/react-extensions/register-react-route-component-options#registerreactroutecomponentoptions'>RegisterReactRouteComponentOptions</a>&#60;Entity, T, Field, R&#62;`} />
+

+ 14 - 0
docs/docs/reference/admin-ui-api/react-hooks/index.md

@@ -0,0 +1,14 @@
+---
+title: "React Hooks"
+isDefaultIndex: true
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+import DocCardList from '@theme/DocCardList';
+
+<DocCardList />

+ 43 - 0
docs/docs/reference/admin-ui-api/react-hooks/use-detail-component-data.md

@@ -0,0 +1,43 @@
+---
+title: "UseDetailComponentData"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## useDetailComponentData
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/react-hooks/use-detail-component-data.ts" sourceLine="34" packageName="@vendure/admin-ui" />
+
+Provides the data available to React-based CustomDetailComponents.
+
+*Example*
+
+```ts
+import { Card, useDetailComponentData } from '@vendure/admin-ui/react';
+import React from 'react';
+
+export function CustomDetailComponent(props: any) {
+    const { entity, detailForm } = useDetailComponentData();
+    const updateName = () => {
+        detailForm.get('name')?.setValue('New name');
+        detailForm.markAsDirty();
+    };
+    return (
+        <Card title={'Custom Detail Component'}>
+            <button className="button" onClick={updateName}>
+                Update name
+            </button>
+            <pre>{JSON.stringify(entity, null, 2)}</pre>
+        </Card>
+    );
+}
+```
+
+```ts title="Signature"
+function useDetailComponentData(): void
+```

+ 40 - 0
docs/docs/reference/admin-ui-api/react-hooks/use-form-control.md

@@ -0,0 +1,40 @@
+---
+title: "UseFormControl"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## useFormControl
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/react-hooks/use-form-control.ts" sourceLine="31" packageName="@vendure/admin-ui" />
+
+Provides access to the current FormControl value and a method to update the value.
+
+*Example*
+
+```ts
+import { useFormControl, ReactFormInputProps } from '@vendure/admin-ui/react';
+import React from 'react';
+
+export function ReactNumberInput({ readonly }: ReactFormInputProps) {
+    const { value, setFormValue } = useFormControl();
+
+    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        setFormValue(val);
+    };
+    return (
+        <div>
+            <input readOnly={readonly} type="number" onChange={handleChange} value={value} />
+        </div>
+    );
+}
+```
+
+```ts title="Signature"
+function useFormControl(): void
+```

+ 43 - 0
docs/docs/reference/admin-ui-api/react-hooks/use-injector.md

@@ -0,0 +1,43 @@
+---
+title: "UseInjector"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## useInjector
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/react-hooks/use-injector.ts" sourceLine="27" packageName="@vendure/admin-ui" />
+
+Exposes the Angular injector which allows the injection of services into React components.
+
+*Example*
+
+```ts
+import { useInjector } from '@vendure/admin-ui/react';
+import { NotificationService } from '@vendure/admin-ui/core';
+
+export const MyComponent = () => {
+    const notificationService = useInjector(NotificationService);
+
+    const handleClick = () => {
+        notificationService.success('Hello world!');
+    };
+    // ...
+    return <div>...</div>;
+}
+```
+
+```ts title="Signature"
+function useInjector<T = any>(token: ProviderToken<T>): T
+```
+Parameters
+
+### token
+
+<MemberInfo kind="parameter" type={`ProviderToken&#60;T&#62;`} />
+

+ 66 - 0
docs/docs/reference/admin-ui-api/react-hooks/use-mutation.md

@@ -0,0 +1,66 @@
+---
+title: "UseMutation"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## useMutation
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/react-hooks/use-query.ts" sourceLine="104" packageName="@vendure/admin-ui" />
+
+A React hook which allows you to execute a GraphQL mutation.
+
+*Example*
+
+```ts
+import { useMutation } from '@vendure/admin-ui/react';
+import { gql } from 'graphql-tag';
+
+const UPDATE_PRODUCT = gql`
+  mutation UpdateProduct($input: UpdateProductInput!) {
+    updateProduct(input: $input) {
+    id
+    name
+  }
+}`;
+
+export const MyComponent = () => {
+    const [updateProduct, { data, loading, error }] = useMutation(UPDATE_PRODUCT);
+
+    const handleClick = () => {
+        updateProduct({
+            input: {
+                id: '1',
+                name: 'New name',
+            },
+        }).then(result => {
+            // do something with the result
+        });
+    };
+
+    if (loading) return <div>Loading...</div>;
+    if (error) return <div>Error! { error }</div>;
+
+    return (
+    <div>
+        <button onClick={handleClick}>Update product</button>
+        {data && <div>Product updated!</div>}
+    </div>
+    );
+};
+```
+
+```ts title="Signature"
+function useMutation<T, V extends Record<string, any> = Record<string, any>>(mutation: DocumentNode | TypedDocumentNode<T, V>): void
+```
+Parameters
+
+### mutation
+
+<MemberInfo kind="parameter" type={`DocumentNode | TypedDocumentNode&#60;T, V&#62;`} />
+

+ 40 - 0
docs/docs/reference/admin-ui-api/react-hooks/use-page-metadata.md

@@ -0,0 +1,40 @@
+---
+title: "UsePageMetadata"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## usePageMetadata
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/react-hooks/use-page-metadata.ts" sourceLine="31" packageName="@vendure/admin-ui" />
+
+Provides functions for setting the current page title and breadcrumb.
+
+*Example*
+
+```ts
+import { usePageMetadata } from '@vendure/admin-ui/react';
+import { useEffect } from 'react';
+
+export const MyComponent = () => {
+    const { setTitle, setBreadcrumb } = usePageMetadata();
+    useEffect(() => {
+        setTitle('My Page');
+        setBreadcrumb([
+            { link: ['./parent'], label: 'Parent Page' },
+            { link: ['./'], label: 'This Page' },
+        ]);
+    }, []);
+    // ...
+    return <div>...</div>;
+}
+```
+
+```ts title="Signature"
+function usePageMetadata(): void
+```

+ 59 - 0
docs/docs/reference/admin-ui-api/react-hooks/use-query.md

@@ -0,0 +1,59 @@
+---
+title: "UseQuery"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## useQuery
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/react/src/react-hooks/use-query.ts" sourceLine="43" packageName="@vendure/admin-ui" />
+
+A React hook which provides access to the results of a GraphQL query.
+
+*Example*
+
+```ts
+import { useQuery } from '@vendure/admin-ui/react';
+import { gql } from 'graphql-tag';
+
+const GET_PRODUCT = gql`
+   query GetProduct($id: ID!) {
+     product(id: $id) {
+       id
+       name
+       description
+     }
+   }`;
+
+export const MyComponent = () => {
+    const { data, loading, error } = useQuery(GET_PRODUCT, { id: '1' });
+
+    if (loading) return <div>Loading...</div>;
+    if (error) return <div>Error! { error }</div>;
+    return (
+        <div>
+            <h1>{data.product.name}</h1>
+            <p>{data.product.description}</p>
+        </div>
+    );
+};
+```
+
+```ts title="Signature"
+function useQuery<T, V extends Record<string, any> = Record<string, any>>(query: DocumentNode | TypedDocumentNode<T, V>, variables?: V): void
+```
+Parameters
+
+### query
+
+<MemberInfo kind="parameter" type={`DocumentNode | TypedDocumentNode&#60;T, V&#62;`} />
+
+### variables
+
+<MemberInfo kind="parameter" type={`V`} />
+

+ 14 - 0
docs/docs/reference/admin-ui-api/routes/index.md

@@ -0,0 +1,14 @@
+---
+title: "Routes"
+isDefaultIndex: true
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+import DocCardList from '@theme/DocCardList';
+
+<DocCardList />

+ 30 - 0
docs/docs/reference/admin-ui-api/routes/register-route-component-options.md

@@ -0,0 +1,30 @@
+---
+title: "RegisterRouteComponentOptions"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## RegisterRouteComponentOptions
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/register-route-component.ts" sourceLine="19" packageName="@vendure/admin-ui" />
+
+Configuration for a route component.
+
+```ts title="Signature"
+type RegisterRouteComponentOptions<Component extends any | BaseDetailComponent<Entity>, Entity extends { id: string; updatedAt?: string }, T extends DocumentNode | TypedDocumentNode<any, { id: string }>, Field extends keyof ResultOf<T>, R extends Field> = {
+    component: Type<Component> | Component;
+    title?: string;
+    breadcrumb?: BreadcrumbValue;
+    path?: string;
+    query?: T;
+    getBreadcrumbs?: (entity: Exclude<ResultOf<T>[R], 'Query'>) => BreadcrumbValue;
+    entityKey?: Component extends BaseDetailComponent<Entity> ? R : undefined;
+    variables?: T extends TypedDocumentNode<any, infer V> ? Omit<V, 'id'> : never;
+    routeConfig?: Route;
+} & (Component extends BaseDetailComponent<Entity> ? { entityKey: R } : unknown)
+```

+ 61 - 0
docs/docs/reference/admin-ui-api/routes/register-route-component.md

@@ -0,0 +1,61 @@
+---
+title: "RegisterRouteComponent"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## registerRouteComponent
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/register-route-component.ts" sourceLine="77" packageName="@vendure/admin-ui" />
+
+Registers an Angular standalone component to be rendered in a route.
+
+*Example*
+
+```ts title="routes.ts"
+import { registerRouteComponent } from '@vendure/admin-ui/core';
+import { registerReactRouteComponent } from '@vendure/admin-ui/react';
+
+import { ProductReviewDetailComponent } from './components/product-review-detail/product-review-detail.component';
+import { AllProductReviewsList } from './components/all-product-reviews-list/all-product-reviews-list.component';
+import { GetReviewDetailDocument } from './generated-types';
+
+export default [
+    registerRouteComponent({
+        path: '',
+        component: AllProductReviewsList,
+        breadcrumb: 'Product reviews',
+    }),
+    registerRouteComponent({
+        path: ':id',
+        component: ProductReviewDetailComponent,
+        query: GetReviewDetailDocument,
+        entityKey: 'productReview',
+        getBreadcrumbs: entity => [
+            {
+                label: 'Product reviews',
+                link: ['/extensions', 'product-reviews'],
+            },
+            {
+                label: `#${entity?.id} (${entity?.product.name})`,
+                link: [],
+            },
+        ],
+    }),
+];
+```
+
+```ts title="Signature"
+function registerRouteComponent<Component extends any | BaseDetailComponent<Entity>, Entity extends { id: string; updatedAt?: string }, T extends DocumentNode | TypedDocumentNode<any, { id: string }>, Field extends keyof ResultOf<T>, R extends Field>(options: RegisterRouteComponentOptions<Component, Entity, T, Field, R>): void
+```
+Parameters
+
+### options
+
+<MemberInfo kind="parameter" type={`<a href='/reference/admin-ui-api/routes/register-route-component-options#registerroutecomponentoptions'>RegisterRouteComponentOptions</a>&#60;Component, Entity, T, Field, R&#62;`} />
+

+ 3 - 3
docs/docs/reference/admin-ui-api/providers/data-service.md → docs/docs/reference/admin-ui-api/services/data-service.md

@@ -29,9 +29,9 @@ class DataService {
 
 ### query
 
-<MemberInfo kind="method" type={`(query: DocumentNode | TypedDocumentNode&#60;T, V&#62;, variables?: V, fetchPolicy: WatchQueryFetchPolicy = 'cache-and-network') => <a href='/reference/admin-ui-api/providers/data-service#queryresult'>QueryResult</a>&#60;T, V&#62;`}   />
+<MemberInfo kind="method" type={`(query: DocumentNode | TypedDocumentNode&#60;T, V&#62;, variables?: V, fetchPolicy: WatchQueryFetchPolicy = 'cache-and-network') => <a href='/reference/admin-ui-api/services/data-service#queryresult'>QueryResult</a>&#60;T, V&#62;`}   />
 
-Perform a GraphQL query. Returns a <a href='/reference/admin-ui-api/providers/data-service#queryresult'>QueryResult</a> which allows further control over
+Perform a GraphQL query. Returns a <a href='/reference/admin-ui-api/services/data-service#queryresult'>QueryResult</a> which allows further control over
 they type of result returned, e.g. stream of values, single value etc.
 
 *Example*
@@ -106,7 +106,7 @@ class QueryResult<T, V extends Record<string, any> = Record<string, any>> {
 
 ### refetchOnChannelChange
 
-<MemberInfo kind="method" type={`() => <a href='/reference/admin-ui-api/providers/data-service#queryresult'>QueryResult</a>&#60;T, V&#62;`}   />
+<MemberInfo kind="method" type={`() => <a href='/reference/admin-ui-api/services/data-service#queryresult'>QueryResult</a>&#60;T, V&#62;`}   />
 
 Re-fetch this query whenever the active Channel changes.
 ### single$

+ 14 - 0
docs/docs/reference/admin-ui-api/services/index.md

@@ -0,0 +1,14 @@
+---
+title: "Services"
+isDefaultIndex: true
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+import DocCardList from '@theme/DocCardList';
+
+<DocCardList />

+ 5 - 5
docs/docs/reference/admin-ui-api/providers/modal-service.md → docs/docs/reference/admin-ui-api/services/modal-service.md

@@ -18,7 +18,7 @@ embedding the specified component within.
 
 ```ts title="Signature"
 class ModalService {
-    constructor(componentFactoryResolver: ComponentFactoryResolver, overlayHostService: OverlayHostService)
+    constructor(overlayHostService: OverlayHostService)
     fromComponent(component: Type<T> & Type<Dialog<R>>, options?: ModalOptions<T>) => Observable<R | undefined>;
     dialog(config: DialogConfig<T>) => Observable<T | undefined>;
 }
@@ -28,14 +28,14 @@ class ModalService {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(componentFactoryResolver: ComponentFactoryResolver, overlayHostService: OverlayHostService) => ModalService`}   />
+<MemberInfo kind="method" type={`(overlayHostService: OverlayHostService) => ModalService`}   />
 
 
 ### fromComponent
 
-<MemberInfo kind="method" type={`(component: Type&#60;T&#62; &#38; Type&#60;<a href='/reference/admin-ui-api/providers/modal-service#dialog'>Dialog</a>&#60;R&#62;&#62;, options?: <a href='/reference/admin-ui-api/providers/modal-service#modaloptions'>ModalOptions</a>&#60;T&#62;) => Observable&#60;R | undefined&#62;`}   />
+<MemberInfo kind="method" type={`(component: Type&#60;T&#62; &#38; Type&#60;<a href='/reference/admin-ui-api/services/modal-service#dialog'>Dialog</a>&#60;R&#62;&#62;, options?: <a href='/reference/admin-ui-api/services/modal-service#modaloptions'>ModalOptions</a>&#60;T&#62;) => Observable&#60;R | undefined&#62;`}   />
 
-Create a modal from a component. The component must implement the <a href='/reference/admin-ui-api/providers/modal-service#dialog'>Dialog</a> interface.
+Create a modal from a component. The component must implement the <a href='/reference/admin-ui-api/services/modal-service#dialog'>Dialog</a> interface.
 Additionally, the component should include templates for the title and the buttons to be
 displayed in the modal dialog. See example:
 
@@ -77,7 +77,7 @@ class MyDialog implements Dialog {
 ```
 ### dialog
 
-<MemberInfo kind="method" type={`(config: <a href='/reference/admin-ui-api/providers/modal-service#dialogconfig'>DialogConfig</a>&#60;T&#62;) => Observable&#60;T | undefined&#62;`}   />
+<MemberInfo kind="method" type={`(config: <a href='/reference/admin-ui-api/services/modal-service#dialogconfig'>DialogConfig</a>&#60;T&#62;) => Observable&#60;T | undefined&#62;`}   />
 
 Displays a modal dialog with the provided title, body and buttons.
 

+ 2 - 2
docs/docs/reference/admin-ui-api/providers/notification-service.md → docs/docs/reference/admin-ui-api/services/notification-service.md

@@ -69,7 +69,7 @@ Display a warning toast notification
 Display an error toast notification
 ### notify
 
-<MemberInfo kind="method" type={`(config: <a href='/reference/admin-ui-api/providers/notification-service#toastconfig'>ToastConfig</a>) => void`}   />
+<MemberInfo kind="method" type={`(config: <a href='/reference/admin-ui-api/services/notification-service#toastconfig'>ToastConfig</a>) => void`}   />
 
 Display a toast notification.
 
@@ -117,7 +117,7 @@ interface ToastConfig {
 
 ### type
 
-<MemberInfo kind="property" type={`<a href='/reference/admin-ui-api/providers/notification-service#notificationtype'>NotificationType</a>`}   />
+<MemberInfo kind="property" type={`<a href='/reference/admin-ui-api/services/notification-service#notificationtype'>NotificationType</a>`}   />
 
 
 ### duration

+ 11 - 12
docs/docs/reference/admin-ui-api/tabs/register-page-tab.md

@@ -11,25 +11,24 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## registerPageTab
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/page/page.service.ts" sourceLine="78" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/register-page-tab.ts" sourceLine="24" packageName="@vendure/admin-ui" />
 
 Add a tab to an existing list or detail page.
 
 *Example*
 
-```ts
-@NgModule({
-  imports: [SharedModule],
-  providers: [
+```ts title="providers.ts"
+import { registerPageTab } from '@vendure/admin-ui/core';
+import { DeletedProductListComponent } from './components/deleted-product-list/deleted-product-list.component';
+
+export default [
     registerPageTab({
-      location: 'product-list',
-      tab: 'Deleted Products',
-      route: 'deleted',
-      component: DeletedProductListComponent,
+        location: 'product-list',
+        tab: 'Deleted Products',
+        route: 'deleted',
+        component: DeletedProductListComponent,
     }),
-  ],
-})
-export class MyUiExtensionModule {}
+];
 ```
 
 ```ts title="Signature"

+ 62 - 45
docs/docs/reference/admin-ui-api/ui-devkit/admin-ui-extension.md

@@ -13,20 +13,25 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="98" packageName="@vendure/ui-devkit" />
 
-Defines extensions to the Admin UI application by specifying additional
-Angular [NgModules](https://angular.io/guide/ngmodules) which are compiled
-into the application.
-
-See [Extending the Admin UI](/guides/extending-the-admin-ui/introduction) for
+Defines extensions to the Admin UI application by specifying additional
+Angular [NgModules](https://angular.io/guide/ngmodules) which are compiled
+into the application.
+
+See [Extending the Admin UI](/guides/extending-the-admin-ui/introduction) for
 detailed instructions.
 
 ```ts title="Signature"
-interface AdminUiExtension extends Partial<TranslationExtension>,
-        Partial<StaticAssetExtension>,
+interface AdminUiExtension extends Partial<TranslationExtension>,
+        Partial<StaticAssetExtension>,
         Partial<GlobalStylesExtension> {
     id?: string;
     extensionPath: string;
-    ngModules: Array<AdminUiExtensionSharedModule | AdminUiExtensionLazyModule>;
+    ngModules?: Array<AdminUiExtensionSharedModule | AdminUiExtensionLazyModule>;
+    providers?: string[];
+    routes?: Array<{
+        route: string;
+        filePath: string;
+    }>;
     pathAlias?: string;
     exclude?: string[];
 }
@@ -41,39 +46,50 @@ interface AdminUiExtension extends Partial<TranslationExtension>,
 
 <MemberInfo kind="property" type={`string`}   />
 
-An optional ID for the extension module. Only used internally for generating
+An optional ID for the extension module. Only used internally for generating
 import paths to your module. If not specified, a unique hash will be used as the id.
 ### extensionPath
 
 <MemberInfo kind="property" type={`string`}   />
 
-The path to the directory containing the extension module(s). The entire contents of this directory
-will be copied into the Admin UI app, including all TypeScript source files, html templates,
+The path to the directory containing the extension module(s). The entire contents of this directory
+will be copied into the Admin UI app, including all TypeScript source files, html templates,
 scss style sheets etc.
 ### ngModules
 
 <MemberInfo kind="property" type={`Array&#60;<a href='/reference/admin-ui-api/ui-devkit/admin-ui-extension#adminuiextensionsharedmodule'>AdminUiExtensionSharedModule</a> | <a href='/reference/admin-ui-api/ui-devkit/admin-ui-extension#adminuiextensionlazymodule'>AdminUiExtensionLazyModule</a>&#62;`}   />
 
 One or more Angular modules which extend the default Admin UI.
+### providers
+
+<MemberInfo kind="property" type={`string[]`}   />
+
+Defines the paths to a file that exports an array of shared providers such as nav menu items, custom form inputs,
+custom detail components, action bar items, custom history entry components.
+### routes
+
+<MemberInfo kind="property" type={`Array&#60;{
         route: string;
         filePath: string;
     }&#62;`}   />
+
+Defines routes that will be lazy-loaded at the `/extensions/` route. The filePath should point to a file
+relative to the `extensionPath` which exports an array of Angular route definitions.
 ### pathAlias
 
 <MemberInfo kind="property" type={`string`}   />
 
-An optional alias for the module so it can be referenced by other UI extension modules.
-
-By default, Angular modules declared in an AdminUiExtension do not have access to code outside the directory
-defined by the `extensionPath`. A scenario in which that can be useful though is in a monorepo codebase where
-a common NgModule is shared across different plugins, each defined in its own package. An example can be found
-below - note that the main `tsconfig.json` also maps the target module but using a path relative to the project's
-root folder. The UI module is not part of the main TypeScript build task as explained in
-[Extending the Admin UI](https://www.vendure.io/docs/plugins/extending-the-admin-ui/) but having `paths`
-properly configured helps with usual IDE code editing features such as code completion and quick navigation, as
+An optional alias for the module so it can be referenced by other UI extension modules.
+
+By default, Angular modules declared in an AdminUiExtension do not have access to code outside the directory
+defined by the `extensionPath`. A scenario in which that can be useful though is in a monorepo codebase where
+a common NgModule is shared across different plugins, each defined in its own package. An example can be found
+below - note that the main `tsconfig.json` also maps the target module but using a path relative to the project's
+root folder. The UI module is not part of the main TypeScript build task as explained in
+[Extending the Admin UI](https://www.vendure.io/docs/plugins/extending-the-admin-ui/) but having `paths`
+properly configured helps with usual IDE code editing features such as code completion and quick navigation, as
 well as linting.
 
 *Example*
 
-```ts
-// packages/common-ui-module/src/ui/ui-shared.module.ts
+```ts title="packages/common-ui-module/src/ui/ui-shared.module.ts"
 import { NgModule } from '@angular/core';
 import { SharedModule } from '@vendure/admin-ui/core';
 import { CommonUiComponent } from './components/common-ui/common-ui.component';
@@ -88,13 +104,13 @@ export { CommonUiComponent };
 export class CommonSharedUiModule {}
 ```
 
-```ts
-// packages/common-ui-module/src/index.ts
+```ts title="packages/common-ui-module/src/index.ts"
 import path from 'path';
 
 import { AdminUiExtension } from '@vendure/ui-devkit/compiler';
 
 export const uiExtensions: AdminUiExtension = {
+  // highlight-next-line
   pathAlias: '@common-ui-module',     // this is the important part
   extensionPath: path.join(__dirname, 'ui'),
   ngModules: [
@@ -107,25 +123,26 @@ export const uiExtensions: AdminUiExtension = {
 };
 ```
 
-```json
-// tsconfig.json
+```json title="tsconfig.json"
 {
   "compilerOptions": {
     "baseUrl": ".",
     "paths": {
+      // highlight-next-line
       "@common-ui-module/*": ["packages/common-ui-module/src/ui/*"]
     }
   }
 }
 ```
 
-```ts
-// packages/sample-plugin/src/ui/ui-extension.module.ts
+```ts title="packages/sample-plugin/src/ui/ui-extension.module.ts"
 import { NgModule } from '@angular/core';
 import { SharedModule } from '@vendure/admin-ui/core';
+// highlight-start
 // the import below works both in the context of the custom Admin UI app as well as the main project
 // '@common-ui-module' is the value of "pathAlias" and 'ui-shared.module' is the file we want to reference inside "extensionPath"
 import { CommonSharedUiModule, CommonUiComponent } from '@common-ui-module/ui-shared.module';
+// highlight-end
 
 @NgModule({
   imports: [
@@ -146,7 +163,7 @@ export class SampleUiExtensionModule {}
 
 <MemberInfo kind="property" type={`string[]`}   />
 
-Optional array specifying filenames or [glob](https://github.com/isaacs/node-glob) patterns that should
+Optional array specifying filenames or [glob](https://github.com/isaacs/node-glob) patterns that should
 be skipped when copying the directory defined by `extensionPath`.
 
 *Example*
@@ -163,7 +180,7 @@ exclude: ['**/*.spec.ts']
 
 <GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="18" packageName="@vendure/ui-devkit" />
 
-Defines extensions to the Admin UI translations. Can be used as a stand-alone extension definition which only adds translations
+Defines extensions to the Admin UI translations. Can be used as a stand-alone extension definition which only adds translations
 without adding new UI functionality, or as part of a full <a href='/reference/admin-ui-api/ui-devkit/admin-ui-extension#adminuiextension'>AdminUiExtension</a>.
 
 ```ts title="Signature"
@@ -178,9 +195,9 @@ interface TranslationExtension {
 
 <MemberInfo kind="property" type={`{ [languageCode in <a href='/reference/typescript-api/common/language-code#languagecode'>LanguageCode</a>]?: string }`}   />
 
-Optional object defining any translation files for the Admin UI. The value should be an object with
-the key as a 2-character [ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes),
-and the value being a [glob](https://github.com/isaacs/node-glob) for any relevant
+Optional object defining any translation files for the Admin UI. The value should be an object with
+the key as a 2-character [ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes),
+and the value being a [glob](https://github.com/isaacs/node-glob) for any relevant
 translation files in JSON format.
 
 *Example*
@@ -214,7 +231,7 @@ interface StaticAssetExtension {
 
 <MemberInfo kind="property" type={`<a href='/reference/admin-ui-api/ui-devkit/admin-ui-extension#staticassetdefinition'>StaticAssetDefinition</a>[]`}   />
 
-Optional array of paths to static assets which will be copied over to the Admin UI app's `/static`
+Optional array of paths to static assets which will be copied over to the Admin UI app's `/static`
 directory.
 
 
@@ -239,7 +256,7 @@ interface GlobalStylesExtension {
 
 <MemberInfo kind="property" type={`string[] | string`}   />
 
-Specifies a path (or array of paths) to global style files (css or Sass) which will be
+Specifies a path (or array of paths) to global style files (css or Sass) which will be
 incorporated into the Admin UI app global stylesheet.
 
 
@@ -264,7 +281,7 @@ interface SassVariableOverridesExtension {
 
 <MemberInfo kind="property" type={`string`}   />
 
-Specifies a path to a Sass style file containing variable declarations, which will take precedence over
+Specifies a path to a Sass style file containing variable declarations, which will take precedence over
 default values defined in Clarity.
 
 
@@ -273,9 +290,9 @@ default values defined in Clarity.
 
 ## StaticAssetDefinition
 
-<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="231" packageName="@vendure/ui-devkit" />
+<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="251" packageName="@vendure/ui-devkit" />
 
-A static asset can be provided as a path to the asset, or as an object containing a path and a new
+A static asset can be provided as a path to the asset, or as an object containing a path and a new
 name, which will cause the compiler to copy and then rename the asset.
 
 ```ts title="Signature"
@@ -285,7 +302,7 @@ type StaticAssetDefinition = string | { path: string; rename: string }
 
 ## AdminUiExtensionSharedModule
 
-<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="240" packageName="@vendure/ui-devkit" />
+<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="260" packageName="@vendure/ui-devkit" />
 
 Configuration defining a single NgModule with which to extend the Admin UI.
 
@@ -303,8 +320,8 @@ interface AdminUiExtensionSharedModule {
 
 <MemberInfo kind="property" type={`'shared'`}   />
 
-Shared modules are directly imported into the main AppModule of the Admin UI
-and should be used to declare custom form components and define custom
+Shared modules are directly imported into the main AppModule of the Admin UI
+and should be used to declare custom form components and define custom
 navigation items.
 ### ngModuleFileName
 
@@ -323,7 +340,7 @@ The name of the extension module class.
 
 ## AdminUiExtensionLazyModule
 
-<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="267" packageName="@vendure/ui-devkit" />
+<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="287" packageName="@vendure/ui-devkit" />
 
 Configuration defining a single NgModule with which to extend the Admin UI.
 
@@ -342,14 +359,14 @@ interface AdminUiExtensionLazyModule {
 
 <MemberInfo kind="property" type={`'lazy'`}   />
 
-Lazy modules are lazy-loaded at the `/extensions/` route and should be used for
+Lazy modules are lazy-loaded at the `/extensions/` route and should be used for
 modules which define new views for the Admin UI.
 ### route
 
 <MemberInfo kind="property" type={`string`}   />
 
-The route specifies the route at which the module will be lazy-loaded. E.g. a value
-of `'foo'` will cause the module to lazy-load when the `/extensions/foo` route
+The route specifies the route at which the module will be lazy-loaded. E.g. a value
+of `'foo'` will cause the module to lazy-load when the `/extensions/foo` route
 is activated.
 ### ngModuleFileName
 

+ 1 - 1
docs/docs/reference/admin-ui-api/ui-devkit/compile-ui-extensions.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## compileUiExtensions
 
-<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/compile.ts" sourceLine="36" packageName="@vendure/ui-devkit" />
+<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/compile.ts" sourceLine="35" packageName="@vendure/ui-devkit" />
 
 Compiles the Admin UI app with the specified extensions.
 

+ 22 - 1
docs/docs/reference/admin-ui-api/ui-devkit/ui-extension-compiler-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## UiExtensionCompilerOptions
 
-<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="307" packageName="@vendure/ui-devkit" />
+<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="327" packageName="@vendure/ui-devkit" />
 
 Options to configure how the Admin UI should be compiled.
 
@@ -19,6 +19,7 @@ Options to configure how the Admin UI should be compiled.
 interface UiExtensionCompilerOptions {
     outputPath: string;
     extensions: Extension[];
+    ngCompilerPath?: string | undefined;
     devMode?: boolean;
     baseHref?: string;
     watchPort?: number;
@@ -40,6 +41,26 @@ The directory into which the sources for the extended Admin UI will be copied.
 
 An array of objects which configure Angular modules and/or
 translations with which to extend the Admin UI.
+### ngCompilerPath
+
+<MemberInfo kind="property" type={`string | undefined`}  since="2.1.0"  />
+
+Allows you to manually specify the path to the Angular CLI compiler script. This can be useful in scenarios
+where for some reason the built-in start/build scripts are unable to locate the `ng` command.
+
+This option should not usually be required.
+
+*Example*
+
+```ts
+compileUiExtensions({
+    ngCompilerPath: path.join(__dirname, '../../node_modules/@angular/cli/bin/ng.js'),
+    outputPath: path.join(__dirname, '../admin-ui'),
+    extensions: [
+      // ...
+    ],
+})
+```
 ### devMode
 
 <MemberInfo kind="property" type={`boolean`} default="false"   />

+ 1 - 1
docs/docs/reference/admin-ui-api/ui-devkit/ui-extension-compiler-process-argument.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## UiExtensionCompilerProcessArgument
 
-<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="299" packageName="@vendure/ui-devkit" />
+<GenerationInfo sourceFile="packages/ui-devkit/src/compiler/types.ts" sourceLine="319" packageName="@vendure/ui-devkit" />
 
 Argument to configure process (watch or compile)
 

+ 7 - 7
docs/docs/reference/core-plugins/elasticsearch-plugin/elasticsearch-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## ElasticsearchOptions
 
-<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/options.ts" sourceLine="22" packageName="@vendure/elasticsearch-plugin" />
+<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/options.ts" sourceLine="30" packageName="@vendure/elasticsearch-plugin" />
 
 Configuration options for the <a href='/reference/core-plugins/elasticsearch-plugin/#elasticsearchplugin'>ElasticsearchPlugin</a>.
 
@@ -30,10 +30,10 @@ interface ElasticsearchOptions {
     batchSize?: number;
     searchConfig?: SearchConfig;
     customProductMappings?: {
-        [fieldName: string]: CustomMapping<[Product, ProductVariant[], LanguageCode]>;
+        [fieldName: string]: CustomMapping<[Product, ProductVariant[], LanguageCode, Injector]>;
     };
     customProductVariantMappings?: {
-        [fieldName: string]: CustomMapping<[ProductVariant, LanguageCode]>;
+        [fieldName: string]: CustomMapping<[ProductVariant, LanguageCode, Injector]>;
     };
     bufferUpdates?: boolean;
     hydrateProductRelations?: Array<EntityRelationPaths<Product>>;
@@ -169,7 +169,7 @@ Batch size for bulk operations (e.g. when rebuilding the indices).
 Configuration of the internal Elasticsearch query.
 ### customProductMappings
 
-<MemberInfo kind="property" type={`{
         [fieldName: string]: CustomMapping&#60;[<a href='/reference/typescript-api/entities/product#product'>Product</a>, <a href='/reference/typescript-api/entities/product-variant#productvariant'>ProductVariant</a>[], <a href='/reference/typescript-api/common/language-code#languagecode'>LanguageCode</a>]&#62;;
     }`}   />
+<MemberInfo kind="property" type={`{
         [fieldName: string]: CustomMapping&#60;[<a href='/reference/typescript-api/entities/product#product'>Product</a>, <a href='/reference/typescript-api/entities/product-variant#productvariant'>ProductVariant</a>[], <a href='/reference/typescript-api/common/language-code#languagecode'>LanguageCode</a>, <a href='/reference/typescript-api/common/injector#injector'>Injector</a>]&#62;;
     }`}   />
 
 Custom mappings may be defined which will add the defined data to the
 Elasticsearch index and expose that data via the SearchResult GraphQL type,
@@ -232,7 +232,7 @@ query SearchProducts($input: SearchInput!) {
 ```
 ### customProductVariantMappings
 
-<MemberInfo kind="property" type={`{
         [fieldName: string]: CustomMapping&#60;[<a href='/reference/typescript-api/entities/product-variant#productvariant'>ProductVariant</a>, <a href='/reference/typescript-api/common/language-code#languagecode'>LanguageCode</a>]&#62;;
     }`}   />
+<MemberInfo kind="property" type={`{
         [fieldName: string]: CustomMapping&#60;[<a href='/reference/typescript-api/entities/product-variant#productvariant'>ProductVariant</a>, <a href='/reference/typescript-api/common/language-code#languagecode'>LanguageCode</a>, <a href='/reference/typescript-api/common/injector#injector'>Injector</a>]&#62;;
     }`}   />
 
 This config option defines custom mappings which are accessible when the "groupByProduct"
 input options is set to `false`. In addition, custom product mappings can be accessed by using
@@ -364,7 +364,7 @@ extend input SearchResultSortParameter {
 
 ## SearchConfig
 
-<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/options.ts" sourceLine="377" packageName="@vendure/elasticsearch-plugin" />
+<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/options.ts" sourceLine="385" packageName="@vendure/elasticsearch-plugin" />
 
 Configuration options for the internal Elasticsearch query which is generated when performing a search.
 
@@ -645,7 +645,7 @@ searchConfig: {
 
 ## BoostFieldsConfig
 
-<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/options.ts" sourceLine="662" packageName="@vendure/elasticsearch-plugin" />
+<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/options.ts" sourceLine="670" packageName="@vendure/elasticsearch-plugin" />
 
 Configuration for [boosting](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#field-boost)
 the scores of given fields when performing a search against a term.

+ 1 - 1
docs/docs/reference/core-plugins/email-plugin/custom-template-loader.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## TemplateLoader
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="390" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="391" packageName="@vendure/email-plugin" />
 
 Load an email template based on the given request context, type and template name
 and return the template as a string.

+ 4 - 4
docs/docs/reference/core-plugins/email-plugin/email-plugin-types.md

@@ -130,7 +130,7 @@ type EmailAttachment = Omit<Attachment, 'raw'> & { path?: string }
 
 ## SetTemplateVarsFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="410" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="411" packageName="@vendure/email-plugin" />
 
 A function used to define template variables available to email templates.
 See <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>.setTemplateVars().
@@ -145,7 +145,7 @@ type SetTemplateVarsFn<Event> = (
 
 ## SetAttachmentsFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="424" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="425" packageName="@vendure/email-plugin" />
 
 A function used to define attachments to be sent with the email.
 See https://nodemailer.com/message/attachments/ for more information about
@@ -158,7 +158,7 @@ type SetAttachmentsFn<Event> = (event: Event) => EmailAttachment[] | Promise<Ema
 
 ## OptionalAddressFields
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="434" packageName="@vendure/email-plugin" since="1.1.0" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="435" packageName="@vendure/email-plugin" since="1.1.0" />
 
 Optional address-related fields for sending the email.
 
@@ -194,7 +194,7 @@ An email address that will appear on the _Reply-To:_ field
 
 ## SetOptionalAddressFieldsFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="460" packageName="@vendure/email-plugin" since="1.1.0" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="461" packageName="@vendure/email-plugin" since="1.1.0" />
 
 A function used to set the <a href='/reference/core-plugins/email-plugin/email-plugin-types#optionaladdressfields'>OptionalAddressFields</a>.
 

+ 1 - 1
docs/docs/reference/core-plugins/harden-plugin/default-vendure-complexity-estimator.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## defaultVendureComplexityEstimator
 
-<GenerationInfo sourceFile="packages/harden-plugin/src/middleware/query-complexity-plugin.ts" sourceLine="95" packageName="@vendure/harden-plugin" />
+<GenerationInfo sourceFile="packages/harden-plugin/src/middleware/query-complexity-plugin.ts" sourceLine="94" packageName="@vendure/harden-plugin" />
 
 A complexity estimator which takes into account List and PaginatedList types and can
 be further configured by providing a customComplexityFactors object.

+ 2 - 2
docs/docs/reference/core-plugins/harden-plugin/index.md

@@ -90,9 +90,9 @@ This evil query has a complexity score of 2,443,203 - much greater than the defa
 The complexity score is calculated by the [graphql-query-complexity library](https://www.npmjs.com/package/graphql-query-complexity),
 and by default uses the <a href='/reference/core-plugins/harden-plugin/default-vendure-complexity-estimator#defaultvendurecomplexityestimator'>defaultVendureComplexityEstimator</a>, which is tuned specifically to the Vendure Shop API.
 
-{{% alert "warning" %}}
+:::caution
 Note: By default, if the "take" argument is omitted from a list query (e.g. the `products` or `collections` query), a default factor of 1000 is applied.
-{{% /alert %}}
+:::
 
 The optimal max complexity score will vary depending on:
 

+ 3 - 3
docs/docs/reference/core-plugins/payments-plugin/stripe-plugin.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## StripePlugin
 
-<GenerationInfo sourceFile="packages/payments-plugin/src/stripe/stripe.plugin.ts" sourceLine="159" packageName="@vendure/payments-plugin" />
+<GenerationInfo sourceFile="packages/payments-plugin/src/stripe/stripe.plugin.ts" sourceLine="160" packageName="@vendure/payments-plugin" />
 
 Plugin to enable payments through [Stripe](https://stripe.com/docs) via the Payment Intents API.
 
@@ -139,10 +139,10 @@ The high-level workflow is:
 3. Once the form is submitted and Stripe processes the payment, the webhook takes care of updating the order without additional action
 in the storefront. As in the code above, the customer will be redirected to `/checkout/confirmation/${orderCode}`.
 
-{{% alert "primary" %}}
+:::info
 A full working storefront example of the Stripe integration can be found in the
 [Remix Starter repo](https://github.com/vendure-ecommerce/storefront-remix-starter/tree/master/app/components/checkout/stripe)
-{{% /alert %}}
+:::
 
 ## Local development
 

+ 1 - 1
docs/docs/reference/typescript-api/assets/asset-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AssetOptions
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="605" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="606" packageName="@vendure/core" />
 
 The AssetOptions define how assets (images and other files) are named and stored, and how preview images are generated.
 

+ 1 - 1
docs/docs/reference/typescript-api/auth/auth-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AuthOptions
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="307" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="308" packageName="@vendure/core" />
 
 The AuthOptions define how authentication and authorization is managed.
 

+ 1 - 1
docs/docs/reference/typescript-api/auth/cookie-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## CookieOptions
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="220" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="221" packageName="@vendure/core" />
 
 Options for the handling of the cookies used to track sessions (only applicable if
 `authOptions.tokenMethod` is set to `'cookie'`). These options are passed directly

Some files were not shown because too many files changed in this diff