Skip to content

Commit a51a61f

Browse files
committed
feat(datepicker): add theming support
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.
1 parent 1b90318 commit a51a61f

File tree

6 files changed

+160
-39
lines changed

6 files changed

+160
-39
lines changed

src/demo-app/datepicker/datepicker-demo.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ <h2>Options</h2>
55
<mat-checkbox [(ngModel)]="yearView">Start in year view</mat-checkbox>
66
<mat-checkbox [(ngModel)]="datepickerDisabled">Disable datepicker</mat-checkbox>
77
<mat-checkbox [(ngModel)]="inputDisabled">Disable input</mat-checkbox>
8+
<mat-form-field>
9+
<mat-select [(ngModel)]="color" placeholder="Color">
10+
<mat-option value="primary">Primary</mat-option>
11+
<mat-option value="accent">Accent</mat-option>
12+
<mat-option value="warn">Warn</mat-option>
13+
</mat-select>
14+
</mat-form-field>
815
</p>
916
<p>
1017
<mat-form-field>
@@ -53,7 +60,8 @@ <h2>Result</h2>
5360
[touchUi]="touch"
5461
[disabled]="datepickerDisabled"
5562
[startAt]="startAt"
56-
[startView]="yearView ? 'year' : 'month'">
63+
[startView]="yearView ? 'year' : 'month'"
64+
[color]="color">
5765
</mat-datepicker>
5866
<mat-error *ngIf="resultPickerModel.hasError('matDatepickerParse')">
5967
"{{resultPickerModel.getError('matDatepickerParse').text}}" is not a valid date!

src/demo-app/datepicker/datepicker-demo.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
import {ChangeDetectionStrategy, Component} from '@angular/core';
1010
import {FormControl} from '@angular/forms';
11-
import {MatDatepickerInputEvent} from '@angular/material';
11+
import {MatDatepickerInputEvent} from '@angular/material/datepicker';
12+
import {ThemePalette} from '@angular/material/core';
13+
1214

