diff --git a/packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.html b/packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.html index 4355b17e85..3d6cf323f0 100644 --- a/packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.html +++ b/packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.html @@ -111,13 +111,14 @@
diff --git a/packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts b/packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts index d29ee72849..36f67ff4e3 100644 --- a/packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts +++ b/packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.ts @@ -7,6 +7,7 @@ import { ContentChildren, EventEmitter, inject, + Injector, Input, OnChanges, OnDestroy, @@ -124,10 +125,11 @@ export class DataTable2Component implements AfterContentInit, OnChanges, OnDe @ContentChild('vdrDt2CustomSearch') customSearchTemplate: TemplateRef; @ContentChildren(TemplateRef) templateRefs: QueryList>; + injector = inject(Injector); route = inject(ActivatedRoute); filterPresetService = inject(FilterPresetService); dataTableCustomComponentService = inject(DataTableCustomComponentService); - protected customComponents = new Map(); + protected customComponents = new Map(); rowTemplate: TemplateRef; currentStart: number; @@ -226,12 +228,13 @@ export class DataTable2Component implements AfterContentInit, OnChanges, OnDe column.setVisibility(column.hiddenByDefault); } column.onColumnChange(updateColumnVisibility); - const customComponent = this.dataTableCustomComponentService.getCustomComponentsFor( - this.id, - column.id, - ); - if (customComponent) { - this.customComponents.set(column.id, customComponent); + const config = this.dataTableCustomComponentService.getCustomComponentsFor(this.id, column.id); + if (config) { + const injector = Injector.create({ + parent: this.injector, + providers: config.providers ?? [], + }); + this.customComponents.set(column.id, { config, injector }); } }); diff --git a/packages/admin-ui/src/lib/react/src/components/react-custom-column.component.ts b/packages/admin-ui/src/lib/react/src/components/react-custom-column.component.ts new file mode 100644 index 0000000000..5de904d729 --- /dev/null +++ b/packages/admin-ui/src/lib/react/src/components/react-custom-column.component.ts @@ -0,0 +1,32 @@ +import { Component, inject, InjectionToken, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { CustomColumnComponent } from '@vendure/admin-ui/core'; +import { ElementType } from 'react'; +import { ReactComponentHostDirective } from '../directives/react-component-host.directive'; + +export const REACT_CUSTOM_COLUMN_COMPONENT_OPTIONS = new InjectionToken<{ + component: ElementType; + props?: Record; +}>('REACT_CUSTOM_COLUMN_COMPONENT_OPTIONS'); + +@Component({ + selector: 'vdr-react-custom-column-component', + template: `
`, + styleUrls: ['./react-global-styles.scss'], + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [ReactComponentHostDirective], +}) +export class ReactCustomColumnComponent implements CustomColumnComponent, OnInit { + @Input() rowItem: any; + + protected reactComponent = inject(REACT_CUSTOM_COLUMN_COMPONENT_OPTIONS).component; + private options = inject(REACT_CUSTOM_COLUMN_COMPONENT_OPTIONS); + protected props: Record; + + ngOnInit() { + this.props = { + rowItem: this.rowItem, + ...(this.options.props ?? {}), + }; + } +} diff --git a/packages/admin-ui/src/lib/react/src/public_api.ts b/packages/admin-ui/src/lib/react/src/public_api.ts index 2d090030c7..1196a577d4 100644 --- a/packages/admin-ui/src/lib/react/src/public_api.ts +++ b/packages/admin-ui/src/lib/react/src/public_api.ts @@ -1,4 +1,5 @@ // This file was generated by the build-public-api.ts script +export * from './components/react-custom-column.component'; export * from './components/react-custom-detail.component'; export * from './components/react-form-input.component'; export * from './components/react-route.component'; @@ -11,6 +12,7 @@ export * from './react-hooks/use-injector'; export * from './react-hooks/use-page-metadata'; export * from './react-hooks/use-query'; export * from './register-react-custom-detail-component'; +export * from './register-react-data-table-component'; export * from './register-react-form-input-component'; export * from './register-react-route-component'; export * from './types'; diff --git a/packages/admin-ui/src/lib/react/src/register-react-data-table-component.ts b/packages/admin-ui/src/lib/react/src/register-react-data-table-component.ts new file mode 100644 index 0000000000..8084c8b25f --- /dev/null +++ b/packages/admin-ui/src/lib/react/src/register-react-data-table-component.ts @@ -0,0 +1,109 @@ +import { APP_INITIALIZER } from '@angular/core'; +import { + DataTableColumnId, + DataTableCustomComponentService, + DataTableLocationId, +} from '@vendure/admin-ui/core'; +import { ElementType } from 'react'; +import { + REACT_CUSTOM_COLUMN_COMPONENT_OPTIONS, + ReactCustomColumnComponent, +} from './components/react-custom-column.component'; + +/** + * @description + * Configures a {@link CustomDetailComponent} to be placed in the given location. + * + * @docsCategory react-extensions + */ +export interface ReactDataTableComponentConfig { + /** + * @description + * The location in the UI where the custom component should be placed. + */ + tableId: DataTableLocationId; + /** + * @description + * The column in the table where the custom component should be placed. + */ + columnId: DataTableColumnId; + /** + * @description + * 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. + */ + component: ElementType; + /** + * @description + * Optional props to pass to the React component. + */ + props?: Record; +} + +/** + * @description + * The props that will be passed to the React component registered via {@link registerReactDataTableComponent}. + */ +export interface ReactDataTableComponentProps { + rowItem: any; + [prop: string]: any; +} + +/** + * @description + * 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 ( + * + * {rowItem.slug} + * + * ); + * } + * ``` + * + * ```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', + * }, + * }), + * ]; + * ``` + */ +export function registerReactDataTableComponent(config: ReactDataTableComponentConfig) { + return { + provide: APP_INITIALIZER, + multi: true, + useFactory: (dataTableCustomComponentService: DataTableCustomComponentService) => () => { + dataTableCustomComponentService.registerCustomComponent({ + ...config, + component: ReactCustomColumnComponent, + providers: [ + { + provide: REACT_CUSTOM_COLUMN_COMPONENT_OPTIONS, + useValue: { + component: config.component, + props: config.props, + }, + }, + ], + }); + }, + deps: [DataTableCustomComponentService], + }; +} diff --git a/packages/dev-server/test-plugins/experimental-ui/components/CustomColumnComponent.tsx b/packages/dev-server/test-plugins/experimental-ui/components/CustomColumnComponent.tsx new file mode 100644 index 0000000000..eeadb9e917 --- /dev/null +++ b/packages/dev-server/test-plugins/experimental-ui/components/CustomColumnComponent.tsx @@ -0,0 +1,10 @@ +import { ReactDataTableComponentProps } from '@vendure/admin-ui/react'; +import React from 'react'; + +export function SlugWithLink({ rowItem }: ReactDataTableComponentProps) { + return ( + + {rowItem.slug} + + ); +} diff --git a/packages/dev-server/test-plugins/experimental-ui/providers.ts b/packages/dev-server/test-plugins/experimental-ui/providers.ts index 2f085d0595..4c386bfc3a 100644 --- a/packages/dev-server/test-plugins/experimental-ui/providers.ts +++ b/packages/dev-server/test-plugins/experimental-ui/providers.ts @@ -1,7 +1,12 @@ import { addNavMenuSection, registerDataTableComponent } from '@vendure/admin-ui/core'; -import { registerReactFormInputComponent, registerReactCustomDetailComponent } from '@vendure/admin-ui/react'; +import { + registerReactFormInputComponent, + registerReactCustomDetailComponent, + registerReactDataTableComponent, +} from '@vendure/admin-ui/react'; import { CustomTableComponent } from './components/custom-table.component'; +import { CustomColumnComponent } from './components/CustomColumnComponent'; import { CustomDetailComponent } from './components/CustomDetailComponent'; import { ReactNumberInput } from './components/ReactNumberInput'; @@ -40,4 +45,12 @@ export default [ tableId: 'product-list', columnId: 'slug', }), + registerReactDataTableComponent({ + component: CustomColumnComponent, + tableId: 'product-list', + columnId: 'id', + props: { + foo: 'bar', + }, + }), ];