diff --git a/src/demo-app/select/select-demo.html b/src/demo-app/select/select-demo.html index f5cddc897323..7b9c87a3ccfe 100644 --- a/src/demo-app/select/select-demo.html +++ b/src/demo-app/select/select-demo.html @@ -4,7 +4,7 @@ ngModel - {{ drink.viewValue }} @@ -22,6 +22,12 @@

+

+ + +

@@ -33,7 +39,7 @@ Multiple selection - {{ creature.viewValue }} @@ -43,6 +49,12 @@

Touched: {{ pokemonControl.touched }}

Dirty: {{ pokemonControl.dirty }}

Status: {{ pokemonControl.control?.status }}

+

+ + +

diff --git a/src/demo-app/select/select-demo.ts b/src/demo-app/select/select-demo.ts index 04813bdae4e9..6e2ff635a202 100644 --- a/src/demo-app/select/select-demo.ts +++ b/src/demo-app/select/select-demo.ts @@ -19,6 +19,8 @@ export class SelectDemo { latestChangeEvent: MdSelectChange; floatPlaceholder: string = 'auto'; foodControl = new FormControl('pizza-1'); + drinksTheme = 'primary'; + pokemonTheme = 'primary'; foods = [ {value: 'steak-0', viewValue: 'Steak'}, @@ -48,6 +50,12 @@ export class SelectDemo { {value: 'psyduck-6', viewValue: 'Psyduck'}, ]; + availableThemes = [ + {value: 'primary', name: 'Primary' }, + {value: 'accent', name: 'Accent' }, + {value: 'warn', name: 'Warn' } + ]; + toggleDisabled() { this.foodControl.enabled ? this.foodControl.disable() : this.foodControl.enable(); } diff --git a/src/lib/core/option/_option-theme.scss b/src/lib/core/option/_option-theme.scss index 499b41c7a989..01fcc8585fd2 100644 --- a/src/lib/core/option/_option-theme.scss +++ b/src/lib/core/option/_option-theme.scss @@ -5,19 +5,29 @@ $foreground: map-get($theme, foreground); $background: map-get($theme, background); $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); .mat-option { &:hover:not(.mat-option-disabled), &:focus:not(.mat-option-disabled) { background: mat-color($background, hover); } - &.mat-selected { + &.mat-selected.mat-primary, .mat-primary &.mat-selected { color: mat-color($primary); + } + + &.mat-selected.mat-accent, .mat-accent &.mat-selected { + color: mat-color($accent); + } - // In multiple mode there is a checkbox to show that the option is selected. - &:not(.mat-option-multiple) { - background: mat-color($background, hover); - } + &.mat-selected.mat-warn, .mat-warn &.mat-selected { + color: mat-color($warn); + } + + // In multiple mode there is a checkbox to show that the option is selected. + &.mat-selected:not(.mat-option-multiple) { + background: mat-color($background, hover); } &.mat-active { @@ -28,6 +38,5 @@ &.mat-option-disabled { color: mat-color($foreground, hint-text); } - } } diff --git a/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss b/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss index 118235123d26..260b8d1601f1 100644 --- a/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss +++ b/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss @@ -1,5 +1,16 @@ @import '../../theming/theming'; +@mixin _mat-pseudo-checkbox-inner-content-theme($theme, $pallete-name) { + $pallete: map-get($theme, $pallete-name); + $color: mat-color($pallete, 500); + + .mat-pseudo-checkbox-checked.mat-#{$pallete-name}, + .mat-pseudo-checkbox-indeterminate.mat-#{$pallete-name}, + .mat-#{$pallete-name} .mat-pseudo-checkbox-checked, + .mat-#{$pallete-name} .mat-pseudo-checkbox-indeterminate { + background: $color; + } +} @mixin mat-pseudo-checkbox-theme($theme) { $is-dark-theme: map-get($theme, is-dark); @@ -14,6 +25,7 @@ $white-30pct-opacity-on-dark: #686868; $black-26pct-opacity-on-light: #b0b0b0; $disabled-color: if($is-dark-theme, $white-30pct-opacity-on-dark, $black-26pct-opacity-on-light); + $colored-box-selector: '.mat-pseudo-checkbox-checked, .mat-pseudo-checkbox-indeterminate'; .mat-pseudo-checkbox { color: mat-color(map-get($theme, foreground), secondary-text); @@ -23,19 +35,11 @@ } } - .mat-pseudo-checkbox-checked, .mat-pseudo-checkbox-indeterminate { - &.mat-primary { - background: mat-color($primary, 500); - } - - &.mat-accent { - background: mat-color($accent, 500); - } - - &.mat-warn { - background: mat-color($warn, 500); - } + @include _mat-pseudo-checkbox-inner-content-theme($theme, primary); + @include _mat-pseudo-checkbox-inner-content-theme($theme, accent); + @include _mat-pseudo-checkbox-inner-content-theme($theme, warn); + .mat-pseudo-checkbox-checked, .mat-pseudo-checkbox-indeterminate { &.mat-pseudo-checkbox-disabled { background: $disabled-color; } diff --git a/src/lib/select/_select-theme.scss b/src/lib/select/_select-theme.scss index 09e7849932e6..d996f4de8165 100644 --- a/src/lib/select/_select-theme.scss +++ b/src/lib/select/_select-theme.scss @@ -1,46 +1,33 @@ @import '../core/theming/palette'; @import '../core/theming/theming'; +@mixin _mat-select-inner-content-theme($palette) { + $color: mat-color($palette); + + .mat-select-trigger, .mat-select-arrow { + color: $color; + } + + .mat-select-underline { + background-color: $color; + } +} + @mixin mat-select-theme($theme) { $foreground: map-get($theme, foreground); $background: map-get($theme, background); $primary: map-get($theme, primary); + $accent: map-get($theme, accent); $warn: map-get($theme, warn); - .mat-select-trigger { - color: mat-color($foreground, hint-text); - - .mat-select:focus:not(.mat-select-disabled) & { - color: mat-color($primary); - } - - .mat-select:not(:focus).ng-invalid.ng-touched:not(.mat-select-disabled) & { - color: mat-color($warn); - } - } - .mat-select-underline { background-color: mat-color($foreground, divider); - - .mat-select:focus:not(.mat-select-disabled) & { - background-color: mat-color($primary); - } - - .mat-select:not(:focus).ng-invalid.ng-touched:not(.mat-select-disabled) & { - background-color: mat-color($warn); - } } - .mat-select-arrow { + .mat-select-disabled .mat-select-value, + .mat-select-arrow, + .mat-select-trigger { color: mat-color($foreground, hint-text); - - .mat-select:focus:not(.mat-select-disabled) & { - color: mat-color($primary); - } - - .mat-select:not(:focus).ng-invalid.ng-touched:not(.mat-select-disabled) & { - color: mat-color($warn); - } } .mat-select-content, .mat-select-panel-done-animating { @@ -49,9 +36,20 @@ .mat-select-value { color: mat-color($foreground, text); + } - .mat-select-disabled & { - color: mat-color($foreground, hint-text); + .mat-select:focus:not(.mat-select-disabled) { + &.mat-primary { + @include _mat-select-inner-content-theme($primary); } + + &.mat-accent { + @include _mat-select-inner-content-theme($accent); + } + } + + .mat-select:focus:not(.mat-select-disabled).mat-warn, + .mat-select:not(:focus).ng-invalid.ng-touched:not(.mat-select-disabled) { + @include _mat-select-inner-content-theme($warn); } } diff --git a/src/lib/select/select.html b/src/lib/select/select.html index c31a4b3f3518..917a89d9c20a 100644 --- a/src/lib/select/select.html +++ b/src/lib/select/select.html @@ -18,7 +18,7 @@ [offsetY]="_offsetY" [offsetX]="_offsetX" (attach)="_setScrollTop()" (detach)="close()">
+ [class.mat-select-panel-done-animating]="_panelDoneAnimating" [ngClass]="'mat-' + color">
diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 0e74b9cc4eda..471726da3a92 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -50,7 +50,8 @@ describe('MdSelect', () => { SelectWithPlainTabindex, SelectEarlyAccessSibling, BasicSelectInitiallyHidden, - BasicSelectNoPlaceholder + BasicSelectNoPlaceholder, + BasicSelectWithTheming ], providers: [ {provide: OverlayContainer, useFactory: () => { @@ -1709,6 +1710,55 @@ describe('MdSelect', () => { }); + describe('theming', () => { + let fixture: ComponentFixture; + let testInstance: BasicSelectWithTheming; + let selectElement: HTMLElement; + + beforeEach(async(() => { + fixture = TestBed.createComponent(BasicSelectWithTheming); + testInstance = fixture.componentInstance; + fixture.detectChanges(); + + selectElement = fixture.debugElement.query(By.css('.mat-select')).nativeElement; + })); + + it('should default to the primary theme', () => { + expect(fixture.componentInstance.select.color).toBe('primary'); + expect(selectElement.classList).toContain('mat-primary'); + }); + + it('should be able to override the theme', () => { + fixture.componentInstance.theme = 'accent'; + fixture.detectChanges(); + + expect(fixture.componentInstance.select.color).toBe('accent'); + expect(selectElement.classList).toContain('mat-accent'); + expect(selectElement.classList).not.toContain('mat-primary'); + }); + + it('should not be able to set a blank theme', () => { + fixture.componentInstance.theme = ''; + fixture.detectChanges(); + + expect(fixture.componentInstance.select.color).toBe('primary'); + expect(selectElement.classList).toContain('mat-primary'); + }); + + it('should pass the theme to the panel', () => { + fixture.componentInstance.theme = 'warn'; + fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click(); + fixture.detectChanges(); + + const panel = overlayContainerElement.querySelector('.mat-select-panel'); + + expect(fixture.componentInstance.select.color).toBe('warn'); + expect(selectElement.classList).toContain('mat-warn'); + expect(panel.classList).toContain('mat-warn'); + }); + + }); + }); @@ -2040,6 +2090,20 @@ class BasicSelectInitiallyHidden { }) class BasicSelectNoPlaceholder { } +@Component({ + selector: 'basic-select-with-theming', + template: ` + + Steak + Pizza + + ` +}) +class BasicSelectWithTheming { + @ViewChild(MdSelect) select: MdSelect; + theme: string; +} + class FakeViewportRuler { getViewportRect() { return { diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 55dd43557a05..a44d58f1d1d2 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -9,7 +9,7 @@ import { Optional, Output, QueryList, - Renderer, + Renderer2, Self, ViewEncapsulation, ViewChild, @@ -111,7 +111,7 @@ export type MdSelectFloatPlaceholderType = 'always' | 'never' | 'auto'; '[class.mat-select-disabled]': 'disabled', '[class.mat-select]': 'true', '(keydown)': '_handleKeydown($event)', - '(blur)': '_onBlur()' + '(blur)': '_onBlur()', }, animations: [ transformPlaceholder, @@ -157,6 +157,9 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal /** Tab index for the element. */ private _tabIndex: number; + /** Theme color for the component. */ + private _color: string; + /** * The width of the trigger. Must be saved to set the min width of the overlay panel * and the width of the selected value. @@ -287,6 +290,17 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal /** Input that can be used to specify the `aria-labelledby` attribute. */ @Input('aria-labelledby') ariaLabelledby: string = ''; + /** Theme color for the component. */ + @Input() + get color(): string { return this._color; } + set color(value: string) { + if (value && value !== this._color) { + this._renderer.removeClass(this._element.nativeElement, `mat-${this._color}`); + this._renderer.addClass(this._element.nativeElement, `mat-${value}`); + this._color = value; + } + } + /** Combined stream of all of the child options' change events. */ get optionSelectionChanges(): Observable { return Observable.merge(...this.options.map(option => option.onSelectionChange)); @@ -301,7 +315,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal /** Event emitted when the selected value has been changed by the user. */ @Output() change: EventEmitter = new EventEmitter(); - constructor(private _element: ElementRef, private _renderer: Renderer, + constructor(private _element: ElementRef, private _renderer: Renderer2, private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef, @Optional() private _dir: Dir, @Self() @Optional() public _control: NgControl, @Attribute('tabindex') tabIndex: string) { @@ -314,6 +328,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal ngOnInit() { this._selectionModel = new SelectionModel(this.multiple, null, false); + this.color = this.color || 'primary'; } ngAfterContentInit() { @@ -686,7 +701,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal /** Focuses the host element when the panel closes. */ private _focusHost(): void { - this._renderer.invokeElementMethod(this._element.nativeElement, 'focus'); + this._element.nativeElement.focus(); } /** Gets the index of the provided option in the option list. */