1315
@Component({
1416
moduleId: module.id,
@@ -29,6 +31,7 @@ export class DatepickerDemo {
2931
date: Date;
3032
lastDateInput: Date | null;
3133
lastDateChange: Date | null;
34+
color: ThemePalette;
3235

3336
dateCtrl = new FormControl();
3437

src/lib/datepicker/_datepicker-theme.scss

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,32 @@
33
@import '../core/typography/typography-utils';
44

55

6+
$mat-datepicker-selected-today-box-shadow-width: 1px;
7+
$mat-datepicker-selected-fade-amount: 0.6;
8+
$mat-datepicker-today-fade-amount: 0.2;
69
$mat-calendar-body-font-size: 13px !default;
710
$mat-calendar-weekday-table-font-size: 11px !default;
811

12+
@mixin _mat-datepicker-color($palette) {
13+
.mat-calendar-body-selected {
14+
background-color: mat-color($palette);
15+
color: mat-color($palette, default-contrast);
16+
}
17+
18+
.mat-calendar-body-disabled > .mat-calendar-body-selected {
19+
background-color: fade-out(mat-color($palette), $mat-datepicker-selected-fade-amount);
20+
}
21+
22+
.mat-calendar-body-today.mat-calendar-body-selected {
23+
box-shadow: inset 0 0 0 $mat-datepicker-selected-today-box-shadow-width
24+
mat-color($palette, default-contrast);
25+
}
26+
}
927

1028
@mixin mat-datepicker-theme($theme) {
11-
$primary: map-get($theme, primary);
1229
$foreground: map-get($theme, foreground);
1330
$background: map-get($theme, background);
1431

15-
$mat-datepicker-selected-today-box-shadow-width: 1px;
16-
$mat-datepicker-selected-fade-amount: 0.6;
17-
$mat-datepicker-today-fade-amount: 0.2;
18-
19-
.mat-datepicker-content {
20-
background-color: mat-color($background, card);
21-
color: mat-color($foreground, text);
22-
}
23-
2432
.mat-calendar-arrow {
2533
border-top-color: mat-color($foreground, icon);
2634
}
@@ -59,30 +67,29 @@ $mat-calendar-weekday-table-font-size: 11px !default;
5967
}
6068
}
6169

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

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

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

78-
&.mat-calendar-body-selected {
79-
box-shadow: inset 0 0 0 $mat-datepicker-selected-today-box-shadow-width
80-
mat-color($primary, default-contrast);
82+
.mat-datepicker-content {
83+
background-color: mat-color($background, card);
84+
color: mat-color($foreground, text);
85+
86+
&.mat-accent {
87+
@include _mat-datepicker-color(map-get($theme, accent));
8188
}
82-
}
8389

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

8895
.mat-datepicker-toggle-active {

src/lib/datepicker/datepicker-input.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,11 @@ export class MatDatepickerInput<D> implements AfterContentInit, ControlValueAcce
334334
this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement));
335335
}
336336

337+
/** Returns the palette used by the input's form field, if any. */
338+
_getThemePalette() {
339+
return this._formField ? this._formField.color : undefined;
340+
}
341+
337342
/**
338343
* @param obj The object to check.
339344
* @returns The given object if it is both a date instance and valid, otherwise null.

src/lib/datepicker/datepicker.spec.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
NativeDateModule,
1515
SEP,
1616
} from '@angular/material/core';
17-
import {MatFormFieldModule} from '@angular/material/form-field';
17+
import {MatFormFieldModule, MatFormField} from '@angular/material/form-field';
1818
import {By} from '@angular/platform-browser';
1919
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
2020
import {MatInputModule} from '../input/index';
@@ -98,6 +98,29 @@ describe('MatDatepicker', () => {
9898
.not.toBeNull();
9999
});
100100

101+
it('should pass the datepicker theme color to the overlay', fakeAsync(() => {
102+
testComponent.datepicker.color = 'primary';
103+
testComponent.datepicker.open();
104+
fixture.detectChanges();
105+
106+
let contentEl = document.querySelector('.mat-datepicker-content')!;
107+
108+
expect(contentEl.classList).toContain('mat-primary');
109+
110+
testComponent.datepicker.close();
111+
fixture.detectChanges();
112+
flush();
113+
114+
testComponent.datepicker.color = 'warn';
115+
testComponent.datepicker.open();
116+
117+
contentEl = document.querySelector('.mat-datepicker-content')!;
118+
fixture.detectChanges();
119+
120+
expect(contentEl.classList).toContain('mat-warn');
121+
expect(contentEl.classList).not.toContain('mat-primary');
122+
}));
123+
101124
it('should open datepicker if opened input is set to true', () => {
102125
testComponent.opened = true;
103126
fixture.detectChanges();
@@ -137,19 +160,21 @@ describe('MatDatepicker', () => {
137160
expect(document.querySelector('.cdk-overlay-pane')).not.toBeNull();
138161
});
139162

140-
it('close should close popup', () => {
163+
it('close should close popup', fakeAsync(() => {
141164
testComponent.datepicker.open();
142165
fixture.detectChanges();
166+
flush();
143167

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

148172
testComponent.datepicker.close();
149173
fixture.detectChanges();
174+
flush();
150175

151176
expect(parseInt(getComputedStyle(popup).height as string)).toBe(0);
152-
});
177+
}));
153178

154179
it('should close the popup when pressing ESCAPE', fakeAsync(() => {
155180
testComponent.datepicker.open();
@@ -848,13 +873,13 @@ describe('MatDatepicker', () => {
848873
beforeEach(fakeAsync(() => {
849874
fixture = createComponent(FormFieldDatepicker, [MatNativeDateModule]);
850875
fixture.detectChanges();
851-
852876
testComponent = fixture.componentInstance;
853877
}));
854878

855879
afterEach(fakeAsync(() => {
856880
testComponent.datepicker.close();
857881
fixture.detectChanges();
882+
flush();
858883
}));
859884

860885
it('should float the placeholder when an invalid value is entered', () => {
@@ -865,6 +890,41 @@ describe('MatDatepicker', () => {
865890
expect(fixture.debugElement.nativeElement.querySelector('mat-form-field').classList)
866891
.toContain('mat-form-field-should-float');
867892
});
893+
894+
it('should pass the form field theme color to the overlay', fakeAsync(() => {
895+
testComponent.formField.color = 'primary';
896+
testComponent.datepicker.open();
897+
fixture.detectChanges();
898+
899+
let contentEl = document.querySelector('.mat-datepicker-content')!;
900+
901+
expect(contentEl.classList).toContain('mat-primary');
902+
903+
testComponent.datepicker.close();
904+
fixture.detectChanges();
905+
flush();
906+
907+
testComponent.formField.color = 'warn';
908+
testComponent.datepicker.open();
909+
910+
contentEl = document.querySelector('.mat-datepicker-content')!;
911+
fixture.detectChanges();
912+
913+
expect(contentEl.classList).toContain('mat-warn');
914+
expect(contentEl.classList).not.toContain('mat-primary');
915+
}));
916+
917+
it('should prefer the datepicker color over the form field one', fakeAsync(() => {
918+
testComponent.datepicker.color = 'accent';
919+
testComponent.formField.color = 'warn';
920+
testComponent.datepicker.open();
921+
fixture.detectChanges();
922+
923+
const contentEl = document.querySelector('.mat-datepicker-content')!;
924+
925+
expect(contentEl.classList).toContain('mat-accent');
926+
expect(contentEl.classList).not.toContain('mat-warn');
927+
}));
868928
});
869929

870930
describe('datepicker with min and max dates and validation', () => {
@@ -1423,6 +1483,7 @@ class DatepickerWithCustomIcon {}
14231483
class FormFieldDatepicker {
14241484
@ViewChild('d') datepicker: MatDatepicker<Date>;
14251485
@ViewChild(MatDatepickerInput) datepickerInput: MatDatepickerInput<Date>;
1486+
@ViewChild(MatFormField) formField: MatFormField;
14261487
}
14271488

14281489

0 commit comments

Comments
 (0)