Skip to content

Commit

Permalink
feat(select): add md-select-header directive
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
crisbeto committed Feb 23, 2017
1 parent a4da08b commit 04f1c07
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 28 deletions.
15 changes: 15 additions & 0 deletions src/demo-app/select/select-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,20 @@
</md-card>
</div>

<md-card *ngIf="showSelect">
<md-select placeholder="Drink" [(ngModel)]="currentDrink" #selectWitHeader="mdSelect">
<md-select-header>
<input type="search" [(ngModel)]="searchTerm" role="combobox" [attr.aria-owns]="selectWitHeader.panelId"
(ngModelChange)="filterDrinks()" placeholder="Search for a drink"/>
</md-select-header>

<md-option *ngFor="let drink of filteredDrinks" [value]="drink.value" [disabled]="drink.disabled">
{{ drink.viewValue }}
</md-option>

</md-select>
</md-card>


</div>
<div style="height: 500px">This div is for testing scrolled selects.</div>
9 changes: 9 additions & 0 deletions src/demo-app/select/select-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class SelectDemo {
isDisabled = false;
showSelect = false;
currentDrink: string;
searchTerm: string;
latestChangeEvent: MdSelectChange;
floatPlaceholder: string = 'auto';
foodControl = new FormControl('pizza-1');
Expand All @@ -35,6 +36,8 @@ export class SelectDemo {
{value: 'milk-8', viewValue: 'Milk'},
];

filteredDrinks = this.drinks.slice();

pokemon = [
{value: 'bulbasaur-0', viewValue: 'Bulbasaur'},
{value: 'charizard-1', viewValue: 'Charizard'},
Expand All @@ -44,4 +47,10 @@ export class SelectDemo {
toggleDisabled() {
this.foodControl.enabled ? this.foodControl.disable() : this.foodControl.enable();
}

filterDrinks() {
this.filteredDrinks = this.searchTerm ? this.drinks.filter(item => {
return item.viewValue.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1;
}) : this.drinks.slice();
}
}
3 changes: 3 additions & 0 deletions src/examples/example-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {SelectOverviewExample} from './select-overview/select-overview-example';
import {ChipsOverviewExample} from './chips-overview/chips-overview-example';
import {ChipsStackedExample} from './chips-stacked/chips-stacked-example';
import {SelectFormExample} from './select-form/select-form-example';
import {SelectHeaderExample} from './select-header/select-header-example';


export interface LiveExample {
Expand Down Expand Up @@ -142,6 +143,7 @@ export const EXAMPLE_COMPONENTS = {
'radio-overview': {title: 'Basic radios', component: RadioOverviewExample},
'select-overview': {title: 'Basic select', component: SelectOverviewExample},
'select-form': {title: 'Select in a form', component: SelectFormExample},
'select-header': {title: 'Select header', component: SelectHeaderExample},
'sidenav-fab': {title: 'Sidenav with a FAB', component: SidenavFabExample},
'sidenav-overview': {title: 'Basic sidenav', component: SidenavOverviewExample},
'slider-configurable': {title: 'Configurable slider', component: SliderConfigurableExample},
Expand Down Expand Up @@ -204,6 +206,7 @@ export const EXAMPLE_LIST = [
SidenavFabExample,
SelectOverviewExample,
SelectFormExample,
SelectHeaderExample,
SidenavOverviewExample,
SliderConfigurableExample,
SliderOverviewExample,
Expand Down
1 change: 1 addition & 0 deletions src/examples/select-header/select-header-example.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/** No CSS for this example */
12 changes: 12 additions & 0 deletions src/examples/select-header/select-header-example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<md-select placeholder="Favorite food" [(ngModel)]="currentDrink" name="food" #select="mdSelect">
<md-select-header>
<input type="search" [(ngModel)]="searchString" role="combobox" [attr.aria-owns]="select.panelId"
(ngModelChange)="filterFoods()" placeholder="Search for food"/>
</md-select-header>

<md-option *ngFor="let food of foods" [value]="food.value">
{{food.viewValue}}
</md-option>
</md-select>

<p> Selected value: {{selectedValue}} </p>
30 changes: 30 additions & 0 deletions src/examples/select-header/select-header-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {Component} from '@angular/core';


@Component({
selector: 'select-form-example',
templateUrl: './select-form-example.html',
})
export class SelectHeaderExample {
selectedValue: string;
searchString: string;

initialFoods = [
{ value: 'steak-0', viewValue: 'Steak' },
{ value: 'pizza-1', viewValue: 'Pizza' },
{ value: 'tacos-2', viewValue: 'Tacos' },
{ value: 'sandwich-3', viewValue: 'Sandwich' },
{ value: 'chips-4', viewValue: 'Chips' },
{ value: 'eggs-5', viewValue: 'Eggs' },
{ value: 'pasta-6', viewValue: 'Pasta' },
{ value: 'sushi-7', viewValue: 'Sushi' },
];

foods = this.initialFoods.slice();

filterFoods() {
this.foods = this.searchString ? this.initialFoods.filter(item => {
return item.viewValue.toLowerCase().indexOf(this.searchString.toLowerCase()) > -1;
}) : this.initialFoods.slice();
}
}
10 changes: 7 additions & 3 deletions src/lib/core/style/_menu-common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ $mat-menu-side-padding: 16px !default;

@mixin mat-menu-base() {
@include mat-elevation(8);
@include mat-menu-scrollable();

min-width: $mat-menu-overlay-min-width;
max-width: $mat-menu-overlay-max-width;

overflow: auto;
-webkit-overflow-scrolling: touch; // for momentum scroll on mobile
}

@mixin mat-menu-item-base() {
Expand Down Expand Up @@ -87,3 +86,8 @@ $mat-menu-side-padding: 16px !default;
}
}
}

@mixin mat-menu-scrollable() {
overflow: auto;
-webkit-overflow-scrolling: touch; // for momentum scroll on mobile
}
4 changes: 4 additions & 0 deletions src/lib/select/_select-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@
color: mat-color($foreground, hint-text);
}
}

.mat-select-header {
color: mat-color($foreground, divider);
}
}
5 changes: 3 additions & 2 deletions src/lib/select/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {NgModule, ModuleWithProviders} from '@angular/core';
import {CommonModule} from '@angular/common';
import {MdSelect} from './select';
import {MdSelectHeader} from './select-header';
import {MdOptionModule} from '../core/option/option';
import {
CompatibilityModule,
Expand All @@ -12,8 +13,8 @@ export {fadeInContent, transformPanel, transformPlaceholder} from './select-anim

@NgModule({
imports: [CommonModule, OverlayModule, MdOptionModule, CompatibilityModule],
exports: [MdSelect, MdOptionModule, CompatibilityModule],
declarations: [MdSelect],
exports: [MdSelect, MdSelectHeader, MdOptionModule, CompatibilityModule],
declarations: [MdSelect, MdSelectHeader],
})
export class MdSelectModule {
/** @deprecated */
Expand Down
13 changes: 13 additions & 0 deletions src/lib/select/select-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {Directive} from '@angular/core';


/**
* Fixed header that will be rendered above a select's options.
*/
@Directive({
selector: 'md-select-header, mat-select-header',
host: {
'class': 'mat-select-header',
}
})
export class MdSelectHeader { }
8 changes: 7 additions & 1 deletion src/lib/select/select.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@
<div class="mat-select-panel" [@transformPanel]="'showing'" (@transformPanel.done)="_onPanelDone()"
(keydown)="_keyManager.onKeydown($event)" [style.transformOrigin]="_transformOrigin"
[class.mat-select-panel-done-animating]="_panelDoneAnimating">
<div class="mat-select-content" [@fadeInContent]="'showing'" (@fadeInContent.done)="_onFadeInDone()">

<div [@fadeInContent]="'showing'">
<ng-content select="md-select-header, mat-select-header"></ng-content>
</div>

<div class="mat-select-content" [attr.id]="panelId" [@fadeInContent]="'showing'"
(@fadeInContent.done)="_onFadeInDone()">
<ng-content></ng-content>
</div>
</div>
Expand Down
26 changes: 22 additions & 4 deletions src/lib/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,32 @@ $mat-select-trigger-font-size: 16px !default;
margin: 0 $mat-select-arrow-margin;
}

.mat-select-panel {
@include mat-menu-base();
padding-top: 0;
padding-bottom: 0;
.mat-select-content {
@include mat-menu-scrollable();
max-height: $mat-select-panel-max-height;

@include cdk-high-contrast {
outline: solid 1px;
}
}

.mat-select-panel {
@include mat-menu-base();
border: none;
}

.mat-select-header {
@include mat-menu-item-base();
border-bottom: solid 1px;
box-sizing: border-box;

input {
display: block;
width: 100%;
height: 100%;
border: none;
outline: none;
padding: 0;
background: transparent;
}
}
Loading

0 comments on commit 04f1c07

Please sign in to comment.