Skip to content

Commit

Permalink
feat(datepicker): add theming support (#9407)
Browse files Browse the repository at this point in the history
Adds support for theming the datepicker using the `color` attribute, as well as inheriting the theme color from the form field around a datepicker input.
  • Loading branch information
crisbeto authored and jelbourn committed Feb 13, 2018
1 parent 21a83ba commit 0383704
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 40 deletions.
10 changes: 9 additions & 1 deletion src/demo-app/datepicker/datepicker-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ <h2>Options</h2>
<mat-checkbox [(ngModel)]="yearView">Start in year view</mat-checkbox>
<mat-checkbox [(ngModel)]="datepickerDisabled">Disable datepicker</mat-checkbox>
<mat-checkbox [(ngModel)]="inputDisabled">Disable input</mat-checkbox>
<mat-form-field>
<mat-select [(ngModel)]="color" placeholder="Color">
<mat-option value="primary">Primary</mat-option>
<mat-option value="accent">Accent</mat-option>
<mat-option value="warn">Warn</mat-option>
</mat-select>
</mat-form-field>
</p>
<p>
<mat-form-field>
Expand Down Expand Up @@ -53,7 +60,8 @@ <h2>Result</h2>
[touchUi]="touch"
[disabled]="datepickerDisabled"
[startAt]="startAt"
[startView]="yearView ? 'year' : 'month'">
[startView]="yearView ? 'year' : 'month'"
[color]="color">
</mat-datepicker>
<mat-error *ngIf="resultPickerModel.hasError('matDatepickerParse')">
"{{resultPickerModel.getError('matDatepickerParse').text}}" is not a valid date!
Expand Down
5 changes: 4 additions & 1 deletion src/demo-app/datepicker/datepicker-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

import {ChangeDetectionStrategy, Component} from '@angular/core';
import {FormControl} from '@angular/forms';
import {MatDatepickerInputEvent} from '@angular/material';
import {MatDatepickerInputEvent} from '@angular/material/datepicker';
import {ThemePalette} from '@angular/material/core';


@Component({
moduleId: module.id,
Expand All @@ -29,6 +31,7 @@ export class DatepickerDemo {
date: Date;
lastDateInput: Date | null;
lastDateChange: Date | null;
color: ThemePalette;

dateCtrl = new FormControl();

Expand Down
71 changes: 43 additions & 28 deletions src/lib/datepicker/_datepicker-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,32 @@
@import '../core/typography/typography-utils';


$mat-datepicker-selected-today-box-shadow-width: 1px;
$mat-datepicker-selected-fade-amount: 0.6;
$mat-datepicker-today-fade-amount: 0.2;
$mat-calendar-body-font-size: 13px !default;
$mat-calendar-weekday-table-font-size: 11px !default;

@mixin _mat-datepicker-color($palette) {
.mat-calendar-body-selected {
background-color: mat-color($palette);
color: mat-color($palette, default-contrast);
}

.mat-calendar-body-disabled > .mat-calendar-body-selected {
background-color: fade-out(mat-color($palette), $mat-datepicker-selected-fade-amount);
}

.mat-calendar-body-today.mat-calendar-body-selected {
box-shadow: inset 0 0 0 $mat-datepicker-selected-today-box-shadow-width
mat-color($palette, default-contrast);
}
}

@mixin mat-datepicker-theme($theme) {
$primary: map-get($theme, primary);
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);

$mat-datepicker-selected-today-box-shadow-width: 1px;
$mat-datepicker-selected-fade-amount: 0.6;
$mat-datepicker-today-fade-amount: 0.2;

.mat-datepicker-content {
background-color: mat-color($background, card);
color: mat-color($foreground, text);
}

.mat-calendar-arrow {
border-top-color: mat-color($foreground, icon);
}
Expand Down Expand Up @@ -59,34 +67,41 @@ $mat-calendar-weekday-table-font-size: 11px !default;
}
}

.mat-calendar-body-selected {
background-color: mat-color($primary);
color: mat-color($primary, default-contrast);
.mat-calendar-body-today:not(.mat-calendar-body-selected) {
// Note: though it's not text, the border is a hint about the fact that this is today's date,
// so we use the hint color.
border-color: mat-color($foreground, hint-text);
}

.mat-calendar-body-disabled > .mat-calendar-body-selected {
background-color: fade-out(mat-color($primary), $mat-datepicker-selected-fade-amount);
.mat-calendar-body-disabled > .mat-calendar-body-today:not(.mat-calendar-body-selected) {
border-color: fade-out(mat-color($foreground, hint-text), $mat-datepicker-today-fade-amount);
}

.mat-calendar-body-today {
&:not(.mat-calendar-body-selected) {
// Note: though it's not text, the border is a hint about the fact that this is today's date,
// so we use the hint color.
border-color: mat-color($foreground, hint-text);
}
@include _mat-datepicker-color(map-get($theme, primary));

.mat-datepicker-content {
background-color: mat-color($background, card);
color: mat-color($foreground, text);

&.mat-calendar-body-selected {
box-shadow: inset 0 0 0 $mat-datepicker-selected-today-box-shadow-width
mat-color($primary, default-contrast);
&.mat-accent {
@include _mat-datepicker-color(map-get($theme, accent));
}
}

.mat-calendar-body-disabled > .mat-calendar-body-today:not(.mat-calendar-body-selected) {
border-color: fade-out(mat-color($foreground, hint-text), $mat-datepicker-today-fade-amount);
&.mat-warn {
@include _mat-datepicker-color(map-get($theme, warn));
}
}

.mat-datepicker-toggle-active {
color: mat-color($primary);
color: mat-color(map-get($theme, primary));

&.mat-accent {
color: mat-color(map-get($theme, accent));
}

&.mat-warn {
color: mat-color(map-get($theme, warn));
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/lib/datepicker/datepicker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,11 @@ export class MatDatepickerInput<D> implements AfterContentInit, ControlValueAcce
this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
}

/** Returns the palette used by the input's form field, if any. */
_getThemePalette() {
return this._formField ? this._formField.color : undefined;
}

/**
* @param obj The object to check.
* @returns The given object if it is both a date instance and valid, otherwise null.
Expand Down
2 changes: 2 additions & 0 deletions src/lib/datepicker/datepicker-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export class MatDatepickerToggleIcon {}
host: {
'class': 'mat-datepicker-toggle',
'[class.mat-datepicker-toggle-active]': 'datepicker && datepicker.opened',
'[class.mat-accent]': 'datepicker && datepicker.color === "accent"',
'[class.mat-warn]': 'datepicker && datepicker.color === "warn"',
},
exportAs: 'matDatepickerToggle',
encapsulation: ViewEncapsulation.None,
Expand Down
69 changes: 65 additions & 4 deletions src/lib/datepicker/datepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
NativeDateModule,
SEP,
} from '@angular/material/core';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatFormFieldModule, MatFormField} from '@angular/material/form-field';
import {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {MatInputModule} from '../input/index';
Expand Down Expand Up @@ -98,6 +98,29 @@ describe('MatDatepicker', () => {
.not.toBeNull();
});

it('should pass the datepicker theme color to the overlay', fakeAsync(() => {
testComponent.datepicker.color = 'primary';
testComponent.datepicker.open();
fixture.detectChanges();

let contentEl = document.querySelector('.mat-datepicker-content')!;

expect(contentEl.classList).toContain('mat-primary');

testComponent.datepicker.close();
fixture.detectChanges();
flush();

testComponent.datepicker.color = 'warn';
testComponent.datepicker.open();

contentEl = document.querySelector('.mat-datepicker-content')!;
fixture.detectChanges();

expect(contentEl.classList).toContain('mat-warn');
expect(contentEl.classList).not.toContain('mat-primary');
}));

it('should open datepicker if opened input is set to true', () => {
testComponent.opened = true;
fixture.detectChanges();
Expand Down Expand Up @@ -137,19 +160,21 @@ describe('MatDatepicker', () => {
expect(document.querySelector('.cdk-overlay-pane')).not.toBeNull();
});

it('close should close popup', () => {
it('close should close popup', fakeAsync(() => {
testComponent.datepicker.open();
fixture.detectChanges();
flush();

let popup = document.querySelector('.cdk-overlay-pane')!;
expect(popup).not.toBeNull();
expect(parseInt(getComputedStyle(popup).height as string)).not.toBe(0);

testComponent.datepicker.close();
fixture.detectChanges();
flush();

expect(parseInt(getComputedStyle(popup).height as string)).toBe(0);
});
}));

it('should close the popup when pressing ESCAPE', fakeAsync(() => {
testComponent.datepicker.open();
Expand Down Expand Up @@ -848,13 +873,13 @@ describe('MatDatepicker', () => {
beforeEach(fakeAsync(() => {
fixture = createComponent(FormFieldDatepicker, [MatNativeDateModule]);
fixture.detectChanges();

testComponent = fixture.componentInstance;
}));

afterEach(fakeAsync(() => {
testComponent.datepicker.close();
fixture.detectChanges();
flush();
}));

it('should float the placeholder when an invalid value is entered', () => {
Expand All @@ -865,6 +890,41 @@ describe('MatDatepicker', () => {
expect(fixture.debugElement.nativeElement.querySelector('mat-form-field').classList)
.toContain('mat-form-field-should-float');
});

it('should pass the form field theme color to the overlay', fakeAsync(() => {
testComponent.formField.color = 'primary';
testComponent.datepicker.open();
fixture.detectChanges();

let contentEl = document.querySelector('.mat-datepicker-content')!;

expect(contentEl.classList).toContain('mat-primary');

testComponent.datepicker.close();
fixture.detectChanges();
flush();

testComponent.formField.color = 'warn';
testComponent.datepicker.open();

contentEl = document.querySelector('.mat-datepicker-content')!;
fixture.detectChanges();

expect(contentEl.classList).toContain('mat-warn');
expect(contentEl.classList).not.toContain('mat-primary');
}));

it('should prefer the datepicker color over the form field one', fakeAsync(() => {
testComponent.datepicker.color = 'accent';
testComponent.formField.color = 'warn';
testComponent.datepicker.open();
fixture.detectChanges();

const contentEl = document.querySelector('.mat-datepicker-content')!;

expect(contentEl.classList).toContain('mat-accent');
expect(contentEl.classList).not.toContain('mat-warn');
}));
});

describe('datepicker with min and max dates and validation', () => {
Expand Down Expand Up @@ -1423,6 +1483,7 @@ class DatepickerWithCustomIcon {}
class FormFieldDatepicker {
@ViewChild('d') datepicker: MatDatepicker<Date>;
@ViewChild(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>;
@ViewChild(MatFormField) formField: MatFormField;
}


Expand Down
Loading

0 comments on commit 0383704

Please sign in to comment.