mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-02 20:19:44 +08:00
feat: Calendar onSelect
support info.source
param (#42432)
* test: add test case * test: add test case * docs: update api * docs: faq update
This commit is contained in:
parent
4efa0b64db
commit
71fc54e53a
@ -5,7 +5,7 @@ import { useContext, useMemo } from 'react';
|
|||||||
import { FormItemInputContext } from '../form/context';
|
import { FormItemInputContext } from '../form/context';
|
||||||
import { Button, Group } from '../radio';
|
import { Button, Group } from '../radio';
|
||||||
import Select from '../select';
|
import Select from '../select';
|
||||||
import type { CalendarMode } from './generateCalendar';
|
import type { CalendarMode, SelectInfo } from './generateCalendar';
|
||||||
|
|
||||||
const YearSelectOffset = 10;
|
const YearSelectOffset = 10;
|
||||||
const YearSelectTotal = 20;
|
const YearSelectTotal = 20;
|
||||||
@ -147,7 +147,7 @@ export interface CalendarHeaderProps<DateType> {
|
|||||||
locale: Locale;
|
locale: Locale;
|
||||||
mode: CalendarMode;
|
mode: CalendarMode;
|
||||||
fullscreen: boolean;
|
fullscreen: boolean;
|
||||||
onChange: (date: DateType) => void;
|
onChange: (date: DateType, source: SelectInfo['source']) => void;
|
||||||
onModeChange: (mode: CalendarMode) => void;
|
onModeChange: (mode: CalendarMode) => void;
|
||||||
}
|
}
|
||||||
function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
|
function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
|
||||||
@ -165,7 +165,6 @@ function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
|
|||||||
|
|
||||||
const sharedProps = {
|
const sharedProps = {
|
||||||
...props,
|
...props,
|
||||||
onChange,
|
|
||||||
fullscreen,
|
fullscreen,
|
||||||
divRef,
|
divRef,
|
||||||
};
|
};
|
||||||
@ -173,8 +172,20 @@ function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
|
|||||||
return (
|
return (
|
||||||
<div className={`${prefixCls}-header`} ref={divRef}>
|
<div className={`${prefixCls}-header`} ref={divRef}>
|
||||||
<FormItemInputContext.Provider value={mergedFormItemInputContext}>
|
<FormItemInputContext.Provider value={mergedFormItemInputContext}>
|
||||||
<YearSelect {...sharedProps} />
|
<YearSelect
|
||||||
{mode === 'month' && <MonthSelect {...sharedProps} />}
|
{...sharedProps}
|
||||||
|
onChange={(v) => {
|
||||||
|
onChange(v, 'year');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{mode === 'month' && (
|
||||||
|
<MonthSelect
|
||||||
|
{...sharedProps}
|
||||||
|
onChange={(v) => {
|
||||||
|
onChange(v, 'month');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</FormItemInputContext.Provider>
|
</FormItemInputContext.Provider>
|
||||||
<ModeSwitch {...sharedProps} onModeChange={onModeChange} />
|
<ModeSwitch {...sharedProps} onModeChange={onModeChange} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,7 +76,7 @@ describe('Calendar', () => {
|
|||||||
const { container } = render(<Calendar onSelect={onSelect} onChange={onChange} />);
|
const { container } = render(<Calendar onSelect={onSelect} onChange={onChange} />);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector('.ant-picker-cell')!);
|
fireEvent.click(container.querySelector('.ant-picker-cell')!);
|
||||||
expect(onSelect).toHaveBeenCalledWith(expect.anything());
|
expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'date' });
|
||||||
|
|
||||||
const value = onSelect.mock.calls[0][0];
|
const value = onSelect.mock.calls[0][0];
|
||||||
expect(Dayjs.isDayjs(value)).toBe(true);
|
expect(Dayjs.isDayjs(value)).toBe(true);
|
||||||
@ -270,7 +270,7 @@ describe('Calendar', () => {
|
|||||||
const end = Dayjs('2019-11-01');
|
const end = Dayjs('2019-11-01');
|
||||||
const onValueChange = jest.fn();
|
const onValueChange = jest.fn();
|
||||||
createWrapper(start, end, value, onValueChange);
|
createWrapper(start, end, value, onValueChange);
|
||||||
expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(3));
|
expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(3), 'year');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if start.month > value.month, set value.month to start.month', () => {
|
it('if start.month > value.month, set value.month to start.month', () => {
|
||||||
@ -279,7 +279,7 @@ describe('Calendar', () => {
|
|||||||
const end = Dayjs('2019-03-01');
|
const end = Dayjs('2019-03-01');
|
||||||
const onValueChange = jest.fn();
|
const onValueChange = jest.fn();
|
||||||
createWrapper(start, end, value, onValueChange);
|
createWrapper(start, end, value, onValueChange);
|
||||||
expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(10));
|
expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(10), 'year');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('if change year and month > end month, set value.month to end.month', () => {
|
it('if change year and month > end month, set value.month to end.month', () => {
|
||||||
@ -302,7 +302,7 @@ describe('Calendar', () => {
|
|||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
Array.from(wrapper.container.querySelectorAll('.ant-select-item-option')).at(-1)!,
|
Array.from(wrapper.container.querySelectorAll('.ant-select-item-option')).at(-1)!,
|
||||||
);
|
);
|
||||||
expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(2));
|
expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(2), 'year');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('onMonthChange should work correctly', () => {
|
it('onMonthChange should work correctly', () => {
|
||||||
@ -324,7 +324,7 @@ describe('Calendar', () => {
|
|||||||
);
|
);
|
||||||
openSelect(wrapper.container, '.ant-picker-calendar-month-select');
|
openSelect(wrapper.container, '.ant-picker-calendar-month-select');
|
||||||
clickSelectItem(wrapper.container);
|
clickSelectItem(wrapper.container);
|
||||||
expect(onValueChange).toHaveBeenCalledWith(value.month(10));
|
expect(onValueChange).toHaveBeenCalledWith(value.month(10), 'month');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('onTypeChange should work correctly', () => {
|
it('onTypeChange should work correctly', () => {
|
||||||
|
95
components/calendar/__tests__/select.test.tsx
Normal file
95
components/calendar/__tests__/select.test.tsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import Dayjs from 'dayjs';
|
||||||
|
import 'dayjs/locale/zh-cn';
|
||||||
|
import MockDate from 'mockdate';
|
||||||
|
import { resetWarned } from 'rc-util/lib/warning';
|
||||||
|
import React from 'react';
|
||||||
|
import Calendar from '..';
|
||||||
|
import { fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||||
|
|
||||||
|
describe('Calendar.onSelect', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
MockDate.set(Dayjs('2000-01-01').valueOf());
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
resetWarned();
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.clearAllTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('source of year select', async () => {
|
||||||
|
const onSelect = jest.fn();
|
||||||
|
const { container } = render(<Calendar onSelect={onSelect} />);
|
||||||
|
|
||||||
|
fireEvent.mouseDown(container.querySelector('.ant-select-selector')!);
|
||||||
|
await waitFakeTimer();
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector('.ant-select-item-option')!);
|
||||||
|
await waitFakeTimer();
|
||||||
|
|
||||||
|
expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'year' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('source of month select', async () => {
|
||||||
|
const onSelect = jest.fn();
|
||||||
|
const { container } = render(<Calendar onSelect={onSelect} />);
|
||||||
|
|
||||||
|
fireEvent.mouseDown(container.querySelectorAll('.ant-select-selector')[1]!);
|
||||||
|
await waitFakeTimer();
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector('.ant-select-item-option')!);
|
||||||
|
await waitFakeTimer();
|
||||||
|
|
||||||
|
expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'month' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('source of customize', async () => {
|
||||||
|
const onSelect = jest.fn();
|
||||||
|
const { container } = render(
|
||||||
|
<Calendar
|
||||||
|
onSelect={onSelect}
|
||||||
|
headerRender={({ onChange }) => (
|
||||||
|
<button
|
||||||
|
className="bamboo"
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
onChange(Dayjs('1999-01-01'));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Trigger
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector('.bamboo')!);
|
||||||
|
await waitFakeTimer();
|
||||||
|
|
||||||
|
expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'customize' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('source of date', () => {
|
||||||
|
const onSelect = jest.fn();
|
||||||
|
const { container } = render(<Calendar onSelect={onSelect} />);
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector('.ant-picker-cell')!);
|
||||||
|
expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'date' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('source of date with month panel', async () => {
|
||||||
|
const onSelect = jest.fn();
|
||||||
|
const onPanelChange = jest.fn();
|
||||||
|
const { container } = render(<Calendar onSelect={onSelect} onPanelChange={onPanelChange} />);
|
||||||
|
|
||||||
|
// Click year radio
|
||||||
|
fireEvent.click(container.querySelectorAll('.ant-radio-button-input')[1]!);
|
||||||
|
expect(onPanelChange).toHaveBeenCalledWith(expect.anything(), 'year');
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector('.ant-picker-cell')!);
|
||||||
|
expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'date' });
|
||||||
|
});
|
||||||
|
});
|
@ -1,12 +1,12 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { PickerPanel as RCPickerPanel } from 'rc-picker';
|
import { PickerPanel as RCPickerPanel } from 'rc-picker';
|
||||||
import type { GenerateConfig } from 'rc-picker/lib/generate';
|
|
||||||
import type { CellRenderInfo } from 'rc-picker/lib/interface';
|
|
||||||
import type {
|
import type {
|
||||||
PickerPanelBaseProps as RCPickerPanelBaseProps,
|
PickerPanelBaseProps as RCPickerPanelBaseProps,
|
||||||
PickerPanelDateProps as RCPickerPanelDateProps,
|
PickerPanelDateProps as RCPickerPanelDateProps,
|
||||||
PickerPanelTimeProps as RCPickerPanelTimeProps,
|
PickerPanelTimeProps as RCPickerPanelTimeProps,
|
||||||
} from 'rc-picker/lib/PickerPanel';
|
} from 'rc-picker/lib/PickerPanel';
|
||||||
|
import type { GenerateConfig } from 'rc-picker/lib/generate';
|
||||||
|
import type { CellRenderInfo } from 'rc-picker/lib/interface';
|
||||||
import useMergedState from 'rc-util/lib/hooks/useMergedState';
|
import useMergedState from 'rc-util/lib/hooks/useMergedState';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ConfigContext } from '../config-provider';
|
import { ConfigContext } from '../config-provider';
|
||||||
@ -14,8 +14,8 @@ import { useLocale } from '../locale';
|
|||||||
import CalendarHeader from './Header';
|
import CalendarHeader from './Header';
|
||||||
import enUS from './locale/en_US';
|
import enUS from './locale/en_US';
|
||||||
|
|
||||||
import useStyle from './style';
|
|
||||||
import warning from '../_util/warning';
|
import warning from '../_util/warning';
|
||||||
|
import useStyle from './style';
|
||||||
|
|
||||||
type InjectDefaultProps<Props> = Omit<
|
type InjectDefaultProps<Props> = Omit<
|
||||||
Props,
|
Props,
|
||||||
@ -43,6 +43,10 @@ export type HeaderRender<DateType> = (config: {
|
|||||||
onTypeChange: (type: CalendarMode) => void;
|
onTypeChange: (type: CalendarMode) => void;
|
||||||
}) => React.ReactNode;
|
}) => React.ReactNode;
|
||||||
|
|
||||||
|
export interface SelectInfo {
|
||||||
|
source: 'year' | 'month' | 'date' | 'customize';
|
||||||
|
}
|
||||||
|
|
||||||
export interface CalendarProps<DateType> {
|
export interface CalendarProps<DateType> {
|
||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -68,7 +72,7 @@ export interface CalendarProps<DateType> {
|
|||||||
fullscreen?: boolean;
|
fullscreen?: boolean;
|
||||||
onChange?: (date: DateType) => void;
|
onChange?: (date: DateType) => void;
|
||||||
onPanelChange?: (date: DateType, mode: CalendarMode) => void;
|
onPanelChange?: (date: DateType, mode: CalendarMode) => void;
|
||||||
onSelect?: (date: DateType) => void;
|
onSelect?: (date: DateType, selectInfo: SelectInfo) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
|
function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
|
||||||
@ -198,10 +202,10 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
|
|||||||
triggerPanelChange(mergedValue, newMode);
|
triggerPanelChange(mergedValue, newMode);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onInternalSelect = (date: DateType) => {
|
const onInternalSelect = (date: DateType, source: SelectInfo['source']) => {
|
||||||
triggerChange(date);
|
triggerChange(date);
|
||||||
|
|
||||||
onSelect?.(date);
|
onSelect?.(date, { source });
|
||||||
};
|
};
|
||||||
|
|
||||||
// ====================== Locale ======================
|
// ====================== Locale ======================
|
||||||
@ -310,7 +314,9 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
|
|||||||
headerRender({
|
headerRender({
|
||||||
value: mergedValue,
|
value: mergedValue,
|
||||||
type: mergedMode,
|
type: mergedMode,
|
||||||
onChange: onInternalSelect,
|
onChange: (nextDate) => {
|
||||||
|
onInternalSelect(nextDate, 'customize');
|
||||||
|
},
|
||||||
onTypeChange: triggerModeChange,
|
onTypeChange: triggerModeChange,
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
@ -332,7 +338,9 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
|
|||||||
locale={contextLocale?.lang}
|
locale={contextLocale?.lang}
|
||||||
generateConfig={generateConfig}
|
generateConfig={generateConfig}
|
||||||
cellRender={mergedCellRender}
|
cellRender={mergedCellRender}
|
||||||
onSelect={onInternalSelect}
|
onSelect={(nextDate) => {
|
||||||
|
onInternalSelect(nextDate, 'date');
|
||||||
|
}}
|
||||||
mode={panelMode}
|
mode={panelMode}
|
||||||
picker={panelMode}
|
picker={panelMode}
|
||||||
disabledDate={mergedDisabledDate}
|
disabledDate={mergedDisabledDate}
|
||||||
|
@ -55,7 +55,7 @@ When data is in the form of dates, such as schedules, timetables, prices calenda
|
|||||||
| value | The current selected date | [dayjs](https://day.js.org/) | - | |
|
| value | The current selected date | [dayjs](https://day.js.org/) | - | |
|
||||||
| onChange | Callback for when date changes | function(date: Dayjs) | - | |
|
| onChange | Callback for when date changes | function(date: Dayjs) | - | |
|
||||||
| onPanelChange | Callback for when panel changes | function(date: Dayjs, mode: string) | - | |
|
| onPanelChange | Callback for when panel changes | function(date: Dayjs, mode: string) | - | |
|
||||||
| onSelect | Callback for when a date is selected | function(date: Dayjs) | - | |
|
| onSelect | Callback for when a date is selected, include source info | function(date: Dayjs, info: { source: 'year' \| 'month' \| 'date' \| 'customize' }) | - | `info`: 5.6.0 |
|
||||||
|
|
||||||
## Design Token
|
## Design Token
|
||||||
|
|
||||||
@ -74,3 +74,17 @@ See [How to set locale for date-related components](/components/date-picker/#loc
|
|||||||
### Date-related components locale is not working?
|
### Date-related components locale is not working?
|
||||||
|
|
||||||
See FAQ [Date-related-components-locale-is-not-working?](/docs/react/faq#date-related-components-locale-is-not-working)
|
See FAQ [Date-related-components-locale-is-not-working?](/docs/react/faq#date-related-components-locale-is-not-working)
|
||||||
|
|
||||||
|
### How to get date from panel click?
|
||||||
|
|
||||||
|
`onSelect` provide `info.source` to help on this:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Calendar
|
||||||
|
onSelect={(date, { source }) => {
|
||||||
|
if (source === 'date') {
|
||||||
|
console.log('Panel Select:', source);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
@ -60,7 +60,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA
|
|||||||
| value | 展示日期 | [dayjs](https://day.js.org/) | - | |
|
| value | 展示日期 | [dayjs](https://day.js.org/) | - | |
|
||||||
| onChange | 日期变化回调 | function(date: Dayjs) | - | |
|
| onChange | 日期变化回调 | function(date: Dayjs) | - | |
|
||||||
| onPanelChange | 日期面板变化回调 | function(date: Dayjs, mode: string) | - | |
|
| onPanelChange | 日期面板变化回调 | function(date: Dayjs, mode: string) | - | |
|
||||||
| onSelect | 点击选择日期回调 | function(date: Dayjs) | - | |
|
| onSelect | 选择日期回调,包含来源信息 | function(date: Dayjs, info: { source: 'year' \| 'month' \| 'date' \| 'customize' }) | - | `info`: 5.6.0 |
|
||||||
|
|
||||||
## Design Token
|
## Design Token
|
||||||
|
|
||||||
@ -79,3 +79,17 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA
|
|||||||
### 为什么时间类组件的国际化 locale 设置不生效?
|
### 为什么时间类组件的国际化 locale 设置不生效?
|
||||||
|
|
||||||
参考 FAQ [为什么时间类组件的国际化 locale 设置不生效?](/docs/react/faq#为什么时间类组件的国际化-locale-设置不生效)。
|
参考 FAQ [为什么时间类组件的国际化 locale 设置不生效?](/docs/react/faq#为什么时间类组件的国际化-locale-设置不生效)。
|
||||||
|
|
||||||
|
### 如何仅获取来自面板点击的日期?
|
||||||
|
|
||||||
|
`onSelect` 事件提供额外的来源信息,你可以通过 `info.source` 来判断来源:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Calendar
|
||||||
|
onSelect={(date, { source }) => {
|
||||||
|
if (source === 'date') {
|
||||||
|
console.log('Panel Select:', source);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
Loading…
Reference in New Issue
Block a user