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:
二货爱吃白萝卜 2023-05-17 21:18:51 +08:00 committed by GitHub
parent 4efa0b64db
commit 71fc54e53a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 162 additions and 20 deletions

View File

@ -5,7 +5,7 @@ import { useContext, useMemo } from 'react';
import { FormItemInputContext } from '../form/context';
import { Button, Group } from '../radio';
import Select from '../select';
import type { CalendarMode } from './generateCalendar';
import type { CalendarMode, SelectInfo } from './generateCalendar';
const YearSelectOffset = 10;
const YearSelectTotal = 20;
@ -147,7 +147,7 @@ export interface CalendarHeaderProps<DateType> {
locale: Locale;
mode: CalendarMode;
fullscreen: boolean;
onChange: (date: DateType) => void;
onChange: (date: DateType, source: SelectInfo['source']) => void;
onModeChange: (mode: CalendarMode) => void;
}
function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
@ -165,7 +165,6 @@ function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
const sharedProps = {
...props,
onChange,
fullscreen,
divRef,
};
@ -173,8 +172,20 @@ function CalendarHeader<DateType>(props: CalendarHeaderProps<DateType>) {
return (
<div className={`${prefixCls}-header`} ref={divRef}>
<FormItemInputContext.Provider value={mergedFormItemInputContext}>
<YearSelect {...sharedProps} />
{mode === 'month' && <MonthSelect {...sharedProps} />}
<YearSelect
{...sharedProps}
onChange={(v) => {
onChange(v, 'year');
}}
/>
{mode === 'month' && (
<MonthSelect
{...sharedProps}
onChange={(v) => {
onChange(v, 'month');
}}
/>
)}
</FormItemInputContext.Provider>
<ModeSwitch {...sharedProps} onModeChange={onModeChange} />
</div>

View File

@ -76,7 +76,7 @@ describe('Calendar', () => {
const { container } = render(<Calendar onSelect={onSelect} onChange={onChange} />);
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];
expect(Dayjs.isDayjs(value)).toBe(true);
@ -270,7 +270,7 @@ describe('Calendar', () => {
const end = Dayjs('2019-11-01');
const onValueChange = jest.fn();
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', () => {
@ -279,7 +279,7 @@ describe('Calendar', () => {
const end = Dayjs('2019-03-01');
const onValueChange = jest.fn();
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', () => {
@ -302,7 +302,7 @@ describe('Calendar', () => {
fireEvent.click(
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', () => {
@ -324,7 +324,7 @@ describe('Calendar', () => {
);
openSelect(wrapper.container, '.ant-picker-calendar-month-select');
clickSelectItem(wrapper.container);
expect(onValueChange).toHaveBeenCalledWith(value.month(10));
expect(onValueChange).toHaveBeenCalledWith(value.month(10), 'month');
});
it('onTypeChange should work correctly', () => {

View 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' });
});
});

View File

@ -1,12 +1,12 @@
import classNames from 'classnames';
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 {
PickerPanelBaseProps as RCPickerPanelBaseProps,
PickerPanelDateProps as RCPickerPanelDateProps,
PickerPanelTimeProps as RCPickerPanelTimeProps,
} 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 * as React from 'react';
import { ConfigContext } from '../config-provider';
@ -14,8 +14,8 @@ import { useLocale } from '../locale';
import CalendarHeader from './Header';
import enUS from './locale/en_US';
import useStyle from './style';
import warning from '../_util/warning';
import useStyle from './style';
type InjectDefaultProps<Props> = Omit<
Props,
@ -43,6 +43,10 @@ export type HeaderRender<DateType> = (config: {
onTypeChange: (type: CalendarMode) => void;
}) => React.ReactNode;
export interface SelectInfo {
source: 'year' | 'month' | 'date' | 'customize';
}
export interface CalendarProps<DateType> {
prefixCls?: string;
className?: string;
@ -68,7 +72,7 @@ export interface CalendarProps<DateType> {
fullscreen?: boolean;
onChange?: (date: DateType) => void;
onPanelChange?: (date: DateType, mode: CalendarMode) => void;
onSelect?: (date: DateType) => void;
onSelect?: (date: DateType, selectInfo: SelectInfo) => void;
}
function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
@ -198,10 +202,10 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
triggerPanelChange(mergedValue, newMode);
};
const onInternalSelect = (date: DateType) => {
const onInternalSelect = (date: DateType, source: SelectInfo['source']) => {
triggerChange(date);
onSelect?.(date);
onSelect?.(date, { source });
};
// ====================== Locale ======================
@ -310,7 +314,9 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
headerRender({
value: mergedValue,
type: mergedMode,
onChange: onInternalSelect,
onChange: (nextDate) => {
onInternalSelect(nextDate, 'customize');
},
onTypeChange: triggerModeChange,
})
) : (
@ -332,7 +338,9 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
locale={contextLocale?.lang}
generateConfig={generateConfig}
cellRender={mergedCellRender}
onSelect={onInternalSelect}
onSelect={(nextDate) => {
onInternalSelect(nextDate, 'date');
}}
mode={panelMode}
picker={panelMode}
disabledDate={mergedDisabledDate}

View File

@ -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/) | - | |
| onChange | Callback for when date changes | function(date: Dayjs) | - | |
| 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
@ -74,3 +74,17 @@ See [How to set locale for date-related components](/components/date-picker/#loc
### 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);
}
}}
/>
```

View File

@ -60,7 +60,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA
| value | 展示日期 | [dayjs](https://day.js.org/) | - | |
| onChange | 日期变化回调 | function(date: Dayjs) | - | |
| 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
@ -79,3 +79,17 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA
### 为什么时间类组件的国际化 locale 设置不生效?
参考 FAQ [为什么时间类组件的国际化 locale 设置不生效?](/docs/react/faq#为什么时间类组件的国际化-locale-设置不生效)。
### 如何仅获取来自面板点击的日期?
`onSelect` 事件提供额外的来源信息,你可以通过 `info.source` 来判断来源:
```tsx
<Calendar
onSelect={(date, { source }) => {
if (source === 'date') {
console.log('Panel Select:', source);
}
}}
/>
```