Skip to content

Commit 39adf26

Browse files
authored
List Component (#1086)
* initial UI work completed * added new list components and directives to public api * api docs added, API for list component finalized * Examples page is almost complete * completed unit tests * updated loop track function * updated examples page to separate logs for virtual and non-virtual scroll examples. * added cypress tests for the list component, updated the changelog and fixed a couple data mismatches on the examples page for the list component * removed unnecessary scss file from list page component * Added the virtualScroll input to the API documentation * fixed grammar mistake in cypress test declaration
1 parent 09d97dd commit 39adf26

27 files changed

+1812
-2
lines changed

cypress/e2e/components/list.cy.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
describe('List', () => {
2+
before(() => {
3+
cy.visit('/list');
4+
cy.get('.page-loader').should('not.exist', { timeout: 20000 });
5+
});
6+
7+
describe('List', () => {
8+
it('displays the correct number of headers, columns and rows', () => {
9+
cy.get('ngx-list').first().as('CUT');
10+
cy.get('@CUT').find('ngx-list-header').should('have.length', 3);
11+
cy.get('@CUT').find('ngx-list-row').should('have.length', 10);
12+
cy.get('@CUT').find('ngx-list-column').should('have.length', 30);
13+
});
14+
15+
it('displays a custom column layout', () => {
16+
cy.get('ngx-list').eq(1).as('CUT');
17+
const expectedStyle = 'display: grid; gap: 1rem; grid-template-columns: 3fr 2fr 1fr;';
18+
cy.get('@CUT').find('div.ngx-list__headers-container').should('have.attr', 'style', expectedStyle);
19+
cy.get('@CUT').find('ngx-list-row').first().find('> div').should('have.attr', 'style', expectedStyle);
20+
});
21+
22+
it('displays the row number using the row index', () => {
23+
cy.get('ngx-list').eq(2).as('CUT');
24+
cy.get('@CUT').find('ngx-list-column').first().should('contain.text', '1.');
25+
});
26+
});
27+
});

projects/swimlane/ngx-ui/CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## HEAD (unreleased)
44

5+
- Feature (`ngx-list`): Added a new list component
6+
57
## 49.0.1 (2025-02-19)
68

79
- Fix: Removing the `indeterminate state value validation` in the 'checked' input of the checkbox component
@@ -49,8 +51,8 @@ Breaking (`ngx-time`): There could be a potential breaking change due to how `ng
4951

5052
## 48.0.4 (2024-09-18)
5153

52-
- Enhancement (`ngx-toggle`): Added `timeStamp` when emiting `change`
53-
- Enhancement (`ngx-checkbox`): Added `timeStamp` when emiting `change`
54+
- Enhancement (`ngx-toggle`): Added `timeStamp` when emitting `change`
55+
- Enhancement (`ngx-checkbox`): Added `timeStamp` when emitting `change`
5456

5557
## 48.0.3 (2024-09-17)
5658

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Directive } from '@angular/core';
2+
3+
@Directive({
4+
selector: '[ngx-list-column-template]',
5+
standalone: false
6+
})
7+
export class ListColumnTemplateDirective {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<ng-container [ngTemplateOutlet]="template"></ng-container>
2+
3+
<ng-template #template>
4+
@if (column) {
5+
<ng-container *ngTemplateOutlet="column.columnTemplate; context: { row: data, rowIndex }"></ng-container>
6+
}
7+
</ng-template>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@use 'colors/colors' as colors;
2+
3+
ngx-list-column.ngx-list-column {
4+
color: colors.$color-blue-grey-050;
5+
font-size: 1rem;
6+
line-height: 20px;
7+
overflow: hidden;
8+
white-space: nowrap;
9+
text-overflow: ellipsis;
10+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Component, ContentChild, Input, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
2+
import { ListColumnTemplateDirective } from './list-column-template.directive';
3+
4+
export interface ListColumnInput {
5+
columnTemplate: TemplateRef<any>;
6+
template: TemplateRef<any>;
7+
}
8+
9+
@Component({
10+
selector: 'ngx-list-column',
11+
templateUrl: './list-column.component.html',
12+
styleUrl: './list-column.component.scss',
13+
standalone: false,
14+
host: {
15+
class: 'ngx-list-column'
16+
},
17+
encapsulation: ViewEncapsulation.None
18+
})
19+
export class ListColumnComponent {
20+
@ViewChild('template', { static: true }) template: TemplateRef<any>;
21+
22+
@Input() column: ListColumnInput;
23+
@Input() data: Record<string, unknown>;
24+
@Input() rowIndex: number;
25+
26+
@ContentChild(ListColumnTemplateDirective, { read: TemplateRef, static: true })
27+
columnTemplate: TemplateRef<ListColumnTemplateDirective>;
28+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Directive } from '@angular/core';
2+
3+
@Directive({
4+
selector: '[ngx-list-header-template]',
5+
standalone: false
6+
})
7+
export class ListHeaderTemplateDirective {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<ng-container [ngTemplateOutlet]="template"></ng-container>
2+
3+
<ng-template #template>
4+
@if (header) {
5+
<ng-container [ngTemplateOutlet]="header.headerTemplate"></ng-container>
6+
}
7+
</ng-template>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ngx-list-header.ngx-list-header {
2+
color: #fff;
3+
font-size: 14px;
4+
font-weight: 700;
5+
line-height: 22px;
6+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Component, ContentChild, Input, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
2+
import { ListHeaderTemplateDirective } from './list-header-template.directive';
3+
4+
@Component({
5+
selector: 'ngx-list-header',
6+
templateUrl: './list-header.component.html',
7+
styleUrl: './list-header.component.scss',
8+
standalone: false,
9+
encapsulation: ViewEncapsulation.None,
10+
host: {
11+
class: 'ngx-list-header'
12+
}
13+
})
14+
export class ListHeaderComponent {
15+
@ViewChild('template', { static: true }) template: TemplateRef<any>;
16+
17+
@Input() header: any;
18+
19+
@ContentChild(ListHeaderTemplateDirective, { read: TemplateRef, static: true })
20+
headerTemplate: TemplateRef<ListHeaderTemplateDirective>;
21+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div [ngStyle]="columnLayout">
2+
@for (column of columns; track column) {
3+
<ng-container *ngComponentOutlet="columnComponent; inputs: { column, data, rowIndex: index }"></ng-container>
4+
}
5+
</div>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
@use 'colors/colors' as colors;
2+
3+
ngx-list-row {
4+
&.ngx-list-row {
5+
background-color: colors.$color-blue-grey-800;
6+
border: 1px solid colors.$color-blue-grey-600;
7+
border-radius: 0.25rem;
8+
display: grid;
9+
align-items: center;
10+
height: 40px;
11+
margin: 0.25rem 1rem 0rem 1rem;
12+
padding-inline: 1rem;
13+
position: relative;
14+
15+
&::before {
16+
content: '';
17+
width: 3px;
18+
position: absolute;
19+
left: 0;
20+
height: 100%;
21+
border-top-left-radius: 0.25rem;
22+
border-bottom-left-radius: 0.25rem;
23+
}
24+
25+
&--error::before {
26+
background-color: colors.$color-red-500;
27+
}
28+
29+
&--success::before {
30+
background-color: colors.$color-green-500;
31+
}
32+
33+
&--warning::before {
34+
background-color: colors.$color-orange-400;
35+
}
36+
}
37+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Component, Input, QueryList, ViewEncapsulation } from '@angular/core';
2+
import { ListColumnComponent } from '../list-column/list-column.component';
3+
import { ListRowStatus } from '../models/list-row-status.enum';
4+
5+
@Component({
6+
selector: 'ngx-list-row',
7+
templateUrl: './list-row.component.html',
8+
styleUrl: './list-row.component.scss',
9+
standalone: false,
10+
host: {
11+
class: 'ngx-list-row',
12+
'[class.ngx-list-row--error]': 'status === ListRowStatus.Error || data?.status === ListRowStatus.Error',
13+
'[class.ngx-list-row--success]': 'status === ListRowStatus.Success || data?.status === ListRowStatus.Success',
14+
'[class.ngx-list-row--warning]': 'status === ListRowStatus.Warning || data?.status === ListRowStatus.Warning'
15+
},
16+
encapsulation: ViewEncapsulation.None
17+
})
18+
export class ListRowComponent {
19+
@Input() columnLayout: Partial<CSSStyleDeclaration>;
20+
@Input() columns: QueryList<ListColumnComponent>;
21+
@Input() data: Record<string, unknown>;
22+
@Input() index: number;
23+
@Input() status: ListRowStatus;
24+
25+
columnComponent = ListColumnComponent;
26+
27+
readonly ListRowStatus = ListRowStatus;
28+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<div
2+
[ngStyle]="_columnLayout"
3+
class="ngx-list__headers-container"
4+
[class.ngx-list__headers-container__scrollable]="hasScrollbar"
5+
>
6+
@for (header of headers; track header) {
7+
<ng-container *ngComponentOutlet="headerComponent; inputs: { header: header }"></ng-container>
8+
}
9+
</div>
10+
<hr />
11+
@if (virtualScroll) {
12+
<cdk-virtual-scroll-viewport #virtualScrollViewport [style.height.px]="height" itemSize="44">
13+
<div *cdkVirtualFor="let data of dataSource; index as i" class="ngx-list__virtual-scroll__item">
14+
<ng-container
15+
*ngComponentOutlet="
16+
rowComponent;
17+
inputs: { columnLayout: _columnLayout, columns, data, row, index: i, status: row.status }
18+
"
19+
></ng-container>
20+
</div>
21+
</cdk-virtual-scroll-viewport>
22+
} @else {
23+
<div #listRowsContainer [style.height.px]="height" class="ngx-list__rows-container">
24+
@for (data of dataSource; track data; let idx = $index) {
25+
<ng-container
26+
*ngComponentOutlet="
27+
rowComponent;
28+
inputs: { columnLayout: _columnLayout, columns, data, row, index: idx, status: row.status }
29+
"
30+
></ng-container>
31+
}
32+
</div>
33+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@use 'colors/colors' as colors;
2+
3+
ngx-list.ngx-list {
4+
.ngx-list {
5+
&__headers-container {
6+
padding-inline: 1rem;
7+
margin-inline: 1rem;
8+
9+
&__scrollable {
10+
margin-right: 1.75rem; // Account for scrollbar to maintain column header alignment
11+
}
12+
}
13+
14+
&__rows-container {
15+
overflow-y: auto;
16+
}
17+
18+
&__virtual-scroll {
19+
&__item {
20+
height: 40px;
21+
}
22+
}
23+
}
24+
25+
hr {
26+
border-top: 1px solid colors.$color-blue-grey-600;
27+
border-bottom: 1px solid colors.$color-blue-grey-600;
28+
opacity: 0.75;
29+
margin: 0.75rem 0 0.5rem 0;
30+
}
31+
}

0 commit comments

Comments
 (0)