feat: New Picker (#20023)

* init generate

* basic style

* basic panel style

* update mode panel style

* update style

* generate More picker

* default clear icon

* chore: Update separator type

* feat: Add ranged start & end className

* update range style

* Add transition effect

* support size config

* adjust range style

* chore: Auto fill time by showTime

* auto set time by format

* update disabled style

* update seperator style

* ranges style

* support extra footer style

* remove useless test case
part is not usable anymore
part is already tested in rc-picker

* init calendar

* all demos

* fix calendar basic test

* fix time-picker test case

* update snapshot

* fix tooltip test case & lint

* fix locale & style lint

* fix compile

* fix style

* fix style lint

* fix calendar style

* update rc-picker version

* adjust style

* move picker placeholder into locale file

* update snapshot

* add hover style

* update picker version

* fix icon position & style

* update picker version

* update deps for pading

* fix: align of suffix

* feat: Year & Month support range effect

* adjust range style to support up-down placement

* update rc-picker

* update range picker style

* adjust extra footer line style

* update snapshot

* fix: Locale error

* fix: style lint

* fix: add missing button style deps

* update test case

* fix firefox additional white line style issue

* rollback demo

* fix ff additional blue color

* docs: Remove placeholder in demo

* rangepicker ranges is tag now

* connect start / end background color with picker range

* update deps

* update deps for fixing blur text issue

* hide start-end demo

* range hover style update

* hover range with ranged value

* black magic of inner hover style

* hover style of range adjust

* fix css select miss hit on DatePicker

* remove one eslint rule

* fade range hovered color

* week should alway not show the cell selection

* update style of selection

* update snapshot

* fix style

* add margin back

* update rc-picker deps

* update date & time picker & form style

* fix disabled demo & update form style

* update docs about allowEmpty

* hide arrow in time range picker

* add hover & focused style

* fix lint

* fix style & update snapshot

* raise disabled selector proirity

* fix disabled today border color

* extra footer provides an bottom line

* time picker hover support transition background

* add padding style

* fix Firefox not correct calculate inline-flex

* fix style

* fix week picker missing today border color

* rm useless padding

* Force padding to 0

* test coverage

* dedup eslint rule

* adjust logic to imporve coverage

* fix render cell logic
This commit is contained in:
二货机器人 2019-12-11 23:32:19 +08:00 committed by GitHub
parent 50b82ca509
commit 407a41a142
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
132 changed files with 205935 additions and 228228 deletions

View File

@ -37,6 +37,7 @@ const eslintrc = {
'react/forbid-prop-types': 0,
'react/jsx-indent': 0,
'react/jsx-wrap-multilines': ['error', { declaration: false, assignment: false }],
'import/extensions': 0,
'import/no-extraneous-dependencies': [
'error',
{
@ -86,7 +87,6 @@ const eslintrc = {
'react/static-property-placement': 0,
'jest/no-test-callback': 0,
'jest/expect-expect': 0,
'import/extensions': 0,
},
globals: {
gtag: true,

View File

@ -1,223 +1,188 @@
import * as React from 'react';
import * as moment from 'moment';
import { GenerateConfig } from 'rc-picker/lib/generate';
import { Locale } from 'rc-picker/lib/interface';
import Select from '../select';
import { Group, Button, RadioChangeEvent } from '../radio';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { Group, Button } from '../radio';
import { CalendarMode } from './generateCalendar';
const { Option } = Select;
const YearSelectOffset = 10;
const YearSelectTotal = 20;
function getMonthsLocale(value: moment.Moment) {
const current = value.clone();
const localeData = value.localeData();
const months: any[] = [];
for (let i = 0; i < 12; i++) {
current.month(i);
months.push(localeData.monthsShort(current));
}
return months;
interface SharedProps<DateType> {
prefixCls: string;
value: DateType;
validRange?: [DateType, DateType];
generateConfig: GenerateConfig<DateType>;
locale: Locale;
fullscreen: boolean;
divRef: React.RefObject<HTMLDivElement>;
onChange: (year: DateType) => void;
}
export interface RenderHeader {
value: moment.Moment;
onChange?: (value: moment.Moment) => void;
type: string;
onTypeChange: (type: string) => void;
}
function YearSelect<DateType>(props: SharedProps<DateType>) {
const {
fullscreen,
validRange,
generateConfig,
locale,
prefixCls,
value,
onChange,
divRef,
} = props;
export type HeaderRender = (headerRender: RenderHeader) => React.ReactNode;
const year = generateConfig.getYear(value);
export interface HeaderProps {
prefixCls?: string;
locale?: any;
fullscreen?: boolean;
yearSelectOffset?: number;
yearSelectTotal?: number;
type?: string;
onValueChange?: (value: moment.Moment) => void;
onTypeChange?: (type: string) => void;
value: moment.Moment;
validRange?: [moment.Moment, moment.Moment];
headerRender?: HeaderRender;
}
let start = year - YearSelectOffset;
let end = start + YearSelectTotal;
export default class Header extends React.Component<HeaderProps, any> {
static defaultProps = {
yearSelectOffset: 10,
yearSelectTotal: 20,
};
private calenderHeaderNode: HTMLDivElement;
getYearSelectElement(prefixCls: string, year: number) {
const { yearSelectOffset, yearSelectTotal, locale = {}, fullscreen, validRange } = this.props;
let start = year - (yearSelectOffset as number);
let end = start + (yearSelectTotal as number);
if (validRange) {
start = validRange[0].get('year');
end = validRange[1].get('year') + 1;
}
const suffix = locale.year === '年' ? '年' : '';
const options: React.ReactElement<any>[] = [];
for (let index = start; index < end; index++) {
const optionValue = `${index}`;
options.push(
<Option key={optionValue} value={optionValue}>
{index + suffix}
</Option>,
);
}
return (
<Select
size={fullscreen ? 'default' : 'small'}
dropdownMatchSelectWidth={100}
className={`${prefixCls}-year-select`}
onChange={this.onYearChange}
value={String(year)}
getPopupContainer={() => this.calenderHeaderNode}
>
{options}
</Select>
);
if (validRange) {
start = generateConfig.getYear(validRange[0]);
end = generateConfig.getYear(validRange[1]) + 1;
}
getMonthSelectElement(prefixCls: string, month: number, months: number[]) {
const { fullscreen, validRange, value } = this.props;
const options: React.ReactElement<any>[] = [];
let start = 0;
let end = 12;
if (validRange) {
const [rangeStart, rangeEnd] = validRange;
const currentYear = value.get('year');
if (rangeEnd.get('year') === currentYear) {
end = rangeEnd.get('month') + 1;
}
if (rangeStart.get('year') === currentYear) {
start = rangeStart.get('month');
}
}
for (let index = start; index < end; index++) {
const optionValue = `${index}`;
options.push(
<Option key={optionValue} value={optionValue}>
{months[index]}
</Option>,
);
}
return (
<Select
size={fullscreen ? 'default' : 'small'}
dropdownMatchSelectWidth={100}
className={`${prefixCls}-month-select`}
value={String(month)}
onChange={this.onMonthChange}
getPopupContainer={() => this.calenderHeaderNode}
>
{options}
</Select>
);
const suffix = locale && locale.year === '年' ? '年' : '';
const options: { label: string; value: number }[] = [];
for (let index = start; index < end; index++) {
options.push({ label: `${index}${suffix}`, value: index });
}
onYearChange = (year: string) => {
const { value, validRange } = this.props;
const newValue = value.clone();
newValue.year(parseInt(year, 10));
// switch the month so that it remains within range when year changes
if (validRange) {
const [start, end] = validRange;
const newYear = newValue.get('year');
const newMonth = newValue.get('month');
if (newYear === end.get('year') && newMonth > end.get('month')) {
newValue.month(end.get('month'));
}
if (newYear === start.get('year') && newMonth < start.get('month')) {
newValue.month(start.get('month'));
}
return (
<Select
size={fullscreen ? 'default' : 'small'}
options={options}
value={year}
className={`${prefixCls}-year-select`}
onChange={numYear => {
let newDate = generateConfig.setYear(value, numYear);
if (validRange) {
const [startDate, endDate] = validRange;
const newYear = generateConfig.getYear(newDate);
const newMonth = generateConfig.getMonth(newDate);
if (
newYear === generateConfig.getYear(endDate) &&
newMonth > generateConfig.getMonth(endDate)
) {
newDate = generateConfig.setMonth(newDate, generateConfig.getMonth(endDate));
}
if (
newYear === generateConfig.getYear(startDate) &&
newMonth < generateConfig.getMonth(startDate)
) {
newDate = generateConfig.setMonth(newDate, generateConfig.getMonth(startDate));
}
}
onChange(newDate);
}}
getPopupContainer={() => divRef!.current!}
/>
);
}
function MonthSelect<DateType>(props: SharedProps<DateType>) {
const {
prefixCls,
fullscreen,
validRange,
value,
generateConfig,
locale,
onChange,
divRef,
} = props;
const month = generateConfig.getMonth(value);
let start = 0;
let end = 12;
if (validRange) {
const [rangeStart, rangeEnd] = validRange;
const currentYear = generateConfig.getYear(value);
if (generateConfig.getYear(rangeEnd) === currentYear) {
end = generateConfig.getMonth(rangeEnd);
}
const { onValueChange } = this.props;
if (onValueChange) {
onValueChange(newValue);
if (generateConfig.getYear(rangeStart) === currentYear) {
start = generateConfig.getMonth(rangeStart);
}
};
}
onMonthChange = (month: string) => {
const newValue = this.props.value.clone();
newValue.month(parseInt(month, 10));
const { onValueChange } = this.props;
if (onValueChange) {
onValueChange(newValue);
}
};
onInternalTypeChange = (e: RadioChangeEvent) => {
this.onTypeChange(e.target.value as string);
};
onTypeChange = (type: string) => {
const { onTypeChange } = this.props;
if (onTypeChange) {
onTypeChange(type);
}
};
getCalenderHeaderNode = (node: HTMLDivElement) => {
this.calenderHeaderNode = node;
};
getMonthYearSelections = (getPrefixCls: ConfigConsumerProps['getPrefixCls']) => {
const { prefixCls: customizePrefixCls, type, value } = this.props;
const prefixCls = getPrefixCls('fullcalendar', customizePrefixCls);
const yearReactNode = this.getYearSelectElement(prefixCls, value.year());
const monthReactNode =
type === 'month'
? this.getMonthSelectElement(prefixCls, value.month(), getMonthsLocale(value))
: null;
return {
yearReactNode,
monthReactNode,
};
};
getTypeSwitch = () => {
const { locale = {}, type, fullscreen } = this.props;
const size = fullscreen ? 'default' : 'small';
return (
<Group onChange={this.onInternalTypeChange} value={type} size={size}>
<Button value="month">{locale.month}</Button>
<Button value="year">{locale.year}</Button>
</Group>
);
};
headerRenderCustom = (headerRender: HeaderRender): React.ReactNode => {
const { type, onValueChange, value } = this.props;
return headerRender({
value,
type: type || 'month',
onChange: onValueChange,
onTypeChange: this.onTypeChange,
const months = locale.shortMonths || generateConfig.locale.getShortMonths!(locale.locale);
const options: { label: string; value: number }[] = [];
for (let index = start; index < end; index += 1) {
options.push({
label: months[index],
value: index,
});
};
renderHeader = ({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls, headerRender } = this.props;
const typeSwitch = this.getTypeSwitch();
const { yearReactNode, monthReactNode } = this.getMonthYearSelections(getPrefixCls);
return headerRender ? (
this.headerRenderCustom(headerRender)
) : (
<div className={`${prefixCls}-header`} ref={this.getCalenderHeaderNode}>
{yearReactNode}
{monthReactNode}
{typeSwitch}
</div>
);
};
render() {
return <ConfigConsumer>{this.renderHeader}</ConfigConsumer>;
}
return (
<Select
size={fullscreen ? 'default' : 'small'}
dropdownMatchSelectWidth={100}
className={`${prefixCls}-month-select`}
value={month}
options={options}
onChange={newMonth => {
onChange(generateConfig.setMonth(value, newMonth));
}}
getPopupContainer={() => divRef!.current!}
/>
);
}
interface ModeSwitchProps<DateType> extends Omit<SharedProps<DateType>, 'onChange'> {
mode: CalendarMode;
onModeChange: (type: CalendarMode) => void;
}
function ModeSwitch<DateType>(props: ModeSwitchProps<DateType>) {
const { prefixCls, locale, mode, fullscreen, onModeChange } = props;
return (
<Group
onChange={({ target: { value } }) => {
onModeChange(value);
}}
value={mode}
size={fullscreen ? 'default' : 'small'}
className={`${prefixCls}-mode-switch`}
>
<Button value="month">{locale.month}</Button>
<Button value="year">{locale.year}</Button>
</Group>
);
}
export interface CalendarHeaderProps<DateType> {
prefixCls: string;
value: DateType;
validRange?: [DateType, DateType];
generateConfig: GenerateConfig<DateType>;
locale: Locale;
mode: CalendarMode;
fullscreen: boolean;
onChange: (date: DateType) => void;
onModeChange: (mode: CalendarMode) => void;
}
function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
const { prefixCls, fullscreen, mode, onChange, onModeChange } = props;
const divRef = React.useRef<HTMLDivElement>(null);
const sharedProps = {
...props,
onChange,
fullscreen,
divRef,
};
return (
<div className={`${prefixCls}-header`} ref={divRef}>
<YearSelect {...sharedProps} />
{mode === 'month' && <MonthSelect {...sharedProps} />}
<ModeSwitch {...sharedProps} onModeChange={onModeChange} />
</div>
);
}
export default CalendarHeader;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
import React from 'react';
import Moment from 'moment';
import momentGenerateConfig from 'rc-picker/lib/generate/moment';
import { mount } from 'enzyme';
import MockDate from 'mockdate';
import Calendar from '..';
@ -11,7 +12,6 @@ import mountTest from '../../../tests/shared/mountTest';
describe('Calendar', () => {
mountTest(Calendar);
mountTest(() => <Header value={Moment()} />);
function openSelect(wrapper, className) {
wrapper
@ -34,7 +34,7 @@ describe('Calendar', () => {
const onSelect = jest.fn();
const wrapper = mount(<Calendar onSelect={onSelect} />);
wrapper
.find('.ant-fullcalendar-cell')
.find('.ant-picker-cell')
.at(0)
.simulate('click');
expect(onSelect).toHaveBeenCalledWith(expect.anything());
@ -49,11 +49,11 @@ describe('Calendar', () => {
<Calendar onSelect={onSelect} validRange={validRange} defaultValue={Moment('2018-02-02')} />,
);
wrapper
.find('[title="February 1, 2018"]')
.find('[title="2018-02-01"]')
.at(0)
.simulate('click');
wrapper
.find('[title="February 2, 2018"]')
.find('[title="2018-02-02"]')
.at(0)
.simulate('click');
expect(onSelect.mock.calls.length).toBe(1);
@ -66,12 +66,10 @@ describe('Calendar', () => {
<Calendar onSelect={onSelect} validRange={validRange} defaultValue={Moment('2018-02-02')} />,
);
wrapper
.find('[title="February 20, 2018"]')
.find('[title="2018-02-20"]')
.at(0)
.simulate('click');
const elem = wrapper
.find('[title="February 20, 2018"]')
.hasClass('ant-fullcalendar-disabled-cell');
const elem = wrapper.find('[title="2018-02-20"]').hasClass('ant-picker-cell-disabled');
expect(elem).toEqual(true);
expect(onSelect.mock.calls.length).toBe(0);
});
@ -89,28 +87,28 @@ describe('Calendar', () => {
);
expect(
wrapper
.find('[title="Jan"]')
.find('[title="2018-01"]')
.at(0)
.hasClass('ant-fullcalendar-month-panel-cell-disabled'),
.hasClass('ant-picker-cell-disabled'),
).toBe(true);
expect(
wrapper
.find('[title="Feb"]')
.find('[title="2018-02"]')
.at(0)
.hasClass('ant-fullcalendar-month-panel-cell-disabled'),
.hasClass('ant-picker-cell-disabled'),
).toBe(false);
expect(
wrapper
.find('[title="Jun"]')
.find('[title="2018-06"]')
.at(0)
.hasClass('ant-fullcalendar-month-panel-cell-disabled'),
.hasClass('ant-picker-cell-disabled'),
).toBe(true);
wrapper
.find('[title="Jan"]')
.find('[title="2018-01"]')
.at(0)
.simulate('click');
wrapper
.find('[title="Mar"]')
.find('[title="2018-03"]')
.at(0)
.simulate('click');
expect(onSelect.mock.calls.length).toBe(1);
@ -119,9 +117,9 @@ describe('Calendar', () => {
it('months other than in valid range should not be shown in header', () => {
const validRange = [Moment('2017-02-02'), Moment('2018-05-18')];
const wrapper = mount(<Calendar validRange={validRange} />);
openSelect(wrapper, '.ant-fullcalendar-year-select');
openSelect(wrapper, '.ant-picker-calendar-year-select');
clickSelectItem(wrapper);
openSelect(wrapper, '.ant-fullcalendar-month-select');
openSelect(wrapper, '.ant-picker-calendar-month-select');
// 2 years and 11 months
expect(wrapper.find('.ant-select-item-option').length).toBe(13);
});
@ -129,8 +127,7 @@ describe('Calendar', () => {
it('getDateRange should returns a disabledDate function', () => {
const validRange = [Moment('2018-02-02'), Moment('2018-05-18')];
const wrapper = mount(<Calendar validRange={validRange} defaultValue={Moment('2018-02-02')} />);
const instance = wrapper.instance();
const disabledDate = instance.getDateRange(validRange);
const { disabledDate } = wrapper.find('PickerPanel').props();
expect(disabledDate(Moment('2018-06-02'))).toBe(true);
expect(disabledDate(Moment('2018-04-02'))).toBe(false);
});
@ -139,9 +136,9 @@ describe('Calendar', () => {
const monthMode = 'month';
const yearMode = 'year';
const wrapper = mount(<Calendar />);
expect(wrapper.state().mode).toEqual(monthMode);
wrapper.setProps({ mode: 'year' });
expect(wrapper.state().mode).toEqual(yearMode);
expect(wrapper.find('CalendarHeader').props().mode).toEqual(monthMode);
wrapper.setProps({ mode: yearMode });
expect(wrapper.find('CalendarHeader').props().mode).toEqual(yearMode);
});
it('Calendar should switch mode', () => {
@ -149,9 +146,9 @@ describe('Calendar', () => {
const yearMode = 'year';
const onPanelChangeStub = jest.fn();
const wrapper = mount(<Calendar mode={yearMode} onPanelChange={onPanelChangeStub} />);
expect(wrapper.state().mode).toEqual(yearMode);
expect(wrapper.find('CalendarHeader').props().mode).toEqual(yearMode);
wrapper.setProps({ mode: monthMode });
expect(wrapper.state().mode).toEqual(monthMode);
expect(wrapper.find('CalendarHeader').props().mode).toEqual(monthMode);
expect(onPanelChangeStub).toHaveBeenCalledTimes(0);
});
@ -170,7 +167,7 @@ describe('Calendar', () => {
const wrapper = mount(<Calendar onPanelChange={onPanelChange} value={date} />);
wrapper
.find('.ant-fullcalendar-cell')
.find('.ant-picker-cell')
.at(0)
.simulate('click');
@ -182,12 +179,14 @@ describe('Calendar', () => {
const onPanelChange = jest.fn();
const date = new Moment(new Date(Date.UTC(2017, 7, 9, 8)));
const wrapper = mount(<Calendar onPanelChange={onPanelChange} value={date} />);
expect(wrapper.state().mode).toBe('month');
expect(wrapper.find('.ant-fullcalendar-table').length).toBe(1);
expect(wrapper.find('.ant-fullcalendar-month-panel-table').length).toBe(0);
expect(wrapper.find('CalendarHeader').props().mode).toBe('month');
expect(wrapper.find('.ant-picker-date-panel').length).toBe(1);
expect(wrapper.find('.ant-picker-month-panel').length).toBe(0);
wrapper.find('.ant-radio-button-input[value="year"]').simulate('change');
expect(wrapper.find('.ant-fullcalendar-table').length).toBe(0);
expect(wrapper.find('.ant-fullcalendar-month-panel-table').length).toBe(1);
expect(wrapper.find('.ant-picker-date-panel').length).toBe(0);
expect(wrapper.find('.ant-picker-month-panel').length).toBe(1);
expect(onPanelChange).toHaveBeenCalled();
expect(onPanelChange.mock.calls[0][1]).toEqual('year');
});
@ -195,13 +194,15 @@ describe('Calendar', () => {
const createWrapper = (start, end, value, onValueChange) => {
const wrapper = mount(
<Header
onValueChange={onValueChange}
prefixCls="ant-picker-calendar"
generateConfig={momentGenerateConfig}
onChange={onValueChange}
value={value}
validRange={[start, end]}
locale={{ year: '年' }}
/>,
);
openSelect(wrapper, '.ant-fullcalendar-year-select');
openSelect(wrapper, '.ant-picker-calendar-year-select');
clickSelectItem(wrapper);
};
@ -223,6 +224,29 @@ describe('Calendar', () => {
expect(onValueChange).toHaveBeenCalledWith(value.year('2019').month('10'));
});
it('if change year and new month > end month, set value.month to end.month ', () => {
const value = new Moment('2018-11-03');
const start = new Moment('2000-01-01');
const end = new Moment('2019-03-01');
const onValueChange = jest.fn();
const wrapper = mount(
<Header
prefixCls="ant-picker-calendar"
generateConfig={momentGenerateConfig}
onChange={onValueChange}
value={value}
validRange={[start, end]}
locale={{ year: '年' }}
/>,
);
openSelect(wrapper, '.ant-picker-calendar-year-select');
wrapper
.find('.ant-select-item-option')
.last()
.simulate('click');
expect(onValueChange).toHaveBeenCalledWith(value.year('2019').month('2'));
});
it('onMonthChange should work correctly', () => {
const start = new Moment('2018-11-01');
const end = new Moment('2019-03-01');
@ -230,14 +254,16 @@ describe('Calendar', () => {
const onValueChange = jest.fn();
const wrapper = mount(
<Header
onValueChange={onValueChange}
prefixCls="ant-picker-calendar"
generateConfig={momentGenerateConfig}
onChange={onValueChange}
value={value}
validRange={[start, end]}
locale={{ year: '年' }}
type="month"
locale={{ year: '年', locale: 'zh_CN' }}
mode="month"
/>,
);
openSelect(wrapper, '.ant-fullcalendar-month-select');
openSelect(wrapper, '.ant-picker-calendar-month-select');
clickSelectItem(wrapper);
expect(onValueChange).toHaveBeenCalledWith(value.month(10));
});
@ -247,8 +273,10 @@ describe('Calendar', () => {
const value = new Moment('2018-12-03');
const wrapper = mount(
<Header
onTypeChange={onTypeChange}
locale={{ year: '年', month: '月' }}
prefixCls="ant-picker-calendar"
generateConfig={momentGenerateConfig}
onModeChange={onTypeChange}
locale={{ year: '年', month: '月', locale: 'zh_CN' }}
value={value}
type="date"
/>,
@ -264,6 +292,8 @@ describe('Calendar', () => {
const onMonthChange = jest.fn();
const onYearChange = jest.fn();
const onTypeChange = jest.fn();
// Year
const headerRender = jest.fn(({ value }) => {
const year = value.year();
const options = [];
@ -297,6 +327,8 @@ describe('Calendar', () => {
.simulate('click');
expect(onYearChange).toHaveBeenCalled();
// Month
const headerRenderWithMonth = jest.fn(({ value }) => {
const start = 0;
const end = 12;
@ -316,14 +348,15 @@ describe('Calendar', () => {
</Select.Option>,
);
}
const month = value.month();
return (
<Select
size="small"
dropdownMatchSelectWidth={false}
value={String(month)}
className="my-mont-select"
className="my-month-select"
onChange={onMonthChange}
value={String(month)}
>
{monthOptions}
</Select>
@ -339,8 +372,10 @@ describe('Calendar', () => {
findSelectItem(wrapperWithMonth)
.last()
.simulate('click');
expect(onMonthChange).toHaveBeenCalled();
// Type
const headerRenderWithTypeChange = jest.fn(({ type }) => {
return (
<Group size="small" onChange={onTypeChange} value={type}>
@ -360,4 +395,28 @@ describe('Calendar', () => {
.simulate('change');
expect(onTypeChange).toHaveBeenCalled();
});
it('dateFullCellRender', () => {
const wrapper = mount(
<Calendar dateFullCellRender={() => <div className="light">Bamboo</div>} />,
);
expect(
wrapper
.find('.light')
.first()
.text(),
).toEqual('Bamboo');
});
it('monthFullCellRender', () => {
const wrapper = mount(
<Calendar mode="year" monthFullCellRender={() => <div className="bamboo">Light</div>} />,
);
expect(
wrapper
.find('.bamboo')
.first()
.text(),
).toEqual('Light');
});
});

View File

@ -17,7 +17,7 @@ A basic calendar component with Year/Month switch.
import { Calendar } from 'antd';
function onPanelChange(value, mode) {
console.log(value, mode);
console.log(value.format('YYYY-MM-DD'), mode);
}
ReactDOM.render(<Calendar onPanelChange={onPanelChange} />, mountNode);

View File

@ -21,7 +21,7 @@ function onPanelChange(value, mode) {
}
ReactDOM.render(
<div style={{ width: 300, border: '1px solid #d9d9d9', borderRadius: 4 }}>
<div style={{ width: 300, border: '1px solid #f0f0f0', borderRadius: 4 }}>
<Calendar fullscreen={false} onPanelChange={onPanelChange} />
</div>,
mountNode,

View File

@ -0,0 +1,287 @@
import * as React from 'react';
import classNames from 'classnames';
import padStart from 'lodash/padStart';
import { PickerPanel as RCPickerPanel } from 'rc-picker';
import { Locale } from 'rc-picker/lib/interface';
import { GenerateConfig } from 'rc-picker/lib/generate';
import {
PickerPanelBaseProps as RCPickerPanelBaseProps,
PickerPanelDateProps as RCPickerPanelDateProps,
PickerPanelTimeProps as RCPickerPanelTimeProps,
} from 'rc-picker/lib/PickerPanel';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import enUS from './locale/en_US';
import { ConfigContext } from '../config-provider';
import CalendarHeader from './Header';
type InjectDefaultProps<Props> = Omit<
Props,
'locale' | 'generateConfig' | 'prevIcon' | 'nextIcon' | 'superPrevIcon' | 'superNextIcon'
> & {
locale?: typeof enUS;
size?: 'large' | 'default' | 'small';
};
// Picker Props
export type PickerPanelBaseProps<DateType> = InjectDefaultProps<RCPickerPanelBaseProps<DateType>>;
export type PickerPanelDateProps<DateType> = InjectDefaultProps<RCPickerPanelDateProps<DateType>>;
export type PickerPanelTimeProps<DateType> = InjectDefaultProps<RCPickerPanelTimeProps<DateType>>;
export type PickerProps<DateType> =
| PickerPanelBaseProps<DateType>
| PickerPanelDateProps<DateType>
| PickerPanelTimeProps<DateType>;
export type CalendarMode = 'year' | 'month';
export type HeaderRender<DateType> = (config: {
value: DateType;
type: CalendarMode;
onChange: (date: DateType) => void;
onTypeChange: (type: CalendarMode) => void;
}) => React.ReactNode;
export interface CalendarProps<DateType> {
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
locale?: typeof enUS;
validRange?: [DateType, DateType];
disabledDate?: (date: DateType) => boolean;
dateFullCellRender?: (date: DateType) => React.ReactNode;
dateCellRender?: (date: DateType) => React.ReactNode;
monthFullCellRender?: (date: DateType) => React.ReactNode;
monthCellRender?: (date: DateType) => React.ReactNode;
headerRender?: HeaderRender<DateType>;
value?: DateType;
defaultValue?: DateType;
mode?: CalendarMode;
fullscreen?: boolean;
onChange?: (date: DateType) => void;
onPanelChange?: (date: DateType, mode: CalendarMode) => void;
onSelect?: (date: DateType) => void;
}
function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
function isSameMonth(date1: DateType, date2: DateType) {
return (
date1 === date2 ||
(date1 &&
date2 &&
generateConfig.getYear(date1) === generateConfig.getYear(date2) &&
generateConfig.getMonth(date1) === generateConfig.getMonth(date2))
);
}
function isSameDate(date1: DateType, date2: DateType) {
return (
isSameMonth(date1, date2) && generateConfig.getDate(date1) === generateConfig.getDate(date2)
);
}
const Calendar = (props: CalendarProps<DateType>) => {
const {
prefixCls: customizePrefixCls,
className,
dateFullCellRender,
dateCellRender,
monthFullCellRender,
monthCellRender,
headerRender,
value,
defaultValue,
disabledDate,
mode,
validRange,
fullscreen = true,
onChange,
onPanelChange,
onSelect,
} = props;
const { getPrefixCls } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('picker', customizePrefixCls);
const calendarPrefixCls = `${prefixCls}-calendar`;
const today = generateConfig.getNow();
// ====================== State =======================
// Value
const [innerValue, setInnerValue] = React.useState(
() => value || defaultValue || generateConfig.getNow(),
);
const mergedValue = value || innerValue;
// Mode
const [innerMode, setInnerMode] = React.useState(() => mode || 'month');
const mergedMode = mode || innerMode;
const panelMode = React.useMemo<'month' | 'date'>(
() => (mergedMode === 'year' ? 'month' : 'date'),
[mergedMode],
);
// Disabled Date
const mergedDisabledDate = React.useMemo(() => {
if (validRange) {
return (date: DateType) => {
return (
generateConfig.isAfter(validRange[0], date) ||
generateConfig.isAfter(date, validRange[1])
);
};
}
return disabledDate;
}, [disabledDate, validRange]);
// ====================== Events ======================
const triggerPanelChange = (date: DateType, newMode: CalendarMode) => {
if (onPanelChange) {
onPanelChange(date, newMode);
}
};
const triggerChange = (date: DateType) => {
setInnerValue(date);
if (!isSameDate(date, mergedValue)) {
triggerPanelChange(date, mergedMode);
if (onChange) {
onChange(date);
}
}
};
const triggerModeChange = (newMode: CalendarMode) => {
setInnerMode(newMode);
triggerPanelChange(mergedValue, newMode);
};
const onInternalSelect = (date: DateType) => {
triggerChange(date);
if (onSelect) {
onSelect(date);
}
};
// ====================== Locale ======================
const getDefaultLocale = () => {
const { locale } = props;
const result = {
...enUS,
...locale,
};
result.lang = {
...result.lang,
...((locale || {}) as any).lang,
};
return result;
};
// ====================== Render ======================
const dateRender = React.useCallback(
(date: DateType): React.ReactNode => {
if (dateFullCellRender) {
return dateFullCellRender(date);
}
return (
<div
className={classNames(`${prefixCls}-cell-inner`, `${calendarPrefixCls}-date`, {
[`${calendarPrefixCls}-date-today`]: isSameDate(today, date),
})}
>
<div className={`${calendarPrefixCls}-date-value`}>
{padStart(String(generateConfig.getDate(date)), 2, '0')}
</div>
<div className={`${calendarPrefixCls}-date-content`}>
{dateCellRender && dateCellRender(date)}
</div>
</div>
);
},
[dateFullCellRender, dateCellRender],
);
const monthRender = React.useCallback(
(date: DateType, locale: Locale): React.ReactNode => {
if (monthFullCellRender) {
return monthFullCellRender(date);
}
const months = locale.shortMonths || generateConfig.locale.getShortMonths!(locale.locale);
return (
<div
className={classNames(`${prefixCls}-cell-inner`, `${calendarPrefixCls}-date`, {
[`${calendarPrefixCls}-date-today`]: isSameMonth(today, date),
})}
>
<div className={`${calendarPrefixCls}-date-value`}>
{months[generateConfig.getMonth(date)]}
</div>
<div className={`${calendarPrefixCls}-date-content`}>
{monthCellRender && monthCellRender(date)}
</div>
</div>
);
},
[monthFullCellRender, monthCellRender],
);
return (
<LocaleReceiver componentName="Calendar" defaultLocale={getDefaultLocale}>
{(mergedLocale: any) => {
return (
<div
className={classNames(calendarPrefixCls, className, {
[`${calendarPrefixCls}-full`]: fullscreen,
[`${calendarPrefixCls}-mini`]: !fullscreen,
})}
>
{headerRender ? (
headerRender({
value: mergedValue,
type: mergedMode,
onChange: onInternalSelect,
onTypeChange: triggerModeChange,
})
) : (
<CalendarHeader
prefixCls={calendarPrefixCls}
value={mergedValue}
generateConfig={generateConfig}
mode={mergedMode}
fullscreen={fullscreen}
locale={mergedLocale.lang}
validRange={validRange}
onChange={onInternalSelect}
onModeChange={triggerModeChange}
/>
)}
<RCPickerPanel
value={mergedValue}
prefixCls={prefixCls}
locale={mergedLocale.lang}
generateConfig={generateConfig}
dateRender={dateRender}
monthCellRender={date => monthRender(date, mergedLocale.lang)}
onSelect={onInternalSelect}
mode={panelMode}
picker={panelMode as any}
disabledDate={mergedDisabledDate}
hideHeader
/>
</div>
);
}}
</LocaleReceiver>
);
};
return Calendar;
}
export default generateCalendar;

View File

@ -1,281 +1,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 { polyfill } from 'react-lifecycles-compat';
import Header, { HeaderRender } from './Header';
import enUS from './locale/en_US';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import interopDefault from '../_util/interopDefault';
import { Moment } from 'moment';
import momentGenerateConfig from 'rc-picker/lib/generate/moment';
import generateCalendar from './generateCalendar';
export { HeaderProps } from './Header';
function noop() {
return null;
}
function zerofixed(v: number) {
if (v < 10) {
return `0${v}`;
}
return `${v}`;
}
export type CalendarMode = 'month' | 'year';
export interface CalendarProps {
prefixCls?: string;
className?: string;
value?: moment.Moment;
defaultValue?: moment.Moment;
mode?: CalendarMode;
fullscreen?: boolean;
dateCellRender?: (date: moment.Moment) => React.ReactNode;
monthCellRender?: (date: moment.Moment) => React.ReactNode;
dateFullCellRender?: (date: moment.Moment) => React.ReactNode;
monthFullCellRender?: (date: moment.Moment) => React.ReactNode;
locale?: any;
style?: React.CSSProperties;
onPanelChange?: (date?: moment.Moment, mode?: CalendarMode) => void;
onSelect?: (date?: moment.Moment) => void;
onChange?: (date?: moment.Moment) => void;
disabledDate?: (current: moment.Moment) => boolean;
validRange?: [moment.Moment, moment.Moment];
headerRender?: HeaderRender;
}
export interface CalendarState {
value: moment.Moment;
mode?: CalendarMode;
}
class Calendar extends React.Component<CalendarProps, CalendarState> {
static defaultProps = {
locale: {},
fullscreen: true,
onSelect: noop,
onPanelChange: noop,
onChange: noop,
};
static propTypes = {
monthCellRender: PropTypes.func,
dateCellRender: PropTypes.func,
monthFullCellRender: PropTypes.func,
dateFullCellRender: PropTypes.func,
fullscreen: PropTypes.bool,
locale: PropTypes.object,
prefixCls: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
onPanelChange: PropTypes.func,
value: PropTypes.object as PropTypes.Requireable<moment.Moment>,
onSelect: PropTypes.func,
onChange: PropTypes.func,
headerRender: PropTypes.func,
};
static getDerivedStateFromProps(nextProps: CalendarProps) {
const newState = {} as CalendarState;
if ('value' in nextProps) {
newState.value = nextProps.value!;
}
if ('mode' in nextProps) {
newState.mode = nextProps.mode;
}
return Object.keys(newState).length > 0 ? newState : null;
}
prefixCls?: string;
constructor(props: CalendarProps) {
super(props);
const value = props.value || props.defaultValue || interopDefault(moment)();
if (!interopDefault(moment).isMoment(value)) {
throw new Error(
'The value/defaultValue of Calendar must be a moment object after `antd@2.0`, ' +
'see: https://u.ant.design/calendar-value',
);
}
this.state = {
value,
mode: props.mode || 'month',
};
}
onHeaderValueChange = (value: moment.Moment) => {
this.setValue(value, 'changePanel');
};
onHeaderTypeChange = (mode: CalendarMode) => {
this.setState({ mode });
this.onPanelChange(this.state.value, mode);
};
onPanelChange(value: moment.Moment, mode: CalendarMode | undefined) {
const { onPanelChange, onChange } = this.props;
if (onPanelChange) {
onPanelChange(value, mode);
}
if (onChange && value !== this.state.value) {
onChange(value);
}
}
onSelect = (value: moment.Moment) => {
this.setValue(value, 'select');
};
setValue = (value: moment.Moment, way: 'select' | 'changePanel') => {
const prevValue = this.props.value || this.state.value;
const { mode } = this.state;
if (!('value' in this.props)) {
this.setState({ value });
}
if (way === 'select') {
if (prevValue && prevValue.month() !== value.month()) {
this.onPanelChange(value, mode);
}
if (this.props.onSelect) {
this.props.onSelect(value);
}
} else if (way === 'changePanel') {
this.onPanelChange(value, mode);
}
};
getDateRange = (
validRange: [moment.Moment, moment.Moment],
disabledDate?: (current: moment.Moment) => boolean,
) => (current: moment.Moment) => {
if (!current) {
return false;
}
const [startDate, endDate] = validRange;
const inRange = !current.isBetween(startDate, endDate, 'days', '[]');
if (disabledDate) {
return disabledDate(current) || inRange;
}
return inRange;
};
getDefaultLocale = () => {
const result = {
...enUS,
...this.props.locale,
};
result.lang = {
...result.lang,
...(this.props.locale || {}).lang,
};
return result;
};
monthCellRender = (value: moment.Moment) => {
const { monthCellRender = noop as Function } = this.props;
const { prefixCls } = this;
return (
<div className={`${prefixCls}-month`}>
<div className={`${prefixCls}-value`}>{value.localeData().monthsShort(value)}</div>
<div className={`${prefixCls}-content`}>{monthCellRender(value)}</div>
</div>
);
};
dateCellRender = (value: moment.Moment) => {
const { dateCellRender = noop as Function } = this.props;
const { prefixCls } = this;
return (
<div className={`${prefixCls}-date`}>
<div className={`${prefixCls}-value`}>{zerofixed(value.date())}</div>
<div className={`${prefixCls}-content`}>{dateCellRender(value)}</div>
</div>
);
};
renderCalendar = (locale: any, localeCode: string) => {
const { state, props } = this;
const { value, mode } = state;
if (value && localeCode) {
value.locale(localeCode);
}
const {
prefixCls: customizePrefixCls,
style,
className,
fullscreen,
headerRender,
dateFullCellRender,
monthFullCellRender,
} = props;
const monthCellRender = monthFullCellRender || this.monthCellRender;
const dateCellRender = dateFullCellRender || this.dateCellRender;
let { disabledDate } = props;
if (props.validRange) {
disabledDate = this.getDateRange(props.validRange, disabledDate);
}
return (
<ConfigConsumer>
{({ getPrefixCls }: ConfigConsumerProps) => {
const prefixCls = getPrefixCls('fullcalendar', customizePrefixCls);
// To support old version react.
// Have to add prefixCls on the instance.
// https://github.com/facebook/react/issues/12397
this.prefixCls = prefixCls;
let cls = className || '';
if (fullscreen) {
cls += ` ${prefixCls}-fullscreen`;
}
return (
<div className={cls} style={style}>
<Header
fullscreen={fullscreen}
type={mode}
headerRender={headerRender}
value={value}
locale={locale.lang}
prefixCls={prefixCls}
onTypeChange={this.onHeaderTypeChange}
onValueChange={this.onHeaderValueChange}
validRange={props.validRange}
/>
<FullCalendar
{...props}
disabledDate={disabledDate}
Select={noop}
locale={locale.lang}
type={mode === 'year' ? 'month' : 'date'}
prefixCls={prefixCls}
showHeader={false}
value={value}
monthCellRender={monthCellRender}
dateCellRender={dateCellRender}
onSelect={this.onSelect}
/>
</div>
);
}}
</ConfigConsumer>
);
};
render() {
return (
<LocaleReceiver componentName="Calendar" defaultLocale={this.getDefaultLocale}>
{this.renderCalendar}
</LocaleReceiver>
);
}
}
polyfill(Calendar);
const Calendar = generateCalendar<Moment>(momentGenerateConfig);
export default Calendar;

View File

@ -1,279 +1,165 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@full-calendar-prefix-cls: ~'@{ant-prefix}-fullcalendar';
@calendar-prefix-cls: ~'@{ant-prefix}-picker-calendar';
@calendar-picker-prefix-cls: ~'@{ant-prefix}-picker';
.@{full-calendar-prefix-cls} {
.@{calendar-prefix-cls} {
.reset-component;
background: @calendar-bg;
border-top: @border-width-base @border-style-base @border-color-base;
outline: none;
.@{ant-prefix}-select&-year-select {
width: 90px;
&.@{ant-prefix}-select-sm {
width: 70px;
}
}
.@{ant-prefix}-select&-month-select {
width: 80px;
margin-left: 8px;
text-align: left;
&.@{ant-prefix}-select-sm {
width: 70px;
}
}
// ========================= Header =========================
&-header {
display: flex;
justify-content: flex-end;
padding: 11px 16px 11px 0;
text-align: right;
.@{ant-prefix}-select-dropdown {
text-align: left;
.@{calendar-prefix-cls}-year-select {
width: 85px;
}
.@{ant-prefix}-radio-group {
margin-left: 8px;
text-align: left;
.@{calendar-prefix-cls}-month-select {
width: 70px;
margin-left: @padding-xs;
}
label.@{ant-prefix}-radio-button {
height: 22px;
padding: 0 10px;
line-height: 20px;
.@{calendar-prefix-cls}-mode-switch {
margin-left: @padding-xs;
}
}
&-date-panel {
position: relative;
outline: none;
}
&-calendar-body {
padding: 8px 12px;
}
table {
width: 100%;
max-width: 100%;
height: 256px;
background-color: transparent;
border-collapse: collapse;
}
table,
th,
td {
.@{calendar-picker-prefix-cls}-panel {
border: 0;
}
td {
position: relative;
}
&-calendar-table {
margin-bottom: 0;
border-spacing: 0;
}
&-column-header {
width: 33px;
padding: 0;
line-height: 18px;
text-align: center;
.@{full-calendar-prefix-cls}-column-header-inner {
display: block;
font-weight: normal;
}
}
&-week-number-header {
.@{full-calendar-prefix-cls}-column-header-inner {
display: none;
}
}
&-month,
&-date {
text-align: center;
transition: all 0.3s;
}
&-value {
display: block;
width: 24px;
height: 24px;
margin: 0 auto;
padding: 0;
color: @text-color;
line-height: 24px;
background: transparent;
border-radius: @border-radius-base;
transition: all 0.3s;
&:hover {
background: @item-hover-bg;
cursor: pointer;
}
&:active {
color: @text-color-inverse;
background: @primary-color;
}
}
&-month-panel-cell &-value {
width: 48px;
}
&-today &-value,
&-month-panel-current-cell &-value {
box-shadow: 0 0 0 1px @primary-color inset;
}
&-selected-day &-value,
&-month-panel-selected-cell &-value {
color: @text-color-inverse;
background: @primary-color;
}
&-disabled-cell-first-of-row &-value {
border-top-left-radius: @border-radius-base;
border-bottom-left-radius: @border-radius-base;
}
&-disabled-cell-last-of-row &-value {
border-top-right-radius: @border-radius-base;
border-bottom-right-radius: @border-radius-base;
}
&-last-month-cell &-value,
&-next-month-btn-day &-value {
color: @disabled-color;
}
&-month-panel-table {
width: 100%;
table-layout: fixed;
border-collapse: separate;
}
&-content {
position: absolute;
bottom: -9px;
left: 0;
width: 100%;
}
&-fullscreen {
border-top: 0;
}
&-fullscreen &-table {
table-layout: fixed;
}
&-fullscreen &-header {
.@{ant-prefix}-radio-group {
margin-left: 16px;
}
label.@{ant-prefix}-radio-button {
height: @input-height-base;
line-height: @input-height-base - 2px;
}
}
&-fullscreen &-month,
&-fullscreen &-date {
display: block;
height: 116px;
margin: 0 4px;
padding: 4px 8px;
color: @text-color;
text-align: left;
border-top: 2px solid @border-color-split;
transition: background 0.3s;
&:hover {
background: @item-hover-bg;
cursor: pointer;
}
&:active {
background: @primary-2;
}
}
&-fullscreen &-column-header {
padding-right: 12px;
padding-bottom: 5px;
text-align: right;
}
&-fullscreen &-value {
width: auto;
text-align: right;
background: transparent;
}
&-fullscreen &-today &-value {
color: @text-color;
}
&-fullscreen &-month-panel-current-cell &-month,
&-fullscreen &-today &-date {
background: transparent;
border-top-color: @primary-color;
}
&-fullscreen &-month-panel-current-cell &-value,
&-fullscreen &-today &-value {
box-shadow: none;
}
&-fullscreen &-month-panel-selected-cell &-month,
&-fullscreen &-selected-day &-date {
background: @primary-1;
}
&-fullscreen &-month-panel-selected-cell &-value,
&-fullscreen &-selected-day &-value {
color: @primary-color;
}
&-fullscreen &-last-month-cell &-date,
&-fullscreen &-next-month-btn-day &-date {
color: @disabled-color;
}
&-fullscreen &-content {
position: static;
width: auto;
height: 88px;
overflow-y: auto;
}
&-disabled-cell &-date {
&,
&:hover {
cursor: not-allowed;
}
}
&-disabled-cell:not(&-today) &-date {
&,
&:hover {
background: transparent;
}
}
&-disabled-cell &-value {
width: auto;
color: @disabled-color;
border-top: @border-width-base @border-style-base @border-color-split;
border-radius: 0;
cursor: not-allowed;
.@{calendar-picker-prefix-cls}-month-panel,
.@{calendar-picker-prefix-cls}-date-panel {
width: auto;
}
.@{calendar-picker-prefix-cls}-body {
padding: @padding-xs @padding-sm;
}
}
.@{calendar-picker-prefix-cls}-panel {
.@{calendar-picker-prefix-cls}-content {
width: 100%;
}
}
// ========================== Mini ==========================
&-mini {
.@{calendar-picker-prefix-cls}-content {
height: 256px;
th {
height: auto;
padding: 0;
line-height: 18px;
}
}
}
// ========================== Full ==========================
&-full {
.@{calendar-prefix-cls}-header {
.@{calendar-prefix-cls}-year-select {
width: 90px;
}
.@{calendar-prefix-cls}-month-select {
width: 80px;
margin-left: @padding-xs;
}
.@{calendar-prefix-cls}-mode-switch {
margin-left: @padding-md;
}
}
.@{calendar-picker-prefix-cls}-panel {
display: block;
width: 100%;
text-align: right;
border: 0;
.@{calendar-picker-prefix-cls}-body {
th,
td {
padding: 0;
}
th {
height: auto;
padding: 0 12px 5px 0;
line-height: 18px;
}
}
// Cell
.@{calendar-picker-prefix-cls}-cell {
&::before {
display: none;
}
&:hover {
.@{calendar-prefix-cls}-date {
background: @item-hover-bg;
}
}
.@{calendar-prefix-cls}-date-today::before {
display: none;
}
&-selected,
&-selected:hover {
.@{calendar-prefix-cls}-date,
.@{calendar-prefix-cls}-date-today {
background: @calendar-item-active-bg;
.@{calendar-prefix-cls}-date-value {
color: @primary-color;
}
}
}
}
// Cell date
.@{calendar-prefix-cls}-date {
display: block;
width: auto;
height: auto;
margin: 0 @padding-xs / 2;
padding: @padding-xs / 2 @padding-xs 0;
border: 0;
border-top: 2px solid @border-color-split;
border-radius: 0;
transition: background 0.3s;
&-value {
line-height: 24px;
transition: color 0.3s;
}
&-content {
position: static;
width: auto;
height: 86px;
overflow-y: auto;
line-height: @line-height-base;
}
&-today {
border-color: @primary-color;
.@{calendar-prefix-cls}-date-value {
color: @text-color;
}
}
}
}
}
}

View File

@ -1,20 +0,0 @@
import * as React from 'react';
import classNames from 'classnames';
import { CalendarOutlined } from '@ant-design/icons';
export default function InputIcon(props: { suffixIcon: React.ReactNode; prefixCls: string }) {
const { suffixIcon, prefixCls } = props;
return (
(suffixIcon &&
(React.isValidElement<{ className?: string }>(suffixIcon) ? (
React.cloneElement(suffixIcon, {
className: classNames({
[suffixIcon.props.className!]: suffixIcon.props.className,
[`${prefixCls}-picker-icon`]: true,
}),
})
) : (
<span className={`${prefixCls}-picker-icon`}>{suffixIcon}</span>
))) || <CalendarOutlined className={`${prefixCls}-picker-icon`} />
);
}

View File

@ -0,0 +1,6 @@
import * as React from 'react';
import Button, { ButtonProps } from '../button';
export default function PickerButton(props: ButtonProps) {
return <Button size="small" type="primary" {...props} />;
}

View File

@ -0,0 +1,6 @@
import * as React from 'react';
import Tag, { TagProps } from '../tag';
export default function PickerButton(props: TagProps) {
return <Tag color="blue" {...props} />;
}

View File

@ -1,444 +0,0 @@
/* tslint:disable jsx-no-multiline-js */
import * as React from 'react';
import * as moment from 'moment';
import { polyfill } from 'react-lifecycles-compat';
import RangeCalendar from 'rc-calendar/lib/RangeCalendar';
import RcDatePicker from 'rc-calendar/lib/Picker';
import classNames from 'classnames';
import shallowequal from 'shallowequal';
import { CloseCircleFilled } from '@ant-design/icons';
import Tag from '../tag';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import warning from '../_util/warning';
import interopDefault from '../_util/interopDefault';
import { RangePickerValue, RangePickerPresetRange, RangePickerProps } from './interface';
import { formatDate } from './utils';
import InputIcon from './InputIcon';
export interface RangePickerState {
value?: RangePickerValue;
showDate?: RangePickerValue;
open?: boolean;
hoverValue?: RangePickerValue;
}
function getShowDateFromValue(value: RangePickerValue, mode?: string | string[]) {
const [start, end] = value;
// value could be an empty array, then we should not reset showDate
if (!start && !end) {
return;
}
if (mode && mode[0] === 'month') {
return [start, end] as RangePickerValue;
}
const newEnd = end && end.isSame(start, 'month') ? end.clone().add(1, 'month') : end;
return [start, newEnd] as RangePickerValue;
}
function pickerValueAdapter(
value?: moment.Moment | RangePickerValue,
): RangePickerValue | undefined {
if (!value) {
return;
}
if (Array.isArray(value)) {
return value;
}
return [value, value.clone().add(1, 'month')];
}
function isEmptyArray(arr: any) {
if (Array.isArray(arr)) {
return arr.length === 0 || arr.every(i => !i);
}
return false;
}
function fixLocale(value: RangePickerValue | undefined, localeCode: string | undefined) {
if (!localeCode) {
return;
}
if (!value || value.length === 0) {
return;
}
const [start, end] = value;
if (start) {
start!.locale(localeCode);
}
if (end) {
end!.locale(localeCode);
}
}
class RangePicker extends React.Component<RangePickerProps, RangePickerState> {
static defaultProps = {
allowClear: true,
showToday: false,
separator: '~',
};
static getDerivedStateFromProps(nextProps: RangePickerProps, prevState: RangePickerState) {
let state = null;
if ('value' in nextProps) {
const value = nextProps.value || [];
state = {
value,
};
if (!shallowequal(nextProps.value, prevState.value)) {
state = {
...state,
showDate: getShowDateFromValue(value, nextProps.mode) || prevState.showDate,
};
}
}
if ('open' in nextProps && prevState.open !== nextProps.open) {
state = {
...state,
open: nextProps.open,
};
}
return state;
}
private picker: HTMLSpanElement;
private prefixCls?: string;
private tagPrefixCls?: string;
constructor(props: any) {
super(props);
const value = props.value || props.defaultValue || [];
const [start, end] = value;
if (
(start && !interopDefault(moment).isMoment(start)) ||
(end && !interopDefault(moment).isMoment(end))
) {
throw new Error(
'The value/defaultValue of RangePicker must be a moment object array after `antd@2.0`, ' +
'see: https://u.ant.design/date-picker-value',
);
}
const pickerValue = !value || isEmptyArray(value) ? props.defaultPickerValue : value;
this.state = {
value,
showDate: pickerValueAdapter(pickerValue || interopDefault(moment)()),
open: props.open,
hoverValue: [],
};
}
componentDidUpdate(_: any, prevState: RangePickerState) {
if (!('open' in this.props) && prevState.open && !this.state.open) {
this.focus();
}
}
setValue(value: RangePickerValue, hidePanel?: boolean) {
this.handleChange(value);
if ((hidePanel || !this.props.showTime) && !('open' in this.props)) {
this.setState({ open: false });
}
}
savePicker = (node: HTMLSpanElement) => {
this.picker = node;
};
clearSelection = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
e.stopPropagation();
this.setState({ value: [] });
this.handleChange([]);
};
clearHoverValue = () => this.setState({ hoverValue: [] });
handleChange = (value: RangePickerValue) => {
const { props } = this;
if (!('value' in props)) {
this.setState(({ showDate }) => ({
value,
showDate: getShowDateFromValue(value) || showDate,
}));
}
if (value[0] && value[0].diff(value[1]) > 0) {
value[1] = undefined;
}
const [start, end] = value;
if (typeof props.onChange === 'function') {
props.onChange(value, [formatDate(start, props.format), formatDate(end, props.format)]);
}
};
handleOpenChange = (open: boolean) => {
if (!('open' in this.props)) {
this.setState({ open });
}
if (open === false) {
this.clearHoverValue();
}
const { onOpenChange } = this.props;
if (onOpenChange) {
onOpenChange(open);
}
};
handleShowDateChange = (showDate: RangePickerValue) => this.setState({ showDate });
handleHoverChange = (hoverValue: any) => this.setState({ hoverValue });
handleRangeMouseLeave = () => {
if (this.state.open) {
this.clearHoverValue();
}
};
handleCalendarInputSelect = (value: RangePickerValue) => {
const [start] = value;
if (!start) {
return;
}
this.setState(({ showDate }) => ({
value,
showDate: getShowDateFromValue(value) || showDate,
}));
};
handleRangeClick = (value: RangePickerPresetRange) => {
if (typeof value === 'function') {
value = value();
}
this.setValue(value, true);
const { onOk, onOpenChange } = this.props;
if (onOk) {
onOk(value);
}
if (onOpenChange) {
onOpenChange(false);
}
};
focus() {
this.picker.focus();
}
blur() {
this.picker.blur();
}
renderFooter = () => {
const { ranges, renderExtraFooter } = this.props;
const { prefixCls, tagPrefixCls } = this;
if (!ranges && !renderExtraFooter) {
return null;
}
const customFooter = renderExtraFooter ? (
<div className={`${prefixCls}-footer-extra`} key="extra">
{renderExtraFooter()}
</div>
) : null;
const operations =
ranges &&
Object.keys(ranges).map(range => {
const value = ranges[range];
const hoverValue = typeof value === 'function' ? value.call(this) : value;
return (
<Tag
key={range}
prefixCls={tagPrefixCls}
color="blue"
onClick={() => this.handleRangeClick(value)}
onMouseEnter={() => this.setState({ hoverValue })}
onMouseLeave={this.handleRangeMouseLeave}
>
{range}
</Tag>
);
});
const rangeNode =
operations && operations.length > 0 ? (
<div className={`${prefixCls}-footer-extra ${prefixCls}-range-quick-selector`} key="range">
{operations}
</div>
) : null;
return [rangeNode, customFooter];
};
renderRangePicker = ({ getPrefixCls }: ConfigConsumerProps) => {
const { state, props } = this;
const { value, showDate, hoverValue, open } = state;
const {
prefixCls: customizePrefixCls,
tagPrefixCls: customizeTagPrefixCls,
popupStyle,
style,
disabledDate,
disabledTime,
showTime,
showToday,
ranges,
onOk,
locale,
// @ts-ignore
localeCode,
format,
dateRender,
onCalendarChange,
suffixIcon,
separator,
} = props;
const prefixCls = getPrefixCls('calendar', customizePrefixCls);
const tagPrefixCls = getPrefixCls('tag', customizeTagPrefixCls);
// To support old version react.
// Have to add prefixCls on the instance.
// https://github.com/facebook/react/issues/12397
this.prefixCls = prefixCls;
this.tagPrefixCls = tagPrefixCls;
fixLocale(value, localeCode);
fixLocale(showDate, localeCode);
warning(
!('onOK' in props),
'RangePicker',
'It should be `RangePicker[onOk]`, instead of `onOK`!',
);
const calendarClassName = classNames({
[`${prefixCls}-time`]: showTime,
[`${prefixCls}-range-with-ranges`]: ranges,
});
// 需要选择时间时,点击 ok 时才触发 onChange
const pickerChangeHandler = {
onChange: this.handleChange,
};
let calendarProps: any = {
onOk: this.handleChange,
};
if (props.timePicker) {
pickerChangeHandler.onChange = changedValue => this.handleChange(changedValue);
} else {
calendarProps = {};
}
if ('mode' in props) {
calendarProps.mode = props.mode;
}
const startPlaceholder = Array.isArray(props.placeholder)
? props.placeholder[0]
: locale.lang.rangePlaceholder[0];
const endPlaceholder = Array.isArray(props.placeholder)
? props.placeholder[1]
: locale.lang.rangePlaceholder[1];
const calendar = (
<RangeCalendar
{...calendarProps}
seperator={separator}
onChange={onCalendarChange}
format={format}
prefixCls={prefixCls}
className={calendarClassName}
renderFooter={this.renderFooter}
timePicker={props.timePicker}
disabledDate={disabledDate}
disabledTime={disabledTime}
dateInputPlaceholder={[startPlaceholder, endPlaceholder]}
locale={locale.lang}
onOk={onOk}
dateRender={dateRender}
value={showDate}
onValueChange={this.handleShowDateChange}
hoverValue={hoverValue}
onHoverChange={this.handleHoverChange}
onPanelChange={props.onPanelChange}
showToday={showToday}
onInputSelect={this.handleCalendarInputSelect}
/>
);
// default width for showTime
const pickerStyle = {} as any;
if (props.showTime) {
pickerStyle.width = (style && style.width) || 350;
}
const [startValue, endValue] = value as RangePickerValue;
const clearIcon =
!props.disabled && props.allowClear && value && (startValue || endValue) ? (
<CloseCircleFilled className={`${prefixCls}-picker-clear`} onClick={this.clearSelection} />
) : null;
const inputIcon = <InputIcon suffixIcon={suffixIcon} prefixCls={prefixCls} />;
const input = ({ value: inputValue }: { value: any }) => {
const [start, end] = inputValue;
return (
<span className={props.pickerInputClass}>
<input
disabled={props.disabled}
readOnly
value={formatDate(start, props.format)}
placeholder={startPlaceholder}
className={`${prefixCls}-range-picker-input`}
tabIndex={-1}
/>
<span className={`${prefixCls}-range-picker-separator`}> {separator} </span>
<input
disabled={props.disabled}
readOnly
value={formatDate(end, props.format)}
placeholder={endPlaceholder}
className={`${prefixCls}-range-picker-input`}
tabIndex={-1}
/>
{clearIcon}
{inputIcon}
</span>
);
};
return (
<span
ref={this.savePicker}
id={typeof props.id === 'number' ? props.id.toString() : props.id}
className={classNames(props.className, props.pickerClass)}
style={{ ...style, ...pickerStyle }}
tabIndex={props.disabled ? -1 : 0}
onFocus={props.onFocus}
onBlur={props.onBlur}
onMouseEnter={props.onMouseEnter}
onMouseLeave={props.onMouseLeave}
>
<RcDatePicker
{...props}
{...pickerChangeHandler}
calendar={calendar}
value={value}
open={open}
onOpenChange={this.handleOpenChange}
prefixCls={`${prefixCls}-picker-container`}
style={popupStyle}
>
{input}
</RcDatePicker>
</span>
);
};
render() {
return <ConfigConsumer>{this.renderRangePicker}</ConfigConsumer>;
}
}
polyfill(RangePicker);
export default RangePicker;

View File

@ -1,226 +0,0 @@
import * as React from 'react';
import * as moment from 'moment';
import { polyfill } from 'react-lifecycles-compat';
import Calendar from 'rc-calendar';
import RcDatePicker from 'rc-calendar/lib/Picker';
import classNames from 'classnames';
import { CloseCircleFilled } from '@ant-design/icons';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import interopDefault from '../_util/interopDefault';
import InputIcon from './InputIcon';
function formatValue(value: moment.Moment | null, format: string): string {
return (value && value.format(format)) || '';
}
interface WeekPickerState {
open: boolean;
value: moment.Moment | null;
}
class WeekPicker extends React.Component<any, WeekPickerState> {
static defaultProps = {
format: 'gggg-wo',
allowClear: true,
};
static getDerivedStateFromProps(nextProps: any) {
if ('value' in nextProps || 'open' in nextProps) {
const state = {} as WeekPickerState;
if ('value' in nextProps) {
state.value = nextProps.value;
}
if ('open' in nextProps) {
state.open = nextProps.open;
}
return state;
}
return null;
}
private input: any;
private prefixCls?: string;
constructor(props: any) {
super(props);
const value = props.value || props.defaultValue;
if (value && !interopDefault(moment).isMoment(value)) {
throw new Error(
'The value/defaultValue of WeekPicker must be ' +
'a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value',
);
}
this.state = {
value,
open: props.open,
};
}
componentDidUpdate(_: any, prevState: WeekPickerState) {
if (!('open' in this.props) && prevState.open && !this.state.open) {
this.focus();
}
}
saveInput = (node: any) => {
this.input = node;
};
weekDateRender = (current: any) => {
const selectedValue = this.state.value;
const { prefixCls } = this;
const { dateRender } = this.props;
const dateNode = dateRender ? dateRender(current) : current.date();
if (
selectedValue &&
current.year() === selectedValue.year() &&
current.week() === selectedValue.week()
) {
return (
<div className={`${prefixCls}-selected-day`}>
<div className={`${prefixCls}-date`}>{dateNode}</div>
</div>
);
}
return <div className={`${prefixCls}-date`}>{dateNode}</div>;
};
handleChange = (value: moment.Moment | null) => {
if (!('value' in this.props)) {
this.setState({ value });
}
this.props.onChange(value, formatValue(value, this.props.format));
};
handleOpenChange = (open: boolean) => {
const { onOpenChange } = this.props;
if (!('open' in this.props)) {
this.setState({ open });
}
if (onOpenChange) {
onOpenChange(open);
}
};
clearSelection = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
e.stopPropagation();
this.handleChange(null);
};
focus() {
this.input.focus();
}
blur() {
this.input.blur();
}
renderFooter = (...args: any[]) => {
const { prefixCls, renderExtraFooter } = this.props;
return renderExtraFooter ? (
<div className={`${prefixCls}-footer-extra`}>{renderExtraFooter(...args)}</div>
) : null;
};
renderWeekPicker = ({ getPrefixCls }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
className,
disabled,
pickerClass,
popupStyle,
pickerInputClass,
format,
allowClear,
locale,
localeCode,
disabledDate,
style,
onFocus,
onBlur,
id,
suffixIcon,
defaultPickerValue,
} = this.props;
const prefixCls = getPrefixCls('calendar', customizePrefixCls);
// To support old version react.
// Have to add prefixCls on the instance.
// https://github.com/facebook/react/issues/12397
this.prefixCls = prefixCls;
const { open, value: pickerValue } = this.state;
if (pickerValue && localeCode) {
pickerValue.locale(localeCode);
}
const placeholder =
'placeholder' in this.props ? this.props.placeholder : locale.lang.placeholder;
const calendar = (
<Calendar
showWeekNumber
dateRender={this.weekDateRender}
prefixCls={prefixCls}
format={format}
locale={locale.lang}
showDateInput={false}
showToday={false}
disabledDate={disabledDate}
renderFooter={this.renderFooter}
defaultValue={defaultPickerValue}
/>
);
const clearIcon =
!disabled && allowClear && this.state.value ? (
<CloseCircleFilled className={`${prefixCls}-picker-clear`} onClick={this.clearSelection} />
) : null;
const inputIcon = <InputIcon suffixIcon={suffixIcon} prefixCls={prefixCls} />;
const input = ({ value }: { value: moment.Moment | undefined }) => (
<span style={{ display: 'inline-block', width: '100%' }}>
<input
ref={this.saveInput}
disabled={disabled}
readOnly
value={(value && value.format(format)) || ''}
placeholder={placeholder}
className={pickerInputClass}
onFocus={onFocus}
onBlur={onBlur}
/>
{clearIcon}
{inputIcon}
</span>
);
return (
<span className={classNames(className, pickerClass)} style={style} id={id}>
<RcDatePicker
{...this.props}
calendar={calendar}
prefixCls={`${prefixCls}-picker-container`}
value={pickerValue}
onChange={this.handleChange}
open={open}
onOpenChange={this.handleOpenChange}
style={popupStyle}
>
{input}
</RcDatePicker>
</span>
);
};
render() {
return <ConfigConsumer>{this.renderWeekPicker}</ConfigConsumer>;
}
}
polyfill(WeekPicker);
export default WeekPicker;

View File

@ -3,13 +3,12 @@ import { mount } from 'enzyme';
import moment from 'moment';
import MockDate from 'mockdate';
import DatePicker from '..';
import { selectDate, openPanel, clearInput, nextYear, nextMonth, hasSelected } from './utils';
import focusTest from '../../../tests/shared/focusTest';
describe('DatePicker', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
focusTest(DatePicker);
focusTest(DatePicker, true);
beforeEach(() => {
MockDate.set(moment('2016-11-22'));
@ -24,14 +23,10 @@ describe('DatePicker', () => {
errorSpy.mockRestore();
});
it('support name prop', () => {
const wrapper = mount(<DatePicker name="bamboo" />);
expect(wrapper.find('input').props().name).toBe('bamboo');
});
it('prop locale should works', () => {
const locale = {
lang: {
locale: 'mk',
placeholder: 'Избери дата',
rangePlaceholder: ['Начална дата', 'Крайна дата'],
today: 'Днес',
@ -69,172 +64,13 @@ describe('DatePicker', () => {
expect(wrapper.render()).toMatchSnapshot();
});
// Fix https://github.com/ant-design/ant-design/issues/8885
it('control value after panel closed', () => {
class Test extends React.Component {
state = {
cleared: false,
value: moment(),
};
onChange = value => {
let { cleared } = this.state;
let newValue = value;
if (cleared) {
newValue = moment(moment(value).format('YYYY-MM-DD 12:12:12'));
cleared = false;
}
if (!newValue) {
cleared = true;
}
this.setState({ value: newValue, cleared });
};
render() {
const { value } = this.state;
return (
<DatePicker
showTime
value={value}
format="YYYY-MM-DD HH:mm:ss"
onChange={this.onChange}
/>
);
}
}
const wrapper = mount(<Test />);
// clear input
clearInput(wrapper);
openPanel(wrapper);
selectDate(wrapper, moment('2016-11-13'));
expect(wrapper.find('.ant-calendar-input').getDOMNode().value).toBe('2016-11-13 12:12:12');
selectDate(wrapper, moment('2016-11-14'));
expect(wrapper.find('.ant-calendar-input').getDOMNode().value).toBe('2016-11-14 12:12:12');
});
it('triggers onChange only when date was selected', () => {
const handleChange = jest.fn();
const wrapper = mount(<DatePicker onChange={handleChange} />);
openPanel(wrapper);
nextYear(wrapper);
expect(handleChange).not.toHaveBeenCalled();
nextMonth(wrapper);
expect(handleChange).not.toHaveBeenCalled();
selectDate(wrapper, moment('2017-12-22'));
expect(handleChange).toHaveBeenCalled();
});
it('clear input', () => {
const wrapper = mount(<DatePicker />);
openPanel(wrapper);
selectDate(wrapper, moment('2016-11-23'));
clearInput(wrapper);
openPanel(wrapper);
expect(hasSelected(wrapper, moment('2016-11-22'))).toBe(true);
});
it('sets data attributes on input', () => {
const wrapper = mount(<DatePicker data-test="test-id" data-id="12345" />);
const input = wrapper.find('.ant-calendar-picker-input').getDOMNode();
expect(input.getAttribute('data-test')).toBe('test-id');
expect(input.getAttribute('data-id')).toBe('12345');
});
it('sets aria attributes on input', () => {
const wrapper = mount(<DatePicker aria-label="some-label" aria-labelledby="label-id" />);
const input = wrapper.find('.ant-calendar-picker-input').getDOMNode();
expect(input.getAttribute('aria-label')).toBe('some-label');
expect(input.getAttribute('aria-labelledby')).toBe('label-id');
});
it('sets role attribute on input', () => {
const wrapper = mount(<DatePicker role="search" />);
const input = wrapper.find('.ant-calendar-picker-input').getDOMNode();
expect(input.getAttribute('role')).toBe('search');
});
it('changes year/month when under control', () => {
const wrapper = mount(<DatePicker value={moment('2018-07-01')} />);
openPanel(wrapper);
expect(wrapper.find('.ant-calendar-my-select').text()).toBe('Jul2018');
wrapper.find('.ant-calendar-prev-year-btn').simulate('click');
wrapper.find('.ant-calendar-prev-month-btn').simulate('click');
expect(wrapper.find('.ant-calendar-my-select').text()).toBe('Jun2017');
});
it('disabled date', () => {
function disabledDate(current) {
return current && current < moment().endOf('day');
}
const wrapper = mount(<DatePicker disabledDate={disabledDate} />);
const wrapper = mount(<DatePicker disabledDate={disabledDate} open />);
expect(wrapper.render()).toMatchSnapshot();
});
it('extra footer works', () => {
const wrapper = mount(
<DatePicker renderExtraFooter={mode => <span className="extra-node">{mode}</span>} />,
);
openPanel(wrapper);
let extraNode = wrapper.find('.extra-node');
expect(extraNode.length).toBe(1);
expect(extraNode.text()).toBe('date');
wrapper
.find('.ant-calendar-month-select')
.hostNodes()
.simulate('click');
extraNode = wrapper.find('.ant-calendar-month-panel .extra-node');
expect(extraNode.length).toBe(1);
expect(extraNode.text()).toBe('month');
wrapper
.find('.ant-calendar-year-select')
.hostNodes()
.simulate('click');
extraNode = wrapper.find('.ant-calendar-year-panel .extra-node');
expect(extraNode.length).toBe(1);
expect(extraNode.text()).toBe('year');
wrapper
.find('.ant-calendar-year-panel-decade-select')
.hostNodes()
.simulate('click');
extraNode = wrapper.find('.ant-calendar-decade-panel .extra-node');
expect(extraNode.length).toBe(1);
expect(extraNode.text()).toBe('decade');
});
it('supports multiple formats', () => {
const wrapper = mount(<DatePicker format={['DD/MM/YYYY', 'DD/MM/YY']} />);
openPanel(wrapper);
wrapper.find('.ant-calendar-input').simulate('change', { target: { value: '02/07/18' } });
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode().value).toBe('02/07/2018');
});
describe('warning use if use invalidate moment', () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const invalidateTime = moment('I AM INVALIDATE');
warnSpy.mockReset();
it('defaultValue', () => {
mount(<DatePicker defaultValue={invalidateTime} />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: DatePicker] `defaultValue` provides invalidate moment time. If you want to set empty value, use `null` instead.',
);
});
it('value', () => {
mount(<DatePicker value={invalidateTime} />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: DatePicker] `value` provides invalidate moment time. If you want to set empty value, use `null` instead.',
);
});
});
});

View File

@ -1,25 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import moment from 'moment';
import DatePicker from '..';
import focusTest from '../../../tests/shared/focusTest';
import { openPanel } from './utils';
const { MonthPicker } = DatePicker;
describe('MonthPicker', () => {
focusTest(MonthPicker);
it('reset select item when popup close', () => {
const wrapper = mount(<MonthPicker value={moment('2018-07-01')} />);
openPanel(wrapper);
wrapper
.find('.ant-calendar-month-panel-month')
.first()
.simulate('click');
wrapper
.find('.ant-calendar-month-panel-cell')
.at(6)
.hasClass('ant-calendar-month-panel-selected-cell');
});
});

View File

@ -1,15 +1,15 @@
import React from 'react';
import { mount, render } from 'enzyme';
import { mount } from 'enzyme';
import moment from 'moment';
import DatePicker from '..';
import { setMockDate, resetMockDate } from '../../../tests/utils';
import { selectDate, openPanel } from './utils';
import { openPicker, selectCell, closePicker } from './utils';
import focusTest from '../../../tests/shared/focusTest';
const { RangePicker } = DatePicker;
describe('RangePicker', () => {
focusTest(RangePicker);
focusTest(RangePicker, true);
beforeEach(() => {
setMockDate();
@ -19,300 +19,21 @@ describe('RangePicker', () => {
resetMockDate();
});
it('show month panel according to value', () => {
const birthday = moment('2000-01-01', 'YYYY-MM-DD').locale('zh-cn');
const wrapper = mount(
<RangePicker getCalendarContainer={trigger => trigger} format="YYYY/MM/DD" showTime open />,
);
wrapper.setProps({ value: [birthday, birthday] });
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
});
it('switch to corresponding month panel when click presetted ranges', () => {
const birthday = moment('2000-01-01', 'YYYY-MM-DD').locale('zh-cn');
const wrapper = mount(
<RangePicker
ranges={{
'My Birthday': [birthday, birthday],
}}
getCalendarContainer={trigger => trigger}
format="YYYY/MM/DD"
showTime
open
/>,
);
const rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
rangeCalendarWrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
});
it('highlight range when hover presetted range', () => {
const wrapper = mount(
<RangePicker
ranges={{
'This Month': [moment().startOf('month'), moment().endOf('month')],
}}
getCalendarContainer={trigger => trigger}
format="YYYY/MM/DD"
open
/>,
);
let rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
rangeCalendarWrapper.find('.ant-calendar-range-quick-selector Tag').simulate('mouseEnter');
rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
expect(rangeCalendarWrapper.find('.ant-calendar-selected-day').length).toBe(2);
});
it('should trigger onCalendarChange when change value', () => {
const onCalendarChangeFn = jest.fn();
const wrapper = mount(
<RangePicker
getCalendarContainer={trigger => trigger}
onCalendarChange={onCalendarChangeFn}
open
/>,
);
const rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
rangeCalendarWrapper
.find('.ant-calendar-cell')
.at(15)
.simulate('click');
expect(onCalendarChangeFn).toHaveBeenCalled();
});
// issue: https://github.com/ant-design/ant-design/issues/5872
it('should not throw error when value is reset to `[]`', () => {
const birthday = moment('2000-01-01', 'YYYY-MM-DD');
const wrapper = mount(
<RangePicker getCalendarContainer={trigger => trigger} value={[birthday, birthday]} open />,
);
const wrapper = mount(<RangePicker value={[birthday, birthday]} open />);
wrapper.setProps({ value: [] });
const rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
expect(() =>
rangeCalendarWrapper
.find('.ant-calendar-cell')
.at(15)
.simulate('click')
.simulate('click'),
).not.toThrow();
});
// issue: https://github.com/ant-design/ant-design/issues/7077
it('should not throw error when select after clear', () => {
const wrapper = mount(<RangePicker getCalendarContainer={trigger => trigger} open />);
let rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
rangeCalendarWrapper
.find('.ant-calendar-cell')
.at(15)
.simulate('click')
.simulate('click');
wrapper.update();
wrapper
.find('.ant-calendar-picker-clear')
.hostNodes()
.simulate('click');
wrapper.find('.ant-calendar-picker-input').simulate('click');
rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
expect(() =>
rangeCalendarWrapper
.find('.ant-calendar-cell')
.at(15)
.simulate('click')
.simulate('click'),
).not.toThrow();
});
expect(() => {
openPicker(wrapper);
selectCell(wrapper, 3);
closePicker(wrapper);
it('clear hover value after panel close', () => {
jest.useFakeTimers();
const wrapper = mount(
<div>
<RangePicker value={[moment(), moment().add(2, 'day')]} />
</div>,
);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper
.find('.ant-calendar-cell')
.at(25)
.simulate('click');
wrapper
.find('.ant-calendar-cell')
.at(27)
.simulate('mouseEnter');
document.dispatchEvent(new MouseEvent('mousedown'));
jest.runAllTimers();
wrapper.find('.ant-calendar-picker-input').simulate('click');
expect(
wrapper
.find('.ant-calendar-cell')
.at(23)
.hasClass('ant-calendar-in-range-cell'),
).toBe(true);
});
describe('preset range', () => {
it('static range', () => {
const range = [moment().subtract(2, 'd'), moment()];
const format = 'YYYY-MM-DD HH:mm:ss';
const wrapper = mount(<RangePicker ranges={{ 'recent two days': range }} format={format} />);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(
wrapper
.find('.ant-calendar-range-picker-input')
.first()
.getDOMNode().value,
).toBe(range[0].format(format));
expect(
wrapper
.find('.ant-calendar-range-picker-input')
.last()
.getDOMNode().value,
).toBe(range[1].format(format));
});
it('function range', () => {
const range = [moment().subtract(2, 'd'), moment()];
const format = 'YYYY-MM-DD HH:mm:ss';
const wrapper = mount(
<RangePicker ranges={{ 'recent two days': () => range }} format={format} />,
);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(
wrapper
.find('.ant-calendar-range-picker-input')
.first()
.getDOMNode().value,
).toBe(range[0].format(format));
expect(
wrapper
.find('.ant-calendar-range-picker-input')
.last()
.getDOMNode().value,
).toBe(range[1].format(format));
});
});
// https://github.com/ant-design/ant-design/issues/6999
it('input date manually', () => {
const wrapper = mount(<RangePicker open />);
const dateString = '2008-12-31';
const input = wrapper.find('.ant-calendar-input').first();
input.simulate('change', { target: { value: dateString } });
expect(input.getDOMNode().value).toBe(dateString);
});
it('triggers onOk when click on preset range', () => {
const handleOk = jest.fn();
const range = [moment().subtract(2, 'd'), moment()];
const wrapper = mount(<RangePicker ranges={{ 'recent two days': range }} onOk={handleOk} />);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(handleOk).toHaveBeenCalledWith(range);
});
// https://github.com/ant-design/ant-design/issues/9267
it('invali end date not throw error', () => {
const wrapper = mount(<RangePicker />);
wrapper.find('.ant-calendar-picker-input').simulate('click');
selectDate(wrapper, moment('2017-09-18'), 0);
selectDate(wrapper, moment('2017-10-18'), 1);
wrapper.find('.ant-calendar-picker-input').simulate('click');
expect(() =>
wrapper
.find('.ant-calendar-input')
.at(1)
.simulate('change', { target: { value: '2016-01-01' } }),
).not.toThrow();
});
it('changes year/month when under control', () => {
const wrapper = mount(<RangePicker value={[moment('2018-07-01'), moment('2018-07-02')]} />);
openPanel(wrapper);
expect(
wrapper
.find('.ant-calendar-my-select')
.first()
.text(),
).toBe('Jul2018');
wrapper
.find('.ant-calendar-prev-year-btn')
.first()
.simulate('click');
wrapper
.find('.ant-calendar-prev-month-btn')
.first()
.simulate('click');
expect(
wrapper
.find('.ant-calendar-my-select')
.first()
.text(),
).toBe('Jun2017');
});
// https://github.com/ant-design/ant-design/issues/11631
it('triggers onOpenChange when click on preset range', () => {
const handleOpenChange = jest.fn();
const range = [moment().subtract(2, 'd'), moment()];
const wrapper = mount(
<RangePicker onOpenChange={handleOpenChange} ranges={{ 'recent two days': range }} />,
);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(handleOpenChange).toHaveBeenCalledWith(false);
openPicker(wrapper, 1);
selectCell(wrapper, 5, 1);
closePicker(wrapper, 1);
}).not.toThrow();
});
it('customize separator', () => {
@ -322,90 +43,36 @@ describe('RangePicker', () => {
// https://github.com/ant-design/ant-design/issues/13302
describe('in "month" mode, when the left and right panels select the same month', () => {
it('left panel and right panel could be the same month', () => {
const wrapper = mount(<RangePicker mode={['month', 'month']} />);
wrapper.setProps({ value: [moment(), moment()] });
expect(
wrapper
.find('.ant-calendar-range-picker-input')
.first()
.getDOMNode().value,
).toBe(
wrapper
.find('.ant-calendar-range-picker-input')
.last()
.getDOMNode().value,
);
});
it('the cell status is correct', () => {
const wrapper = mount(<RangePicker mode={['month', 'month']} />);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper
.find('.ant-calendar-range-left .ant-calendar-month-panel-cell')
.at(3)
.simulate('click');
wrapper
.find('.ant-calendar-range-right .ant-calendar-month-panel-cell')
.at(3)
.simulate('click');
expect(
wrapper
.find('.ant-calendar-range-left .ant-calendar-month-panel-cell')
.at(3)
.hasClass('ant-calendar-month-panel-selected-cell'),
).toBe(true);
expect(
wrapper
.find('.ant-calendar-range-left .ant-calendar-month-panel-cell')
.at(3)
.hasClass('ant-calendar-month-panel-selected-cell'),
).toBe(true);
expect(
wrapper
.find('.ant-calendar-range-left .ant-calendar-month-panel-cell')
.at(2)
.hasClass('ant-calendar-month-panel-cell-disabled'),
).toBe(false);
expect(
wrapper
.find('.ant-calendar-range-left .ant-calendar-month-panel-cell')
.at(4)
.hasClass('ant-calendar-month-panel-cell-disabled'),
).toBe(true);
expect(
wrapper
.find('.ant-calendar-range-right .ant-calendar-month-panel-cell')
.at(2)
.hasClass('ant-calendar-month-panel-cell-disabled'),
).toBe(true);
expect(
wrapper
.find('.ant-calendar-range-right .ant-calendar-month-panel-cell')
.at(4)
.hasClass('ant-calendar-month-panel-cell-disabled'),
).toBe(false);
});
});
class Test extends React.Component {
state = {
value: null,
};
// https://github.com/ant-design/ant-design/issues/17135
it('the end time should be less than the start time', () => {
const wrapper = mount(<RangePicker defaultValue={[moment(), moment()]} />);
wrapper.find('.ant-calendar-picker-input').simulate('click');
const firstInput = wrapper.find('.ant-calendar-input').first();
const secondInput = wrapper.find('.ant-calendar-input').last();
firstInput.simulate('change', {
target: {
value: moment()
.add(1, 'day')
.format('YYYY-MM-DD'),
},
onPanelChange = value => {
this.setState({ value });
};
render() {
return (
<RangePicker
value={this.state.value}
mode={['month', 'month']}
onPanelChange={this.onPanelChange}
/>
);
}
}
const wrapper = mount(<Test />);
openPicker(wrapper);
selectCell(wrapper, 'Feb');
openPicker(wrapper, 1);
selectCell(wrapper, 'Feb');
closePicker(wrapper, 1);
const { value } = wrapper.state();
expect(value[0].isSame(value[1], 'date')).toBeTruthy();
});
expect(firstInput.getDOMNode().value).toBe(
moment()
.add(1, 'day')
.format('YYYY-MM-DD'),
);
expect(secondInput.getDOMNode().value).toBe('');
});
});

View File

@ -1,10 +1,8 @@
import React from 'react';
import { mount, render } from 'enzyme';
import moment from 'moment';
import { mount } from 'enzyme';
import { setMockDate, resetMockDate } from '../../../tests/utils';
import DatePicker from '..';
import focusTest from '../../../tests/shared/focusTest';
import { openPanel } from './utils';
const { WeekPicker } = DatePicker;
@ -17,71 +15,10 @@ describe('WeekPicker', () => {
resetMockDate();
});
focusTest(WeekPicker);
focusTest(WeekPicker, true);
it('should support style prop', () => {
const wrapper = mount(<WeekPicker style={{ width: 400 }} />);
expect(wrapper.render()).toMatchSnapshot();
});
it('extra footer works', () => {
const wrapper = mount(
<WeekPicker renderExtraFooter={mode => <span className="extra-node">{mode}</span>} />,
);
openPanel(wrapper);
let extraNode = wrapper.find('.extra-node');
expect(extraNode.length).toBe(1);
expect(extraNode.text()).toBe('date');
wrapper
.find('.ant-calendar-month-select')
.hostNodes()
.simulate('click');
extraNode = wrapper.find('.ant-calendar-month-panel .extra-node');
expect(extraNode.length).toBe(1);
expect(extraNode.text()).toBe('month');
wrapper
.find('.ant-calendar-year-select')
.hostNodes()
.simulate('click');
extraNode = wrapper.find('.ant-calendar-year-panel .extra-node');
expect(extraNode.length).toBe(1);
expect(extraNode.text()).toBe('year');
wrapper
.find('.ant-calendar-year-panel-decade-select')
.hostNodes()
.simulate('click');
extraNode = wrapper.find('.ant-calendar-decade-panel .extra-node');
expect(extraNode.length).toBe(1);
expect(extraNode.text()).toBe('decade');
});
it('should support dateRender', () => {
const wrapper = mount(
<WeekPicker open dateRender={current => <span>{current.format('YYYY-MM-DD')}</span>} />,
);
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
});
it('should support allowClear', () => {
const onChange = jest.fn();
const wrapper = mount(
<WeekPicker defaultValue={moment()} onChange={onChange} />,
);
wrapper
.find('.ant-calendar-picker-clear')
.hostNodes()
.simulate('click');
expect(onChange).toHaveBeenCalledWith(null, '');
});
});

View File

@ -1,826 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`WeekPicker should support dateRender 1`] = `
<div>
<div
class="ant-calendar-picker-container"
style="opacity:0"
>
<div
class="ant-calendar ant-calendar-week-number"
tabindex="0"
>
<div
class="ant-calendar-panel"
>
<div
class="ant-calendar-date-panel"
tabindex="0"
>
<div
class="ant-calendar-header"
>
<div
style="position:relative"
>
<a
class="ant-calendar-prev-year-btn"
role="button"
title="Last year (Control + left)"
/>
<a
class="ant-calendar-prev-month-btn"
role="button"
title="Previous month (PageUp)"
/>
<span
class="ant-calendar-my-select"
>
<a
class="ant-calendar-month-select"
role="button"
title="Choose a month"
>
Sep
</a>
<a
class="ant-calendar-year-select"
role="button"
title="Choose a year"
>
2017
</a>
</span>
<a
class="ant-calendar-next-month-btn"
title="Next month (PageDown)"
/>
<a
class="ant-calendar-next-year-btn"
title="Next year (Control + right)"
/>
</div>
</div>
<div
class="ant-calendar-body"
>
<table
cellspacing="0"
class="ant-calendar-table"
role="grid"
>
<thead>
<tr
role="row"
>
<th
class="ant-calendar-column-header ant-calendar-week-number-header"
role="columnheader"
>
<span
class="ant-calendar-column-header-inner"
>
x
</span>
</th>
<th
class="ant-calendar-column-header"
role="columnheader"
title="Sun"
>
<span
class="ant-calendar-column-header-inner"
>
Su
</span>
</th>
<th
class="ant-calendar-column-header"
role="columnheader"
title="Mon"
>
<span
class="ant-calendar-column-header-inner"
>
Mo
</span>
</th>
<th
class="ant-calendar-column-header"
role="columnheader"
title="Tue"
>
<span
class="ant-calendar-column-header-inner"
>
Tu
</span>
</th>
<th
class="ant-calendar-column-header"
role="columnheader"
title="Wed"
>
<span
class="ant-calendar-column-header-inner"
>
We
</span>
</th>
<th
class="ant-calendar-column-header"
role="columnheader"
title="Thu"
>
<span
class="ant-calendar-column-header-inner"
>
Th
</span>
</th>
<th
class="ant-calendar-column-header"
role="columnheader"
title="Fri"
>
<span
class="ant-calendar-column-header-inner"
>
Fr
</span>
</th>
<th
class="ant-calendar-column-header"
role="columnheader"
title="Sat"
>
<span
class="ant-calendar-column-header-inner"
>
Sa
</span>
</th>
</tr>
</thead>
<tbody
class="ant-calendar-tbody"
>
<tr
class=""
role="row"
>
<td
class="ant-calendar-week-number-cell"
role="gridcell"
>
35
</td>
<td
class="ant-calendar-cell ant-calendar-last-month-cell"
role="gridcell"
title="August 27, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-08-27
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-last-month-cell"
role="gridcell"
title="August 28, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-08-28
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-last-month-cell"
role="gridcell"
title="August 29, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-08-29
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-last-month-cell"
role="gridcell"
title="August 30, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-08-30
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-last-month-cell ant-calendar-last-day-of-month"
role="gridcell"
title="August 31, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-08-31
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 1, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-01
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 2, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-02
</span>
</div>
</td>
</tr>
<tr
class=""
role="row"
>
<td
class="ant-calendar-week-number-cell"
role="gridcell"
>
36
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 3, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-03
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 4, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-04
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 5, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-05
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 6, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-06
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 7, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-07
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 8, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-08
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 9, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-09
</span>
</div>
</td>
</tr>
<tr
class=""
role="row"
>
<td
class="ant-calendar-week-number-cell"
role="gridcell"
>
37
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 10, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-10
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 11, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-11
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 12, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-12
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 13, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-13
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 14, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-14
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 15, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-15
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 16, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-16
</span>
</div>
</td>
</tr>
<tr
class="ant-calendar-current-week ant-calendar-active-week"
role="row"
>
<td
class="ant-calendar-week-number-cell"
role="gridcell"
>
38
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 17, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-17
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-today ant-calendar-selected-day"
role="gridcell"
title="September 18, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-18
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 19, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-19
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 20, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-20
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 21, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-21
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 22, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-22
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 23, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-23
</span>
</div>
</td>
</tr>
<tr
class=""
role="row"
>
<td
class="ant-calendar-week-number-cell"
role="gridcell"
>
39
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 24, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-24
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 25, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-25
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 26, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-26
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 27, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-27
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 28, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-28
</span>
</div>
</td>
<td
class="ant-calendar-cell"
role="gridcell"
title="September 29, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-29
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-last-day-of-month"
role="gridcell"
title="September 30, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-09-30
</span>
</div>
</td>
</tr>
<tr
class=""
role="row"
>
<td
class="ant-calendar-week-number-cell"
role="gridcell"
>
40
</td>
<td
class="ant-calendar-cell ant-calendar-next-month-btn-day"
role="gridcell"
title="October 1, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-10-01
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-next-month-btn-day"
role="gridcell"
title="October 2, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-10-02
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-next-month-btn-day"
role="gridcell"
title="October 3, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-10-03
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-next-month-btn-day"
role="gridcell"
title="October 4, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-10-04
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-next-month-btn-day"
role="gridcell"
title="October 5, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-10-05
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-next-month-btn-day"
role="gridcell"
title="October 6, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-10-06
</span>
</div>
</td>
<td
class="ant-calendar-cell ant-calendar-next-month-btn-day"
role="gridcell"
title="October 7, 2017"
>
<div
class="ant-calendar-date"
>
<span>
2017-10-07
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`WeekPicker should support style prop 1`] = `
<span
class="ant-calendar-picker"
<div
class="ant-picker"
style="width: 400px;"
>
<span
style="display: inline-block; width: 100%;"
<div
class="ant-picker-input"
>
<input
class="ant-calendar-picker-input ant-input"
placeholder="Select date"
placeholder="Select week"
readonly=""
size="12"
value=""
/>
<span
aria-label="calendar"
class="anticon anticon-calendar ant-calendar-picker-icon"
role="img"
class="ant-picker-suffix"
>
<svg
aria-hidden="true"
class=""
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
<svg
aria-hidden="true"
class=""
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</span>
</span>
</div>
</div>
`;

View File

@ -1,89 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import moment from 'moment';
import MockDate from 'mockdate';
import DatePicker from '..';
import Input from '../../input';
import { selectDate, openPanel } from './utils';
const { MonthPicker, WeekPicker, RangePicker } = DatePicker;
describe('DatePicker', () => {
beforeEach(() => {
MockDate.set(moment('2016-11-22'));
});
afterEach(() => {
MockDate.reset();
});
it('should focus trigger input after select date in DatePicker', () => {
const wrapper = mount(<DatePicker />);
openPanel(wrapper);
selectDate(wrapper, moment('2016-11-23'));
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode()).toBe(document.activeElement);
});
it('should focus trigger input after select date in RangePicker', () => {
const wrapper = mount(<RangePicker />);
openPanel(wrapper);
selectDate(wrapper, moment('2016-11-23'), 0);
selectDate(wrapper, moment('2016-11-28'), 1);
expect(wrapper.find('.ant-calendar-picker').getDOMNode()).toBe(document.activeElement);
});
it('should focus trigger input after select date in MonthPicker', () => {
const wrapper = mount(<MonthPicker />);
openPanel(wrapper);
wrapper
.find('.ant-calendar-month-panel-month')
.first()
.simulate('click');
wrapper
.find('.ant-calendar-month-panel-cell')
.at(6)
.hasClass('ant-calendar-month-panel-selected-cell');
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode()).toBe(document.activeElement);
});
it('should focus trigger input after select date in WeekPicker', () => {
const wrapper = mount(<WeekPicker />);
openPanel(wrapper);
selectDate(wrapper, moment('2016-11-23'));
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode()).toBe(document.activeElement);
});
it('should not auto focus trigger input when open prop is true in DatePicker', () => {
const wrapper = mount(<DatePicker open />);
const wrapperInput = mount(<Input />);
wrapperInput.instance().select();
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode()).not.toBe(
document.activeElement,
);
});
it('should not auto focus trigger input when open prop is true in RangePicker', () => {
const wrapper = mount(<RangePicker open />);
const wrapperInput = mount(<Input />);
wrapperInput.instance().select();
expect(wrapper.find('.ant-calendar-picker').getDOMNode()).not.toBe(document.activeElement);
});
it('should not auto focus trigger input when open prop is true in WeekPicker', () => {
const wrapper = mount(<WeekPicker open />);
const wrapperInput = mount(<Input />);
wrapperInput.instance().select();
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode()).not.toBe(
document.activeElement,
);
});
it('should not auto focus trigger input when open prop is true in MonthPicker', () => {
const wrapper = mount(<MonthPicker open />);
const wrapperInput = mount(<Input />);
wrapperInput.instance().select();
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode()).not.toBe(
document.activeElement,
);
});
});

View File

@ -1,52 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import DatePicker from '..';
const { MonthPicker, WeekPicker, RangePicker } = DatePicker;
describe('invalid value or defaultValue', () => {
beforeAll(() => {
jest.spyOn(console, 'error').mockImplementation(() => undefined);
});
afterAll(() => {
// eslint-disable-next-line no-console
console.error.mockRestore();
});
it('DatePicker should throw error when value or defaultValue is not moment object', () => {
expect(() => {
mount(<DatePicker value={{}} />);
}).toThrow('The value/defaultValue of DatePicker or MonthPicker must be a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value');
expect(() => {
mount(<DatePicker defaultValue={{}} />);
}).toThrow('The value/defaultValue of DatePicker or MonthPicker must be a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value')
});
it('WeekPicker should throw error when value or defaultValue is not moment object', () => {
expect(() => {
mount(<WeekPicker value={{}} />);
}).toThrow('The value/defaultValue of WeekPicker must be a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value');
expect(() => {
mount(<WeekPicker defaultValue={{}} />);
}).toThrow('The value/defaultValue of WeekPicker must be a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value');
});
it('RangePicker should throw error when value or defaultValue is not moment object', () => {
expect(() => {
mount(<RangePicker value={[{}, {}]} />);
}).toThrow('The value/defaultValue of RangePicker must be a moment object array after `antd@2.0`, see: https://u.ant.design/date-picker-value');
expect(() => {
mount(<RangePicker defaultValue={[{}, {}]} />);
}).toThrow('The value/defaultValue of RangePicker must be a moment object array after `antd@2.0`, see: https://u.ant.design/date-picker-value')
});
it('MonthPicker should throw error when value or defaultValue is not moment object', () => {
expect(() => {
mount(<MonthPicker value={{}} />);
}).toThrow('The value/defaultValue of DatePicker or MonthPicker must be a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value');
expect(() => {
mount(<MonthPicker defaultValue={{}} />);
}).toThrow('The value/defaultValue of DatePicker or MonthPicker must be a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value')
});
});

View File

@ -1,155 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import moment from 'moment';
import DatePicker from '..';
const { RangePicker } = DatePicker;
describe('DatePicker with showTime', () => {
it('should trigger onChange when select value', () => {
const onChangeFn = jest.fn();
const onOpenChangeFn = jest.fn();
const wrapper = mount(
<DatePicker showTime open onChange={onChangeFn} onOpenChange={onOpenChangeFn} />,
);
const calendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
calendarWrapper
.find('.ant-calendar-date')
.at(0)
.simulate('click');
expect(onChangeFn).toHaveBeenCalled();
expect(onOpenChangeFn).not.toHaveBeenCalled();
});
it('should trigger onOk when press ok button', () => {
const onOkFn = jest.fn();
const onOpenChangeFn = jest.fn();
const onChangeFn = jest.fn();
const wrapper = mount(
<DatePicker
showTime
open
onChange={onChangeFn}
onOk={onOkFn}
onOpenChange={onOpenChangeFn}
defaultValue={moment()}
/>,
);
const calendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
calendarWrapper.find('.ant-calendar-ok-btn').simulate('click');
expect(onOkFn).toHaveBeenCalled();
expect(onOpenChangeFn).toHaveBeenCalledWith(false);
expect(onChangeFn).not.toHaveBeenCalled();
});
it('should trigger onChange when click Now link', () => {
const onOpenChangeFn = jest.fn();
const onChangeFn = jest.fn();
const wrapper = mount(
<DatePicker showTime open onChange={onChangeFn} onOpenChange={onOpenChangeFn} />,
);
const calendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
calendarWrapper.find('.ant-calendar-today-btn').simulate('click');
expect(onOpenChangeFn).toHaveBeenCalledWith(false);
expect(onChangeFn).toHaveBeenCalled();
});
it('should have correct className when use12Hours is true', () => {
const wrapper = mount(<DatePicker showTime={{ use12Hours: true }} open />);
const calendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
expect(calendarWrapper.find('.ant-calendar-time-picker-column-4').length).toBe(0);
calendarWrapper
.find('.ant-calendar-time-picker-btn')
.at(0)
.simulate('click');
expect(calendarWrapper.find('.ant-calendar-time-picker-column-4').hostNodes().length).toBe(1);
});
});
describe('RangePicker with showTime', () => {
it('should trigger onChange when select value', () => {
const onChangeFn = jest.fn();
const onOpenChangeFn = jest.fn();
const wrapper = mount(
<RangePicker showTime open onChange={onChangeFn} onOpenChange={onOpenChangeFn} />,
);
function findNode(selector) {
return wrapper.find('Trigger').find(selector);
}
expect(
findNode('.ant-calendar-time-picker-btn').hasClass('ant-calendar-time-picker-btn-disabled'),
).toBe(true);
expect(findNode('.ant-calendar-ok-btn').hasClass('ant-calendar-ok-btn-disabled')).toBe(true);
findNode('.ant-calendar-date')
.at(10)
.simulate('click');
findNode('.ant-calendar-date')
.at(11)
.simulate('click');
expect(
findNode('.ant-calendar-time-picker-btn').hasClass('ant-calendar-time-picker-btn-disabled'),
).toBe(false);
expect(findNode('.ant-calendar-ok-btn').hasClass('ant-calendar-ok-btn-disabled')).toBe(false);
expect(onChangeFn).toHaveBeenCalled();
expect(onOpenChangeFn).not.toHaveBeenCalled();
});
it('should trigger onOk when press ok button', () => {
const onOkFn = jest.fn();
const onChangeFn = jest.fn();
const onOpenChangeFn = jest.fn();
const wrapper = mount(
<RangePicker
showTime
open
onOk={onOkFn}
onChange={onChangeFn}
onOpenChange={onOpenChangeFn}
/>,
);
function findNode(selector) {
return wrapper.find('Trigger').find(selector);
}
findNode('.ant-calendar-date')
.at(10)
.simulate('click');
findNode('.ant-calendar-date')
.at(11)
.simulate('click');
onChangeFn.mockClear();
findNode('.ant-calendar-ok-btn').simulate('click');
expect(onOkFn).toHaveBeenCalled();
expect(onOpenChangeFn).toHaveBeenCalledWith(false);
expect(onChangeFn).not.toHaveBeenCalled();
});
});

View File

@ -31,3 +31,38 @@ export function nextYear(wrapper) {
export function nextMonth(wrapper) {
wrapper.find('.ant-calendar-next-month-btn').simulate('click');
}
export function openPicker(wrapper, index = 0) {
wrapper
.find('input')
.at(index)
.simulate('mousedown')
.simulate('focus');
}
export function closePicker(wrapper, index = 0) {
wrapper
.find('input')
.at(index)
.simulate('blur');
}
export function selectCell(wrapper, text, index = 0) {
let matchCell;
wrapper
.find('table')
.at(index)
.find('td')
.forEach(td => {
if (td.text() === String(text) && td.props().className.includes('-in-view')) {
matchCell = td;
td.simulate('click');
}
});
if (!matchCell) {
throw new Error('Cell not match in picker panel.');
}
return matchCell;
}

View File

@ -1,275 +0,0 @@
import * as React from 'react';
import * as moment from 'moment';
import { polyfill } from 'react-lifecycles-compat';
import MonthCalendar from 'rc-calendar/lib/MonthCalendar';
import RcDatePicker from 'rc-calendar/lib/Picker';
import classNames from 'classnames';
import omit from 'omit.js';
import { CloseCircleFilled, CalendarOutlined } from '@ant-design/icons';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import warning from '../_util/warning';
import interopDefault from '../_util/interopDefault';
import getDataOrAriaProps from '../_util/getDataOrAriaProps';
import { formatDate } from './utils';
export interface PickerProps {
value?: moment.Moment;
open?: boolean;
prefixCls: string;
}
export interface PickerState {
open: boolean;
value: moment.Moment | null;
showDate: moment.Moment | null;
}
export default function createPicker(TheCalendar: React.ComponentClass): any {
class CalenderWrapper extends React.Component<any, PickerState> {
static defaultProps = {
allowClear: true,
showToday: true,
};
static getDerivedStateFromProps(nextProps: PickerProps, prevState: PickerState) {
const state: Partial<PickerState> = {};
let { open } = prevState;
if ('open' in nextProps) {
state.open = nextProps.open;
open = nextProps.open || false;
}
if ('value' in nextProps) {
state.value = nextProps.value;
if (
nextProps.value !== prevState.value ||
(!open && nextProps.value !== prevState.showDate)
) {
state.showDate = nextProps.value;
}
}
return Object.keys(state).length > 0 ? state : null;
}
private input: any;
private prefixCls?: string;
constructor(props: any) {
super(props);
const value = props.value || props.defaultValue;
if (value && !interopDefault(moment).isMoment(value)) {
throw new Error(
'The value/defaultValue of DatePicker or MonthPicker must be ' +
'a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value',
);
}
this.state = {
value,
showDate: value,
open: false,
};
}
componentDidUpdate(_: PickerProps, prevState: PickerState) {
if (!('open' in this.props) && prevState.open && !this.state.open) {
this.focus();
}
}
saveInput = (node: any) => {
this.input = node;
};
clearSelection = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
e.stopPropagation();
this.handleChange(null);
};
handleChange = (value: moment.Moment | null) => {
const { props } = this;
if (!('value' in props)) {
this.setState({
value,
showDate: value,
});
}
props.onChange(value, formatDate(value, props.format));
};
handleCalendarChange = (value: moment.Moment) => {
this.setState({ showDate: value });
};
handleOpenChange = (open: boolean) => {
const { onOpenChange } = this.props;
if (!('open' in this.props)) {
this.setState({ open });
}
if (onOpenChange) {
onOpenChange(open);
}
};
focus() {
this.input.focus();
}
blur() {
this.input.blur();
}
renderFooter = (...args: any[]) => {
const { renderExtraFooter } = this.props;
const { prefixCls } = this;
return renderExtraFooter ? (
<div className={`${prefixCls}-footer-extra`}>{renderExtraFooter(...args)}</div>
) : null;
};
renderPicker = ({ getPrefixCls }: ConfigConsumerProps) => {
const { value, showDate, open } = this.state;
const props = omit(this.props, ['onChange']);
const { prefixCls: customizePrefixCls, locale, localeCode, suffixIcon } = props;
const prefixCls = getPrefixCls('calendar', customizePrefixCls);
// To support old version react.
// Have to add prefixCls on the instance.
// https://github.com/facebook/react/issues/12397
this.prefixCls = prefixCls;
const placeholder = 'placeholder' in props ? props.placeholder : locale.lang.placeholder;
const disabledTime = props.showTime ? props.disabledTime : null;
const calendarClassName = classNames({
[`${prefixCls}-time`]: props.showTime,
[`${prefixCls}-month`]: MonthCalendar === TheCalendar,
});
if (value && localeCode) {
value.locale(localeCode);
}
let pickerProps: Object = {};
let calendarProps: any = {};
const pickerStyle: { minWidth?: number } = {};
if (props.showTime) {
calendarProps = {
// fix https://github.com/ant-design/ant-design/issues/1902
onSelect: this.handleChange,
};
pickerStyle.minWidth = 195;
} else {
pickerProps = {
onChange: this.handleChange,
};
}
if ('mode' in props) {
calendarProps.mode = props.mode;
}
warning(
!('onOK' in props),
'DatePicker',
'It should be `DatePicker[onOk]` or `MonthPicker[onOk]`, instead of `onOK`!',
);
const calendar = (
<TheCalendar
{...calendarProps}
disabledDate={props.disabledDate}
disabledTime={disabledTime}
locale={locale.lang}
timePicker={props.timePicker}
defaultValue={props.defaultPickerValue || interopDefault(moment)()}
dateInputPlaceholder={placeholder}
prefixCls={prefixCls}
className={calendarClassName}
onOk={props.onOk}
dateRender={props.dateRender}
format={props.format}
showToday={props.showToday}
monthCellContentRender={props.monthCellContentRender}
renderFooter={this.renderFooter}
onPanelChange={props.onPanelChange}
onChange={this.handleCalendarChange}
value={showDate}
/>
);
const clearIcon =
!props.disabled && props.allowClear && value ? (
<CloseCircleFilled
className={`${prefixCls}-picker-clear`}
onClick={this.clearSelection}
/>
) : null;
const inputIcon = (suffixIcon &&
(React.isValidElement<{ className?: string }>(suffixIcon) ? (
React.cloneElement(suffixIcon, {
className: classNames({
[suffixIcon.props.className!]: suffixIcon.props.className,
[`${prefixCls}-picker-icon`]: true,
}),
})
) : (
<span className={`${prefixCls}-picker-icon`}>{suffixIcon}</span>
))) || <CalendarOutlined className={`${prefixCls}-picker-icon`} />;
const dataOrAriaProps = getDataOrAriaProps(props);
const input = ({ value: inputValue }: { value: moment.Moment | null }) => (
<div>
<input
ref={this.saveInput}
disabled={props.disabled}
readOnly
value={formatDate(inputValue, props.format)}
placeholder={placeholder}
className={props.pickerInputClass}
tabIndex={props.tabIndex}
name={props.name}
{...dataOrAriaProps}
/>
{clearIcon}
{inputIcon}
</div>
);
return (
<span
id={props.id}
className={classNames(props.className, props.pickerClass)}
style={{ ...pickerStyle, ...props.style }}
onFocus={props.onFocus}
onBlur={props.onBlur}
onMouseEnter={props.onMouseEnter}
onMouseLeave={props.onMouseLeave}
>
<RcDatePicker
{...props}
{...pickerProps}
calendar={calendar}
value={value}
prefixCls={`${prefixCls}-picker-container`}
style={props.popupStyle}
open={open}
onOpenChange={this.handleOpenChange}
>
{input}
</RcDatePicker>
</span>
);
};
render() {
return <ConfigConsumer>{this.renderPicker}</ConfigConsumer>;
}
}
polyfill(CalenderWrapper);
return CalenderWrapper;
}

View File

@ -16,8 +16,6 @@ Basic use case. Users can select or input a date in panel.
```jsx
import { DatePicker } from 'antd';
const { MonthPicker, RangePicker, WeekPicker } = DatePicker;
function onChange(date, dateString) {
console.log(date, dateString);
}
@ -26,11 +24,11 @@ ReactDOM.render(
<div>
<DatePicker onChange={onChange} />
<br />
<MonthPicker onChange={onChange} placeholder="Select month" />
<DatePicker onChange={onChange} picker="week" />
<br />
<RangePicker onChange={onChange} />
<DatePicker onChange={onChange} picker="month" />
<br />
<WeekPicker onChange={onChange} placeholder="Select week" />
<DatePicker onChange={onChange} picker="year" />
</div>,
mountNode,
);

View File

@ -17,7 +17,7 @@ Disabled part of dates and time by `disabledDate` and `disabledTime` respectivel
import moment from 'moment';
import { DatePicker } from 'antd';
const { MonthPicker, RangePicker } = DatePicker;
const { RangePicker } = DatePicker;
function range(start, end) {
const result = [];
@ -64,7 +64,7 @@ ReactDOM.render(
showTime={{ defaultValue: moment('00:00:00', 'HH:mm:ss') }}
/>
<br />
<MonthPicker disabledDate={disabledDate} placeholder="Select month" />
<DatePicker picker="month" disabledDate={disabledDate} />
<br />
<RangePicker
disabledDate={disabledDate}

View File

@ -7,11 +7,11 @@ title:
## zh-CN
选择框的不可用状态。
选择框的不可用状态。你也可以通过数组单独禁用 RangePicker 的其中一项。
## en-US
A disabled state of the `DatePicker`.
A disabled state of the `DatePicker`. You can also set as array to disable one of input.
```jsx
import { DatePicker } from 'antd';
@ -30,6 +30,11 @@ ReactDOM.render(
defaultValue={[moment('2015-06-06', dateFormat), moment('2015-06-06', dateFormat)]}
disabled
/>
<br />
<RangePicker
defaultValue={[moment('2019-09-03', dateFormat), moment('2019-11-22', dateFormat)]}
disabled={[false, true]}
/>
</div>,
mountNode,
);

View File

@ -16,15 +16,19 @@ Render extra footer in panel for customized requirements.
```jsx
import { DatePicker } from 'antd';
const { RangePicker, MonthPicker } = DatePicker;
const { RangePicker } = DatePicker;
ReactDOM.render(
<div>
<DatePicker renderExtraFooter={() => 'extra footer'} />
<br />
<DatePicker renderExtraFooter={() => 'extra footer'} showTime />
<br />
<RangePicker renderExtraFooter={() => 'extra footer'} />
<br />
<RangePicker renderExtraFooter={() => 'extra footer'} showTime />
<MonthPicker renderExtraFooter={() => 'extra footer'} placeholder="Select month" />
<br />
<DatePicker renderExtraFooter={() => 'extra footer'} picker="month" />
</div>,
mountNode,
);

View File

@ -1,5 +1,5 @@
---
order: 1
order: 2
title:
zh-CN: 日期格式
en-US: Date Format
@ -17,7 +17,7 @@ We can set the date format by `format`.
import { DatePicker } from 'antd';
import moment from 'moment';
const { MonthPicker, RangePicker } = DatePicker;
const { RangePicker } = DatePicker;
const dateFormat = 'YYYY/MM/DD';
const monthFormat = 'YYYY/MM';
@ -30,7 +30,7 @@ ReactDOM.render(
<br />
<DatePicker defaultValue={moment('01/01/2015', dateFormatList[0])} format={dateFormatList} />
<br />
<MonthPicker defaultValue={moment('2015/01', monthFormat)} format={monthFormat} />
<DatePicker defaultValue={moment('2015/01', monthFormat)} format={monthFormat} picker="month" />
<br />
<RangePicker
defaultValue={[moment('2015/01/01', dateFormat), moment('2015/01/01', dateFormat)]}

View File

@ -1,8 +1,9 @@
---
order: 11
order: 99
title:
zh-CN: 受控面板
en-US: Controlled Panels
debug: true
---
## zh-CN

View File

@ -0,0 +1,35 @@
---
order: 1
title:
zh-CN: 范围选择器
en-US: Range Picker
---
## zh-CN
通过设置 `picker` 属性,指定范围选择器类型。
## en-US
Set range picker type by `picker` prop.
```jsx
import { DatePicker } from 'antd';
const { RangePicker } = DatePicker;
ReactDOM.render(
<div>
<RangePicker />
<br />
<RangePicker showTime />
<br />
<RangePicker picker="week" />
<br />
<RangePicker picker="month" />
<br />
<RangePicker picker="year" />
</div>,
mountNode,
);
```

View File

@ -1,5 +1,5 @@
---
order: 2
order: 11
title:
zh-CN: 三种大小
en-US: Three Sizes
@ -16,7 +16,7 @@ The input box comes in three sizes. `default` will be used if `size` is omitted.
```jsx
import { DatePicker, Radio } from 'antd';
const { MonthPicker, RangePicker, WeekPicker } = DatePicker;
const { RangePicker } = DatePicker;
class PickerSizesDemo extends React.Component {
state = {
@ -40,11 +40,11 @@ class PickerSizesDemo extends React.Component {
<br />
<DatePicker size={size} />
<br />
<MonthPicker size={size} placeholder="Select Month" />
<DatePicker size={size} picker="month" />
<br />
<RangePicker size={size} />
<br />
<WeekPicker size={size} placeholder="Select Week" />
<DatePicker size={size} picker="week" />
</div>
);
}

View File

@ -1,8 +1,9 @@
---
order: 6
order: 99
title:
zh-CN: 自定义日期范围选择
en-US: Customized Range Picker
debug: true
---
## zh-CN

View File

@ -1,5 +1,5 @@
---
order: 13
order: 99
debug: true
title:
zh-CN: 后缀图标
@ -19,7 +19,7 @@ import { DatePicker } from 'antd';
import { SmileOutlined } from '@ant-design/icons';
const smileIcon = <SmileOutlined />;
const { MonthPicker, RangePicker, WeekPicker } = DatePicker;
const { RangePicker } = DatePicker;
function onChange(date, dateString) {
console.log(date, dateString);
@ -29,19 +29,19 @@ ReactDOM.render(
<div>
<DatePicker suffixIcon={smileIcon} onChange={onChange} />
<br />
<MonthPicker suffixIcon={smileIcon} onChange={onChange} placeholder="Select month" />
<DatePicker suffixIcon={smileIcon} onChange={onChange} picker="month" />
<br />
<RangePicker suffixIcon={smileIcon} onChange={onChange} />
<br />
<WeekPicker suffixIcon={smileIcon} onChange={onChange} placeholder="Select week" />
<DatePicker suffixIcon={smileIcon} onChange={onChange} picker="week" />
<br />
<DatePicker suffixIcon="ab" onChange={onChange} />
<br />
<MonthPicker suffixIcon="ab" onChange={onChange} placeholder="Select month" />
<DatePicker suffixIcon="ab" onChange={onChange} picker="month" />
<br />
<RangePicker suffixIcon="ab" onChange={onChange} />
<br />
<WeekPicker suffixIcon="ab" onChange={onChange} placeholder="Select week" />
<DatePicker suffixIcon="ab" onChange={onChange} picker="week" />
</div>,
mountNode,
);

View File

@ -29,12 +29,11 @@ function onOk(value) {
ReactDOM.render(
<div>
<DatePicker showTime placeholder="Select Time" onChange={onChange} onOk={onOk} />
<DatePicker showTime onChange={onChange} onOk={onOk} />
<br />
<RangePicker
showTime={{ format: 'HH:mm' }}
format="YYYY-MM-DD HH:mm"
placeholder={['Start Time', 'End Time']}
onChange={onChange}
onOk={onOk}
/>

View File

@ -0,0 +1,317 @@
import * as React from 'react';
import classNames from 'classnames';
import RCPicker, { RangePicker as RCRangePicker } from 'rc-picker';
import { GenerateConfig } from 'rc-picker/lib/generate/index';
import {
PickerBaseProps as RCPickerBaseProps,
PickerDateProps as RCPickerDateProps,
PickerTimeProps as RCPickerTimeProps,
} from 'rc-picker/lib/Picker';
import { SharedTimeProps } from 'rc-picker/lib/panels/TimePanel';
import {
RangePickerBaseProps as RCRangePickerBaseProps,
RangePickerDateProps as RCRangePickerDateProps,
RangePickerTimeProps as RCRangePickerTimeProps,
} from 'rc-picker/lib/RangePicker';
import { PickerMode } from 'rc-picker/lib/interface';
import { CalendarOutlined, ClockCircleOutlined, CloseCircleFilled } from '@ant-design/icons';
import { ConfigContext, ConfigConsumerProps } from '../config-provider';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import enUS from './locale/en_US';
import { getPlaceholder, getRangePlaceholder } from './util';
import PickerButton from './PickerButton';
import PickerTag from './PickerTag';
const Components = { button: PickerButton, rangeItem: PickerTag };
function toArray<T>(list: T | T[]): T[] {
if (!list) {
return [];
}
return Array.isArray(list) ? list : [list];
}
function getTimeProps<DateType>(
props: { format?: string; picker?: PickerMode } & SharedTimeProps<DateType>,
) {
const { format, picker, showHour, showMinute, showSecond, use12Hours } = props;
const firstFormat = toArray(format)[0];
const showTimeObj: SharedTimeProps<DateType> = { ...props };
if (firstFormat) {
if (!firstFormat.includes('s') && showSecond === undefined) {
showTimeObj.showSecond = false;
}
if (!firstFormat.includes('m') && showMinute === undefined) {
showTimeObj.showMinute = false;
}
if (!firstFormat.includes('H') && !firstFormat.includes('h') && showHour === undefined) {
showTimeObj.showHour = false;
}
if ((firstFormat.includes('a') || firstFormat.includes('A')) && use12Hours === undefined) {
showTimeObj.use12Hours = true;
}
}
if (picker === 'time') {
return showTimeObj;
}
return {
showTime: showTimeObj,
};
}
type InjectDefaultProps<Props> = Omit<
Props,
| 'locale'
| 'generateConfig'
| 'prevIcon'
| 'nextIcon'
| 'superPrevIcon'
| 'superNextIcon'
| 'hideHeader'
| 'components'
> & {
locale?: typeof enUS;
size?: 'large' | 'default' | 'small';
};
// Picker Props
export type PickerBaseProps<DateType> = InjectDefaultProps<RCPickerBaseProps<DateType>>;
export type PickerDateProps<DateType> = InjectDefaultProps<RCPickerDateProps<DateType>>;
export type PickerTimeProps<DateType> = InjectDefaultProps<RCPickerTimeProps<DateType>>;
export type PickerProps<DateType> =
| PickerBaseProps<DateType>
| PickerDateProps<DateType>
| PickerTimeProps<DateType>;
// Range Picker Props
export type RangePickerBaseProps<DateType> = InjectDefaultProps<RCRangePickerBaseProps<DateType>>;
export type RangePickerDateProps<DateType> = InjectDefaultProps<RCRangePickerDateProps<DateType>>;
export type RangePickerTimeProps<DateType> = InjectDefaultProps<RCRangePickerTimeProps<DateType>>;
export type RangePickerProps<DateType> =
| RangePickerBaseProps<DateType>
| RangePickerDateProps<DateType>
| RangePickerTimeProps<DateType>;
function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
// =========================== Picker ===========================
type DatePickerProps = PickerProps<DateType>;
function getPicker<InnerPickerProps extends DatePickerProps>(
picker?: PickerMode,
displayName?: string,
) {
class Picker extends React.Component<InnerPickerProps> {
static contextType = ConfigContext;
static displayName: string;
context: ConfigConsumerProps;
pickerRef = React.createRef<RCPicker<DateType>>();
focus = () => {
if (this.pickerRef.current) {
this.pickerRef.current.focus();
}
};
blur = () => {
if (this.pickerRef.current) {
this.pickerRef.current.blur();
}
};
getDefaultLocale = () => {
const { locale } = this.props;
const result = {
...enUS,
...locale,
};
result.lang = {
...result.lang,
...((locale || {}) as any).lang,
};
return result;
};
renderPicker = (locale: any) => {
const { getPrefixCls } = this.context;
const { prefixCls: customizePrefixCls, className, size, ...restProps } = this.props;
const { format, showTime } = this.props as any;
const prefixCls = getPrefixCls('picker', customizePrefixCls);
const additionalProps = {
showToday: true,
};
let additionalOverrideProps: any = {};
if (picker) {
additionalOverrideProps.picker = picker;
}
const mergedPicker = picker || this.props.picker;
additionalOverrideProps = {
...additionalOverrideProps,
...(showTime ? getTimeProps({ format, picker: mergedPicker, ...showTime }) : {}),
...(mergedPicker === 'time'
? getTimeProps({ format, ...this.props, picker: mergedPicker })
: {}),
};
return (
<RCPicker<DateType>
ref={this.pickerRef}
placeholder={getPlaceholder(mergedPicker, locale)}
suffixIcon={mergedPicker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />}
clearIcon={<CloseCircleFilled />}
allowClear
transitionName="slide-up"
{...additionalProps}
{...restProps}
{...additionalOverrideProps}
locale={locale!.lang}
className={classNames(className, {
[`${prefixCls}-${size}`]: size,
})}
prefixCls={prefixCls}
generateConfig={generateConfig}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
components={Components}
/>
);
};
render() {
return (
<LocaleReceiver componentName="DatePicker" defaultLocale={this.getDefaultLocale}>
{this.renderPicker}
</LocaleReceiver>
);
}
}
if (displayName) {
Picker.displayName = displayName;
}
return Picker as React.ComponentClass<InnerPickerProps>;
}
const DatePicker = getPicker<DatePickerProps>();
const WeekPicker = getPicker<Omit<PickerDateProps<DateType>, 'picker'>>('week', 'WeekPicker');
const MonthPicker = getPicker<Omit<PickerDateProps<DateType>, 'picker'>>('month', 'MonthPicker');
const YearPicker = getPicker<Omit<PickerDateProps<DateType>, 'picker'>>('year', 'YearPicker');
const TimePicker = getPicker<Omit<PickerTimeProps<DateType>, 'picker'>>('time', 'TimePicker');
// ======================== Range Picker ========================
class RangePicker extends React.Component<RangePickerProps<DateType>> {
static contextType = ConfigContext;
context: ConfigConsumerProps;
pickerRef = React.createRef<RCRangePicker<DateType>>();
focus = () => {
if (this.pickerRef.current) {
this.pickerRef.current.focus();
}
};
blur = () => {
if (this.pickerRef.current) {
this.pickerRef.current.blur();
}
};
getDefaultLocale = () => {
const { locale } = this.props;
const result = {
...enUS,
...locale,
};
result.lang = {
...result.lang,
...((locale || {}) as any).lang,
};
return result;
};
renderPicker = (locale: any) => {
const { getPrefixCls } = this.context;
const { prefixCls: customizePrefixCls, className, size, ...restProps } = this.props;
const { format, showTime, picker } = this.props as any;
const prefixCls = getPrefixCls('picker', customizePrefixCls);
let additionalOverrideProps: any = {};
additionalOverrideProps = {
...additionalOverrideProps,
...(showTime ? getTimeProps({ format, picker, ...showTime }) : {}),
...(picker === 'time' ? getTimeProps({ format, ...this.props, picker }) : {}),
};
return (
<RCRangePicker<DateType>
separator={<span className={`${prefixCls}-separator`}></span>}
ref={this.pickerRef}
placeholder={getRangePlaceholder(picker, locale)}
suffixIcon={picker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />}
clearIcon={<CloseCircleFilled />}
allowClear
transitionName="slide-up"
{...restProps}
className={classNames(className, {
[`${prefixCls}-${size}`]: size,
})}
{...additionalOverrideProps}
locale={locale!.lang}
prefixCls={prefixCls}
generateConfig={generateConfig}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
components={Components}
/>
);
};
render() {
return (
<LocaleReceiver componentName="DatePicker" defaultLocale={this.getDefaultLocale}>
{this.renderPicker}
</LocaleReceiver>
);
}
}
// =========================== Export ===========================
type MergedDatePicker = typeof DatePicker & {
WeekPicker: typeof WeekPicker;
MonthPicker: typeof MonthPicker;
YearPicker: typeof YearPicker;
RangePicker: React.ComponentClass<RangePickerProps<DateType>>;
TimePicker: typeof TimePicker;
};
const MergedDatePicker = DatePicker as MergedDatePicker;
MergedDatePicker.WeekPicker = WeekPicker;
MergedDatePicker.MonthPicker = MonthPicker;
MergedDatePicker.YearPicker = YearPicker;
MergedDatePicker.RangePicker = RangePicker;
MergedDatePicker.TimePicker = TimePicker;
return MergedDatePicker;
}
export default generatePicker;

View File

@ -12,12 +12,13 @@ By clicking the input box, you can select a date from a popup calendar.
## API
There are four kinds of picker:
There are five kinds of picker:
- DatePicker
- MonthPicker
- RangePicker
- WeekPicker
- YearPicker
### Localization
@ -31,19 +32,16 @@ import locale from 'antd/es/date-picker/locale/zh_CN';
<DatePicker locale={locale} />;
```
**Note:** Part of locale of DatePicker, MonthPicker, RangePicker, WeekPicker is read from value. So, please set the locale of moment correctly.
```jsx
// The default locale is en-US, if you want to use other locale, just set locale in entry file globally.
import moment from 'moment';
import 'moment/locale/zh-cn';
<DatePicker defaultValue={moment('2015-01-01', 'YYYY-MM-DD')} />;
```
### Common API
The following APIs are shared by DatePicker, MonthPicker, RangePicker, WeekPicker.
The following APIs are shared by DatePicker, YearPicker, MonthPicker, RangePicker, WeekPicker.
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
@ -54,10 +52,11 @@ The following APIs are shared by DatePicker, MonthPicker, RangePicker, WeekPicke
| disabled | determine whether the DatePicker is disabled | boolean | false | |
| disabledDate | specify the date that cannot be selected | (currentDate: moment) => boolean | - | |
| dropdownClassName | to customize the className of the popup calendar | string | - | |
| getCalendarContainer | to set the container of the floating layer, while the default is to create a `div` element in `body` | function(trigger) | - | |
| getPopupContainer | to set the container of the floating layer, while the default is to create a `div` element in `body` | function(trigger) | - | |
| locale | localization configuration | object | [default](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | |
| mode | picker panel mode[Cannot select year or month anymore?](/docs/react/faq#When-set-mode-to-DatePicker/RangePicker,-cannot-select-year-or-month-anymore?) | `time|date|month|year|decade` | 'date' | |
| mode | picker panel mode[Cannot select year or month anymore?](/docs/react/faq#When-set-mode-to-DatePicker/RangePicker,-cannot-select-year-or-month-anymore?) | `time|date|month|year|decade` | - | |
| open | open state of picker | boolean | - | |
| picker | Set picker type | `date`, `week`, `month`, `year` | `date` | |
| placeholder | placeholder of date input | string\|RangePicker\[] | - | |
| popupStyle | to customize the style of the popup calendar | object | {} | |
| size | determine the size of the input box, the height of `large` and `small`, are 40px and 24px respectively, while default size is 32px | string | - | |
@ -90,6 +89,17 @@ The following APIs are shared by DatePicker, MonthPicker, RangePicker, WeekPicke
| onOk | callback when click ok button | function() | - | |
| onPanelChange | Callback function for panel changing | function(value, mode) | - | |
### YearPicker
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| defaultValue | to set default date | [moment](http://momentjs.com/) | - | |
| defaultPickerValue | to set default picker date | [moment](http://momentjs.com/) | - | |
| format | to set the date format, refer to [moment.js](http://momentjs.com/) | string | "YYYY" | |
| renderExtraFooter | render extra footer in panel | () => React.ReactNode | - | |
| value | to set date | [moment](http://momentjs.com/) | - | |
| onChange | a callback function, can be executed when the selected time is changing | function(date: moment, dateString: string) | - | |
### MonthPicker
| Property | Description | Type | Default | Version |
@ -117,8 +127,10 @@ The following APIs are shared by DatePicker, MonthPicker, RangePicker, WeekPicke
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| allowEmpty | Allow start or end input leave empty | \[boolean, boolean] | \[false, false] | |
| defaultValue | to set default date | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - | |
| defaultPickerValue | to set default picker date | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)\] | - | |
| disabled | disable start or end | [boolean, boolean] | - | |
| disabledTime | to specify the time that cannot be selected | function(dates: \[moment, moment], partial: `'start'|'end'`) | - | |
| format | to set the date format, refer to [moment.js](http://momentjs.com/). When an array is provided, all values are used for parsing and first value is used for formatting. | string \| string[] | "YYYY-MM-DD HH:mm:ss" | |
| ranges | preseted ranges for quick selection | { \[range: string]: [moment](http://momentjs.com/)\[] } \| { \[range: string]: () => [moment](http://momentjs.com/)\[] } | - | |
@ -129,10 +141,9 @@ The following APIs are shared by DatePicker, MonthPicker, RangePicker, WeekPicke
| value | to set date | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - | |
| onCalendarChange | a callback function, can be executed when the start time or the end time of the range is changing | function(dates: \[moment, moment], dateStrings: \[string, string]) | - | |
| onChange | a callback function, can be executed when the selected time is changing | function(dates: \[moment, moment], dateStrings: \[string, string]) | - | |
| onOk | callback when click ok button | function(dates: [moment](http://momentjs.com/)\[]) | - | |
<style>
.code-box-demo .ant-calendar-picker {
.code-box-demo .ant-picker {
margin: 0 8px 12px 0;
}
</style>

View File

@ -1,22 +1,7 @@
import * as React from 'react';
import RcCalendar from 'rc-calendar';
import MonthCalendar from 'rc-calendar/lib/MonthCalendar';
import createPicker from './createPicker';
import wrapPicker from './wrapPicker';
import RangePicker from './RangePicker';
import WeekPicker from './WeekPicker';
import { DatePickerProps, DatePickerDecorator } from './interface';
import { Moment } from 'moment';
import momentGenerateConfig from 'rc-picker/lib/generate/moment';
import generatePicker from './generatePicker';
const DatePicker = wrapPicker(createPicker(RcCalendar), 'date') as React.ClassicComponentClass<
DatePickerProps
>;
const DatePicker = generatePicker<Moment>(momentGenerateConfig);
const MonthPicker = wrapPicker(createPicker(MonthCalendar), 'month');
Object.assign(DatePicker, {
RangePicker: wrapPicker(RangePicker, 'date'),
MonthPicker,
WeekPicker: wrapPicker(WeekPicker, 'week'),
});
export default DatePicker as DatePickerDecorator;
export default DatePicker;

View File

@ -13,12 +13,13 @@ subtitle: 日期选择框
## API
日期类组件包括以下种形式。
日期类组件包括以下种形式。
- DatePicker
- MonthPicker
- RangePicker
- WeekPicker
- YearPicker
### 国际化配置
@ -32,20 +33,17 @@ import locale from 'antd/es/date-picker/locale/zh_CN';
<DatePicker locale={locale} />;
```
**注意:**DatePicker、MonthPicker、RangePicker、WeekPicker 部分 locale 是从 value 中读取,所以请先正确设置 moment 的 locale。
```jsx
// 默认语言为 en-US如果你需要设置其他语言推荐在入口文件全局设置 locale
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
<DatePicker defaultValue={moment('2015-01-01', 'YYYY-MM-DD')} />;
```
### 共同的 API
以下 API 为 DatePicker、MonthPicker、RangePicker, WeekPicker 共享的 API。
以下 API 为 DatePicker、YearPicker、MonthPicker、RangePicker, WeekPicker 共享的 API。
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
@ -56,10 +54,11 @@ moment.locale('zh-cn');
| disabled | 禁用 | boolean | false | |
| disabledDate | 不可选择的日期 | (currentDate: moment) => boolean | 无 | |
| dropdownClassName | 额外的弹出日历 className | string | - | |
| getCalendarContainer | 定义浮层的容器,默认为 body 上新建 div | function(trigger) | 无 | |
| getPopupContainer | 定义浮层的容器,默认为 body 上新建 div | function(trigger) | 无 | |
| locale | 国际化配置 | object | [默认配置](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | |
| mode | 日期面板的状态([设置后无法选择年份/月份?](/docs/react/faq#当我指定了-DatePicker/RangePicker-的-mode-属性后,点击后无法选择年份/月份?) | `time|date|month|year|decade` | 'date' | |
| mode | 日期面板的状态([设置后无法选择年份/月份?](/docs/react/faq#当我指定了-DatePicker/RangePicker-的-mode-属性后,点击后无法选择年份/月份?) | `time|date|month|year|decade` | - | |
| open | 控制弹层是否展开 | boolean | - | |
| picker | 设置选择器类型 | `date`, `week`, `month`, `year` | `date` | |
| placeholder | 输入框提示文字 | string\|RangePicker\[] | - | |
| popupStyle | 额外的弹出日历样式 | object | {} | |
| size | 输入框大小,`large` 高度为 40px`small` 为 24px默认是 32px | string | 无 | |
@ -92,6 +91,17 @@ moment.locale('zh-cn');
| onOk | 点击确定按钮的回调 | function() | - | |
| onPanelChange | 日期面板变化时的回调 | function(value, mode) | - | |
### YearPicker
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| defaultValue | 默认日期 | [moment](http://momentjs.com/) | 无 | |
| defaultPickerValue | 默认面板日期 | [moment](http://momentjs.com/) | 无 | |
| format | 展示的日期格式,配置参考 [moment.js](http://momentjs.com/) | string | "YYYY" | |
| renderExtraFooter | 在面板中添加额外的页脚 | () => React.ReactNode | - | |
| value | 日期 | [moment](http://momentjs.com/) | 无 | |
| onChange | 时间发生变化的回调,发生在用户选择时间时 | function(date: moment, dateString: string) | - | |
### MonthPicker
| 参数 | 说明 | 类型 | 默认值 | 版本 |
@ -119,8 +129,10 @@ moment.locale('zh-cn');
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| allowEmpty | 允许起始项部分为空 | \[boolean, boolean] | \[false, false] | |
| defaultValue | 默认日期 | [moment](http://momentjs.com/)\[] | 无 | |
| defaultPickerValue | 默认面板日期 | [moment](http://momentjs.com/)\[] | 无 | |
| disabled | 禁用起始项 | [boolean, boolean] | 无 | |
| disabledTime | 不可选择的时间 | function(dates: \[moment, moment\], partial: `'start'|'end'`) | 无 | |
| format | 展示的日期格式 | string | "YYYY-MM-DD HH:mm:ss" | |
| ranges | 预设时间范围快捷选择 | { \[range: string]: [moment](http://momentjs.com/)\[] } \| { \[range: string]: () => [moment](http://momentjs.com/)\[] } | 无 | |
@ -131,10 +143,9 @@ moment.locale('zh-cn');
| value | 日期 | [moment](http://momentjs.com/)\[] | 无 | |
| onCalendarChange | 待选日期发生变化的回调 | function(dates: \[moment, moment\], dateStrings: \[string, string\]) | 无 | |
| onChange | 日期范围发生变化的回调 | function(dates: \[moment, moment\], dateStrings: \[string, string\]) | 无 | |
| onOk | 点击确定按钮的回调 | function(dates: [moment](http://momentjs.com/)\[]) | - | |
<style>
.code-box-demo .ant-calendar-picker {
.code-box-demo .ant-picker {
margin: 0 8px 12px 0;
}
</style>

View File

@ -1,114 +0,0 @@
import * as React from 'react';
import * as moment from 'moment';
import { TimePickerProps } from '../time-picker';
import { tuple } from '../_util/type';
export interface PickerProps {
id?: number | string;
name?: string;
prefixCls?: string;
inputPrefixCls?: string;
format?: string | string[];
disabled?: boolean;
allowClear?: boolean;
className?: string;
pickerClass?: string;
pickerInputClass?: string;
suffixIcon?: React.ReactNode;
style?: React.CSSProperties;
popupStyle?: React.CSSProperties;
dropdownClassName?: string;
locale?: any;
size?: 'large' | 'small' | 'default';
getCalendarContainer?: (triggerNode: Element) => HTMLElement;
open?: boolean;
onOpenChange?: (status: boolean) => void;
disabledDate?: (current: moment.Moment | undefined) => boolean;
dateRender?: (current: moment.Moment, today: moment.Moment) => React.ReactNode;
autoFocus?: boolean;
onFocus?: React.FocusEventHandler;
onBlur?: (e: React.SyntheticEvent) => void;
}
export interface SinglePickerProps {
value?: moment.Moment;
defaultValue?: moment.Moment;
defaultPickerValue?: moment.Moment;
placeholder?: string;
renderExtraFooter?: (mode: DatePickerMode) => React.ReactNode;
onChange?: (date: moment.Moment | null, dateString: string) => void;
}
const DatePickerModes = tuple('time', 'date', 'month', 'year', 'decade');
export type DatePickerMode = typeof DatePickerModes[number];
export interface DatePickerProps extends PickerProps, SinglePickerProps {
showTime?: TimePickerProps | boolean;
showToday?: boolean;
open?: boolean;
disabledTime?: (
current: moment.Moment | undefined,
) => {
disabledHours?: () => number[];
disabledMinutes?: () => number[];
disabledSeconds?: () => number[];
};
onOpenChange?: (status: boolean) => void;
onPanelChange?: (value: moment.Moment | undefined, mode: DatePickerMode) => void;
onOk?: (selectedTime: moment.Moment) => void;
mode?: DatePickerMode;
}
export interface MonthPickerProps extends PickerProps, SinglePickerProps {
monthCellContentRender?: (date: moment.Moment, locale: any) => React.ReactNode;
}
export type RangePickerValue =
| undefined[]
| [moment.Moment]
| [undefined, moment.Moment]
| [moment.Moment, undefined]
| [moment.Moment, moment.Moment];
export type RangePickerPresetRange = RangePickerValue | (() => RangePickerValue);
export interface RangePickerProps extends PickerProps {
className?: string;
tagPrefixCls?: string;
value?: RangePickerValue;
defaultValue?: RangePickerValue;
defaultPickerValue?: RangePickerValue;
timePicker?: React.ReactNode;
onChange?: (dates: RangePickerValue, dateStrings: [string, string]) => void;
onCalendarChange?: (dates: RangePickerValue, dateStrings: [string, string]) => void;
onOk?: (selectedTime: RangePickerPresetRange) => void;
showTime?: TimePickerProps | boolean;
showToday?: boolean;
ranges?: {
[range: string]: RangePickerPresetRange;
};
placeholder?: [string, string];
mode?: string | string[];
separator?: React.ReactNode;
disabledTime?: (
current: moment.Moment | undefined,
type: string,
) => {
disabledHours?: () => number[];
disabledMinutes?: () => number[];
disabledSeconds?: () => number[];
};
onPanelChange?: (value?: RangePickerValue, mode?: string | string[]) => void;
renderExtraFooter?: () => React.ReactNode;
onMouseEnter?: (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => void;
onMouseLeave?: (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => void;
}
export interface WeekPickerProps extends PickerProps, SinglePickerProps {
// - currently no own props -
}
export interface DatePickerDecorator extends React.ClassicComponentClass<DatePickerProps> {
RangePicker: React.ClassicComponentClass<RangePickerProps>;
MonthPicker: React.ClassicComponentClass<MonthPickerProps>;
WeekPicker: React.ClassicComponentClass<WeekPickerProps>;
}

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/ar_EG';
import CalendarLocale from 'rc-picker/lib/locale/ar_EG';
import TimePickerLocale from '../../time-picker/locale/ar_EG';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/bg_BG';
import CalendarLocale from 'rc-picker/lib/locale/bg_BG';
import TimePickerLocale from '../../time-picker/locale/bg_BG';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/ca_ES';
import CalendarLocale from 'rc-picker/lib/locale/ca_ES';
import TimePickerLocale from '../../time-picker/locale/ca_ES';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/cs_CZ';
import CalendarLocale from 'rc-picker/lib/locale/cs_CZ';
import TimePickerLocale from '../../time-picker/locale/cs_CZ';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/da_DK';
import CalendarLocale from 'rc-picker/lib/locale/da_DK';
import TimePickerLocale from '../../time-picker/locale/da_DK';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/de_DE';
import CalendarLocale from 'rc-picker/lib/locale/de_DE';
import TimePickerLocale from '../../time-picker/locale/de_DE';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/el_GR';
import CalendarLocale from 'rc-picker/lib/locale/el_GR';
import TimePickerLocale from '../../time-picker/locale/el_GR';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/en_GB';
import CalendarLocale from 'rc-picker/lib/locale/en_GB';
import TimePickerLocale from '../../time-picker/locale/en_GB';
// Merge into a locale object

View File

@ -1,11 +1,17 @@
import CalendarLocale from 'rc-calendar/lib/locale/en_US';
import CalendarLocale from 'rc-picker/lib/locale/en_US';
import TimePickerLocale from '../../time-picker/locale/en_US';
// Merge into a locale object
const locale = {
lang: {
placeholder: 'Select date',
yearPlaceholder: 'Select year',
monthPlaceholder: 'Select month',
weekPlaceholder: 'Select week',
rangePlaceholder: ['Start date', 'End date'],
rangeYearPlaceholder: ['Start year', 'End year'],
rangeMonthPlaceholder: ['Start month', 'End month'],
rangeWeekPlaceholder: ['Start week', 'End week'],
...CalendarLocale,
},
timePickerLocale: {

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/es_ES';
import CalendarLocale from 'rc-picker/lib/locale/es_ES';
import TimePickerLocale from '../../time-picker/locale/es_ES';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/et_EE';
import CalendarLocale from 'rc-picker/lib/locale/et_EE';
import TimePickerLocale from '../../time-picker/locale/et_EE';
// 统一合并为完整的 Locale

View File

@ -1,5 +1,6 @@
{
"lang": {
"locale": "en_US",
"placeholder": "Select date",
"rangePlaceholder": ["Start date", "End date"],
"today": "Today",

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/fa_IR';
import CalendarLocale from 'rc-picker/lib/locale/fa_IR';
import TimePickerLocale from '../../time-picker/locale/fa_IR';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/fi_FI';
import CalendarLocale from 'rc-picker/lib/locale/fi_FI';
import TimePickerLocale from '../../time-picker/locale/fi_FI';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/fr_BE';
import CalendarLocale from 'rc-picker/lib/locale/fr_BE';
import TimePickerLocale from '../../time-picker/locale/fr_BE';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/fr_FR';
import CalendarLocale from 'rc-picker/lib/locale/fr_FR';
import TimePickerLocale from '../../time-picker/locale/fr_FR';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/he_IL';
import CalendarLocale from 'rc-picker/lib/locale/he_IL';
import TimePickerLocale from '../../time-picker/locale/he_IL';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/hi_IN';
import CalendarLocale from 'rc-picker/lib/locale/hi_IN';
import TimePickerLocale from '../../time-picker/locale/hi_IN';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/hr_HR';
import CalendarLocale from 'rc-picker/lib/locale/hr_HR';
import TimePickerLocale from '../../time-picker/locale/hr_HR';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/hu_HU';
import CalendarLocale from 'rc-picker/lib/locale/hu_HU';
import TimePickerLocale from '../../time-picker/locale/hu_HU';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/id_ID';
import CalendarLocale from 'rc-picker/lib/locale/id_ID';
import TimePickerLocale from '../../time-picker/locale/id_ID';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/is_IS';
import CalendarLocale from 'rc-picker/lib/locale/is_IS';
import TimePickerLocale from '../../time-picker/locale/is_IS';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/it_IT';
import CalendarLocale from 'rc-picker/lib/locale/it_IT';
import TimePickerLocale from '../../time-picker/locale/it_IT';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/ja_JP';
import CalendarLocale from 'rc-picker/lib/locale/ja_JP';
import TimePickerLocale from '../../time-picker/locale/ja_JP';
const locale = {

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/kn_IN';
import CalendarLocale from 'rc-picker/lib/locale/kn_IN';
import TimePickerLocale from '../../time-picker/locale/kn_IN';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/ko_KR';
import CalendarLocale from 'rc-picker/lib/locale/ko_KR';
import TimePickerLocale from '../../time-picker/locale/ko_KR';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/ku_IQ';
import CalendarLocale from 'rc-picker/lib/locale/ku_IQ';
import TimePickerLocale from '../../time-picker/locale/ku_IQ';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/lv_LV';
import CalendarLocale from 'rc-picker/lib/locale/lv_LV';
import TimePickerLocale from '../../time-picker/locale/lv_LV';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/mk_MK';
import CalendarLocale from 'rc-picker/lib/locale/mk_MK';
import TimePickerLocale from '../../time-picker/locale/mk_MK';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/mn_MN';
import CalendarLocale from 'rc-picker/lib/locale/mn_MN';
import TimePickerLocale from '../../time-picker/locale/mn_MN';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/ms_MY';
import CalendarLocale from 'rc-picker/lib/locale/ms_MY';
import TimePickerLocale from '../../time-picker/locale/ms_MY';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/nb_NO';
import CalendarLocale from 'rc-picker/lib/locale/nb_NO';
import TimePickerLocale from '../../time-picker/locale/nb_NO';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/nl_BE';
import CalendarLocale from 'rc-picker/lib/locale/nl_BE';
import TimePickerLocale from '../../time-picker/locale/nl_BE';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/nl_NL';
import CalendarLocale from 'rc-picker/lib/locale/nl_NL';
import TimePickerLocale from '../../time-picker/locale/nl_NL';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/pl_PL';
import CalendarLocale from 'rc-picker/lib/locale/pl_PL';
import TimePickerLocale from '../../time-picker/locale/pl_PL';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/pt_BR';
import CalendarLocale from 'rc-picker/lib/locale/pt_BR';
import TimePickerLocale from '../../time-picker/locale/pt_BR';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/pt_PT';
import CalendarLocale from 'rc-picker/lib/locale/pt_PT';
import TimePickerLocale from '../../time-picker/locale/pt_PT';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/ro_RO';
import CalendarLocale from 'rc-picker/lib/locale/ro_RO';
import TimePickerLocale from '../../time-picker/locale/ro_RO';
// Merge into a locale object

View File

@ -2,7 +2,7 @@
* Created by Andrey Gayvoronsky on 13/04/16.
*/
import CalendarLocale from 'rc-calendar/lib/locale/ru_RU';
import CalendarLocale from 'rc-picker/lib/locale/ru_RU';
import TimePickerLocale from '../../time-picker/locale/ru_RU';
const locale = {

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/sk_SK';
import CalendarLocale from 'rc-picker/lib/locale/sk_SK';
import TimePickerLocale from '../../time-picker/locale/sk_SK';
// 统一合并为完整的 Locale

View File

@ -3,6 +3,7 @@ import TimePickerLocale from '../../time-picker/locale/sl_SI';
// Merge into a locale object
const locale = {
lang: {
locale: 'sl',
placeholder: 'Izberite datum',
rangePlaceholder: ['Začetni datum', 'Končni datum'],
today: 'Danes',

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/sr_RS';
import CalendarLocale from 'rc-picker/lib/locale/sr_RS';
import TimePickerLocale from '../../time-picker/locale/sr_RS';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/sv_SE';
import CalendarLocale from 'rc-picker/lib/locale/sv_SE';
import TimePickerLocale from '../../time-picker/locale/sv_SE';
const locale = {

View File

@ -1,5 +1,5 @@
// Tamil Locale added to rc-calendar
import CalendarLocale from 'rc-calendar/lib/locale/ta_IN';
import CalendarLocale from 'rc-picker/lib/locale/ta_IN';
import TimePickerLocale from '../../time-picker/locale/ta_IN';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/th_TH';
import CalendarLocale from 'rc-picker/lib/locale/th_TH';
import TimePickerLocale from '../../time-picker/locale/th_TH';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/tr_TR';
import CalendarLocale from 'rc-picker/lib/locale/tr_TR';
import TimePickerLocale from '../../time-picker/locale/tr_TR';
// Merge into a locale object

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/uk_UA';
import CalendarLocale from 'rc-picker/lib/locale/uk_UA';
import TimePickerLocale from '../../time-picker/locale/uk_UA';
const locale = {

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/vi_VN';
import CalendarLocale from 'rc-picker/lib/locale/vi_VN';
import TimePickerLocale from '../../time-picker/locale/vi_VN';
// Merge into a locale object

View File

@ -1,10 +1,16 @@
import CalendarLocale from 'rc-calendar/lib/locale/zh_CN';
import CalendarLocale from 'rc-picker/lib/locale/zh_CN';
import TimePickerLocale from '../../time-picker/locale/zh_CN';
const locale = {
lang: {
placeholder: '请选择日期',
yearPlaceholder: '请选择年份',
monthPlaceholder: '请选择月份',
weekPlaceholder: '请选择周',
rangePlaceholder: ['开始日期', '结束日期'],
rangeYearPlaceholder: ['开始年份', '结束年份'],
rangeMonthPlaceholder: ['开始月份', '结束月份'],
rangeWeekPlaceholder: ['开始周', '结束周'],
...CalendarLocale,
},
timePickerLocale: {

View File

@ -1,4 +1,4 @@
import CalendarLocale from 'rc-calendar/lib/locale/zh_TW';
import CalendarLocale from 'rc-picker/lib/locale/zh_TW';
import TimePickerLocale from '../../time-picker/locale/zh_TW';
const locale = {

View File

@ -1,402 +0,0 @@
.calendarLeftArrow() {
height: 100%;
&::before,
&::after {
position: relative;
top: -1px;
display: inline-block;
width: 8px;
height: 8px;
vertical-align: middle;
border: 0 solid #aaa;
border-width: 1.5px 0 0 1.5px;
border-radius: 1px;
transform: rotate(-45deg) scale(0.8);
transition: all 0.3s;
content: '';
}
&:hover::before,
&:hover::after {
border-color: @text-color;
}
&::after {
display: none;
}
}
.calendarLeftDoubleArrow() {
.calendarLeftArrow;
&::after {
position: relative;
left: -3px;
display: inline-block;
}
}
.calendarRightArrow() {
.calendarLeftArrow;
&::before,
&::after {
transform: rotate(135deg) scale(0.8);
}
}
.calendarRightDoubleArrow() {
.calendarRightArrow;
&::before {
position: relative;
left: 3px;
}
&::after {
display: inline-block;
}
}
.calendarPanelHeader(@calendar-prefix-cls) {
height: 40px;
line-height: 40px;
text-align: center;
border-bottom: @border-width-base @border-style-base @border-color-split;
user-select: none;
a:hover {
color: @link-hover-color;
}
.@{calendar-prefix-cls}-century-select,
.@{calendar-prefix-cls}-decade-select,
.@{calendar-prefix-cls}-year-select,
.@{calendar-prefix-cls}-month-select {
display: inline-block;
padding: 0 2px;
color: @heading-color;
font-weight: 500;
line-height: 40px;
}
.@{calendar-prefix-cls}-century-select-arrow,
.@{calendar-prefix-cls}-decade-select-arrow,
.@{calendar-prefix-cls}-year-select-arrow,
.@{calendar-prefix-cls}-month-select-arrow {
display: none;
}
.@{calendar-prefix-cls}-prev-century-btn,
.@{calendar-prefix-cls}-next-century-btn,
.@{calendar-prefix-cls}-prev-decade-btn,
.@{calendar-prefix-cls}-next-decade-btn,
.@{calendar-prefix-cls}-prev-month-btn,
.@{calendar-prefix-cls}-next-month-btn,
.@{calendar-prefix-cls}-prev-year-btn,
.@{calendar-prefix-cls}-next-year-btn {
position: absolute;
top: 0;
display: inline-block;
padding: 0 5px;
color: @text-color-secondary;
font-size: 16px;
font-family: Arial, 'Hiragino Sans GB', 'Microsoft Yahei', 'Microsoft Sans Serif', sans-serif;
line-height: 40px;
}
.@{calendar-prefix-cls}-prev-century-btn,
.@{calendar-prefix-cls}-prev-decade-btn,
.@{calendar-prefix-cls}-prev-year-btn {
left: 7px;
.calendarLeftDoubleArrow;
}
.@{calendar-prefix-cls}-next-century-btn,
.@{calendar-prefix-cls}-next-decade-btn,
.@{calendar-prefix-cls}-next-year-btn {
right: 7px;
.calendarRightDoubleArrow;
}
.@{calendar-prefix-cls}-prev-month-btn {
left: 29px;
.calendarLeftArrow;
}
.@{calendar-prefix-cls}-next-month-btn {
right: 29px;
.calendarRightArrow;
}
}
.calendar-selected-cell() {
.@{calendar-prefix-cls}-date {
color: @text-color-inverse;
background: @primary-color;
border: @border-width-base @border-style-base transparent;
&:hover {
background: @primary-color;
}
}
}
.@{calendar-prefix-cls} {
position: relative;
width: 280px;
font-size: @font-size-base;
line-height: @line-height-base;
text-align: left;
list-style: none;
background-color: @calendar-bg;
background-clip: padding-box;
border: @border-width-base @border-style-base @calendar-border-color;
border-radius: @border-radius-base;
outline: none;
box-shadow: @box-shadow-base;
&-input-wrap {
height: 34px;
padding: 6px @control-padding-horizontal - 2px;
border-bottom: @border-width-base @border-style-base @border-color-split;
}
&-input {
width: 100%;
height: 22px;
color: @input-color;
background: @calendar-input-bg;
border: 0;
outline: 0;
cursor: auto;
.placeholder;
}
&-week-number {
width: 286px;
&-cell {
text-align: center;
}
}
&-header {
.calendarPanelHeader(@calendar-prefix-cls);
}
&-body {
padding: 8px 12px;
}
table {
width: 100%;
max-width: 100%;
background-color: transparent;
border-collapse: collapse;
}
table,
th,
td {
text-align: center;
border: 0;
}
&-calendar-table {
margin-bottom: 0;
border-spacing: 0;
}
&-column-header {
width: 33px;
padding: 6px 0;
line-height: 18px;
text-align: center;
.@{calendar-prefix-cls}-column-header-inner {
display: block;
font-weight: normal;
}
}
&-week-number-header {
.@{calendar-prefix-cls}-column-header-inner {
display: none;
}
}
&-cell {
height: 30px;
padding: 3px 0;
}
&-date {
display: block;
width: 24px;
height: 24px;
margin: 0 auto;
padding: 0;
color: @text-color;
line-height: 22px;
text-align: center;
background: transparent;
border: @border-width-base @border-style-base transparent;
border-radius: @border-radius-base;
transition: background 0.3s ease;
&-panel {
position: relative;
outline: none;
}
&:hover {
background: @item-hover-bg;
cursor: pointer;
}
&:active {
color: @text-color-inverse;
background: @primary-5;
}
}
&-today &-date {
color: @primary-color;
font-weight: bold;
border-color: @primary-color;
}
&-selected-day &-date {
background: @primary-2;
}
&-last-month-cell &-date,
&-next-month-btn-day &-date {
&,
&:hover {
color: @disabled-color;
background: transparent;
border-color: transparent;
}
}
&-disabled-cell &-date {
position: relative;
width: auto;
color: @disabled-color;
background: @disabled-bg;
border: @border-width-base @border-style-base transparent;
border-radius: 0;
cursor: not-allowed;
&:hover {
background: @disabled-bg;
}
}
&-disabled-cell&-selected-day &-date::before {
position: absolute;
top: -1px;
left: 5px;
width: 24px;
height: 24px;
background: rgba(0, 0, 0, 0.1);
border-radius: @border-radius-base;
content: '';
}
&-disabled-cell&-today &-date {
position: relative;
padding-right: 5px;
padding-left: 5px;
&::before {
position: absolute;
top: -1px;
left: 5px;
width: 24px;
height: 24px;
border: @border-width-base @border-style-base @disabled-color;
border-radius: @border-radius-base;
content: ' ';
}
}
&-disabled-cell-first-of-row &-date {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
&-disabled-cell-last-of-row &-date {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
&-footer {
padding: 0 12px;
line-height: 38px;
border-top: @border-width-base @border-style-base @border-color-split;
&:empty {
border-top: 0;
}
&-btn {
display: block;
text-align: center;
}
&-extra {
text-align: left;
}
}
.@{calendar-prefix-cls}-today-btn,
.@{calendar-prefix-cls}-clear-btn {
display: inline-block;
margin: 0 0 0 8px;
text-align: center;
&-disabled {
color: @disabled-color;
cursor: not-allowed;
}
&:only-child {
margin: 0;
}
}
.@{calendar-prefix-cls}-clear-btn {
position: absolute;
top: 7px;
right: 5px;
display: none;
width: 20px;
height: 20px;
margin: 0;
overflow: hidden;
line-height: 20px;
text-align: center;
text-indent: -76px;
}
.@{calendar-prefix-cls}-clear-btn::after {
display: inline-block;
width: 20px;
color: @disabled-color;
font-size: @font-size-base;
line-height: 1;
text-indent: 43px;
transition: color 0.3s ease;
}
.@{calendar-prefix-cls}-clear-btn:hover::after {
color: @text-color-secondary;
}
.@{calendar-prefix-cls}-ok-btn {
.btn;
.btn-primary;
.button-size(@btn-height-sm; @btn-padding-sm; @font-size-base; @border-radius-base);
line-height: @btn-height-sm - 2px;
.button-disabled();
}
}

View File

@ -1,11 +0,0 @@
.@{calendar-prefix-cls}-month {
.@{calendar-prefix-cls}-month-header-wrap {
position: relative;
height: 288px;
}
.@{calendar-prefix-cls}-month-panel,
.@{calendar-prefix-cls}-year-panel {
top: 0;
height: 100%;
}
}

View File

@ -1,109 +0,0 @@
@import '../../button/style/mixin';
.@{calendar-prefix-cls}-picker-container {
.reset-component;
position: absolute;
z-index: @zindex-picker;
font-family: @font-family;
&.slide-up-enter.slide-up-enter-active&-placement-topLeft,
&.slide-up-enter.slide-up-enter-active&-placement-topRight,
&.slide-up-appear.slide-up-appear-active&-placement-topLeft,
&.slide-up-appear.slide-up-appear-active&-placement-topRight {
animation-name: antSlideDownIn;
}
&.slide-up-enter.slide-up-enter-active&-placement-bottomLeft,
&.slide-up-enter.slide-up-enter-active&-placement-bottomRight,
&.slide-up-appear.slide-up-appear-active&-placement-bottomLeft,
&.slide-up-appear.slide-up-appear-active&-placement-bottomRight {
animation-name: antSlideUpIn;
}
&.slide-up-leave.slide-up-leave-active&-placement-topLeft,
&.slide-up-leave.slide-up-leave-active&-placement-topRight {
animation-name: antSlideDownOut;
}
&.slide-up-leave.slide-up-leave-active&-placement-bottomLeft,
&.slide-up-leave.slide-up-leave-active&-placement-bottomRight {
animation-name: antSlideUpOut;
}
}
.@{calendar-prefix-cls}-picker {
.reset-component;
position: relative;
display: inline-block;
outline: none;
cursor: text;
transition: opacity 0.3s;
&-input {
outline: none;
&.@{ant-prefix}-input {
line-height: @line-height-base;
}
}
&-input.@{ant-prefix}-input-sm {
padding-top: 0;
padding-bottom: 0;
}
&:hover &-input:not(.@{ant-prefix}-input-disabled) {
border-color: @input-hover-border-color;
}
&:focus &-input:not(.@{ant-prefix}-input-disabled) {
.active();
}
&-clear,
&-icon {
position: absolute;
top: 50%;
right: @control-padding-horizontal;
z-index: 1;
width: 14px;
height: 14px;
margin-top: -7px;
font-size: @font-size-sm;
line-height: 14px;
transition: all 0.3s;
user-select: none;
}
&-clear {
z-index: 2;
color: @disabled-color;
font-size: @font-size-base;
background: @input-bg;
cursor: pointer;
opacity: 0;
pointer-events: none;
&:hover {
color: @text-color-secondary;
}
}
&:hover &-clear {
opacity: 1;
pointer-events: auto;
}
&-icon {
display: inline-block;
color: @disabled-color;
font-size: @font-size-base;
line-height: 1;
}
&-small &-clear,
&-small &-icon {
right: @control-padding-horizontal-sm;
}
}

View File

@ -1,248 +0,0 @@
@input-box-height: 34px;
.@{calendar-prefix-cls}-range-picker-input {
width: 44%;
height: 99%;
text-align: center;
background-color: transparent;
border: 0;
outline: 0;
.placeholder();
&[disabled] {
cursor: not-allowed;
}
}
.@{calendar-prefix-cls}-range-picker-separator {
display: inline-block;
min-width: 10px;
height: 100%;
color: @text-color-secondary;
white-space: nowrap;
text-align: center;
vertical-align: top;
pointer-events: none;
}
.@{calendar-prefix-cls}-range {
width: 552px;
overflow: hidden;
.@{calendar-prefix-cls}-date-panel {
&::after {
display: block;
clear: both;
height: 0;
visibility: hidden;
content: '.';
}
}
&-part {
position: relative;
width: 50%;
}
&-left {
float: left;
.@{calendar-prefix-cls} {
&-time-picker-inner {
border-right: 1px solid @border-color-split;
}
}
}
&-right {
float: right;
.@{calendar-prefix-cls} {
&-time-picker-inner {
border-left: 1px solid @border-color-split;
}
}
}
&-middle {
position: absolute;
left: 50%;
z-index: 1;
height: @input-box-height;
margin: 1px 0 0 0;
padding: 0 200px 0 0;
color: @text-color-secondary;
line-height: @input-box-height;
text-align: center;
transform: translateX(-50%);
pointer-events: none;
}
&-right .@{calendar-prefix-cls}-date-input-wrap {
margin-left: -90px;
}
&.@{calendar-prefix-cls}-time &-middle {
padding: 0 10px 0 0;
transform: translateX(-50%);
}
.@{calendar-prefix-cls}-today
:not(.@{calendar-prefix-cls}-disabled-cell)
:not(.@{calendar-prefix-cls}-last-month-cell)
:not(.@{calendar-prefix-cls}-next-month-btn-day) {
.@{calendar-prefix-cls}-date {
color: @primary-color;
background: @primary-2;
border-color: @primary-color;
}
}
.@{calendar-prefix-cls}-selected-start-date,
.@{calendar-prefix-cls}-selected-end-date {
.calendar-selected-cell;
}
&.@{calendar-prefix-cls}-time &-right .@{calendar-prefix-cls}-date-input-wrap {
margin-left: 0;
}
.@{calendar-prefix-cls}-input-wrap {
position: relative;
height: @input-box-height;
}
.@{calendar-prefix-cls}-input,
.@{calendar-timepicker-prefix-cls}-input {
.input;
height: @input-height-sm;
padding-right: 0;
padding-left: 0;
line-height: @input-height-sm;
border: 0;
box-shadow: none;
&:focus {
box-shadow: none;
}
}
.@{calendar-timepicker-prefix-cls}-icon {
display: none;
}
&.@{calendar-prefix-cls}-week-number {
width: 574px;
.@{calendar-prefix-cls}-range-part {
width: 286px;
}
}
.@{calendar-prefix-cls}-year-panel,
.@{calendar-prefix-cls}-month-panel,
.@{calendar-prefix-cls}-decade-panel {
top: @input-box-height;
}
.@{calendar-prefix-cls}-month-panel .@{calendar-prefix-cls}-year-panel {
top: 0;
}
.@{calendar-prefix-cls}-decade-panel-table,
.@{calendar-prefix-cls}-year-panel-table,
.@{calendar-prefix-cls}-month-panel-table {
height: 208px;
}
.@{calendar-prefix-cls}-in-range-cell {
position: relative;
border-radius: 0;
> div {
position: relative;
z-index: 1;
}
&::before {
position: absolute;
top: 4px;
right: 0;
bottom: 4px;
left: 0;
display: block;
background: @calendar-item-active-bg;
border: 0;
border-radius: 0;
content: '';
}
}
.@{calendar-prefix-cls}-footer-extra {
float: left;
}
// `div` for selector specificity
div&-quick-selector {
text-align: left;
> a {
margin-right: 8px;
}
}
.@{calendar-prefix-cls},
.@{calendar-prefix-cls}-month-panel,
.@{calendar-prefix-cls}-year-panel,
.@{calendar-prefix-cls}-decade-panel {
&-header {
border-bottom: 0;
}
&-body {
border-top: @border-width-base @border-style-base @border-color-split;
}
}
&.@{calendar-prefix-cls}-time {
.@{calendar-timepicker-prefix-cls} {
top: 68px;
z-index: 2; // cover .ant-calendar-range .ant-calendar-in-range-cell > div (z-index: 1)
width: 100%;
height: 207px;
&-panel {
height: 267px;
margin-top: -34px;
}
&-inner {
height: 100%;
padding-top: 40px;
background: none;
}
&-combobox {
display: inline-block;
height: 100%;
background-color: @component-background;
border-top: @border-width-base @border-style-base @border-color-split;
}
&-select {
height: 100%;
ul {
max-height: 100%;
}
}
}
.@{calendar-prefix-cls}-footer .@{calendar-prefix-cls}-time-picker-btn {
margin-right: 8px;
}
.@{calendar-prefix-cls}-today-btn {
height: 22px;
margin: 8px 12px;
line-height: 22px;
}
}
&-with-ranges.@{calendar-prefix-cls}-time .@{calendar-timepicker-prefix-cls} {
height: 233px;
}
}
.@{calendar-prefix-cls}-range.@{calendar-prefix-cls}-show-time-picker {
.@{calendar-prefix-cls}-body {
border-top-color: transparent;
}
}

Some files were not shown because too many files have changed in this diff Show More