Skip to content

Commit bcabda9

Browse files
committed
feat(select): add md-select-header directive
Adds a `md-select-header` component, which is a fixed header above the select's options. It allows for the user to project an input to be used for filtering long lists of options. **Note:** This component only handles the positioning, styling and exposes the panel id for a11y. The functionality is up to the user to handle. Fixes #2812.
1 parent de68202 commit bcabda9

File tree

14 files changed

+242
-21
lines changed

14 files changed

+242
-21
lines changed

src/demo-app/select/select-demo.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,20 @@
113113
</md-card>
114114
</div>
115115

116+
<md-card *ngIf="showSelect">
117+
<md-select placeholder="Drink" [(ngModel)]="currentDrink" #selectWitHeader="mdSelect">
118+
<md-select-header>
119+
<input type="search" [(ngModel)]="searchTerm" role="combobox" [attr.aria-owns]="selectWitHeader.panelId"
120+
(ngModelChange)="filterDrinks()" placeholder="Search for a drink"/>
121+
</md-select-header>
122+
123+
<md-option *ngFor="let drink of filteredDrinks" [value]="drink.value" [disabled]="drink.disabled">
124+
{{ drink.viewValue }}
125+
</md-option>
126+
127+
</md-select>
128+
</md-card>
129+
130+
116131
</div>
117132
<div style="height: 500px">This div is for testing scrolled selects.</div>

src/demo-app/select/select-demo.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class SelectDemo {
1717
currentDrink: string;
1818
currentPokemon: string[];
1919
currentPokemonFromGroup: string;
20+
searchTerm: string;
2021
latestChangeEvent: MdSelectChange;
2122
floatPlaceholder: string = 'auto';
2223
foodControl = new FormControl('pizza-1');
@@ -43,6 +44,8 @@ export class SelectDemo {
4344
{value: 'milk-8', viewValue: 'Milk'},
4445
];
4546

47+
filteredDrinks = this.drinks.slice();
48+
4649
pokemon = [
4750
{value: 'bulbasaur-0', viewValue: 'Bulbasaur'},
4851
{value: 'charizard-1', viewValue: 'Charizard'},
@@ -101,4 +104,10 @@ export class SelectDemo {
101104
setPokemonValue() {
102105
this.currentPokemon = ['eevee-4', 'psyduck-6'];
103106
}
107+
108+
filterDrinks() {
109+
this.filteredDrinks = this.searchTerm ? this.drinks.filter(item => {
110+
return item.viewValue.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1;
111+
}) : this.drinks.slice();
112+
}
104113
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/** No CSS for this example */
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<md-select placeholder="Favorite food" [(ngModel)]="currentDrink" name="food" #select="mdSelect">
2+
<md-select-header>
3+
<input type="search" [(ngModel)]="searchString" role="combobox" [attr.aria-owns]="select.panelId"
4+
(ngModelChange)="filterFoods()" placeholder="Search for food"/>
5+
</md-select-header>
6+
7+
<md-option *ngFor="let food of foods" [value]="food.value">
8+
{{food.viewValue}}
9+
</md-option>
10+
</md-select>
11+
12+
<p> Selected value: {{selectedValue}} </p>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {Component} from '@angular/core';
2+
3+
4+
@Component({
5+
selector: 'select-form-example',
6+
templateUrl: './select-form-example.html',
7+
})
8+
export class SelectHeaderExample {
9+
selectedValue: string;
10+
searchString: string;
11+
12+
initialFoods = [
13+
{ value: 'steak-0', viewValue: 'Steak' },
14+
{ value: 'pizza-1', viewValue: 'Pizza' },
15+
{ value: 'tacos-2', viewValue: 'Tacos' },
16+
{ value: 'sandwich-3', viewValue: 'Sandwich' },
17+
{ value: 'chips-4', viewValue: 'Chips' },
18+
{ value: 'eggs-5', viewValue: 'Eggs' },
19+
{ value: 'pasta-6', viewValue: 'Pasta' },
20+
{ value: 'sushi-7', viewValue: 'Sushi' },
21+
];
22+
23+
foods = this.initialFoods.slice();
24+
25+
filterFoods() {
26+
this.foods = this.searchString ? this.initialFoods.filter(item => {
27+
return item.viewValue.toLowerCase().indexOf(this.searchString.toLowerCase()) > -1;
28+
}) : this.initialFoods.slice();
29+
}
30+
}

src/lib/core/style/_menu-common.scss

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ $mat-menu-icon-margin: 16px !default;
1515

1616
@mixin mat-menu-base() {
1717
@include mat-elevation(8);
18+
@include mat-menu-scrollable();
19+
1820
min-width: $mat-menu-overlay-min-width;
1921
max-width: $mat-menu-overlay-max-width;
20-
21-
overflow: auto;
22-
-webkit-overflow-scrolling: touch; // for momentum scroll on mobile
2322
}
2423

2524
@mixin mat-menu-item-base() {
@@ -91,3 +90,8 @@ $mat-menu-icon-margin: 16px !default;
9190
}
9291
}
9392
}
93+
94+
@mixin mat-menu-scrollable() {
95+
overflow: auto;
96+
-webkit-overflow-scrolling: touch; // for momentum scroll on mobile
97+
}

src/lib/select/_select-theme.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
color: mat-color($foreground, hint-text);
2727
}
2828

