[ADF-4122] Add sticky header feature to datatable and refactor styles (#4370)

* [ADF-4122] Add sticky header config to datatable and refactor styles

* [ADF-4122] Fix core unit tests

* [ADF-4122] Commit requested changes

* [ADF-4122] Fix e2e tests

* Update docs/core/datatable.component.md

Co-Authored-By: davidcanonieto <david.cano@alfresco.com>
This commit is contained in:
davidcanonieto
2019-03-01 17:23:31 +01:00
committed by Eugenio Romano
parent a7d058fd2e
commit 7da9bd89cb
27 changed files with 418 additions and 323 deletions

View File

@@ -1,25 +1,26 @@
<div
role="grid"
*ngIf="data" class="adf-full-width"
[class.adf-data-table-card]="display === 'gallery'"
[class.adf-data-table]="display === 'list'"
[class.adf-data-table--empty]="!isHeaderVisible()">
[class.adf-datatable-card]="display === 'gallery'"
[class.adf-datatable-list]="display === 'list'"
[class.adf-sticky-header]="stickyHeader"
[class.adf-datatable--empty]="!isHeaderVisible()">
<div *ngIf="showHeader && isHeaderVisible()" class="adf-datatable-header" role="rowgroup">
<div class="adf-datatable-row" *ngIf="display === 'list'" role="row">
<!-- Actions (left) -->
<div *ngIf="actions && actionsPosition === 'left'" class="adf-actions-column adf-datatable-table-cell-header">
<div *ngIf="actions && actionsPosition === 'left'" class="adf-actions-column adf-datatable-cell-header">
<span class="adf-sr-only">Actions</span>
</div>
<!-- Columns -->
<div *ngIf="multiselect" class="adf-datatable-table-cell-header">
<div *ngIf="multiselect" class="adf-datatable-cell-header adf-datatable-checkbox">
<mat-checkbox [checked]="isSelectAllChecked" (change)="onSelectAllClick($event)"></mat-checkbox>
</div>
<div class="adf-data-table-cell--{{col.type || 'text'}} {{col.cssClass}} adf-datatable-table-cell-header"
<div class="adf-datatable-cell--{{col.type || 'text'}} {{col.cssClass}} adf-datatable-cell-header"
*ngFor="let col of data.getColumns()"
[class.adf-sortable]="col.sortable"
[attr.data-automation-id]="'auto_id_' + col.key"
[class.adf-data-table__header--sorted-asc]="isColumnSorted(col, 'asc')"
[class.adf-data-table__header--sorted-desc]="isColumnSorted(col, 'desc')"
[class.adf-datatable__header--sorted-asc]="isColumnSorted(col, 'asc')"
[class.adf-datatable__header--sorted-desc]="isColumnSorted(col, 'desc')"
(click)="onColumnHeaderClick(col)"
(keyup.enter)="onColumnHeaderClick(col)"
role="columnheader"
@@ -29,7 +30,7 @@
<span *ngIf="col.title">{{ col.title | translate}}</span>
</div>
<!-- Actions (right) -->
<div *ngIf="actions && actionsPosition === 'right'" class="adf-actions-column adf-datatable-table-cell-header">
<div *ngIf="actions && actionsPosition === 'right'" class="adf-actions-column adf-datatable-cell-header adf-datatable__actions-cell">
<span class="adf-sr-only">Actions</span>
</div>
</div>
@@ -57,7 +58,7 @@
[ngClass]="getRowStyle(row)"
(keyup)="onRowKeyUp(row, $event)">
<!-- Actions (left) -->
<div *ngIf="actions && actionsPosition === 'left'" role="gridcell" class="adf-datatable-table-cell">
<div *ngIf="actions && actionsPosition === 'left'" role="gridcell" class="adf-datatable-cell">
<button mat-icon-button [matMenuTriggerFor]="menu"
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
[attr.id]="'action_menu_left_' + idx"
@@ -75,7 +76,7 @@
</mat-menu>
</div>
<div *ngIf="multiselect" class="adf-datatable-table-cell adf-datatable-table-checkbox">
<div *ngIf="multiselect" class="adf-datatable-cell adf-datatable-checkbox">
<mat-checkbox
[checked]="row.isSelected"
[attr.aria-checked]="row.isSelected"
@@ -85,7 +86,7 @@
</div>
<div *ngFor="let col of data.getColumns()"
role="gridcell"
class="adf-data-table-cell adf-datatable-table-cell adf-data-table-cell--{{col.type || 'text'}} {{col.cssClass}}"
class=" adf-datatable-cell adf-datatable-cell--{{col.type || 'text'}} {{col.cssClass}}"
[attr.title]="col.title | translate"
[attr.filename]="getFilename(row)"
tabindex="0"
@@ -93,7 +94,7 @@
(keydown.enter)="onEnterKeyPressed(row, $event)"
[adf-context-menu]="getContextMenuActions(row, col)"
[adf-context-menu-enabled]="contextMenu">
<div *ngIf="!col.template" class="adf-cell-container">
<div *ngIf="!col.template" class="adf-datatable-cell-container">
<ng-container [ngSwitch]="col.type">
<div *ngSwitchCase="'image'" class="adf-cell-value">
<mat-icon *ngIf="isIconValue(row, col); else no_iconvalue">{{ asIconValue(row, col) }}
@@ -156,7 +157,7 @@
</span>
</ng-container>
</div>
<div *ngIf="col.template" class="adf-cell-container">
<div *ngIf="col.template" class="adf-datatable-cell-container">
<ng-container
[ngTemplateOutlet]="col.template"
[ngTemplateOutletContext]="{ $implicit: { data: data, row: row, col: col }, value: data.getValue(row, col) }">
@@ -167,7 +168,7 @@
<!-- Actions (right) -->
<div *ngIf="actions && actionsPosition === 'right'"
role="gridcell"
class="adf-datatable-table-cell adf-datatable__actions-cell">
class="adf-datatable-cell adf-datatable__actions-cell">
<button mat-icon-button [matMenuTriggerFor]="menu"
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
[attr.id]="'action_menu_right_' + idx"
@@ -190,8 +191,8 @@
<div *ngIf="isEmpty()"
role="row"
[class.adf-datatable-row]="display === 'list'"
[class.adf-data-table-card-empty]="display === 'gallery'">
<div class="adf-no-content-container adf-datatable-table-cell" role="gridcell">
[class.adf-datatable-card-empty]="display === 'gallery'">
<div class="adf-no-content-container adf-datatable-cell" role="gridcell">
<ng-template *ngIf="noContentTemplate"
ngFor [ngForOf]="[data]"
[ngForTemplate]="noContentTemplate">
@@ -206,9 +207,9 @@
<div *ngIf="!loading && noPermission"
role="row"
[class.adf-datatable-row]="display === 'list'"
[class.adf-data-table-card-permissions]="display === 'gallery'"
[class.adf-datatable-card-permissions]="display === 'gallery'"
class="adf-no-permission__row">
<div class="adf-no-permission__cell adf-no-content-container adf-datatable-table-cell">
<div class="adf-no-permission__cell adf-no-content-container adf-datatable-cell">
<ng-template *ngIf="noPermissionTemplate"
ngFor [ngForOf]="[data]"
[ngForTemplate]="noPermissionTemplate">
@@ -217,8 +218,8 @@
</div>
<div *ngIf="loading"
[class.adf-datatable-row]="display === 'list'"
[class.adf-data-table-card-loading]="display === 'gallery'">
<div class="adf-no-content-container adf-datatable-table-cell">
[class.adf-datatable-card-loading]="display === 'gallery'">
<div class="adf-no-content-container adf-datatable-cell">
<ng-template *ngIf="loadingTemplate"
ngFor [ngForOf]="[data]"
[ngForTemplate]="loadingTemplate">

View File

@@ -20,7 +20,7 @@
$data-table-cell-top: $data-table-card-padding / 2;
$data-table-drag-border: 1px dashed rgb(68, 138, 255);
.adf-data-table-card {
.adf-datatable-card {
border: 1px solid mat-color($foreground, divider);
@@ -68,34 +68,34 @@
padding-bottom: 31px;
}
.adf-data-table-card-permission {
.adf-datatable-card-permission {
width: 100%;
min-height: 250px;
.adf-datatable-table-cell {
.adf-datatable-cell {
height: 240px !important;
}
}
.adf-data-table-card-loading {
.adf-datatable-card-loading {
width: 100%;
min-height: 250px;
.adf-datatable-table-cell {
.adf-datatable-cell {
height: 240px !important;
}
}
.adf-data-table-card-empty {
.adf-datatable-card-empty {
width: 100%;
min-height: 380px;
.adf-datatable-table-cell {
.adf-datatable-cell {
height: 370px !important;
}
}
.adf-datatable-table-cell {
.adf-datatable-cell {
text-align: left;
flex: 0 1 24%;
height: 136px !important;
@@ -123,7 +123,6 @@
height: 42px !important;
width: 42px !important;
right: 0;
text-align: right;
}
.adf-image-table-cell {
@@ -134,7 +133,7 @@
border-bottom-width: 1px;
border-bottom-style: solid;
.adf-cell-container {
.adf-datatable-cell-container {
float: left;
width: 42px;
}
@@ -150,7 +149,7 @@
}
}
.adf-datatable-table-checkbox {
.adf-datatable-checkbox {
margin: 8px;
}
@@ -163,65 +162,27 @@
}
.adf-data-table {
display: table;
width: 100%;
position: relative;
border: $data-table-dividers;
// border-collapse: collapse;
white-space: nowrap;
font-size: $data-table-font-size;
.adf-datatable-list {
display: flex;
flex-direction: column;
background-color: mat-color($background, card);
/* Firefox fixes */
border-collapse: unset;
border-spacing: 0;
.adf-datatable-link {
text-decoration: none;
color: mat-color($foreground, text);
&:hover {
color: #2196f3;
text-decoration: underline;
}
}
.adf-datatable-row {
display: table-row;
vertical-align: inherit;
border-color: inherit;
}
.adf-datatable-body {
display: table-row-group;
vertical-align: middle;
border-color: inherit;
}
.adf-datatable-table-cell {
display: table-cell;
}
.adf-datatable-table-cell-header {
display: table-cell;
}
border: $data-table-dividers;
.adf-datatable-header {
padding-bottom: 3px;
display: table-header-group;
vertical-align: middle;
border-color: inherit;
display: flex;
flex-direction: column;
}
.adf-datatable-body {
display: flex;
flex-direction: column;
.adf-datatable-row {
cursor: pointer;
position: relative;
height: $data-table-row-height;
@include material-animation-default(0.28s);
transition-property: background-color;
border-top: $data-table-dividers;
padding-top: 12px;
padding-bottom: 12px;
&:hover {
background-color: $data-table-hover-color;
@@ -230,55 +191,49 @@
&.adf-is-selected, &.adf-is-selected:hover {
background-color: $data-table-selection-color;
}
&:focus {
outline-offset: -1px;
outline-width: 1px;
outline-color: rgb(68, 138, 255);
outline-style: solid;
}
&:last-child {
& > td {
border-bottom: $data-table-dividers;
}
}
}
}
.adf-datatable-table-cell, .adf-datatable-table-cell-header {
padding: 0 $data-table-column-padding 12px $data-table-column-padding;
.adf-datatable-row {
display: flex;
align-items: center;
padding-left: 20px;
padding-right: 20px;
.adf-datatable-checkbox {
max-width: 50px;
}
}
.adf-datatable-cell {
text-align: left;
&:first-of-type {
padding-left: 24px;
&--text {
text-align: left;
}
&:last-of-type {
&--date {
text-align: left;
}
&--number {
text-align: right;
}
&--image {
padding-left: 24px;
padding-right: 24px;
width: 10px;
text-align: left;
}
&:focus {
outline-offset: -1px;
outline-width: 1px;
outline-color: rgb(68, 138, 255);
outline-style: solid;
outline-width: 0;
}
}
.adf-datatable-table-cell {
color: mat-color($foreground, text);
position: relative;
vertical-align: middle;
height: $data-table-row-height;
border-top: $data-table-dividers;
padding-top: $data-table-cell-top;
box-sizing: border-box;
@include adf-no-select;
}
.adf-datatable-table-cell-header {
.adf-datatable-cell-header {
@include adf-no-select;
cursor: pointer;
position: relative;
@@ -299,10 +254,13 @@
&:hover {
cursor: pointer;
}
padding-top: 12px;
display: flex;
align-items: center;
}
&.adf-data-table__header--sorted-asc,
&.adf-data-table__header--sorted-desc {
&.adf-datatable__header--sorted-asc,
&.adf-datatable__header--sorted-desc {
color: $data-table-header-sorted-color;
&::before {
@include typo-icon;
@@ -312,48 +270,63 @@
vertical-align: sub;
}
}
&.adf-data-table__header--sorted-desc::before {
&.adf-datatable__header--sorted-desc::before {
content: '\e5db';
}
}
.adf-data-table-cell {
text-align: left;
height: 100%;
&--text {
text-align: left;
}
&--date {
text-align: left;
}
&--number {
text-align: right;
}
&--image {
padding-left: 24px;
padding-right: 24px;
width: 10px;
text-align: left;
.adf-cell-value {
height: 24px;
}
img {
height: 100%;
}
}
.adf-cell-container {
&.adf-datatable-checkbox {
display: flex;
align-items: center;
}
}
.adf-datatable-cell-header.adf-expand-cell-1, .adf-datatable-cell.adf-expand-cell-1 {
flex-grow: 1;
}
.adf-datatable-cell-header.adf-expand-cell-2, .adf-datatable-cell.adf-expand-cell-2 {
flex-grow: 2;
}
.adf-datatable-cell-header.adf-expand-cell-3, .adf-datatable-cell.adf-expand-cell-3 {
flex-grow: 3;
}
.adf-datatable-cell-header.adf-expand-cell-4, .adf-datatable-cell.adf-expand-cell-4 {
flex-grow: 4;
}
.adf-datatable-cell-header.adf-expand-cell-5, .adf-datatable-cell.adf-expand-cell-5 {
flex-grow: 5;
}
.adf-datatable-cell, .adf-datatable-cell-header {
flex: 1;
padding: 0;
.adf-datatable-cell-container {
overflow: hidden;
}
.adf-datatable-cell-value {
overflow: hidden;
text-overflow: ellipsis;
}
}
.adf-cell-value {
display: flex;
}
.adf-datatable__actions-cell, .adf-datatable-cell--image {
max-width: 50px;
display: flex;
}
.adf-datatable-cell--image {
max-width: 50px;
}
.adf-location-cell {
a {
text-decoration: none;
@@ -366,8 +339,71 @@
}
}
.adf-full-width {
width: 100%;
/* [Accessibility] For screen reader only */
.adf-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.adf-is-selected {
background: mat-color($primary, 100);
}
.adf-datatable-link {
text-decoration: none;
color: mat-color($foreground, text);
&:hover {
color: #2196f3;
text-decoration: underline;
}
}
.adf-expand-cell {
}
.adf-ellipsis-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.adf-datatable-cell, .adf-datatable-cell-header {
overflow: hidden;
.adf-datatable-cell-container {
overflow: hidden;
}
.adf-datatable-cell-value {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
width: calc(100% - 2em);
}
}
/* query for Microsoft IE 11*/
@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
.adf-cell-value {
top: 100%;
}
}
/* cell stretching content */
& > div::after {
content: attr(title);
overflow: hidden;
height: 0;
display: block;
}
}
/* Empty folder */
@@ -402,60 +438,7 @@
}
}
.adf-ellipsis-cell {
.adf-cell-container {
height: 100%;
}
.adf-cell-container > * {
display: block;
position: absolute;
max-width: calc(100% - 2em);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.2em;
}
/* visible content */
.adf-cell-value {
display: block;
position: absolute;
max-width: calc(100% - 2em);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* query for Microsoft IE 11*/
@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
.adf-cell-value {
top: 100%;
}
}
/* cell stretching content */
& > div::after {
content: attr(title);
overflow: hidden;
height: 0;
display: block;
}
}
/* [Accessibility] For screen reader only */
.adf-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
/* Utils */
/* Utils */
.adf-hidden {
display: none;
}
@@ -465,6 +448,10 @@
.adf-desktop-only {
display: none;
}
.adf-sticky-header {
width: 100%;
}
}
@media (max-device-width: 768px) {
@@ -472,6 +459,24 @@
display: none;
}
}
}
.adf-sticky-header {
border-top: 0;
.adf-datatable-header {
position: absolute;
background-color: mat-color($background, card);
display: flex;
z-index: 10;
border-top: $data-table-dividers;
border-bottom: $data-table-dividers;
width: calc(100% - 16em);
}
.adf-datatable-body {
margin-top: 56px;
}
}
.adf-upload__dragging {
@@ -492,7 +497,7 @@
}
}
.adf-data-table--empty {
.adf-datatable--empty {
@include flex-column;
justify-content: center;
align-items: center;

View File

@@ -120,8 +120,8 @@ describe('DataTable', () => {
fixture.detectChanges();
expect(element.querySelector('.adf-data-table-card')).not.toBeNull();
expect(element.querySelector('.adf-data-table')).toBeNull();
expect(element.querySelector('.adf-datatable-card')).not.toBeNull();
expect(element.querySelector('.adf-datatable')).toBeNull();
});
it('should use the cardview style if cardview is false', () => {
@@ -139,8 +139,8 @@ describe('DataTable', () => {
fixture.detectChanges();
expect(element.querySelector('.adf-data-table-card')).toBeNull();
expect(element.querySelector('.adf-data-table')).not.toBeNull();
expect(element.querySelector('.adf-datatable-card')).toBeNull();
expect(element.querySelector('.adf-datatable-list')).not.toBeNull();
});
it('should hide the header if showHeader is false', () => {
@@ -1021,12 +1021,12 @@ describe('Accesibility', () => {
});
fixture.detectChanges();
const datatableAttributes = element.querySelector('.adf-data-table').attributes;
const datatableHeaderAttributes = element.querySelector('.adf-data-table .adf-datatable-header').attributes;
const datatableHeaderCellAttributes = element.querySelector('.adf-datatable-table-cell-header').attributes;
const datatableAttributes = element.querySelector('.adf-datatable-list').attributes;
const datatableHeaderAttributes = element.querySelector('.adf-datatable-list .adf-datatable-header').attributes;
const datatableHeaderCellAttributes = element.querySelector('.adf-datatable-cell-header').attributes;
const datatableBodyAttributes = element.querySelector('.adf-datatable-body').attributes;
const datatableBodyRowAttributes = element.querySelector('.adf-datatable-body .adf-datatable-row').attributes;
const datatableBodyCellAttributes = element.querySelector('.adf-datatable-body .adf-datatable-table-cell').attributes;
const datatableBodyCellAttributes = element.querySelector('.adf-datatable-body .adf-datatable-cell').attributes;
expect(datatableAttributes.getNamedItem('role').value).toEqual('grid');
expect(datatableHeaderAttributes.getNamedItem('role').value).toEqual('rowgroup');

View File

@@ -119,6 +119,10 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
@Input()
showHeader: boolean = true;
/** Toggles the sticky header mode. */
@Input()
stickyHeader: boolean = false;
/** Emitted when the user clicks a row. */
@Output()
rowClick = new EventEmitter<DataRowEvent>();