diff --git a/apps/angular-app/src/app/app.routes.ts b/apps/angular-app/src/app/app.routes.ts
index 518b77e..18f1b39 100644
--- a/apps/angular-app/src/app/app.routes.ts
+++ b/apps/angular-app/src/app/app.routes.ts
@@ -2,6 +2,7 @@ import { Routes } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { AppShellComponent } from './shell/app-shell.component';
import { BrowseAppsComponent } from './browse-apps/browse-apps.component';
+import { CustomDataTableComponent } from './custom-data-table/custom-data-table.component';
export const routes: Routes = [
{
@@ -23,4 +24,9 @@ export const routes: Routes = [
},
],
},
+ {
+ path: 'table',
+ pathMatch: 'full',
+ component: CustomDataTableComponent,
+ },
];
diff --git a/apps/angular-app/src/app/custom-data-table/custom-data-table.component.html b/apps/angular-app/src/app/custom-data-table/custom-data-table.component.html
new file mode 100644
index 0000000..9d2ada5
--- /dev/null
+++ b/apps/angular-app/src/app/custom-data-table/custom-data-table.component.html
@@ -0,0 +1,15 @@
+
+ Dit is bijna dezelfde tabel, maar deze gebruikt een wrapper component die tabelconfiguratie
+ grotendeels standaardiseert, waardoor het gebruik er van een stuk eenvoudiger is. Het wordt
+ daarmee ook opinionated, maar dat wil je tot op zekere hoogte ook om consistentie af te dwingen.
+
+
+
+
+
+Laatst uitgevoerde actie: {{ lastActionDisplay }}
diff --git a/apps/angular-app/src/app/custom-data-table/custom-data-table.component.ts b/apps/angular-app/src/app/custom-data-table/custom-data-table.component.ts
new file mode 100644
index 0000000..f9c7c9f
--- /dev/null
+++ b/apps/angular-app/src/app/custom-data-table/custom-data-table.component.ts
@@ -0,0 +1,106 @@
+import { Component } from '@angular/core';
+import { provideIcons } from '@ng-icons/core';
+import { phosphorPlus } from '@ng-icons/phosphor-icons/regular';
+import {
+ ICurveDataTableColumn,
+ CurveDataTableImports,
+ CurveDataTableActionColumn,
+ CurveDataTableImageColumn,
+ CurveDataTableSelectionColumn,
+ CurveDataTableStringColumn,
+ CurveDataTableRowEvent,
+} from '@surfnet/curve-angular';
+
+type App = {
+ id: string;
+ imageUrl?: string;
+ app: string;
+ vendor: string;
+ revenue: number;
+};
+
+const data: App[] = [
+ {
+ id: '1',
+ imageUrl: 'https://picsum.photos/seed/123/40',
+ app: '10voordeleraar-loa1',
+ vendor: '10voordeleraar-loa1',
+ revenue: 199,
+ },
+ {
+ id: '2',
+ imageUrl: 'https://picsum.photos/seed/321/40',
+ app: '10voordeleraar-loa2-without-interaction-with-mail',
+ vendor: '10voordeleraar-loa2-without-interaction-with-mail',
+ revenue: 199,
+ },
+ {
+ id: '3',
+ app: '10voordeleraar-loa2-without-interaction-with-mail',
+ vendor: '10voordeleraar-loa2-without-interaction-with-mail',
+ revenue: 199,
+ },
+ {
+ id: '4',
+ app: '10voordeleraar-loa2-without-interaction-with-mail',
+ vendor: '10voordeleraar-loa2-without-interaction-with-mail',
+ revenue: 199,
+ },
+ {
+ id: '5',
+ imageUrl: 'https://picsum.photos/seed/123/40',
+ app: '10voordeleraar-loa1',
+ vendor: '10voordeleraar-loa1',
+ revenue: 199,
+ },
+ {
+ id: '6',
+ app: '10voordeleraar-loa2-without-interaction-with-mail',
+ vendor: '10voordeleraar-loa2-without-interaction-with-mail',
+ revenue: 199,
+ },
+ {
+ id: '7',
+ imageUrl: 'https://picsum.photos/seed/123/40',
+ app: '10voordeleraar-loa1',
+ vendor: '10voordeleraar-loa1',
+ revenue: 199,
+ },
+ {
+ id: '8',
+ imageUrl: 'https://picsum.photos/seed/123/40',
+ app: '10voordeleraar-loa1',
+ vendor: '10voordeleraar-loa1',
+ revenue: 199,
+ },
+];
+
+@Component({
+ selector: 'app-custom-data-table',
+ templateUrl: './custom-data-table.component.html',
+ imports: [CurveDataTableImports],
+ providers: [provideIcons({ phosphorPlus })],
+})
+export class CustomDataTableComponent {
+ public lastActionDisplay: string = '';
+ public data = data;
+ public columns: ICurveDataTableColumn[] = [
+ new CurveDataTableSelectionColumn(),
+ new CurveDataTableImageColumn('imageUrl'),
+ new CurveDataTableStringColumn('app', 'App'),
+ new CurveDataTableStringColumn('vendor', 'Vendor'),
+ new CurveDataTableStringColumn('revenue', 'Revenue', (value: number) =>
+ this.currencyFormatter.format(value),
+ ),
+ new CurveDataTableActionColumn(['Go to details', 'Set inactive']),
+ ];
+
+ private currencyFormatter = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ });
+
+ protected rowActionTriggered(event: CurveDataTableRowEvent) {
+ this.lastActionDisplay = `'${event.action}' op rij ${event.record.id}`;
+ }
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/index.ts b/packages/angular/src/lib/ui/curve-data-table/src/index.ts
new file mode 100644
index 0000000..b992958
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/index.ts
@@ -0,0 +1,11 @@
+import { CurveDataTableComponent } from './lib/curve-data-table';
+
+export * from './lib/curve-data-table';
+export * from './lib/model/curve-data-table-column';
+export * from './lib/model/curve-data-table-action-column';
+export * from './lib/model/curve-data-table-image-column';
+export * from './lib/model/curve-data-table-selection-column';
+export * from './lib/model/curve-data-table-string-column';
+export * from './lib/model/curve-data-table-row-event';
+
+export const CurveDataTableImports = [CurveDataTableComponent] as const;
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-action-cell.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-action-cell.ts
new file mode 100644
index 0000000..3632d08
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-action-cell.ts
@@ -0,0 +1,36 @@
+import { Component, ChangeDetectionStrategy, input, output } from '@angular/core';
+import { NgIcon, provideIcons } from '@ng-icons/core';
+import { phosphorDotsThree } from '@ng-icons/phosphor-icons/regular';
+import { HlmDropdownMenuImports } from '../../../dropdown-menu/src';
+import { CurveDataTableRowEvent } from './model/curve-data-table-row-event';
+
+@Component({
+ selector: 'data-table-action-cell',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [HlmDropdownMenuImports, NgIcon],
+ providers: [provideIcons({ phosphorDotsThree })],
+ template: `
+
+
+
+
+
+ @for (action of actions(); track action) {
+
+ {{ action }}
+
+ }
+
+
+ `,
+})
+export class DataTableActionCell {
+ public readonly record = input.required();
+ public readonly actions = input.required();
+ public actionTriggered = output>();
+
+ onActionClick(action: string) {
+ const event: CurveDataTableRowEvent = { action: action, record: this.record() };
+ this.actionTriggered.emit(event);
+ }
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-header-sortable.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-header-sortable.ts
new file mode 100644
index 0000000..7ca0a61
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-header-sortable.ts
@@ -0,0 +1,27 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+import { NgIcon, provideIcons } from '@ng-icons/core';
+import { phosphorArrowsDownUp } from '@ng-icons/phosphor-icons/regular';
+import { Column } from '@tanstack/angular-table';
+import { HlmButton } from '../../../button/src';
+
+@Component({
+ selector: 'data-table-header-sortable',
+ imports: [HlmButton, NgIcon],
+ providers: [provideIcons({ phosphorArrowsDownUp })],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+ {{ label() }}
+
+
+ `,
+})
+export class DataTableHeaderSortable {
+ public readonly label = input.required();
+ public readonly column = input.required>();
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-image-cell.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-image-cell.ts
new file mode 100644
index 0000000..ef10826
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-image-cell.ts
@@ -0,0 +1,20 @@
+import { Component, ChangeDetectionStrategy, input } from '@angular/core';
+import { NgIcon, provideIcons } from '@ng-icons/core';
+import { phosphorImage } from '@ng-icons/phosphor-icons/regular';
+
+@Component({
+ selector: 'data-table-image-cell',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [NgIcon],
+ providers: [provideIcons({ phosphorImage })],
+ template: `
+ @if (imageUrl()) {
+
+ } @else {
+
+ }
+ `,
+})
+export class DataTableImageCell {
+ public readonly imageUrl = input();
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-select-all.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-select-all.ts
new file mode 100644
index 0000000..750a050
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-select-all.ts
@@ -0,0 +1,20 @@
+import { Component, ChangeDetectionStrategy, input } from '@angular/core';
+import { Table } from '@tanstack/angular-table';
+import { HlmCheckbox } from '../../../checkbox/src';
+
+@Component({
+ selector: 'data-table-select-all',
+ imports: [HlmCheckbox],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+ `,
+})
+export class DataTableSelectAll {
+ public readonly table = input.required>();
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-select-row-cell.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-select-row-cell.ts
new file mode 100644
index 0000000..63c4e9c
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-select-row-cell.ts
@@ -0,0 +1,19 @@
+import { Component, ChangeDetectionStrategy, input } from '@angular/core';
+import { Row } from '@tanstack/angular-table';
+import { HlmCheckbox } from '../../../checkbox/src';
+
+@Component({
+ selector: 'data-table-select-row',
+ imports: [HlmCheckbox],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+ `,
+})
+export class DataTableSelectRowCell {
+ public readonly row = input.required>();
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-string-cell.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-string-cell.ts
new file mode 100644
index 0000000..c4ca398
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-string-cell.ts
@@ -0,0 +1,11 @@
+import { Component, ChangeDetectionStrategy, input } from '@angular/core';
+
+@Component({
+ selector: 'data-table-string-cell',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `{{ value() }}
`,
+})
+export class DataTableStringCell {
+ public readonly value = input.required();
+ public readonly alignRight = input.required();
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.html b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.html
new file mode 100644
index 0000000..ffd3ece
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.html
@@ -0,0 +1,38 @@
+@if (searchColumn() || hasColumnFilter()) {
+
+ @if (searchColumn()) {
+
+ } @if (hasColumnFilter()) {
+
+ Columns
+
+
+
+ @for (column of hideableColumns(); track column.id) {
+
+ {{ column.id }}
+
+
+ }
+
+
+ }
+
+}
+
+
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.stories.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.stories.ts
new file mode 100644
index 0000000..eac10f4
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.stories.ts
@@ -0,0 +1,119 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular';
+import { curveDataTableContract } from '@surfnet/curve-contracts';
+import {
+ CurveDataTableActionColumn,
+ CurveDataTableImports,
+ CurveDataTableSelectionColumn,
+ CurveDataTableStringColumn,
+ ICurveDataTableColumn,
+} from '..';
+import { CurveDataTableRowEvent } from './model/curve-data-table-row-event';
+
+// ---------------------------------------------------------------------------
+// Shared fixtures
+// ---------------------------------------------------------------------------
+
+type Payment = {
+ id: string;
+ amount: number;
+ status: 'pending' | 'processing' | 'success' | 'failed';
+ email: string;
+};
+
+const data: Payment[] = [
+ { id: 'm5gr84i9', amount: 630.44, status: 'success', email: 'Michael.MITC@example.com' },
+ { id: '3u1reuv4', amount: 767.5, status: 'success', email: 'felicia.reid@example.com' },
+ { id: 'derv1ws0', amount: 396.84, status: 'processing', email: 'Georgia.Young@example.com' },
+ { id: '5kma53ae', amount: 475.22, status: 'success', email: 'alma.lawson@example.com' },
+ { id: 'bhqecj4p', amount: 275.43, status: 'failed', email: 'dolores.chambers@example.com' },
+];
+
+const currency = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
+
+// ---------------------------------------------------------------------------
+// Shared columns
+// ---------------------------------------------------------------------------
+
+const columns: ICurveDataTableColumn[] = [
+ new CurveDataTableSelectionColumn(),
+ new CurveDataTableStringColumn('status', 'Status'),
+ new CurveDataTableStringColumn('email', 'Email', undefined, true),
+ new CurveDataTableStringColumn('amount', 'Amount', (value: number) => currency.format(value)),
+ new CurveDataTableActionColumn(['Copy payment ID', 'View customer', 'View payment details']),
+];
+
+// ---------------------------------------------------------------------------
+// Basic render component that uses the data and columns above
+// ---------------------------------------------------------------------------
+
+@Component({
+ selector: 'default-data-table',
+ imports: [CurveDataTableImports],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+ `,
+})
+class DefaultDataTable {
+ public searchColumn = input.required();
+ public hasColumnFilter = input.required();
+ public readonly data = data;
+ public readonly columns = columns;
+
+ protected copyId(event: CurveDataTableRowEvent): void {
+ void navigator.clipboard?.writeText(event.record.id);
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Meta + stories
+// ---------------------------------------------------------------------------
+
+const meta: Meta = {
+ title: 'Components/CurveDataTable',
+ parameters: {
+ docs: {
+ description: {
+ component: curveDataTableContract.docs.description,
+ },
+ },
+ },
+ argTypes: {
+ searchColumn: {
+ control: 'inline-radio',
+ options: ['status', 'email', 'amount', null],
+ description:
+ "Stel in op welke kolom gefilterd kan worden. Bij 'null' wordt het filter verborgen.",
+ },
+ },
+ args: {
+ searchColumn: 'email',
+ hasColumnFilter: true,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+/**
+ * Full table: filter input, column-visibility toggle, row selection, sortable Email column,
+ * right-aligned Amount, row actions, and Previous/Next pagination.
+ * NOTE:
+ * - Filtering on amount currenty does not work. This interactive option is not present in the other data-table component.
+ * - This component does currently not support passing CSS classes to cells, for example tailing utility classes for rendering uppercase or lowercase. Right-alignment of numbers is currently automatic.
+ * - For simplicity there are only single-typed column, so making a column with an image and text is not supported currently.
+ */
+export const Default: Story = {
+ render: (args) => ({
+ props: args,
+ moduleMetadata: { imports: [DefaultDataTable] },
+ template: ` `,
+ }),
+};
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.ts
new file mode 100644
index 0000000..761ad3d
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.ts
@@ -0,0 +1,166 @@
+import { ChangeDetectionStrategy, Component, input, OnInit, output, signal } from '@angular/core';
+import { NgIcon, provideIcons } from '@ng-icons/core';
+import { phosphorMagnifyingGlass } from '@ng-icons/phosphor-icons/regular';
+import { flexRenderComponent, type ColumnDef, type Table } from '@tanstack/angular-table';
+import { DataTableActionCell } from './curve-data-table-action-cell';
+import { DataTableImageCell } from './curve-data-table-image-cell';
+import { DataTableSelectAll } from './curve-data-table-select-all';
+import { DataTableSelectRowCell } from './curve-data-table-select-row-cell';
+import { DataTableStringCell } from './curve-data-table-string-cell';
+import { DataTableHeaderSortable } from './curve-data-table-header-sortable';
+import { CurveDataTableRowEvent } from './model/curve-data-table-row-event';
+import { HlmButtonImports } from '../../../button/src';
+import {
+ CurveDataTableActionColumn,
+ CurveDataTableImageColumn,
+ CurveDataTableSelectionColumn,
+ CurveDataTableStringColumn,
+ ICurveDataTableColumn,
+} from '..';
+import { HlmDataTableImports, injectDataTable } from '../../../data-table/src';
+import { HlmDropdownMenuImports } from '../../../dropdown-menu/src';
+import { HlmInputImports } from '../../../input/src';
+import { HlmInputGroupImports } from '../../../input-group/src';
+
+@Component({
+ selector: 'curve-data-table',
+ templateUrl: './curve-data-table.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ HlmButtonImports,
+ HlmDataTableImports,
+ HlmDropdownMenuImports,
+ HlmInputImports,
+ HlmInputGroupImports,
+ NgIcon,
+ ],
+ providers: [
+ provideIcons({
+ phosphorMagnifyingGlass,
+ }),
+ ],
+})
+export class CurveDataTableComponent implements OnInit {
+ public data = input.required();
+ public columns = input.required();
+ public searchColumn = input(null);
+ public hasColumnFilter = input(true);
+ public rowActionTriggered = output>();
+
+ protected tanstackColumns!: ColumnDef[];
+ protected table!: Table;
+ protected readonly appFilter = signal('');
+
+ ngOnInit(): void {
+ this.tanstackColumns = this.createColumns();
+
+ this.table = injectDataTable(() => ({ data: this.data(), columns: this.tanstackColumns }));
+ }
+
+ protected hideableColumns() {
+ return this.table.getAllColumns().filter((column) => column.getCanHide());
+ }
+
+ protected setAppFilter(event: Event): void {
+ const value = (event.target as HTMLInputElement).value;
+ this.appFilter.set(value);
+ this.table.getColumn(this.searchColumn()!)?.setFilterValue(value);
+ }
+
+ private createColumns(): ColumnDef[] {
+ const columns: ColumnDef[] = [];
+
+ this.columns().forEach((column) => {
+ if (column instanceof CurveDataTableSelectionColumn) {
+ columns.push(this.createCheckboxColumn());
+ }
+ if (column instanceof CurveDataTableStringColumn) {
+ columns.push(
+ this.createStringColumn(
+ column.fieldName,
+ column.header,
+ column.displayMethod,
+ column.sortable,
+ ),
+ );
+ } else if (column instanceof CurveDataTableImageColumn) {
+ columns.push(this.createImageColumn(column.fieldName, column.header));
+ } else if (column instanceof CurveDataTableActionColumn) {
+ columns.push(this.createActionColumn(column.actions, column.header));
+ }
+ });
+
+ return columns;
+ }
+
+ private createCheckboxColumn(): ColumnDef {
+ const column: ColumnDef = {
+ id: 'select',
+ header: ({ table }) => flexRenderComponent(DataTableSelectAll, { inputs: { table } }),
+ cell: ({ row }) => flexRenderComponent(DataTableSelectRowCell, { inputs: { row } }),
+ enableSorting: false,
+ enableHiding: false,
+ };
+
+ return column;
+ }
+
+ private createStringColumn(
+ fieldName: string,
+ header?: string,
+ displayMethod?: (value: any) => string,
+ sortable: boolean = false,
+ ): ColumnDef {
+ const column: ColumnDef = {
+ accessorKey: fieldName,
+ header: sortable
+ ? ({ column }) =>
+ flexRenderComponent(DataTableHeaderSortable, {
+ inputs: { column, label: header },
+ })
+ : header,
+ cell: ({ row }) => {
+ const value = row.getValue(fieldName);
+ const isNumber = typeof row.getValue(fieldName) === 'number';
+ const displayValue = displayMethod ? displayMethod(value) : value;
+ return flexRenderComponent(DataTableStringCell, {
+ inputs: { value: displayValue, alignRight: isNumber },
+ });
+ },
+ };
+
+ return column;
+ }
+
+ private createImageColumn(fieldName: string, header?: string): ColumnDef {
+ const column: ColumnDef = {
+ accessorKey: fieldName,
+ header: header,
+ cell: ({ row }) =>
+ flexRenderComponent(DataTableImageCell, {
+ inputs: { imageUrl: row.getValue(fieldName) },
+ }),
+ };
+
+ return column;
+ }
+
+ private createActionColumn(actions: string[], header?: string): ColumnDef {
+ const column: ColumnDef = {
+ id: 'actions',
+ header: header,
+ enableHiding: false,
+ cell: ({ row }) =>
+ flexRenderComponent(DataTableActionCell, {
+ inputs: { record: row.original, actions: actions },
+ outputs: {
+ actionTriggered: (event: CurveDataTableRowEvent) => {
+ this.rowActionTriggered.emit(event);
+ },
+ },
+ }),
+ };
+
+ return column;
+ }
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-action-column.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-action-column.ts
new file mode 100644
index 0000000..5588979
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-action-column.ts
@@ -0,0 +1,11 @@
+import { ICurveDataTableColumn } from '../..';
+
+export class CurveDataTableActionColumn implements ICurveDataTableColumn {
+ public header?: string;
+ public actions: string[];
+
+ constructor(actions: string[], header?: string) {
+ this.header = header;
+ this.actions = actions;
+ }
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-column.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-column.ts
new file mode 100644
index 0000000..b6ab098
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-column.ts
@@ -0,0 +1,3 @@
+export interface ICurveDataTableColumn {
+ header?: string;
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-image-column.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-image-column.ts
new file mode 100644
index 0000000..301dd3f
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-image-column.ts
@@ -0,0 +1,11 @@
+import { ICurveDataTableColumn } from '../..';
+
+export class CurveDataTableImageColumn implements ICurveDataTableColumn {
+ public header?: string;
+ public fieldName!: string;
+
+ constructor(fieldName: string, header?: string) {
+ this.header = header;
+ this.fieldName = fieldName;
+ }
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-row-event.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-row-event.ts
new file mode 100644
index 0000000..973f374
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-row-event.ts
@@ -0,0 +1,4 @@
+export class CurveDataTableRowEvent {
+ public action!: string;
+ public record!: TData;
+}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-selection-column.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-selection-column.ts
new file mode 100644
index 0000000..10cbaba
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-selection-column.ts
@@ -0,0 +1,3 @@
+import { ICurveDataTableColumn } from '../..';
+
+export class CurveDataTableSelectionColumn implements ICurveDataTableColumn {}
diff --git a/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-string-column.ts b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-string-column.ts
new file mode 100644
index 0000000..c6dae1b
--- /dev/null
+++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-string-column.ts
@@ -0,0 +1,22 @@
+import { ICurveDataTableColumn } from '../..';
+
+export class CurveDataTableStringColumn implements ICurveDataTableColumn {
+ public header?: string;
+ public fieldName!: string;
+ public sortable?: boolean;
+ public displayMethod?: (fieldValue: TFieldType) => string;
+
+ constructor(
+ fieldName: string,
+ header?: string,
+ displayMethod?: (fieldValue: TFieldType) => string,
+ sortable = false,
+ ) {
+ this.header = header;
+ this.fieldName = fieldName;
+ this.sortable = sortable;
+ this.displayMethod = displayMethod
+ ? displayMethod
+ : (fieldValue: TFieldType) => `${fieldValue}`;
+ }
+}
diff --git a/packages/angular/src/public-api.ts b/packages/angular/src/public-api.ts
index 4ae1061..671c0db 100644
--- a/packages/angular/src/public-api.ts
+++ b/packages/angular/src/public-api.ts
@@ -11,6 +11,7 @@ export * from './lib/ui/button/src';
export * from './lib/ui/card/src';
export * from './lib/ui/checkbox/src';
export * from './lib/ui/data-table/src';
+export * from './lib/ui/curve-data-table/src';
export * from './lib/ui/dropdown-menu/src';
export * from './lib/ui/field/src';
export * from './lib/ui/input/src';
diff --git a/packages/angular/tsconfig.json b/packages/angular/tsconfig.json
index 545e7d5..9127363 100644
--- a/packages/angular/tsconfig.json
+++ b/packages/angular/tsconfig.json
@@ -19,6 +19,7 @@
"@spartan-ng/helm/card": ["./src/lib/ui/card/src/index.ts"],
"@spartan-ng/helm/checkbox": ["./src/lib/ui/checkbox/src/index.ts"],
"@spartan-ng/helm/data-table": ["./src/lib/ui/data-table/src/index.ts"],
+ "@spartan-ng/helm/curve-data-table": ["./src/lib/ui/curve-data-table/src/index.ts"],
"@spartan-ng/helm/dropdown-menu": ["./src/lib/ui/dropdown-menu/src/index.ts"],
"@spartan-ng/helm/field": ["./src/lib/ui/field/src/index.ts"],
"@spartan-ng/helm/input": ["./src/lib/ui/input/src/index.ts"],
diff --git a/packages/contracts/src/curve-data-table.ts b/packages/contracts/src/curve-data-table.ts
new file mode 100644
index 0000000..09a2181
--- /dev/null
+++ b/packages/contracts/src/curve-data-table.ts
@@ -0,0 +1,8 @@
+import { defineContract } from './define-contract.js';
+
+export const curveDataTableContract = defineContract({
+ docs: {
+ description:
+ 'A custom opinionated data table built on the data table component to reduce the amount of configuration needed to use it.',
+ },
+});
diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts
index 9b05c39..87609d7 100644
--- a/packages/contracts/src/index.ts
+++ b/packages/contracts/src/index.ts
@@ -4,6 +4,7 @@ export { breadcrumbContract } from './breadcrumb.js';
export { buttonContract, type ButtonVariantName, type ButtonSizeName } from './button.js';
export { checkboxContract } from './checkbox.js';
export { dataTableContract } from './data-table.js';
+export { curveDataTableContract } from './curve-data-table.js';
export { dropdownMenuContract, type DropdownMenuItemVariantName } from './dropdown-menu.js';
export { fieldContract, type FieldOrientationName } from './field.js';
export { inputContract } from './input.js';