feat: New calendar apis, headerRender method (#16535)

* added new calendar api, renderHeader now we can customize calendar header

* fixed typo for tests

* error handling for renderHeader

* covering all cases with tests

* fixed tests and change console error to warning

* fixed feedbacks and code optimization

* cleanup callback function arguments

* removed unused changes

* fixed tests

* added extra classes

* fixed some comments

* tying to fix test for remote

* tying to fix test for remote in my local machin it works fine

* tying to fix test for remote in my local machin it works fine

* tying to fix test for remote in my local machin it works fine

* updated test snapshots

* fixed comment

* fixed linting

* fixed some texts

* added header for CN and added argument types

* removed extra row
pull/16798/head
Harut 6 years ago committed by zombieJ
parent 0952696a8c
commit 982871522b

@ -5,6 +5,13 @@ import { Group, Button, RadioChangeEvent } from '../radio';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
const Option = Select.Option;
export interface RenderHeader {
value: moment.Moment;
onChange?: (value: moment.Moment) => void;
type: string;
onTypeChange: (type: string) => void;
}
export interface HeaderProps {
prefixCls?: string;
locale?: any;
@ -14,8 +21,9 @@ export interface HeaderProps {
type?: string;
onValueChange?: (value: moment.Moment) => void;
onTypeChange?: (type: string) => void;
value: any;
value: moment.Moment;
validRange?: [moment.Moment, moment.Moment];
headerRender: (header: RenderHeader) => React.ReactNode;
}
export default class Header extends React.Component<HeaderProps, any> {
@ -79,6 +87,7 @@ export default class Header extends React.Component<HeaderProps, any> {
start = rangeStart.get('month');
}
}
for (let index = start; index < end; index++) {
options.push(<Option key={`${index}`}>{months[index]}</Option>);
}
@ -128,10 +137,14 @@ export default class Header extends React.Component<HeaderProps, any> {
}
};
onTypeChange = (e: RadioChangeEvent) => {
onInternalTypeChange = (e: RadioChangeEvent) => {
this.onTypeChange(e.target.value);
};
onTypeChange = (type: string) => {
const onTypeChange = this.props.onTypeChange;
if (onTypeChange) {
onTypeChange(e.target.value);
onTypeChange(type);
}
};
@ -139,26 +152,53 @@ export default class Header extends React.Component<HeaderProps, any> {
this.calenderHeaderNode = node;
};
renderHeader = ({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, type, value, locale, fullscreen } = this.props;
getMonthYearSelections = (getPrefixCls: ConfigConsumerProps['getPrefixCls']) => {
const { prefixCls: customizePrefixCls, type, value } = this.props;
const prefixCls = getPrefixCls('fullcalendar', customizePrefixCls);
const yearSelect = this.getYearSelectElement(prefixCls, value.year());
const monthSelect =
const yearReactNode = this.getYearSelectElement(prefixCls, value.year());
const monthReactNode =
type === 'month'
? this.getMonthSelectElement(prefixCls, value.month(), this.getMonthsLocale(value))
: null;
return {
yearReactNode,
monthReactNode,
};
};
getTypeSwitch = () => {
const { locale, type, fullscreen } = this.props;
const size = fullscreen ? 'default' : 'small';
const typeSwitch = (
<Group onChange={this.onTypeChange} value={type} size={size}>
return (
<Group onChange={this.onInternalTypeChange} value={type} size={size}>
<Button value="month">{locale.month}</Button>
<Button value="year">{locale.year}</Button>
</Group>
);
};
return (
headerRenderCustom = (): React.ReactNode => {
const { headerRender, type, onValueChange, value } = this.props;
return headerRender({
value,
type: type || 'month',
onChange: onValueChange,
onTypeChange: this.onTypeChange,
});
};
renderHeader = ({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls, headerRender } = this.props;
const typeSwitch = this.getTypeSwitch();
const { yearReactNode, monthReactNode } = this.getMonthYearSelections(getPrefixCls);
return headerRender ? (
this.headerRenderCustom()
) : (
<div className={`${prefixCls}-header`} ref={this.getCalenderHeaderNode}>
{yearSelect}
{monthSelect}
{yearReactNode}
{monthReactNode}
{typeSwitch}
</div>
);

@ -4,6 +4,9 @@ import { mount } from 'enzyme';
import MockDate from 'mockdate';
import Calendar from '..';
import Header from '../Header';
import Select from '../../select';
import Group from '../../radio/group';
import Button from '../../radio/radioButton';
describe('Calendar', () => {
it('Calendar should be selectable', () => {
@ -256,4 +259,107 @@ describe('Calendar', () => {
.simulate('change');
expect(onTypeChange).toHaveBeenCalledWith('year');
});
it('headerRender should work correctly', () => {
const onMonthChange = jest.fn();
const onYearChange = jest.fn();
const onTypeChange = jest.fn();
const headerRender = jest.fn(({ value }) => {
const year = value.year();
const options = [];
for (let i = year - 100; i < year + 100; i += 1) {
options.push(
<Select.Option className="year-item" key={i} value={i}>
{i}
</Select.Option>,
);
}
return (
<Select
size="small"
dropdownMatchSelectWidth={false}
className="my-year-select"
onChange={onYearChange}
value={String(year)}
>
{options}
</Select>
);
});
const wrapperWithYear = mount(<Calendar fullscreen={false} headerRender={headerRender} />);
wrapperWithYear.find('.ant-select').simulate('click');
wrapperWithYear.update();
wrapperWithYear
.find('.year-item')
.last()
.simulate('click');
expect(onYearChange).toHaveBeenCalled();
const headerRenderWithMonth = jest.fn(({ value }) => {
const start = 0;
const end = 12;
const monthOptions = [];
const current = value.clone();
const localeData = value.localeData();
const months = [];
for (let i = 0; i < 12; i += 1) {
current.month(i);
months.push(localeData.monthsShort(current));
}
for (let index = start; index < end; index += 1) {
monthOptions.push(
<Select.Option className="month-item" key={`${index}`}>
{months[index]}
</Select.Option>,
);
}
const month = value.month();
return (
<Select
size="small"
dropdownMatchSelectWidth={false}
value={String(month)}
className="my-mont-select"
onChange={onMonthChange}
>
{monthOptions}
</Select>
);
});
const wrapperWithMonth = mount(
<Calendar fullscreen={false} headerRender={headerRenderWithMonth} />,
);
wrapperWithMonth.find('.ant-select').simulate('click');
wrapperWithMonth.update();
wrapperWithMonth
.find('.month-item')
.last()
.simulate('click');
expect(onMonthChange).toHaveBeenCalled();
const headerRenderWithTypeChange = jest.fn(({ type }) => {
return (
<Group size="small" onChange={onTypeChange} value={type}>
<Button value="month">Month</Button>
<Button value="year">Year</Button>
</Group>
);
});
const wrapperWithTypeChange = mount(
<Calendar fullscreen={false} headerRender={headerRenderWithTypeChange} />,
);
wrapperWithTypeChange
.find('.ant-radio-button-input')
.last()
.simulate('change');
expect(onTypeChange).toHaveBeenCalled();
});
});

@ -0,0 +1,107 @@
---
order: 4
title:
zh-CN: 自定义头部
en-US: Customize Header
---
## zh-CN
自定义日历头部内容。
## en-US
Customize Calendar header content.
```jsx
import { Calendar, Select, Radio, Col, Row } from 'antd';
const { Group, Button } = Radio;
function onPanelChange(value, mode) {
console.log(value, mode);
}
ReactDOM.render(
<div style={{ width: 300, border: '1px solid #d9d9d9', borderRadius: 4 }}>
<Calendar
fullscreen={false}
headerRender={({ value, type, onChange, onTypeChange }) => {
const start = 0;
const end = 12;
const monthOptions = [];
const current = value.clone();
const localeData = value.localeData();
const months = [];
for (let i = 0; i < 12; i++) {
current.month(i);
months.push(localeData.monthsShort(current));
}
for (let index = start; index < end; index++) {
monthOptions.push(
<Select.Option className="month-item" key={`${index}`}>
{months[index]}
</Select.Option>,
);
}
const month = value.month();
const year = value.year();
const options = [];
for (let i = year - 10; i < year + 10; i += 1) {
options.push(
<Select.Option key={i} value={i} className="year-item">
{i}
</Select.Option>,
);
}
return (
<div style={{ padding: 10 }}>
<div style={{ marginBottom: '10px' }}>Custom header </div>
<Row type="flex" justify="space-between">
<Col>
<Group size="small" onChange={e => onTypeChange(e.target.value)} value={type}>
<Button value="month">Month</Button>
<Button value="year">Year</Button>
</Group>
</Col>
<Col>
<Select
size="small"
dropdownMatchSelectWidth={false}
className="my-year-select"
onChange={newYear => {
const now = value.clone().year(newYear);
onChange(now);
}}
value={String(year)}
>
{options}
</Select>
</Col>
<Col>
<Select
size="small"
dropdownMatchSelectWidth={false}
value={String(month)}
onChange={selectedMonth => {
const newValue = value.clone();
newValue.month(parseInt(selectedMonth, 10));
onChange(newValue);
}}
>
{monthOptions}
</Select>
</Col>
</Row>
</div>
);
}}
onPanelChange={onPanelChange}
/>
</div>,
mountNode,
);
```

@ -29,19 +29,20 @@ When data is in the form of dates, such as schedules, timetables, prices calenda
/>
```
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| dateCellRender | Customize the display of the date cell, the returned content will be appended to the cell | function(date: moment): ReactNode | - |
| dateFullCellRender | Customize the display of the date cell, the returned content will override the cell | function(date: moment): ReactNode | - |
| defaultValue | The date selected by default | [moment](http://momentjs.com/) | default date |
| disabledDate | Function that specifies the dates that cannot be selected | (currentDate: moment) => boolean | - |
| fullscreen | Whether to display in full-screen | boolean | `true` |
| locale | The calendar's locale | object | [default](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) |
| mode | The display mode of the calendar | `month` \| `year` | `month` |
| monthCellRender | Customize the display of the month cell, the returned content will be appended to the cell | function(date: moment): ReactNode | - |
| monthFullCellRender | Customize the display of the month cell, the returned content will override the cell | function(date: moment): ReactNode | - |
| validRange | to set valid range | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| dateCellRender | Customize the display of the date cell, the returned content will be appended to the cell | function(date: moment): ReactNode | - | |
| dateFullCellRender | Customize the display of the date cell, the returned content will override the cell | function(date: moment): ReactNode | - | |
| defaultValue | The date selected by default | [moment](http://momentjs.com/) | default date | |
| disabledDate | Function that specifies the dates that cannot be selected | (currentDate: moment) => boolean | - | |
| fullscreen | Whether to display in full-screen | boolean | `true` | |
| locale | The calendar's locale | object | [default](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | |
| mode | The display mode of the calendar | `month` \| `year` | `month` | |
| monthCellRender | Customize the display of the month cell, the returned content will be appended to the cell | function(date: moment): ReactNode | - | |
| monthFullCellRender | Customize the display of the month cell, the returned content will override the cell | function(date: moment): ReactNode | - | |
| validRange | to set valid range | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - | |
| value | The current selected date | [moment](http://momentjs.com/) | current date |
| onPanelChange | Callback for when panel changes | function(date: moment, mode: string) | - |
| onSelect | Callback for when a date is selected | function(date: moment | - |
| onChange | Callback for when date changes | function(date: moment | - |
| onPanelChange | Callback for when panel changes | function(date: moment, mode: string) | - | |
| onSelect | Callback for when a date is selected | function(date: moment | - | |
| onChange | Callback for when date changes | function(date: moment | - | |
| headerRender | render custom header in panel | function(object:{value: moment, type: string, onChange: f(), onTypeChange: f()}) | - | 3.19.0 |

@ -2,7 +2,7 @@ import * as React from 'react';
import * as PropTypes from 'prop-types';
import * as moment from 'moment';
import FullCalendar from 'rc-calendar/lib/FullCalendar';
import Header from './Header';
import Header, { RenderHeader } from './Header';
import enUS from './locale/en_US';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
@ -23,7 +23,6 @@ function zerofixed(v: number) {
}
export type CalendarMode = 'month' | 'year';
export interface CalendarProps {
prefixCls?: string;
className?: string;
@ -42,6 +41,7 @@ export interface CalendarProps {
onChange?: (date?: moment.Moment) => void;
disabledDate?: (current: moment.Moment) => boolean;
validRange?: [moment.Moment, moment.Moment];
headerRender: (header: RenderHeader) => React.ReactNode;
}
export interface CalendarState {
@ -56,6 +56,7 @@ class Calendar extends React.Component<CalendarProps, CalendarState> {
onSelect: noop,
onPanelChange: noop,
onChange: noop,
headerRender: null,
};
static propTypes = {
@ -205,6 +206,7 @@ class Calendar extends React.Component<CalendarProps, CalendarState> {
style,
className,
fullscreen,
headerRender,
dateFullCellRender,
monthFullCellRender,
} = props;
@ -237,6 +239,7 @@ class Calendar extends React.Component<CalendarProps, CalendarState> {
<Header
fullscreen={fullscreen}
type={mode}
headerRender={headerRender}
value={value}
locale={locale.lang}
prefixCls={prefixCls}

@ -30,19 +30,20 @@ title: Calendar
/>
```
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| dateCellRender | 自定义渲染日期单元格,返回内容会被追加到单元格 | function(date: moment): ReactNode | 无 |
| dateFullCellRender | 自定义渲染日期单元格,返回内容覆盖单元格 | function(date: moment): ReactNode | 无 |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| dateCellRender | 自定义渲染日期单元格,返回内容会被追加到单元格 | function(date: moment): ReactNode | 无 | |
| dateFullCellRender | 自定义渲染日期单元格,返回内容覆盖单元格 | function(date: moment): ReactNode | 无 | |
| defaultValue | 默认展示的日期 | [moment](http://momentjs.com/) | 默认日期 |
| disabledDate | 不可选择的日期 | (currentDate: moment) => boolean | 无 |
| fullscreen | 是否全屏显示 | boolean | true |
| locale | 国际化配置 | object | [默认配置](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) |
| disabledDate | 不可选择的日期 | (currentDate: moment) => boolean | 无 | |
| fullscreen | 是否全屏显示 | boolean | true | |
| locale | 国际化配置 | object | [默认配置](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | |
| mode | 初始模式,`month/year` | string | month |
| monthCellRender | 自定义渲染月单元格,返回内容会被追加到单元格 | function(date: moment): ReactNode | 无 |
| monthFullCellRender | 自定义渲染月单元格,返回内容覆盖单元格 | function(date: moment): ReactNode | 无 |
| validRange | 设置可以显示的日期 | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | 无 |
| monthCellRender | 自定义渲染月单元格,返回内容会被追加到单元格 | function(date: moment): ReactNode | 无 | |
| monthFullCellRender | 自定义渲染月单元格,返回内容覆盖单元格 | function(date: moment): ReactNode | 无 | |
| validRange | 设置可以显示的日期 | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | 无 | |
| value | 展示日期 | [moment](http://momentjs.com/) | 当前日期 |
| onPanelChange | 日期面板变化回调 | function(date: moment, mode: string) | 无 |
| onSelect | 点击选择日期回调 | function(date: moment | 无 |
| onChange | 日期变化回调 | function(date: moment | 无 |
| onPanelChange | 日期面板变化回调 | function(date: moment, mode: string) | 无 | |
| onSelect | 点击选择日期回调 | function(date: moment | 无 | |
| onChange | 日期变化回调 | function(date: moment | 无 | |
| headerRender | 自定义头部内容 | function(object:{value: moment, type: string, onChange: f(), onTypeChange: f()}) | 无 | 3.19.0 |

Loading…
Cancel
Save