From 3a5931e3efb253dc6e44c76acbcfbe94f2ca918f Mon Sep 17 00:00:00 2001 From: Pieter Loof Date: Tue, 7 Jul 2026 17:02:03 +0200 Subject: [PATCH 1/2] feat: add a custom 'curve-data-table' component that wraps the complex configuration. --- apps/angular-app/src/app/app.routes.ts | 6 + .../custom-data-table.component.html | 15 ++ .../custom-data-table.component.ts | 106 +++++++++++ .../src/lib/ui/curve-data-table/src/index.ts | 11 ++ .../src/lib/curve-data-table-action-cell.ts | 36 ++++ .../lib/curve-data-table-header-sortable.ts | 27 +++ .../src/lib/curve-data-table-image-cell.ts | 18 ++ .../src/lib/curve-data-table-select-all.ts | 20 ++ .../lib/curve-data-table-select-row-cell.ts | 19 ++ .../src/lib/curve-data-table-string-cell.ts | 11 ++ .../src/lib/curve-data-table.html | 38 ++++ .../src/lib/curve-data-table.stories.ts | 119 ++++++++++++ .../src/lib/curve-data-table.ts | 175 ++++++++++++++++++ .../model/curve-data-table-action-column.ts | 11 ++ .../src/lib/model/curve-data-table-column.ts | 3 + .../model/curve-data-table-image-column.ts | 11 ++ .../lib/model/curve-data-table-row-event.ts | 4 + .../curve-data-table-selection-column.ts | 3 + .../model/curve-data-table-string-column.ts | 22 +++ packages/angular/src/public-api.ts | 1 + packages/angular/tsconfig.json | 1 + packages/contracts/src/curve-data-table.ts | 4 + packages/contracts/src/index.ts | 1 + 23 files changed, 662 insertions(+) create mode 100644 apps/angular-app/src/app/custom-data-table/custom-data-table.component.html create mode 100644 apps/angular-app/src/app/custom-data-table/custom-data-table.component.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/index.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-action-cell.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-header-sortable.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-image-cell.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-select-all.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-select-row-cell.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-string-cell.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.html create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.stories.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-action-column.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-column.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-image-column.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-row-event.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-selection-column.ts create mode 100644 packages/angular/src/lib/ui/curve-data-table/src/lib/model/curve-data-table-string-column.ts create mode 100644 packages/contracts/src/curve-data-table.ts diff --git a/apps/angular-app/src/app/app.routes.ts b/apps/angular-app/src/app/app.routes.ts index b49437f..4d116de 100644 --- a/apps/angular-app/src/app/app.routes.ts +++ b/apps/angular-app/src/app/app.routes.ts @@ -1,5 +1,6 @@ import { Routes } from '@angular/router'; import { DemoComponent } from './demo/demo.component'; +import { CustomDataTableComponent } from './custom-data-table/custom-data-table.component'; export const routes: Routes = [ { @@ -12,4 +13,9 @@ export const routes: Routes = [ pathMatch: 'full', component: DemoComponent, }, + { + 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..3bbf4f0 --- /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 opiniated, 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..187894b --- /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-icon-cell', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [HlmDropdownMenuImports, NgIcon], + providers: [provideIcons({ phosphorDotsThree })], + template: ` + + + + @for (action of actions(); track 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: ` + + `, +}) +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..12925b3 --- /dev/null +++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table-image-cell.ts @@ -0,0 +1,18 @@ +import { Component, ChangeDetectionStrategy, input } from '@angular/core'; +import { NgIcon } from '@ng-icons/core'; + +@Component({ + selector: 'data-table-image-cell', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [NgIcon], + 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()) { + + + + @for (column of hideableColumns(); track 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..7250898 --- /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.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, the image column is a separate column, whereas in the other data-table component it is in the 'App' column. + */ +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..21e964a --- /dev/null +++ b/packages/angular/src/lib/ui/curve-data-table/src/lib/curve-data-table.ts @@ -0,0 +1,175 @@ +import { Component, input, OnInit, output, signal } from '@angular/core'; +import { NgIcon, provideIcons } from '@ng-icons/core'; +import { + phosphorCaretUpDown, + phosphorDotsThree, + phosphorImage, + phosphorMagnifyingGlass, + phosphorPlus, +} 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 '../../../curve-data-table/src'; +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', + imports: [ + HlmButtonImports, + HlmDataTableImports, + HlmDropdownMenuImports, + HlmInputImports, + HlmInputGroupImports, + NgIcon, + ], + providers: [ + provideIcons({ + phosphorCaretUpDown, + phosphorDotsThree, + phosphorImage, + phosphorMagnifyingGlass, + phosphorPlus, + }), + ], +}) +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[] { + let 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..1decc93 --- /dev/null +++ b/packages/contracts/src/curve-data-table.ts @@ -0,0 +1,4 @@ +export const curveDataTableContract = { + description: + 'A custom opiniated data table built on the data table component to reduce the amount of configuration needed to use it.', +} as const; 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'; From d6ea25b4b1a1bc537118012f6069a3dc77c1ff0e Mon Sep 17 00:00:00 2001 From: Pieter Loof Date: Tue, 7 Jul 2026 17:39:55 +0200 Subject: [PATCH 2/2] fix: contract for curve-data-table to conform with updates from main branch. --- .../src/lib/curve-data-table.stories.ts | 2 +- packages/contracts/src/curve-data-table.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) 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 index 7250898..466339e 100644 --- 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 @@ -81,7 +81,7 @@ const meta: Meta = { parameters: { docs: { description: { - component: curveDataTableContract.description, + component: curveDataTableContract.docs.description, }, }, }, diff --git a/packages/contracts/src/curve-data-table.ts b/packages/contracts/src/curve-data-table.ts index 1decc93..b3aed93 100644 --- a/packages/contracts/src/curve-data-table.ts +++ b/packages/contracts/src/curve-data-table.ts @@ -1,4 +1,8 @@ -export const curveDataTableContract = { - description: - 'A custom opiniated data table built on the data table component to reduce the amount of configuration needed to use it.', -} as const; +import { defineContract } from './define-contract.js'; + +export const curveDataTableContract = defineContract({ + docs: { + description: + 'A custom opiniated data table built on the data table component to reduce the amount of configuration needed to use it.', + }, +});