Skip to content

Commit 5cc7856

Browse files
committed
feat(material/sort): header icon customization
close #32002, #30563
1 parent 74060b1 commit 5cc7856

20 files changed

+521
-12
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
export {SortOverviewExample} from './sort-overview/sort-overview-example';
2+
export {SortHeaderIconsExample} from './sort-header-icons/sort-header-icons-example';
3+
export {SortHeaderIconsWithDefaultExample} from './sort-header-icons-with-default/sort-header-icons-with-default-example';
4+
export {SortHeaderCustomExample} from './sort-header-custom/sort-header-custom-example';
5+
export {SortHeaderCustomWithDefaultExample} from './sort-header-custom-with-default/sort-header-custom-with-default-example';
26
export {SortHarnessExample} from './sort-harness/sort-harness-example';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.mat-sort-header-container {
2+
align-items: center;
3+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<ng-template #sortIconsTemplate let-sortHeader>
2+
@if (!sortHeader.isDisabled && sortHeader.isSorted) {
3+
@if (sortHeader.direction === 'desc') {
4+
<mat-icon focusable="false" aria-hidden="true" style="color: red;">keyboard_arrow_down</mat-icon>
5+
}
6+
@else {
7+
<mat-icon focusable="false" aria-hidden="true" style="color: green;">keyboard_arrow_up</mat-icon>
8+
}
9+
}
10+
@else {
11+
<mat-icon focusable="false" aria-hidden="true">unfold_more</mat-icon>
12+
}
13+
</ng-template>
14+
15+
<table matSort (matSortChange)="sortData($event)">
16+
<tr>
17+
<th mat-sort-header="name" [matSortIconsTemplate]="sortIconsTemplate">Dessert (100g)</th>
18+
<th mat-sort-header="calories" [matSortIconsTemplate]="sortIconsTemplate">Calories</th>
19+
<th mat-sort-header="fat" [matSortIconsTemplate]="sortIconsTemplate">Fat (g)</th>
20+
<th mat-sort-header="carbs" [matSortIconsTemplate]="sortIconsTemplate">Carbs (g)</th>
21+
<th mat-sort-header="protein" [matSortIconsTemplate]="sortIconsTemplate">Protein (g)</th>
22+
</tr>
23+
24+
@for (dessert of sortedData; track dessert) {
25+
<tr>
26+
<td>{{dessert.name}}</td>
27+
<td>{{dessert.calories}}</td>
28+
<td>{{dessert.fat}}</td>
29+
<td>{{dessert.carbs}}</td>
30+
<td>{{dessert.protein}}</td>
31+
</tr>
32+
}
33+
</table>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {Component} from '@angular/core';
2+
import {MatIcon} from '@angular/material/icon';
3+
import {Sort, MatSortModule} from '@angular/material/sort';
4+
5+
export interface Dessert {
6+
calories: number;
7+
carbs: number;
8+
fat: number;
9+
name: string;
10+
protein: number;
11+
}
12+
13+
/**
14+
* @title Sorting header with custom template (including default).
15+
*/
16+
@Component({
17+
selector: 'sort-header-custom-with-default-example',
18+
templateUrl: 'sort-header-custom-with-default-example.html',
19+
styleUrl: 'sort-header-custom-with-default-example.css',
20+
imports: [MatSortModule, MatIcon],
21+
})
22+
export class SortHeaderCustomWithDefaultExample {
23+
desserts: Dessert[] = [
24+
{name: 'Frozen yogurt', calories: 159, fat: 6, carbs: 24, protein: 4},
25+
{name: 'Ice cream sandwich', calories: 237, fat: 9, carbs: 37, protein: 4},
26+
{name: 'Eclair', calories: 262, fat: 16, carbs: 24, protein: 6},
27+
{name: 'Cupcake', calories: 305, fat: 4, carbs: 67, protein: 4},
28+
{name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4},
29+
];
30+
31+
sortedData: Dessert[];
32+
33+
constructor() {
34+
this.sortedData = this.desserts.slice();
35+
}
36+
37+
sortData(sort: Sort) {
38+
const data = this.desserts.slice();
39+
if (!sort.active || sort.direction === '') {
40+
this.sortedData = data;
41+
return;
42+
}
43+
44+
this.sortedData = data.sort((a, b) => {
45+
const isAsc = sort.direction === 'asc';
46+
switch (sort.active) {
47+
case 'name':
48+
return compare(a.name, b.name, isAsc);
49+
case 'calories':
50+
return compare(a.calories, b.calories, isAsc);
51+
case 'fat':
52+
return compare(a.fat, b.fat, isAsc);
53+
case 'carbs':
54+
return compare(a.carbs, b.carbs, isAsc);
55+
case 'protein':
56+
return compare(a.protein, b.protein, isAsc);
57+
default:
58+
return 0;
59+
}
60+
});
61+
}
62+
}
63+
64+
function compare(a: number | string, b: number | string, isAsc: boolean) {
65+
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
66+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.mat-sort-header-container {
2+
align-items: center;
3+
}
4+
5+
sup {
6+
font-size: 7px;
7+
top: -3px;
8+
position: absolute;
9+
display: block;
10+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<ng-template #sortIconsTemplate let-sortHeader>
2+
@if (!sortHeader.isDisabled && sortHeader.isSorted) {
3+
<sup>{{ sortHeader.direction === 'desc' ? 'Z-A' : 'A-Z' }}</sup>
4+
}
5+
</ng-template>
6+
7+
<table matSort (matSortChange)="sortData($event)">
8+
<tr>
9+
<th mat-sort-header="name" [matSortIconsTemplate]="sortIconsTemplate">Dessert (100g)</th>
10+
<th mat-sort-header="calories" [matSortIconsTemplate]="sortIconsTemplate">Calories</th>
11+
<th mat-sort-header="fat" [matSortIconsTemplate]="sortIconsTemplate">Fat (g)</th>
12+
<th mat-sort-header="carbs" [matSortIconsTemplate]="sortIconsTemplate">Carbs (g)</th>
13+
<th mat-sort-header="protein" [matSortIconsTemplate]="sortIconsTemplate">Protein (g)</th>
14+
</tr>
15+
16+
@for (dessert of sortedData; track dessert) {
17+
<tr>
18+
<td>{{dessert.name}}</td>
19+
<td>{{dessert.calories}}</td>
20+
<td>{{dessert.fat}}</td>
21+
<td>{{dessert.carbs}}</td>
22+
<td>{{dessert.protein}}</td>
23+
</tr>
24+
}
25+
</table>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {Component} from '@angular/core';
2+
import {Sort, MatSortModule} from '@angular/material/sort';
3+
4+
export interface Dessert {
5+
calories: number;
6+
carbs: number;
7+
fat: number;
8+
name: string;
9+
protein: number;
10+
}
11+
12+
/**
13+
* @title Sorting header with custom template.
14+
*/
15+
@Component({
16+
selector: 'sort-header-custom-example',
17+
templateUrl: 'sort-header-custom-example.html',
18+
styleUrl: 'sort-header-custom-example.css',
19+
imports: [MatSortModule],
20+
})
21+
export class SortHeaderCustomExample {
22+
desserts: Dessert[] = [
23+
{name: 'Frozen yogurt', calories: 159, fat: 6, carbs: 24, protein: 4},
24+
{name: 'Ice cream sandwich', calories: 237, fat: 9, carbs: 37, protein: 4},
25+
{name: 'Eclair', calories: 262, fat: 16, carbs: 24, protein: 6},
26+
{name: 'Cupcake', calories: 305, fat: 4, carbs: 67, protein: 4},
27+
{name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4},
28+
];
29+
30+
sortedData: Dessert[];
31+
32+
constructor() {
33+
this.sortedData = this.desserts.slice();
34+
}
35+
36+
sortData(sort: Sort) {
37+
const data = this.desserts.slice();
38+
if (!sort.active || sort.direction === '') {
39+
this.sortedData = data;
40+
return;
41+
}
42+
43+
this.sortedData = data.sort((a, b) => {
44+
const isAsc = sort.direction === 'asc';
45+
switch (sort.active) {
46+
case 'name':
47+
return compare(a.name, b.name, isAsc);
48+
case 'calories':
49+
return compare(a.calories, b.calories, isAsc);
50+
case 'fat':
51+
return compare(a.fat, b.fat, isAsc);
52+
case 'carbs':
53+
return compare(a.carbs, b.carbs, isAsc);
54+
case 'protein':
55+
return compare(a.protein, b.protein, isAsc);
56+
default:
57+
return 0;
58+
}
59+
});
60+
}
61+
}
62+
63+
function compare(a: number | string, b: number | string, isAsc: boolean) {
64+
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
65+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.mat-sort-header-container {
2+
align-items: center;
3+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<table matSort (matSortChange)="sortData($event)">
2+
<tr>
3+
<th mat-sort-header="name">Dessert (100g)</th>
4+
<th mat-sort-header="calories">Calories</th>
5+
<th mat-sort-header="fat">Fat (g)</th>
6+
<th mat-sort-header="carbs">Carbs (g)</th>
7+
<th mat-sort-header="protein">Protein (g)</th>
8+
</tr>
9+
10+
@for (dessert of sortedData; track dessert) {
11+
<tr>
12+
<td>{{dessert.name}}</td>
13+
<td>{{dessert.calories}}</td>
14+
<td>{{dessert.fat}}</td>
15+
<td>{{dessert.carbs}}</td>
16+
<td>{{dessert.protein}}</td>
17+
</tr>
18+
}
19+
</table>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {Component} from '@angular/core';
2+
import {Sort, MatSortModule, MAT_SORT_DEFAULT_OPTIONS} from '@angular/material/sort';
3+
4+
export interface Dessert {
5+
calories: number;
6+
carbs: number;
7+
fat: number;
8+
name: string;
9+
protein: number;
10+
}
11+
12+
/**
13+
* @title Sorting header with configured default icons.
14+
*/
15+
@Component({
16+
selector: 'sort-header-icons-with-default-example',
17+
templateUrl: 'sort-header-icons-with-default-example.html',
18+
styleUrl: 'sort-header-icons-with-default-example.css',
19+
imports: [MatSortModule],
20+
providers: [
21+
{
22+
provide: MAT_SORT_DEFAULT_OPTIONS,
23+
useValue: {
24+
icons: {
25+
ascending: 'keyboard_arrow_up',
26+
descending: 'keyboard_arrow_down',
27+
default: 'unfold_more', // if you'd like to have always visible default sorting icon
28+
},
29+
},
30+
},
31+
],
32+
})
33+
export class SortHeaderIconsWithDefaultExample {
34+
desserts: Dessert[] = [
35+
{name: 'Frozen yogurt', calories: 159, fat: 6, carbs: 24, protein: 4},
36+
{name: 'Ice cream sandwich', calories: 237, fat: 9, carbs: 37, protein: 4},
37+
{name: 'Eclair', calories: 262, fat: 16, carbs: 24, protein: 6},
38+
{name: 'Cupcake', calories: 305, fat: 4, carbs: 67, protein: 4},
39+
{name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4},
40+
];
41+
42+
sortedData: Dessert[];
43+
44+
constructor() {
45+
this.sortedData = this.desserts.slice();
46+
}
47+
48+
sortData(sort: Sort) {
49+
const data = this.desserts.slice();
50+
if (!sort.active || sort.direction === '') {
51+
this.sortedData = data;
52+
return;
53+
}
54+
55+
this.sortedData = data.sort((a, b) => {
56+
const isAsc = sort.direction === 'asc';
57+
switch (sort.active) {
58+
case 'name':
59+
return compare(a.name, b.name, isAsc);
60+
case 'calories':
61+
return compare(a.calories, b.calories, isAsc);
62+
case 'fat':
63+
return compare(a.fat, b.fat, isAsc);
64+
case 'carbs':
65+
return compare(a.carbs, b.carbs, isAsc);
66+
case 'protein':
67+
return compare(a.protein, b.protein, isAsc);
68+
default:
69+
return 0;
70+
}
71+
});
72+
}
73+
}
74+
75+
function compare(a: number | string, b: number | string, isAsc: boolean) {
76+
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
77+
}

0 commit comments

Comments
 (0)