Skip to content

Commit

Permalink
feat: table column sticky (#129)
Browse files Browse the repository at this point in the history
* feat: table column sticky

* refactor: workaround for custom stickyCssClass

Co-authored-by: JounQin <admin@1stg.me>
  • Loading branch information
2eron and JounQin authored Jun 18, 2021
1 parent e77a0d8 commit c464cd5
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/table/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './table-header-row-def.directive';
export * from './table-cell-def.directive';
export * from './table-header-cell-def.directive';
export * from './table-column-def.directive';
export * from './table-scroll-wrapper.directive';
59 changes: 59 additions & 0 deletions src/table/table-scroll-wrapper.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
AfterViewChecked,
AfterViewInit,
Directive,
ElementRef,
OnDestroy,
} from '@angular/core';
import { fromEvent, Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';

const CLASS_PREFIX = 'aui-table';
const WRAPPER_CLASS = `${CLASS_PREFIX}__scroll-wrapper`;
const HAS_SCROLL_CLASS = `${WRAPPER_CLASS}--has-scroll`;
const SCROLLING_CLASS = `${WRAPPER_CLASS}--scrolling`;
const SCROLL_BEFORE_END_CLASS = `${WRAPPER_CLASS}--before-end`;

@Directive({
selector: '[auiTableScrollWrapper]',
host: {
class: `${WRAPPER_CLASS} ${SCROLL_BEFORE_END_CLASS}`,
},
})
export class TableScrollWrapperDirective
implements AfterViewChecked, AfterViewInit, OnDestroy {
scrollDis = 0;
destroy$$ = new Subject<void>();
constructor(private readonly el: ElementRef<HTMLElement>) {}

ngAfterViewChecked() {
this.scrollDis =
this.el.nativeElement.scrollWidth - this.el.nativeElement.offsetWidth;
if (this.scrollDis > 0) {
this.el.nativeElement.classList.add(HAS_SCROLL_CLASS);
}
}

ngAfterViewInit() {
fromEvent(this.el.nativeElement, 'scroll')
.pipe(debounceTime(100), takeUntil(this.destroy$$))
.subscribe(() => {
const scrollLeft = this.el.nativeElement.scrollLeft;
if (scrollLeft > 0) {
this.el.nativeElement.classList.add(SCROLLING_CLASS);
} else {
this.el.nativeElement.classList.remove(SCROLLING_CLASS);
}
if (scrollLeft === this.scrollDis) {
this.el.nativeElement.classList.remove(SCROLL_BEFORE_END_CLASS);
} else {
this.el.nativeElement.classList.add(SCROLL_BEFORE_END_CLASS);
}
});
}

ngOnDestroy() {
this.destroy$$.next();
this.destroy$$.complete();
}
}
67 changes: 67 additions & 0 deletions src/table/table.component.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,75 @@
@import '../theme/var';

$stickyCssClass: 'aui-table-sticky';

.aui-table {
font-size: $table-font-size-base;

&__scroll-wrapper {
overflow: auto;
.aui-table__row {
padding: 0;
border: none;
}
.aui-table__header-cell {
background-color: $table-header-row-bg;
border: $table-border-width solid $table-header-row-border-color;
border-width: $table-border-width 0;
}
.aui-table__cell {
padding: $table-row-vertical-padding $table-cell-horizontal-padding;
border-bottom: $table-border-width solid $table-row-border-color;
background: $table-row-bg;
}
&--has-scroll {
.#{$stickyCssClass} {
&:after {
position: absolute;
top: 0;
bottom: -1px;
width: 30px;
transform: translateX(-100%);
// stylelint-disable-next-line plugin/no-low-performance-animation-properties
transition: box-shadow 0.3s;
content: '';
pointer-events: none;
}
&-border-elem-left {
&:after {
right: -50px;
}
}
&-border-elem-right {
overflow: visible;
&:after {
left: 0;
}
}
}
}
&--scrolling {
.#{$stickyCssClass} {
&-border-elem-left {
&:after {
// stylelint-disable-next-line plugin/no-low-performance-animation-properties
box-shadow: inset 10px 0 8px -8px rgb(0 0 0 / 15%);
}
}
}
}

&--before-end {
.#{$stickyCssClass} {
&-border-elem-right {
&:after {
// stylelint-disable-next-line plugin/no-low-performance-animation-properties
box-shadow: inset -10px 0 8px -8px rgb(0 0 0 / 15%);
}
}
}
}
}

&__row,
&__header-row {
display: flex;
Expand Down
11 changes: 9 additions & 2 deletions src/table/table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ import {
],
})
export class TableComponent<T> extends CdkTable<T> {
// FIXME: disable override because it will break constructor, but why MatTable works?
// protected stickyCssClass = 'aui-table-sticky';
// FIXME: workaround to override because it will break constructor if it is field, but why MatTable works?
// @ts-expect-error
protected get stickyCssClass() {
return 'aui-table-sticky';
}

protected set stickyCssClass(_stickyCssClass: string) {
//
}
}
3 changes: 3 additions & 0 deletions src/table/table.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { TableHeaderRowDefDirective } from './table-header-row-def.directive';
import { TableHeaderRowComponent } from './table-header-row.component';
import { TableRowDefDirective } from './table-row-def.directive';
import { TableRowComponent } from './table-row.component';
import { TableScrollWrapperDirective } from './table-scroll-wrapper.directive';
import { TableComponent } from './table.component';

