Skip to content

Commit

Permalink
feat(module:calendar): custom header (#8418)
Browse files Browse the repository at this point in the history
  • Loading branch information
ParsaArvanehPA authored Mar 10, 2024
1 parent d575c53 commit ec7ec35
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 38 deletions.
72 changes: 40 additions & 32 deletions components/calendar/calendar-header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/

import { NgTemplateOutlet } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnInit,
Output,
TemplateRef,
ViewEncapsulation
} from '@angular/core';
import { FormsModule } from '@angular/forms';

import { NzStringTemplateOutletDirective } from 'ng-zorro-antd/core/outlet';
import { CandyDate } from 'ng-zorro-antd/core/time';
import { DateHelperService, NzI18nService as I18n } from 'ng-zorro-antd/i18n';
import { NzRadioModule } from 'ng-zorro-antd/radio';
Expand All @@ -25,55 +28,60 @@ import { NzSelectModule, NzSelectSizeType } from 'ng-zorro-antd/select';
selector: 'nz-calendar-header',
exportAs: 'nzCalendarHeader',
template: `
<div class="ant-picker-calendar-header">
<nz-select
class="ant-picker-calendar-year-select"
[nzSize]="size"
[nzDropdownMatchSelectWidth]="false"
[ngModel]="activeYear"
(ngModelChange)="updateYear($event)"
>
@for (year of years; track year.value) {
<nz-option [nzLabel]="year.label" [nzValue]="year.value" />
}
</nz-select>
@if (mode === 'month') {
@if (nzCustomHeader) {
<ng-container *nzStringTemplateOutlet="nzCustomHeader">{{ nzCustomHeader }}</ng-container>
} @else {
<div class="ant-picker-calendar-header">
<nz-select
class="ant-picker-calendar-month-select"
class="ant-picker-calendar-year-select"
[nzSize]="size"
[nzDropdownMatchSelectWidth]="false"
[ngModel]="activeMonth"
(ngModelChange)="monthChange.emit($event)"
[ngModel]="activeYear"
(ngModelChange)="updateYear($event)"
>
@for (month of months; track month.value) {
<nz-option [nzLabel]="month.label" [nzValue]="month.value" />
@for (year of years; track year.value) {
<nz-option [nzLabel]="year.label" [nzValue]="year.value" />
}
</nz-select>
}
<nz-radio-group
class="ant-picker-calendar-mode-switch"
[(ngModel)]="mode"
(ngModelChange)="modeChange.emit($event)"
[nzSize]="size"
>
<label nz-radio-button nzValue="month">{{ monthTypeText }}</label>
<label nz-radio-button nzValue="year">{{ yearTypeText }}</label>
</nz-radio-group>
</div>
@if (mode === 'month') {
<nz-select
class="ant-picker-calendar-month-select"
[nzSize]="size"
[nzDropdownMatchSelectWidth]="false"
[ngModel]="activeMonth"
(ngModelChange)="monthChange.emit($event)"
>
@for (month of months; track month.value) {
<nz-option [nzLabel]="month.label" [nzValue]="month.value" />
}
</nz-select>
}
<nz-radio-group
class="ant-picker-calendar-mode-switch"
[(ngModel)]="mode"
(ngModelChange)="modeChange.emit($event)"
[nzSize]="size"
>
<label nz-radio-button nzValue="month">{{ monthTypeText }}</label>
<label nz-radio-button nzValue="year">{{ yearTypeText }}</label>
</nz-radio-group>
</div>
}
`,
host: {
class: 'ant-fullcalendar-header',
'[style.display]': `'block'`
},
imports: [NzSelectModule, FormsModule, NzRadioModule],
imports: [NzSelectModule, FormsModule, NzRadioModule, NgTemplateOutlet, NzStringTemplateOutletDirective],
standalone: true
})
export class NzCalendarHeaderComponent implements OnInit {
@Input() mode: 'month' | 'year' = 'month';
@Input() fullscreen: boolean = true;
@Input() activeDate: CandyDate = new CandyDate();
@Input() nzCustomHeader?: string | TemplateRef<void>;

@Output() readonly modeChange: EventEmitter<'month' | 'year'> = new EventEmitter();
@Output() readonly yearChange: EventEmitter<number> = new EventEmitter();
Expand Down
54 changes: 49 additions & 5 deletions components/calendar/calendar-header.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { registerLocaleData } from '@angular/common';
import zh from '@angular/common/locales/zh';
import { Component } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { Component, TemplateRef, ViewChild } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { FormsModule, NgModel } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';

import { CandyDate } from 'ng-zorro-antd/core/time';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { NzI18nModule } from 'ng-zorro-antd/i18n';
import { NzSelectModule } from 'ng-zorro-antd/select';

Expand Down Expand Up @@ -61,6 +62,8 @@ describe('Calendar Header', () => {
});

it('should emit change event for mode selection', () => {
fixture.detectChanges();

const modeNgModel = fixture.debugElement
.queryAll(By.directive(CalendarHeader))[1]
.query(By.directive(RadioGroup))
Expand Down Expand Up @@ -150,7 +153,10 @@ describe('Calendar Header', () => {
component = fixture.componentInstance;
}));

it('should emit yearChange when year changed', () => {
it('should emit yearChange when year changed', fakeAsync(() => {
tick(1);
fixture.detectChanges();

const header = fixture.debugElement.queryAll(By.directive(CalendarHeader))[0];
const [yearModel] = header.queryAll(By.directive(Select)).map(x => x.injector.get(NgModel));

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

expect(component.year).toBe(2010);
});
}));

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

expect(component.month).toBe(2);
});

