Skip to content

Commit

Permalink
feat(admin-ui): Allow custom React components in data table columns
Browse files Browse the repository at this point in the history
Relates to #2347, relates to #2353
  • Loading branch information
michaelbromley committed Sep 7, 2023
1 parent c6f08c6 commit 5cde775
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,14 @@
<td *ngFor="let column of visibleSortedColumns" [class.active]="activeIndex === i">
<div class="cell-content" [ngClass]="column.align">
<ng-container
*ngIf="
customComponents.get(column.id) as customComponetConfig;
else defaultComponent
"
*ngIf="customComponents.get(column.id) as componentConfig; else defaultComponent"
>
<ng-container
*ngComponentOutlet="customComponetConfig.component; inputs: { rowItem: item }"
*ngComponentOutlet="
componentConfig.config.component;
inputs: { rowItem: item };
injector: componentConfig.injector
"
></ng-container>
</ng-container>
<ng-template #defaultComponent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ContentChildren,
EventEmitter,
inject,
Injector,
Input,
OnChanges,
OnDestroy,
Expand Down Expand Up @@ -124,10 +125,11 @@ export class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDe
@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, DataTableComponentConfig>();
protected customComponents = new Map<string, { config: DataTableComponentConfig; injector: Injector }>();

rowTemplate: TemplateRef<any>;
currentStart: number;
Expand Down Expand Up @@ -226,12 +228,13 @@ export class DataTable2Component<T> 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 });
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, any>;
}>('REACT_CUSTOM_COLUMN_COMPONENT_OPTIONS');

@Component({
selector: 'vdr-react-custom-column-component',
template: ` <div [vdrReactComponentHost]="reactComponent" [props]="props"></div> `,
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<string, any>;

ngOnInit() {
this.props = {
rowItem: this.rowItem,
...(this.options.props ?? {}),
};
}
}
2 changes: 2 additions & 0 deletions packages/admin-ui/src/lib/react/src/public_api.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Original file line number Diff line number Diff line change
@@ -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<string, any>;
}

/**
* @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 (
* <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',
* },
* }),
* ];
* ```
*/
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],
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
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>
);
}
15 changes: 14 additions & 1 deletion packages/dev-server/test-plugins/experimental-ui/providers.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -40,4 +45,12 @@ export default [
tableId: 'product-list',
columnId: 'slug',
}),
registerReactDataTableComponent({
component: CustomColumnComponent,
tableId: 'product-list',
columnId: 'id',
props: {
foo: 'bar',
},
}),
];

0 comments on commit 5cde775

Please sign in to comment.