Skip to content

Commit 86e3bb2

Browse files
ethanWallacedainemelaniebmngithub-code-quality[bot]
authored
feat: Add gcds-table component (#1218)
* First commit: table with sorting, filtering and pagination * Fix and add sort labels * feat: parse strings for information properties(data and columns) * feat: cellRenderer * Start of dialog * Add rowHeader to TableColumn * Improve rowHeader rendering * filter/sort modal * Update search to filter * Filter fix * Update table status rendering * Add I18N * Update TableColumn interface based on feedback * Update functionality to new design * Start of optimization * Continue optimization work * Default labels + pill render functions * Add table-helpers file * New default label format * Add React and Vue examples * Add gcds-table-slots for testing use of slots * Comment cleanup * fix: sorting pill issue * test(gcds-table): add unit tests (#1231) * chore: update package.json to enable running tests from root * feat: add basic unit test * chore: bring back first "renders" test and change it to pass current implementation * chore: add test to parse string as json * chore: add more tests * chore: add more tests * chore: add row count and pagination tests * chore: add more tests, french values on filter and sort not working currently * chore: comment out non-working e2e test for this PR (will cover in another PR) * fix: pagination issue & modal elements issue * style(gcds-table): add styling for table component (#1236) * Add storybook stories * feat: add dynamic slots to gcds-table (#1238) * feat: add dynamic slots to gcds-table * Update alignment property in Vue app * Fix styleUrl path * Undo caption change * Fix pagination issue * Change caption to div * Remove old gcds-table from Angular test app * Update caption spec test * Add watchers to pagination props * Remove console.log * feat(gcds-table): Light-dom slots (#1246) * feat(gcds-table): Light-dom slots * Update gcds-table(web) to allow light-dom slots * Add syncSlots + update tests * Add managed flag check to syncSlottedElements * update filter/sort button label + table status rendering * Update tests * Optimize slot rendering + add initializing logic to prevent multiple renders * Pass down attributes to gcds-table in framework wrappers * Empty states * Optimize data binding function + start of french * last french strings * Potential fix for pull request finding 'Semicolon insertion' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Potential fix for pull request finding 'Semicolon insertion' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Fix type colours in index.html * Doc + update tests * Add missing merge conflict * fix package-lock.json * test(gcds-table): add e2e tests (#1248) * chore: add some e2e tests * chore: remove added comments/test name * chore: add sorting tests * chore: add pagination tests * chore: add filter e2e tests * refactor: filter tests * chore: update tests * chore: update tests * chore: add/update a11y tests * chore: change version * chore: fix spec test * chore: fix tests * chore: revert package-lock versions and deps * fix: failing contrast tests on other a11y e2e tests * style(gcds-table): add styling for table states (default, hover, focus, empty) (#1262) * feat(gcds-table): add new table icons to table component * Fixes from feedback * Remove pagination when empty * fix: update sort button icon * feat: add bg colour to empty table * Format index.html * fix: sort heading + icon alignment * feat: add hover + focus styles for table pill * Update table based on PR feedback * Add last French string * Rework GcdsTableStateChange to return SortingState in one property * PR feedback * Optimize parseSizeOptions * Prevent pagination from rendering when filtering rows below page size * style(gcds-table): fix table th vertical alignment and th button width * style(gcds-table): fix th focus + adjust empty state typography styles * Document managed property in TableColumn * chore: update tokens package * Remove wcag2aaa from a11y tests --------- Co-authored-by: Daine Trinidad <daine.trinidad@cds-snc.ca> Co-authored-by: Melanie Boeckmann <melanie.bockmann@gmail.com> Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
1 parent f42307e commit 86e3bb2

54 files changed

Lines changed: 6391 additions & 65 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package-lock.json

Lines changed: 19 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
"test:unit": "npm run test:angular:unit && npm run pretest:web && npm run test:unit --workspace=packages/web",
1717
"pretest:web": "npm run patch:stenciljs",
1818
"pretest:angular": "npm i --prefix packages/angular/tests/app && npm run build:web",
19+
"test:web:unit": "npm run test:unit --workspace=packages/web",
20+
"test:web:e2e": "npm run test:e2e --workspace=packages/web",
21+
"test:web:watch": "npm run test:watch --workspace=packages/web",
22+
"test:web:coverage": "npm run test:coverage --workspace=packages/web",
1923
"test:angular": "playwright test -c packages/angular/playwright.config.ts",
2024
"test:angular:unit": "jest --config=packages/angular/jest.config.js",
2125
"test:angular:unit:watch": "jest --config=packages/angular/jest.config.js --watch",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Directive, Input, TemplateRef } from '@angular/core';
2+
3+
@Directive({
4+
selector: '[gcdsCell]',
5+
standalone: false,
6+
})
7+
export class GcdsCellDirective {
8+
@Input('gcdsCell') field!: string;
9+
10+
constructor(public template: TemplateRef<any>) {}
11+
}

packages/angular/src/lib/gcds-components.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
import { NgModule } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
23
import { DIRECTIVES } from './stencil-generated';
34
import { defineCustomElements } from '@gcds-core/components/loader';
45

56
import { SelectValueAccessor } from './stencil-generated/select-value-accessor';
67
import { TextValueAccessor } from './stencil-generated/text-value-accessor';
78
import { GcdsRouterDirective } from '../lib/directives/gcds-router-link';
9+
import { GcdsCellDirective } from '../lib/directives/gcds-cell.directive';
10+
import { GcdsTableWithSlotsComponent } from './gcds-table-with-slots.component';
811

912
const DECLARATIONS = [
1013
...DIRECTIVES,
1114
// ngModel Accessors
1215
SelectValueAccessor,
1316
TextValueAccessor,
1417
GcdsRouterDirective,
18+
GcdsCellDirective,
19+
GcdsTableWithSlotsComponent,
1520
];
1621

1722
defineCustomElements(window);
1823

1924
@NgModule({
25+
imports: [CommonModule],
2026
declarations: DECLARATIONS,
2127
exports: DECLARATIONS,
2228
})
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import {
2+
Component,
3+
Input,
4+
TemplateRef,
5+
QueryList,
6+
ContentChildren,
7+
AfterContentInit,
8+
AfterViewInit,
9+
OnChanges,
10+
ElementRef,
11+
Renderer2,
12+
ViewChild,
13+
} from '@angular/core';
14+
import type { TableColumn } from '@gcds-core/components';
15+
import { GcdsCellDirective } from './directives/gcds-cell.directive';
16+
17+
export interface AngularTableColumn extends TableColumn {
18+
cellTemplate?: TemplateRef<{ $implicit: unknown; rowId: string }>;
19+
}
20+
21+
const COMPONENT_INPUTS = new Set([
22+
'columns',
23+
'data',
24+
'filter',
25+
'filtervalue',
26+
'pagination',
27+
'paginationcurrentpage',
28+
'paginationsize',
29+
'paginationsizeoptions',
30+
'sort',
31+
]);
32+
33+
@Component({
34+
selector: 'gcds-table-ng',
35+
standalone: false,
36+
template: `
37+
<gcds-table
38+
#gcdsTable
39+
[columns]="wcColumns"
40+
[data]="data"
41+
[filter]="filter"
42+
[filterValue]="filterValue"
43+
[pagination]="pagination"
44+
[paginationCurrentPage]="paginationCurrentPage"
45+
[paginationSize]="paginationSize"
46+
[paginationSizeOptions]="paginationSizeOptions"
47+
[sort]="sort"
48+
>
49+
<ng-content select="[slot='caption']"></ng-content>
50+
51+
<ng-container *ngFor="let row of data; let rowIndex = index">
52+
<ng-container *ngFor="let column of columns">
53+
<span
54+
*ngIf="getTemplate(column.field) as template"
55+
[attr.slot]="getSlotName(row, column, rowIndex)"
56+
>
57+
<ng-container
58+
[ngTemplateOutlet]="template"
59+
[ngTemplateOutletContext]="{
60+
$implicit: row,
61+
row: row,
62+
rowIndex: rowIndex,
63+
column: column,
64+
value: getCellValue(row, column.field),
65+
}"
66+
></ng-container>
67+
</span>
68+
</ng-container>
69+
</ng-container>
70+
71+
<ng-content></ng-content>
72+
</gcds-table>
73+
`,
74+
})
75+
export class GcdsTableWithSlotsComponent
76+
implements AfterContentInit, AfterViewInit, OnChanges
77+
{
78+
@Input() columns: AngularTableColumn[] = [];
79+
@Input() data: Record<string, unknown>[] = [];
80+
@Input() filter = false;
81+
@Input() filterValue = '';
82+
@Input() pagination = false;
83+
@Input() paginationCurrentPage = 1;
84+
@Input() paginationSize = 10;
85+
@Input() paginationSizeOptions: number[] = [10, 25, 50, 0];
86+
@Input() sort = false;
87+
88+
@ContentChildren(GcdsCellDirective)
89+
cellTemplates!: QueryList<GcdsCellDirective>;
90+
91+
@ViewChild('gcdsTable', { read: ElementRef })
92+
gcdsTableEl!: ElementRef<HTMLElement>;
93+
94+
private _wcColumns: TableColumn[] = [];
95+
96+
get wcColumns(): TableColumn[] {
97+
return this._wcColumns;
98+
}
99+
100+
constructor(
101+
private host: ElementRef<HTMLElement>,
102+
private renderer: Renderer2,
103+
) {}
104+
105+
ngAfterViewInit(): void {
106+
this.forwardHostAttrs();
107+
}
108+
109+
ngAfterContentInit(): void {
110+
this._wcColumns = this.computeWcColumns();
111+
112+
this.cellTemplates.changes.subscribe(() => {
113+
this._wcColumns = this.computeWcColumns();
114+
});
115+
}
116+
117+
ngOnChanges(): void {
118+
if (this.cellTemplates) {
119+
this._wcColumns = this.computeWcColumns();
120+
}
121+
}
122+
123+
private forwardHostAttrs(): void {
124+
const hostAttrs = this.host.nativeElement.attributes;
125+
126+
for (let i = 0; i < hostAttrs.length; i++) {
127+
const { name, value } = hostAttrs[i];
128+
129+
// Skip Angular internals and anything owned by @Input
130+
if (
131+
name.startsWith('_ng') ||
132+
name.startsWith('ng-') ||
133+
COMPONENT_INPUTS.has(name.toLowerCase())
134+
) {
135+
continue;
136+
}
137+
138+
this.renderer.setAttribute(this.gcdsTableEl.nativeElement, name, value);
139+
this.renderer.removeAttribute(this.host.nativeElement, name);
140+
}
141+
}
142+
143+
private computeWcColumns(): TableColumn[] {
144+
const templateMap = new Map(
145+
this.cellTemplates.map(d => [d.field, d.template]),
146+
);
147+
148+
return this.columns.map(col => ({
149+
...col,
150+
managed: templateMap.has(col.field) ? true : undefined,
151+
}));
152+
}
153+
154+
get slottedColumns(): { field: string; template: TemplateRef<any> }[] {
155+
return this.cellTemplates.map(d => ({
156+
field: d.field,
157+
template: d.template,
158+
}));
159+
}
160+
161+
private get templateMap(): Map<string, TemplateRef<any>> {
162+
return new Map(this.cellTemplates.map(d => [d.field, d.template]));
163+
}
164+
165+
getTemplate(field: string): TemplateRef<any> | null {
166+
return this.templateMap.get(field) ?? null;
167+
}
168+
169+
getCellValue(row: Record<string, unknown>, field: string): unknown {
170+
return row?.[field as keyof typeof row];
171+
}
172+
173+
getRowKey(row: Record<string, unknown>, rowIndex: number): string {
174+
const candidate = row?.['id'];
175+
176+
if (candidate === null || candidate === undefined || candidate === '') {
177+
return String(rowIndex);
178+
}
179+
180+
return String(candidate);
181+
}
182+
183+
getSlotName(
184+
row: Record<string, unknown>,
185+
column: AngularTableColumn,
186+
rowIndex: number,
187+
): string {
188+
return `cell-${this.getRowKey(row, rowIndex)}-${column.field}`;
189+
}
190+
}

packages/angular/src/lib/stencil-generated/components.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2222,6 +2222,75 @@ export class GcdsStepper {
22222222
export declare interface GcdsStepper extends Components.GcdsStepper {}
22232223

22242224

2225+
@ProxyCmp({
2226+
inputs: ['columns', 'data', 'filter', 'filterValue', 'pagination', 'paginationCurrentPage', 'paginationSize', 'paginationSizeOptions', 'sort'],
2227+
methods: ['getVisibleRows'],
2228+
outputs: ['gcdsTableStateChange']
2229+
})
2230+
@Component({
2231+
selector: 'gcds-table',
2232+
changeDetection: ChangeDetectionStrategy.OnPush,
2233+
template: '<ng-content></ng-content>',
2234+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2235+
inputs: ['columns', 'data', 'filter', 'filterValue', 'pagination', 'paginationCurrentPage', 'paginationSize', 'paginationSizeOptions', 'sort'],
2236+
outputs: ['gcdsTableStateChange'],
2237+
standalone: false,
2238+
})
2239+
export class GcdsTable {
2240+
protected el: HTMLGcdsTableElement;
2241+
/**
2242+
* Column definitions @default []
2243+
*/
2244+
set columns(_: Components.GcdsTable['columns']) {};
2245+
/**
2246+
* Row data @default []
2247+
*/
2248+
set data(_: Components.GcdsTable['data']) {};
2249+
/**
2250+
* Enable global column sorting (can be overridden per column) @default false
2251+
*/
2252+
set sort(_: Components.GcdsTable['sort']) {};
2253+
/**
2254+
* Enable pagination @default false
2255+
*/
2256+
set pagination(_: Components.GcdsTable['pagination']) {};
2257+
/**
2258+
* Current page index @default 1
2259+
*/
2260+
set paginationCurrentPage(_: Components.GcdsTable['paginationCurrentPage']) {};
2261+
/**
2262+
* Number of rows per page @default 10
2263+
*/
2264+
set paginationSize(_: Components.GcdsTable['paginationSize']) {};
2265+
/**
2266+
* Available page-size options.
2267+
Use 0 to represent "All rows". @default [10, 25, 50, 0]
2268+
*/
2269+
set paginationSizeOptions(_: Components.GcdsTable['paginationSizeOptions']) {};
2270+
/**
2271+
* Enable global filter @default false
2272+
*/
2273+
set filter(_: Components.GcdsTable['filter']) {};
2274+
/**
2275+
* Current filter string @default ''
2276+
*/
2277+
set filterValue(_: Components.GcdsTable['filterValue']) {};
2278+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
2279+
c.detach();
2280+
this.el = r.nativeElement;
2281+
proxyOutputs(this, ['gcdsTableStateChange']);
2282+
}
2283+
}
2284+
2285+
2286+
import type { GcdsTableStateChange as IGcdsTableGcdsTableStateChange } from '@gcds-core/components';
2287+
2288+
export declare interface GcdsTable extends Components.GcdsTable {
2289+
2290+
gcdsTableStateChange: EventEmitter<CustomEvent<IGcdsTableGcdsTableStateChange>>;
2291+
}
2292+
2293+
22252294
@ProxyCmp({
22262295
inputs: ['characterLimit', 'display', 'marginBottom', 'marginTop', 'size', 'textRole']
22272296
})

packages/angular/src/lib/stencil-generated/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const DIRECTIVES = [
3838
d.GcdsSignature,
3939
d.GcdsSrOnly,
4040
d.GcdsStepper,
41+
d.GcdsTable,
4142
d.GcdsText,
4243
d.GcdsTextarea,
4344
d.GcdsTopNav,

packages/angular/src/public-api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@ export * from './lib/stencil-generated/components';
88
export { SelectValueAccessor } from './lib/stencil-generated/select-value-accessor';
99
export { TextValueAccessor } from './lib/stencil-generated/text-value-accessor';
1010
export { GcdsRouterDirective } from './lib/directives/gcds-router-link';
11+
export { GcdsCellDirective } from './lib/directives/gcds-cell.directive';
12+
export {
13+
GcdsTableWithSlotsComponent,
14+
AngularTableColumn,
15+
} from './lib/gcds-table-with-slots.component';
1116

1217
export { GcdsComponentsModule } from './lib/gcds-components.module';

0 commit comments

Comments
 (0)