@NgModule({
Expand All @@ -26,6 +27,7 @@ import { TableComponent } from './table.component';
TableCellDefDirective,
TableHeaderCellDefDirective,
TableColumnDefDirective,
TableScrollWrapperDirective,
],
exports: [
TableComponent,
Expand All @@ -38,6 +40,7 @@ import { TableComponent } from './table.component';
TableCellDefDirective,
TableHeaderCellDefDirective,
TableColumnDefDirective,
TableScrollWrapperDirective,
],
})
export class TableModule {}
8 changes: 8 additions & 0 deletions stories/table/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { storiesOf } from '@storybook/angular';

import { ExpandDemoComponent } from './expand-demo/expand-demo.component';
import { SortDemoComponent } from './sort-demo/sort-demo.component';
import { StickyColumnsDemoComponent } from './sticky-columns/sticky-columns-demo.component';

export const DATA_SOURCE: Element[] = [
{ id: 1, name: 'element1', displayName: 'Element One', value: 5 },
Expand Down Expand Up @@ -80,6 +81,13 @@ storiesOf('Table', module)
declarations: [ExpandDemoComponent],
},
component: ExpandDemoComponent,
}))
.add('sticky-columns', () => ({
moduleMetadata: {
imports: [TableModule],
declarations: [StickyColumnsDemoComponent],
},
component: StickyColumnsDemoComponent,
}));

export interface Element {
Expand Down
94 changes: 94 additions & 0 deletions stories/table/sticky-columns/sticky-columns-demo.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<div auiTableScrollWrapper>
<aui-table [dataSource]="dataSource" #scrollTable>
<ng-container auiTableColumnDef="no" [sticky]="true">
<aui-table-header-cell *auiTableHeaderCellDef>
No.
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">{{ item[0] }}</aui-table-cell>
</ng-container>
<ng-container auiTableColumnDef="cell1">
<aui-table-header-cell *auiTableHeaderCellDef>
header cell
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">
{{ item[1] }}
</aui-table-cell>
</ng-container>
<ng-container auiTableColumnDef="cell2">
<aui-table-header-cell *auiTableHeaderCellDef>
header cell
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">
{{ item[2] }}
</aui-table-cell>
</ng-container>
<ng-container auiTableColumnDef="cell3">
<aui-table-header-cell *auiTableHeaderCellDef>
header cell
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">
{{ item[3] }}
</aui-table-cell>
</ng-container>
<ng-container auiTableColumnDef="cell4">
<aui-table-header-cell *auiTableHeaderCellDef>
header cell
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">
{{ item[4] }}
</aui-table-cell>
</ng-container>
<ng-container auiTableColumnDef="cell5">
<aui-table-header-cell *auiTableHeaderCellDef>
header cell
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">
{{ item[5] }}
</aui-table-cell>
</ng-container>
<ng-container auiTableColumnDef="cell6">
<aui-table-header-cell *auiTableHeaderCellDef>
header cell
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">
{{ item[6] }}
</aui-table-cell>
</ng-container>
<ng-container auiTableColumnDef="cell7">
<aui-table-header-cell *auiTableHeaderCellDef>
header cell
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">
{{ item[7] }}
</aui-table-cell>
</ng-container>
<ng-container auiTableColumnDef="cell8">
<aui-table-header-cell *auiTableHeaderCellDef>
header cell
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">
{{ item[8] }}
</aui-table-cell>
</ng-container>
<ng-container auiTableColumnDef="cell9">
<aui-table-header-cell *auiTableHeaderCellDef>
header cell
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">
{{ item[9] }}
</aui-table-cell>
</ng-container>
<ng-container auiTableColumnDef="cell10" stickyEnd>
<aui-table-header-cell *auiTableHeaderCellDef>
header cell
</aui-table-header-cell>
<aui-table-cell *auiTableCellDef="let item">
{{ item[10] }}
</aui-table-cell>
</ng-container>
<aui-table-header-row
*auiTableHeaderRowDef="columns"
></aui-table-header-row>
<aui-table-row *auiTableRowDef="let row; columns: columns"></aui-table-row>
</aui-table>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.aui-table__scroll-wrapper {
.aui-table__header-cell,
.aui-table__cell {
flex: 1 0 100px;
}
}
16 changes: 16 additions & 0 deletions stories/table/sticky-columns/sticky-columns-demo.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';

@Component({
templateUrl: 'sticky-columns-demo.component.html',
styleUrls: ['sticky-columns-demo.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StickyColumnsDemoComponent {
columns = ['no'].concat(
Array.from({ length: 10 }).map((_, index) => `cell${index + 1}`),
);

dataSource = Array.from({ length: 20 }).map((_, i) =>
['1'].concat(Array.from({ length: 10 }).map((_, j) => `cell${i}${j}`)),
);
}

0 comments on commit c464cd5

Please sign in to comment.