Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions projects/components/src/checkbox/checkbox.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
::ng-deep .mat-checkbox-checked .mat-checkbox-background,
.mat-checkbox-indeterminate {
background-color: $blue-4;

.mat-checkbox-mixedmark {
background-color: white;
}
}

::ng-deep .mat-checkbox-disabled {
Expand Down
6 changes: 5 additions & 1 deletion projects/components/src/checkbox/checkbox.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { MatCheckboxChange } from '@angular/material/checkbox';
labelPosition="after"
[checked]="this.isChecked"
[disabled]="this.isDisabled"
[(indeterminate)]="this.indeterminate"
(change)="this.onCheckboxChange($event)"
class="ht-checkbox"
[ngClass]="{ disabled: this.isDisabled }"
Expand All @@ -35,10 +36,13 @@ export class CheckboxComponent implements ControlValueAccessor {
this.isChecked = checked ?? false;
}

public get checked(): boolean {
public get checked(): boolean | undefined {
return this.isChecked;
}

@Input()
public indeterminate?: boolean;

@Input()
public set disabled(disabled: boolean | undefined) {
this.isDisabled = disabled ?? false;
Expand Down
4 changes: 4 additions & 0 deletions projects/components/src/table/data/table-cdk-data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ export class TableCdkDataSource implements DataSource<TableRow> {
this.rowsChange$.next(TableCdkRowUtil.mergeRowStates(this.cachedRows, unselectedRows));
}

public getAllRows(): StatefulTableRow[] {
return this.cachedRows;
}

/****************************
* Change Detection
****************************/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
@import 'mixins';

:host {
width: 100%; // Sorry...
}

.table-header-cell-renderer {
@include ellipsis-overflow();
@include overline($gray-5);
display: flex;
align-items: center;
width: 100%;
height: 32px;

&.sortable {
cursor: pointer;
Expand All @@ -17,18 +15,6 @@
color: $gray-9;
}

&.left {
text-align: left;
}

&.center {
text-align: center;
}

&.right {
text-align: right;
}

.options-button {
display: none;
color: $gray-7;
Expand All @@ -46,29 +32,33 @@
}

.title {
@include overline($gray-5);

min-width: 0;
width: 100%;
flex: 1 1 auto;
display: flex;

&.asc,
&.desc {
color: $gray-9;
&.left {
justify-content: flex-start;
}

&:after {
display: inline-block;
&.center {
justify-content: center;
}

&.desc:after {
content: '▼';
font-size: 9px;
&.right {
justify-content: flex-end;
}

&.asc:after {
content: '▼';
font-size: 10px;
transform: scale(1, -1) translateY(1.5px); // That's right! Half pixels!
.sort-icon {
margin-left: 4px;
color: $gray-9;
}
}

.state-checkbox {
margin-left: 12px;
}
}

.popover-content {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('Table Header Cell Renderer', () => {
}
});

expect(spectator.query('.table-header-cell-renderer')).toHaveClass('sortable');
expect(spectator.query('.table-header-cell-renderer > .title')).toHaveClass('sortable');
});

test('should not have sortable class, if column cannot be sorted', () => {
Expand All @@ -53,7 +53,7 @@ describe('Table Header Cell Renderer', () => {
}
});

expect(spectator.query('.table-header-cell-renderer')).not.toHaveClass('sortable');
expect(spectator.query('.table-header-cell-renderer > .title')).not.toHaveClass('sortable');
});

test('should sort column when header title is clicked', fakeAsync(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,42 @@ import { TableColumnConfigExtended } from '../table.service';
template: `
<div
*ngIf="this.columnConfig"
[ngClass]="this.classes"
[htTooltip]="this.getTooltip(this.columnConfig.titleTooltip, this.columnConfig.title)"
class="table-header-cell-renderer"
>
<ng-container *ngIf="this.isShowOptionButton && this.leftAlignFilterButton">
<ng-container *ngTemplateOutlet="optionsButton"></ng-container>
</ng-container>
<div class="title" [ngClass]="this.classes" (click)="this.onSortChange()">{{ this.columnConfig.title }}</div>
<ng-container *ngIf="this.isShowOptionButton && !this.leftAlignFilterButton">
<ng-container *ngTemplateOutlet="optionsButton"></ng-container>
<ng-container *ngIf="!this.isStateColumn; else stateColumnTemplate">
<ng-container *ngIf="this.isShowOptionButton && this.leftAlignFilterButton">
<ng-container *ngTemplateOutlet="optionsButton"></ng-container>
</ng-container>
<div class="title" [ngClass]="this.classes" (click)="this.onSortChange()">
<span>{{ this.columnConfig.title }}</span>
<ng-container *ngIf="this.sort">
<ht-icon
class="sort-icon"
[icon]="
this.sort === '${TableSortDirection.Descending}' ? '${IconType.ArrowDown}' : '${IconType.ArrowUp}'
"
size="${IconSize.ExtraSmall}"
></ht-icon>
</ng-container>
</div>

<ng-container *ngIf="this.isShowOptionButton && !this.leftAlignFilterButton">
<ng-container *ngTemplateOutlet="optionsButton"></ng-container>
</ng-container>
</ng-container>

<ng-template #stateColumnTemplate>
<ng-container *ngIf="this.isMultipleSelectionStateColumn">
<ht-checkbox
class="state-checkbox"
[htTooltip]="this.getHeaderCheckboxTooltip()"
[indeterminate]="this.indeterminateRowsSelected"
(checkedChange)="this.onToggleAllSelectedChange($event)"
></ht-checkbox>
</ng-container>
</ng-template>

<ng-template #htmlTooltip>
<div [innerHTML]="this.columnConfig?.titleTooltip"></div>
</ng-template>
Expand Down Expand Up @@ -104,19 +128,28 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges {
@Input()
public sort?: TableSortDirection;

@Input()
public indeterminateRowsSelected?: boolean;

@Output()
public readonly sortChange: EventEmitter<TableSortDirection | undefined> = new EventEmitter();

@Output()
public readonly columnsChange: EventEmitter<TableColumnConfigExtended[]> = new EventEmitter();

@Output()
public readonly allRowsSelectionChange: EventEmitter<boolean> = new EventEmitter();

public alignment?: TableCellAlignmentType;
public leftAlignFilterButton: boolean = false;
public classes: string[] = [];

public isFilterable: boolean = false;
public isEditableAvailableColumns: boolean = false;
public isShowOptionButton: boolean = false;
public isStateColumn: boolean = false;
public isMultipleSelectionStateColumn: boolean = false;
private allRowsSelected: boolean = false;

@ViewChild('htmlTooltip')
public htmlTooltipTemplate?: TemplateRef<unknown>;
Expand All @@ -137,6 +170,8 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges {
this.isEditableAvailableColumns = this.areAnyAvailableColumnsEditable();
this.isShowOptionButton =
this.isFilterable || this.isEditableAvailableColumns || this.columnConfig?.sortable === true;
this.isStateColumn = this.columnConfig?.id === '$$selected' || this.columnConfig?.id === '$$expanded';
this.isMultipleSelectionStateColumn = this.columnConfig?.id === '$$selected';
}
}

Expand All @@ -155,6 +190,19 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges {
this.classes = this.buildClasses();
}

public onToggleAllSelectedChange(allSelected: boolean): void {
this.allRowsSelected = allSelected;
this.allRowsSelectionChange.emit(allSelected);
}

public getHeaderCheckboxTooltip(): string {
return this.indeterminateRowsSelected
? 'Some rows are selected'
: this.allRowsSelected
? 'All rows in the current page are selected'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two of these tooltips say what is currently the state. However, the final tooltip says what interacting with the control will do. Should we change the last one to 'None of the rows in the current page are selected' to align with the others? Or, should we change the others to state what action will happen if clicked?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I added that because when the tooltip is not selected and the user hovers over it for the first time, they will get to know what the selection would do. I am fine with updating the text like you suggested.

: 'None of the rows in the current page are selected';
}

private buildClasses(): string[] {
return [
...(this.alignment !== undefined ? [this.alignment.toLowerCase()] : []),
Expand Down
5 changes: 0 additions & 5 deletions projects/components/src/table/table.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ $header-height: 32px;

.header-cell-renderer {
width: 100%;
padding: 10px 12px 10px 7px;
}

.header-cell-renderer:first-child {
padding-left: 12px;
}

.header-column-resize-handle {
Expand Down
20 changes: 20 additions & 0 deletions projects/components/src/table/table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ import { TableColumnConfigExtended, TableService } from './table.service';
[availableColumns]="this.columnConfigs$ | async"
[index]="index"
[sort]="columnDef.sort"
[indeterminateRowsSelected]="this.indeterminateRowsSelected"
(sortChange)="this.onSortChange($event, columnDef)"
(columnsChange)="this.onColumnsEdit($event)"
(allRowsSelectionChange)="this.onHeaderAllRowsSelectionChange($event)"
>
</ht-table-header-cell-renderer>
</cdk-header-cell>
Expand Down Expand Up @@ -380,6 +382,7 @@ export class TableComponent
private resizeHeaderOffsetLeft: number = 0;
private resizeStartX: number = 0;
private resizeColumns?: ResizeColumns;
public indeterminateRowsSelected?: boolean;

public constructor(
private readonly elementRef: ElementRef,
Expand Down Expand Up @@ -564,6 +567,22 @@ export class TableComponent
this.columnConfigsChange.emit(columnConfigs);
}

public onHeaderAllRowsSelectionChange(allRowsSelected: boolean): void {
if (this.hasMultiSelect()) {
if (allRowsSelected) {
this.dataSource?.selectAllRows();
this.selections = this.dataSource?.getAllRows();
} else {
this.dataSource?.unselectAllRows();
this.selections = [];
}

this.selectionsChange.emit(this.selections);
this.indeterminateRowsSelected = false;
this.changeDetector.markForCheck();
}
}

public onDataCellClick(row: StatefulTableRow): void {
// NOTE: Cell Renderers generally handle their own clicks. We should only perform table actions here.
// Propagate the cell click to the row
Expand Down Expand Up @@ -654,6 +673,7 @@ export class TableComponent
this.selections = [toggledRow];
}
this.selectionsChange.emit(this.selections);
this.indeterminateRowsSelected = this.selections?.length !== this.dataSource?.getAllRows().length;
this.changeDetector.markForCheck();
}

Expand Down