Skip to content

Commit ec7ec35

Browse files
feat(module:calendar): custom header (#8418)
1 parent d575c53 commit ec7ec35

7 files changed

Lines changed: 128 additions & 38 deletions

File tree

components/calendar/calendar-header.component.ts

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
44
*/
55

6+
import { NgTemplateOutlet } from '@angular/common';
67
import {
78
ChangeDetectionStrategy,
89
Component,
910
EventEmitter,
1011
Input,
1112
OnInit,
1213
Output,
14+
TemplateRef,
1315
ViewEncapsulation
1416
} from '@angular/core';
1517
import { FormsModule } from '@angular/forms';
1618

19+
import { NzStringTemplateOutletDirective } from 'ng-zorro-antd/core/outlet';
1720
import { CandyDate } from 'ng-zorro-antd/core/time';
1821
import { DateHelperService, NzI18nService as I18n } from 'ng-zorro-antd/i18n';
1922
import { NzRadioModule } from 'ng-zorro-antd/radio';
@@ -25,55 +28,60 @@ import { NzSelectModule, NzSelectSizeType } from 'ng-zorro-antd/select';
2528
selector: 'nz-calendar-header',
2629
exportAs: 'nzCalendarHeader',
2730
template: `
28-
<div class="ant-picker-calendar-header">
29-
<nz-select
30-
class="ant-picker-calendar-year-select"
31-
[nzSize]="size"
32-
[nzDropdownMatchSelectWidth]="false"
33-
[ngModel]="activeYear"
34-
(ngModelChange)="updateYear($event)"
35-
>
36-
@for (year of years; track year.value) {
37-
<nz-option [nzLabel]="year.label" [nzValue]="year.value" />
38-
}
39-
</nz-select>
40-
41-
@if (mode === 'month') {
31+
@if (nzCustomHeader) {
32+
<ng-container *nzStringTemplateOutlet="nzCustomHeader">{{ nzCustomHeader }}</ng-container>
33+
} @else {
34+
<div class="ant-picker-calendar-header">
4235
<nz-select
43-
class="ant-picker-calendar-month-select"
36+
class="ant-picker-calendar-year-select"
4437
[nzSize]="size"
4538
[nzDropdownMatchSelectWidth]="false"
46-
[ngModel]="activeMonth"
47-
(ngModelChange)="monthChange.emit($event)"
39+
[ngModel]="activeYear"
40+
(ngModelChange)="updateYear($event)"
4841
>
49-
@for (month of months; track month.value) {
50-
<nz-option [nzLabel]="month.label" [nzValue]="month.value" />
42+
@for (year of years; track year.value) {
43+
<nz-option [nzLabel]="year.label" [nzValue]="year.value" />
5144
}
5245
</nz-select>
53-
}
54-
55-
<nz-radio-group
56-
class="ant-picker-calendar-mode-switch"
57-
[(ngModel)]="mode"
58-
(ngModelChange)="modeChange.emit($event)"
59-
[nzSize]="size"
60-
>
61-
<label nz-radio-button nzValue="month">{{ monthTypeText }}</label>
62-
<label nz-radio-button nzValue="year">{{ yearTypeText }}</label>
63-
</nz-radio-group>
64-
</div>
46+
47+
@if (mode === 'month') {
48+
<nz-select
49+
class="ant-picker-calendar-month-select"
50+
[nzSize]="size"
51+
[nzDropdownMatchSelectWidth]="false"
52+
[ngModel]="activeMonth"
53+
(ngModelChange)="monthChange.emit($event)"
54+
>
55+
@for (month of months; track month.value) {
56+
<nz-option [nzLabel]="month.label" [nzValue]="month.value" />
57+
}
58+
</nz-select>
59+
}
60+
61+
<nz-radio-group
62+
class="ant-picker-calendar-mode-switch"
63+
[(ngModel)]="mode"
64+
(ngModelChange)="modeChange.emit($event)"
65+
[nzSize]="size"
66+
>
67+
<label nz-radio-button nzValue="month">{{ monthTypeText }}</label>
68+
<label nz-radio-button nzValue="year">{{ yearTypeText }}</label>
69+
</nz-radio-group>
70+
</div>
71+
}
6572
`,
6673
host: {
6774
class: 'ant-fullcalendar-header',
6875
'[style.display]': `'block'`
6976
},
70-
imports: [NzSelectModule, FormsModule, NzRadioModule],
77+
imports: [NzSelectModule, FormsModule, NzRadioModule, NgTemplateOutlet, NzStringTemplateOutletDirective],
7178
standalone: true
7279
})
7380
export class NzCalendarHeaderComponent implements OnInit {
7481
@Input() mode: 'month' | 'year' = 'month';
7582
@Input() fullscreen: boolean = true;
7683
@Input() activeDate: CandyDate = new CandyDate();
84+
@Input() nzCustomHeader?: string | TemplateRef<void>;
7785

7886
@Output() readonly modeChange: EventEmitter<'month' | 'year'> = new EventEmitter();
7987
@Output() readonly yearChange: EventEmitter<number> = new EventEmitter();

components/calendar/calendar-header.spec.ts

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { registerLocaleData } from '@angular/common';
22
import zh from '@angular/common/locales/zh';
3-
import { Component } from '@angular/core';
4-
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3+
import { Component, TemplateRef, ViewChild } from '@angular/core';
4+
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
55
import { FormsModule, NgModel } from '@angular/forms';
66
import { By } from '@angular/platform-browser';
77
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
88

99
import { CandyDate } from 'ng-zorro-antd/core/time';
10+
import { NzSafeAny } from 'ng-zorro-antd/core/types';
1011
import { NzI18nModule } from 'ng-zorro-antd/i18n';
1112
import { NzSelectModule } from 'ng-zorro-antd/select';
1213

@@ -61,6 +62,8 @@ describe('Calendar Header', () => {
6162
});
6263

6364
it('should emit change event for mode selection', () => {
65+
fixture.detectChanges();
66+
6467
const modeNgModel = fixture.debugElement
6568
.queryAll(By.directive(CalendarHeader))[1]
6669
.query(By.directive(RadioGroup))
@@ -150,7 +153,10 @@ describe('Calendar Header', () => {
150153
component = fixture.componentInstance;
151154
}));
152155

153-
it('should emit yearChange when year changed', () => {
156+
it('should emit yearChange when year changed', fakeAsync(() => {
157+
tick(1);
158+
fixture.detectChanges();
159+
154160
const header = fixture.debugElement.queryAll(By.directive(CalendarHeader))[0];
155161
const [yearModel] = header.queryAll(By.directive(Select)).map(x => x.injector.get(NgModel));
156162

@@ -159,7 +165,7 @@ describe('Calendar Header', () => {
159165
fixture.detectChanges();
160166

161167
expect(component.year).toBe(2010);
162-
});
168+
}));
163169

164170
it('should emit monthChange when month changed', () => {
165171
fixture.detectChanges();
@@ -172,13 +178,38 @@ describe('Calendar Header', () => {
172178

173179
expect(component.month).toBe(2);
174180
});
181+
175182
it('should update years when change year', () => {
176183
const header = fixture.debugElement.queryAll(By.directive(CalendarHeader))[0];
177184
const headerComponent = header.injector.get(NzCalendarHeaderComponent);
178185
headerComponent.updateYear(2010);
179186
expect(headerComponent.years[0].value).toBe(2000);
180187
});
181188
});
189+
190+
describe('custom Header', () => {
191+
let fixture: ComponentFixture<NzTestCalendarHeaderChangesComponent>;
192+
193+
beforeEach(waitForAsync(() => {
194+
fixture = TestBed.createComponent(NzTestCalendarHeaderChangesComponent);
195+
}));
196+
197+
it('should have the default header if custom header is not passed', fakeAsync(() => {
198+
fixture.componentInstance.customHeader = undefined;
199+
tick(1);
200+
fixture.detectChanges();
201+
202+
const defaultHeader = fixture.debugElement.query(By.css('.ant-picker-calendar-header'));
203+
expect(defaultHeader).toBeTruthy();
204+
205+
fixture.componentInstance.customHeader = fixture.componentInstance.customHeaderElement;
206+
tick(1);
207+
fixture.detectChanges();
208+
209+
const defaultHeader2 = fixture.debugElement.query(By.css('.ant-picker-calendar-header'));
210+
expect(defaultHeader2).toBeFalsy();
211+
}));
212+
});
182213
});
183214

184215
@Component({
@@ -212,9 +243,22 @@ class NzTestCalendarHeaderActiveDateComponent {
212243
}
213244

214245
@Component({
215-
template: ` <nz-calendar-header (yearChange)="year = $event" (monthChange)="month = $event"></nz-calendar-header> `
246+
template: `
247+
<nz-calendar-header
248+
[nzCustomHeader]="customHeader"
249+
(yearChange)="year = $event"
250+
(monthChange)="month = $event"
251+
></nz-calendar-header>
252+
253+
<ng-template #customHeaderElement>
254+
<p>custom header</p>
255+
</ng-template>
256+
`
216257
})
217258
class NzTestCalendarHeaderChangesComponent {
259+
@ViewChild('customHeaderElement', { static: true }) customHeaderElement!: TemplateRef<NzSafeAny>;
260+
218261
year: number | null = null;
219262
month: number | null = null;
263+
customHeader?: TemplateRef<void>;
220264
}

components/calendar/calendar.component.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type NzCalendarDateTemplate = TemplateRef<{ $implicit: Date }>;
5050
<nz-calendar-header
5151
[fullscreen]="nzFullscreen"
5252
[activeDate]="activeDate"
53+
[nzCustomHeader]="nzCustomHeader"
5354
[(mode)]="nzMode"
5455
(modeChange)="onModeChange($event)"
5556
(yearChange)="onYearSelect($event)"
@@ -142,7 +143,11 @@ export class NzCalendarComponent implements ControlValueAccessor, OnChanges, OnI
142143
return (this.nzMonthFullCell || this.nzMonthFullCellChild)!;
143144
}
144145

145-
@Input() @InputBoolean() nzFullscreen: boolean = true;
146+
@Input() nzCustomHeader?: string | TemplateRef<void>;
147+
148+
@Input()
149+
@InputBoolean()
150+
nzFullscreen: boolean = true;
146151

147152
constructor(
148153
private cdr: ChangeDetectorRef,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
order: 4
3+
title:
4+
zh-CN: 自定义头部
5+
en-US: Customize Header
6+
---
7+
8+
## zh-CN
9+
10+
自定义日历头部内容。
11+
12+
## en-US
13+
14+
Customize Calendar header content.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
selector: 'nz-demo-calendar-customize-header',
5+
template: `
6+
<div [ngStyle]="{ width: '300px', border: '1px solid #d9d9d9', borderRadius: '4px' }">
7+
<nz-calendar [nzFullscreen]="false" [nzCustomHeader]="customHeader"></nz-calendar>
8+
</div>
9+
10+
<ng-template #customHeader>
11+
<div style="padding: 8px">
12+
<h4>Custom header</h4>
13+
</div>
14+
</ng-template>
15+
`
16+
})
17+
export class NzDemoCalendarCustomizeHeaderComponent {}

components/calendar/doc/index.en-US.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ registerLocaleData(en);
5454
| `[nzDateFullCell]` | (Contentable) Customize the display of the date cell, the template content will override the cell | `TemplateRef<Date>` | - |
5555
| `[nzMonthCell]` | (Contentable) Customize the display of the month cell, the template content will be appended to the cell | `TemplateRef<Date>` | - |
5656
| `[nzMonthFullCell]` | (Contentable) Customize the display of the month cell, the template content will override the cell | `TemplateRef<Date>` | - |
57+
| `[nzCustomHeader]` | Render custom header in panel | `string \| TemplateRef<void>` | - |
5758
| `[nzDisabledDate]` | specify the date that cannot be selected | `(current: Date) => boolean` | - |
5859
| `(nzPanelChange)` | Callback for when panel changes | `EventEmitter<{ date: Date, mode: 'month' \| 'year' }>` | - |
5960
| `(nzSelectChange)` | A callback function of selected item | `EventEmitter<Date>` | - |

components/calendar/doc/index.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ registerLocaleData(zh);
5555
| `[nzDateFullCell]` | (可作为内容)自定义渲染日期单元格,模版内容覆盖单元格 | `TemplateRef<Date>` | - |
5656
| `[nzMonthCell]` | (可作为内容)自定义渲染月单元格,模版内容会被追加到单元格 | `TemplateRef<Date>` | - |
5757
| `[nzMonthFullCell]` | (可作为内容)自定义渲染月单元格,模版内容覆盖单元格 | `TemplateRef<Date>` | - |
58+
| `[nzCustomHeader]` | 自定义头部内容 | `string \| TemplateRef<void>` | - |
5859
| `[nzDisabledDate]` | 不可选择的日期 | `(current: Date) => boolean` | - |
5960
| `(nzPanelChange)` | 面板变化的回调 | `EventEmitter<{ date: Date, mode: 'month' \| 'year' }>` | - |
6061
| `(nzSelectChange)` | 选择日期的回调 | `EventEmitter<Date>` | - |

0 commit comments

Comments
 (0)