29+
.mat-select-header {
30+
color: mat-color($foreground, divider);
31+
}
32+
2933
.mat-select-underline {
3034
background-color: mat-color($foreground, divider);
3135
}

src/lib/select/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {NgModule} from '@angular/core';
22
import {CommonModule} from '@angular/common';
33
import {MdSelect} from './select';
4+
import {MdSelectHeader} from './select-header';
45
import {MdCommonModule, OverlayModule, MdOptionModule} from '../core';
56

67

@@ -11,11 +12,12 @@ import {MdCommonModule, OverlayModule, MdOptionModule} from '../core';
1112
MdOptionModule,
1213
MdCommonModule,
1314
],
14-
exports: [MdSelect, MdOptionModule, MdCommonModule],
15-
declarations: [MdSelect],
15+
exports: [MdSelect, MdSelectHeader, MdOptionModule, MdCommonModule],
16+
declarations: [MdSelect, MdSelectHeader],
1617
})
1718
export class MdSelectModule {}
1819

1920

2021
export * from './select';
22+
export * from './select-header';
2123
export {fadeInContent, transformPanel, transformPlaceholder} from './select-animations';

src/lib/select/select-header.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {Directive} from '@angular/core';
2+
3+
4+
/**
5+
* Fixed header that will be rendered above a select's options.
6+
*/
7+
@Directive({
8+
selector: 'md-select-header, mat-select-header',
9+
host: {
10+
'class': 'mat-select-header',
11+
}
12+
})
13+
export class MdSelectHeader { }

src/lib/select/select.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@
3535
[style.transformOrigin]="_transformOrigin"
3636
[class.mat-select-panel-done-animating]="_panelDoneAnimating">
3737

38-
<div class="mat-select-content" [@fadeInContent]="'showing'" (@fadeInContent.done)="_onFadeInDone()">
38+
<div [@fadeInContent]="'showing'">
39+
<ng-content select="md-select-header, mat-select-header"></ng-content>
40+
</div>
41+
42+
<div class="mat-select-content" [attr.id]="panelId" [@fadeInContent]="'showing'"
43+
(@fadeInContent.done)="_onFadeInDone()">
3944
<ng-content></ng-content>
4045
</div>
4146
</div>

src/lib/select/select.scss

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,33 @@ $mat-select-panel-max-height: 256px !default;
117117
margin: 0 $mat-select-arrow-margin;
118118
}
119119

120-
.mat-select-panel {
121-
@include mat-menu-base();
122-
padding-top: 0;
123-
padding-bottom: 0;
120+
.mat-select-content {
121+
@include mat-menu-scrollable();
124122
max-height: $mat-select-panel-max-height;
125123
min-width: 100%; // prevents some animation twitching and test inconsistencies in IE11
126124

127125
@include cdk-high-contrast {
128126
outline: solid 1px;
129127
}
130128
}
129+
130+
.mat-select-panel {
131+
@include mat-menu-base();
132+
border: none;
133+
}
134+
135+
.mat-select-header {
136+
@include mat-menu-item-base();
137+
border-bottom: solid 1px;
138+
box-sizing: border-box;
139+
140+
input {
141+
display: block;
142+
width: 100%;
143+
height: 100%;
144+
border: none;
145+
outline: none;
146+
padding: 0;
147+
background: transparent;
148+
}
149+
}

0 commit comments

Comments
 (0)