Skip to content

Commit

Permalink
Add table component (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
FelixTJDietrich authored Sep 13, 2024
1 parent 2f28091 commit bafe553
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 0 deletions.
15 changes: 15 additions & 0 deletions webapp/src/app/ui/table/table-body.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Directive, computed, input } from '@angular/core';
import { ClassValue } from 'clsx';
import { cn } from 'app/utils';

@Directive({
selector: 'tbody[appTableBody]',
standalone: true,
host: {
'[class]': 'computedClass()'
}
})
export class TableBodyDirective {
class = input<ClassValue>();
computedClass = computed(() => cn('[&_tr:last-child]:border-0', this.class()));
}
15 changes: 15 additions & 0 deletions webapp/src/app/ui/table/table-caption.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Directive, computed, input } from '@angular/core';
import { ClassValue } from 'clsx';
import { cn } from 'app/utils';

@Directive({
selector: 'caption[appTableCaption]',
standalone: true,
host: {
'[class]': 'computedClass()'
}
})
export class TableCaptionDirective {
class = input<ClassValue>();
computedClass = computed(() => cn('mt-4 text-sm text-muted-foreground', this.class()));
}
15 changes: 15 additions & 0 deletions webapp/src/app/ui/table/table-cell.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Directive, computed, input } from '@angular/core';
import { ClassValue } from 'clsx';
import { cn } from 'app/utils';

@Directive({
selector: 'td[appTableCell]',
standalone: true,
host: {
'[class]': 'computedClass()'
}
})
export class TableCellDirective {
class = input<ClassValue>();
computedClass = computed(() => cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', this.class()));
}
15 changes: 15 additions & 0 deletions webapp/src/app/ui/table/table-footer.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Directive, computed, input } from '@angular/core';
import { ClassValue } from 'clsx';
import { cn } from 'app/utils';

@Directive({
selector: 'tfoot[appTableFooter]',
standalone: true,
host: {
'[class]': 'computedClass()'
}
})
export class TableFooterDirective {
class = input<ClassValue>();
computedClass = computed(() => cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', this.class()));
}
15 changes: 15 additions & 0 deletions webapp/src/app/ui/table/table-head.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Directive, computed, input } from '@angular/core';
import { ClassValue } from 'clsx';
import { cn } from 'app/utils';

@Directive({
selector: 'th[appTableHead]',
standalone: true,
host: {
'[class]': 'computedClass()'
}
})
export class TableHeadDirective {
class = input<ClassValue>();
computedClass = computed(() => cn('h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0', this.class()));
}
15 changes: 15 additions & 0 deletions webapp/src/app/ui/table/table-header.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Directive, computed, input } from '@angular/core';
import { ClassValue } from 'clsx';
import { cn } from 'app/utils';

@Directive({
selector: 'thead[appTableHeader]',
standalone: true,
host: {
'[class]': 'computedClass()'
}
})
export class TableHeaderDirective {
class = input<ClassValue>();
computedClass = computed(() => cn('w-full caption-bottom text-sm', this.class()));
}
15 changes: 15 additions & 0 deletions webapp/src/app/ui/table/table-row.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Directive, computed, input } from '@angular/core';
import { ClassValue } from 'clsx';
import { cn } from 'app/utils';

@Directive({
selector: 'tr[appTableRow]',
standalone: true,
host: {
'[class]': 'computedClass()'
}
})
export class TableRowDirective {
class = input<ClassValue>();
computedClass = computed(() => cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', this.class()));
}
18 changes: 18 additions & 0 deletions webapp/src/app/ui/table/table.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component, computed, input } from '@angular/core';
import { ClassValue } from 'clsx';
import { cn } from 'app/utils';

@Component({
selector: 'app-table',
standalone: true,
template: `<table [class]="computedClass()">
<ng-content />
</table>`,
host: {
class: 'relative w-full overflow-auto'
}
})
export class TableComponent {
class = input<ClassValue>();
computedClass = computed(() => cn('w-full caption-bottom text-sm', this.class()));
}
111 changes: 111 additions & 0 deletions webapp/src/app/ui/table/table.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { moduleMetadata, type Meta, type StoryObj } from '@storybook/angular';
import { TableComponent } from './table.component';
import { TableBodyDirective } from './table-body.directive';
import { TableCaptionDirective } from './table-caption.directive';
import { TableCellDirective } from './table-cell.directive';
import { TableFooterDirective } from './table-footer.directive';
import { TableHeaderDirective } from './table-header.directive';
import { TableHeadDirective } from './table-head.directive';
import { TableRowDirective } from './table-row.directive';

type CustomArgs = {
invoices: {
invoice: string;
paymentStatus: string;
totalAmount: string;
paymentMethod: string;
}[];
};

const meta: Meta<CustomArgs> = {
title: 'UI/Table',
component: TableComponent,
decorators: [
moduleMetadata({
imports: [TableBodyDirective, TableCaptionDirective, TableCellDirective, TableFooterDirective, TableHeaderDirective, TableHeadDirective, TableRowDirective]
})
],
tags: ['autodocs'],
args: {
invoices: [
{
invoice: 'INV001',
paymentStatus: 'Paid',
totalAmount: '$250.00',
paymentMethod: 'Credit Card'
},
{
invoice: 'INV002',
paymentStatus: 'Pending',
totalAmount: '$150.00',
paymentMethod: 'PayPal'
},
{
invoice: 'INV003',
paymentStatus: 'Unpaid',
totalAmount: '$350.00',
paymentMethod: 'Bank Transfer'
},
{
invoice: 'INV004',
paymentStatus: 'Paid',
totalAmount: '$450.00',
paymentMethod: 'Credit Card'
},
{
invoice: 'INV005',
paymentStatus: 'Paid',
totalAmount: '$550.00',
paymentMethod: 'PayPal'
},
{
invoice: 'INV006',
paymentStatus: 'Pending',
totalAmount: '$200.00',
paymentMethod: 'Bank Transfer'
},
{
invoice: 'INV007',
paymentStatus: 'Unpaid',
totalAmount: '$300.00',
paymentMethod: 'Credit Card'
}
]
}
};

export default meta;
type Story = StoryObj<TableComponent>;

export const Default: Story = {
render: (args) => ({
props: args,
template: `
<app-table>
<caption appTableCaption>A list of your recent invoices.</caption>
<thead appTableHeader>
<tr appTableRow>
<th appTableHead class="w-[100px]">Invoice</th>
<th appTableHead>Status</th>
<th appTableHead>Method</th>
<th appTableHead class="text-right">Amount</th>
</tr>
</thead>
<tbody appTableBody>
<tr appTableRow *ngFor="let invoice of invoices; trackBy: trackByInvoice">
<td appTableCell class="font-medium">{{ invoice.invoice }}</td>
<td appTableCell>{{ invoice.paymentStatus }}</td>
<td appTableCell>{{ invoice.paymentMethod }}</td>
<td appTableCell class="text-right">{{ invoice.totalAmount }}</td>
</tr>
</tbody>
<tfoot appTableFooter>
<tr appTableRow>
<td appTableCell colspan="3">Total</td>
<td appTableCell class="text-right">$2,500.00</td>
</tr>
</tfoot>
</app-table>
`
})
};

0 comments on commit bafe553

Please sign in to comment.