it('should update years when change year', () => {
const header = fixture.debugElement.queryAll(By.directive(CalendarHeader))[0];
const headerComponent = header.injector.get(NzCalendarHeaderComponent);
headerComponent.updateYear(2010);
expect(headerComponent.years[0].value).toBe(2000);
});
});

describe('custom Header', () => {
let fixture: ComponentFixture<NzTestCalendarHeaderChangesComponent>;

beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(NzTestCalendarHeaderChangesComponent);
}));

it('should have the default header if custom header is not passed', fakeAsync(() => {
fixture.componentInstance.customHeader = undefined;
tick(1);
fixture.detectChanges();

const defaultHeader = fixture.debugElement.query(By.css('.ant-picker-calendar-header'));
expect(defaultHeader).toBeTruthy();

fixture.componentInstance.customHeader = fixture.componentInstance.customHeaderElement;
tick(1);
fixture.detectChanges();

const defaultHeader2 = fixture.debugElement.query(By.css('.ant-picker-calendar-header'));
expect(defaultHeader2).toBeFalsy();
}));
});
});

@Component({
Expand Down Expand Up @@ -212,9 +243,22 @@ class NzTestCalendarHeaderActiveDateComponent {
}

@Component({
template: ` <nz-calendar-header (yearChange)="year = $event" (monthChange)="month = $event"></nz-calendar-header> `
template: `
<nz-calendar-header
[nzCustomHeader]="customHeader"
(yearChange)="year = $event"
(monthChange)="month = $event"
></nz-calendar-header>
<ng-template #customHeaderElement>
<p>custom header</p>
</ng-template>
`
})
class NzTestCalendarHeaderChangesComponent {
@ViewChild('customHeaderElement', { static: true }) customHeaderElement!: TemplateRef<NzSafeAny>;

year: number | null = null;
month: number | null = null;
customHeader?: TemplateRef<void>;
}
7 changes: 6 additions & 1 deletion components/calendar/calendar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type NzCalendarDateTemplate = TemplateRef<{ $implicit: Date }>;
<nz-calendar-header
[fullscreen]="nzFullscreen"
[activeDate]="activeDate"
[nzCustomHeader]="nzCustomHeader"
[(mode)]="nzMode"
(modeChange)="onModeChange($event)"
(yearChange)="onYearSelect($event)"
Expand Down Expand Up @@ -142,7 +143,11 @@ export class NzCalendarComponent implements ControlValueAccessor, OnChanges, OnI
return (this.nzMonthFullCell || this.nzMonthFullCellChild)!;
}

@Input() @InputBoolean() nzFullscreen: boolean = true;
@Input() nzCustomHeader?: string | TemplateRef<void>;

@Input()
@InputBoolean()
nzFullscreen: boolean = true;

constructor(
private cdr: ChangeDetectorRef,
Expand Down
14 changes: 14 additions & 0 deletions components/calendar/demo/customize-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
order: 4
title:
zh-CN: 自定义头部
en-US: Customize Header
---

## zh-CN

自定义日历头部内容。

## en-US

Customize Calendar header content.
17 changes: 17 additions & 0 deletions components/calendar/demo/customize-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Component } from '@angular/core';

@Component({
selector: 'nz-demo-calendar-customize-header',
template: `
<div [ngStyle]="{ width: '300px', border: '1px solid #d9d9d9', borderRadius: '4px' }">
<nz-calendar [nzFullscreen]="false" [nzCustomHeader]="customHeader"></nz-calendar>
</div>
<ng-template #customHeader>
<div style="padding: 8px">
<h4>Custom header</h4>
</div>
</ng-template>
`
})
export class NzDemoCalendarCustomizeHeaderComponent {}
1 change: 1 addition & 0 deletions components/calendar/doc/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ registerLocaleData(en);
| `[nzDateFullCell]` | (Contentable) Customize the display of the date cell, the template content will override the cell | `TemplateRef<Date>` | - |
| `[nzMonthCell]` | (Contentable) Customize the display of the month cell, the template content will be appended to the cell | `TemplateRef<Date>` | - |
| `[nzMonthFullCell]` | (Contentable) Customize the display of the month cell, the template content will override the cell | `TemplateRef<Date>` | - |
| `[nzCustomHeader]` | Render custom header in panel | `string \| TemplateRef<void>` | - |
| `[nzDisabledDate]` | specify the date that cannot be selected | `(current: Date) => boolean` | - |
| `(nzPanelChange)` | Callback for when panel changes | `EventEmitter<{ date: Date, mode: 'month' \| 'year' }>` | - |
| `(nzSelectChange)` | A callback function of selected item | `EventEmitter<Date>` | - |
1 change: 1 addition & 0 deletions components/calendar/doc/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ registerLocaleData(zh);
| `[nzDateFullCell]` | (可作为内容)自定义渲染日期单元格,模版内容覆盖单元格 | `TemplateRef<Date>` | - |
| `[nzMonthCell]` | (可作为内容)自定义渲染月单元格,模版内容会被追加到单元格 | `TemplateRef<Date>` | - |
| `[nzMonthFullCell]` | (可作为内容)自定义渲染月单元格,模版内容覆盖单元格 | `TemplateRef<Date>` | - |
| `[nzCustomHeader]` | 自定义头部内容 | `string \| TemplateRef<void>` | - |
| `[nzDisabledDate]` | 不可选择的日期 | `(current: Date) => boolean` | - |
| `(nzPanelChange)` | 面板变化的回调 | `EventEmitter<{ date: Date, mode: 'month' \| 'year' }>` | - |
| `(nzSelectChange)` | 选择日期的回调 | `EventEmitter<Date>` | - |

0 comments on commit ec7ec35

Please sign in to comment.