Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DatePicker): support disableTime api #3226

Merged
merged 4 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/date-picker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
timePickerProps,
presetsPlacement,
needConfirm,
disableTime,
multiple,
onPick,
} = props;
Expand Down Expand Up @@ -296,6 +297,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
onMonthChange,
onTimePickerChange,
onPanelClick: () => inputRef.current?.focus?.(),
disableTime,
};

return (
Expand Down
2 changes: 2 additions & 0 deletions src/date-picker/DatePickerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const DatePickerPanel = forwardRef<HTMLDivElement, DatePickerPanelProps>((origin
presetsPlacement,
needConfirm,
onPanelClick,
disableTime,
} = props;

const { format } = getDefaultFormat({
Expand Down Expand Up @@ -188,6 +189,7 @@ const DatePickerPanel = forwardRef<HTMLDivElement, DatePickerPanelProps>((origin
onMonthChange,
onTimePickerChange,
onPanelClick,
disableTime,
};

return <SinglePanel ref={ref} className={className} style={style} {...panelProps} />;
Expand Down
4 changes: 3 additions & 1 deletion src/date-picker/DateRangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>((origin
timePickerProps,
presetsPlacement,
panelPreselection,
onPick,
cancelRangeSelectLimit,
onPick,
disableTime,
} = props;

const {
Expand Down Expand Up @@ -367,6 +368,7 @@ const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>((origin
onYearChange,
onMonthChange,
onTimePickerChange,
disableTime,
};

return (
Expand Down
18 changes: 18 additions & 0 deletions src/date-picker/__tests__/date-picker-panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import { render, vi, fireEvent } from '@test/utils';

import { DatePickerPanel } from '..';

const disableTime = (time: Date) => {
if (dayjs(time).format('YYYY-MM-DD') === dayjs('2024-11-26').format('YYYY-MM-DD')) {
return {
hour: [0, 1, 2, 3, 4, 5, 6],
};
}
return {};
};

describe('DatePickerPanel', () => {
beforeEach(() => {
const mockDate = new Date(2023, 8, 1);
Expand Down Expand Up @@ -143,4 +152,13 @@ describe('DatePickerPanel', () => {
const monthSelect = container.querySelector('.t-date-picker__header-controller-month');
fireEvent.click(monthSelect);
});

test('disableTime', async () => {
const { container } = render(
<DatePickerPanel value="2024-11-26 07:00:00" enableTimePicker disableTime={disableTime} />,
);

expect(container.querySelector('.t-date-picker__cell--active').firstChild.firstChild).toHaveTextContent('26');
expect(container.querySelectorAll('.t-time-picker__panel-body-scroll')[0].children).toHaveLength(17);
});
});
20 changes: 20 additions & 0 deletions src/date-picker/__tests__/date-picker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import { render, fireEvent, waitFor, vi } from '@test/utils';
import DatePicker from '..';
import type { DateValue } from '../type';

const disableTime = (time: Date) => {
if (dayjs(time).format('YYYY-MM-DD') === dayjs('2024-11-26').format('YYYY-MM-DD')) {
return {
hour: [0, 1, 2, 3, 4, 5, 6],
};
}
return {};
};

describe('DatePicker', () => {
beforeEach(() => {
const mockDate = new Date(2022, 7, 27);
Expand Down Expand Up @@ -326,4 +335,15 @@ describe('DatePicker', () => {
const monthSelect = await waitFor(() => document.querySelector('.t-date-picker__header-controller-month'));
fireEvent.click(monthSelect);
});

test('disableTime', async () => {
const { container } = render(<DatePicker value="2024-11-26 07:00:00" enableTimePicker disableTime={disableTime} />);

fireEvent.mouseDown(container.querySelector('input'));

await waitFor(() => {
expect(document.querySelector('.t-date-picker__cell--active')?.firstChild?.firstChild).toHaveTextContent('26');
expect(document.querySelectorAll('.t-time-picker__panel-body-scroll')[0].children).toHaveLength(17);
});
});
});
28 changes: 10 additions & 18 deletions src/date-picker/_example/disable-date.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import React, { useState, useMemo } from 'react';
import React from 'react';
import dayjs from 'dayjs';
import { DatePicker, DateRangePicker, Space } from 'tdesign-react';

export default function YearDatePicker() {
const [pickDate, setPickDate] = useState('');

const timePickerProps = useMemo(
() => ({
disableTime: () => {
if (pickDate === dayjs().format('YYYY-MM-DD')) {
return {
hour: [0, 1, 2, 3, 4, 5, 6],
};
}
return {};
},
}),
[pickDate],
);
const disableTime = (time: Date) => {
if (dayjs(time).format('YYYY-MM-DD') === dayjs().format('YYYY-MM-DD')) {
return {
hour: [0, 1, 2, 3, 4, 5, 6],
};
}
return {};
};

return (
<Space direction="vertical">
Expand All @@ -44,8 +37,7 @@ export default function YearDatePicker() {
placeholder="禁用日期精确到时间"
enableTimePicker
disableDate={{ before: dayjs().subtract(1, 'day').format() }}
timePickerProps={timePickerProps}
onPick={(date) => setPickDate(dayjs(date).format('YYYY-MM-DD'))}
disableTime={disableTime}
/>
<DateRangePicker
placeholder="禁用最近 5 天外的日期"
Expand Down
8 changes: 5 additions & 3 deletions src/date-picker/date-picker.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ borderless | Boolean | false | \- | N
clearable | Boolean | false | \- | N
defaultTime | String | '00:00:00' | Time selector default value | N
disableDate | Object / Array / Function | - | Typescript:`DisableDate` `type DisableDate = Array<DateValue> \| DisableDateObj \| ((date: DateValue) => boolean)` `interface DisableDateObj { from?: string; to?: string; before?: string; after?: string }`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
disabled | Boolean | undefined | make DatePicker to be disabled | N
disableTime | Function | - | disable time config function。Typescript:`(time: Date) => Partial<{ hour: Array<number>, minute: Array<number>, second: Array<number>, millisecond: Array<number> }>` | N
disabled | Boolean | - | make DatePicker to be disabled | N
enableTimePicker | Boolean | false | \- | N
firstDayOfWeek | Number | 7 | options: 1/2/3/4/5/6/7 | N
format | String | 'YYYY-MM-DD' | \- | N
Expand Down Expand Up @@ -56,7 +57,8 @@ cancelRangeSelectLimit | Boolean | false | The default date selection interactio
clearable | Boolean | false | \- | N
defaultTime | Array | ["00:00:00", "23:59:59"] | Time selector default value。Typescript:`string[]` | N
disableDate | Object / Array / Function | - | Typescript:`DisableRangeDate` `type DisableRangeDate = Array<DateValue> \| DisableDateObj \| ((context: { date: DateRangeValue; partial: DateRangePickerPartial }) => boolean)` `interface DisableDateObj { from?: string; to?: string; before?: string; after?: string }` `type DateRangePickerPartial = 'start' \| 'end'`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
disabled | Boolean | undefined | \- | N
disableTime | Function | - | disable time config function。Typescript:`(times: Array<Date \| null>, context: { partial: DateRangePickerPartial }) => Partial<{ hour: Array<number>, minute: Array<number>, second: Array<number> }>` | N
disabled | Boolean | - | \- | N
enableTimePicker | Boolean | false | \- | N
firstDayOfWeek | Number | - | options: 1/2/3/4/5/6/7 | N
format | String | - | \- | N
Expand Down Expand Up @@ -94,7 +96,7 @@ name | type | default | description | required
className | String | - | className of component | N
style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N
defaultTime | String | '00:00:00' | Time selector default value | N
`Pick<DatePickerProps, 'value' \| 'defaultValue' \| 'disableDate' \| 'enableTimePicker' \| 'firstDayOfWeek' \| 'format' \| 'mode' \| 'presets' \| 'presetsPlacement' \| 'timePickerProps' \| 'needConfirm'>` | \- | - | extends `Pick<DatePickerProps, 'value' \| 'defaultValue' \| 'disableDate' \| 'enableTimePicker' \| 'firstDayOfWeek' \| 'format' \| 'mode' \| 'presets' \| 'presetsPlacement' \| 'timePickerProps' \| 'needConfirm'>` | N
`Pick<DatePickerProps, 'value' \| 'defaultValue' \| 'disableDate' \| 'disableTime' \| 'enableTimePicker' \| 'firstDayOfWeek' \| 'format' \| 'mode' \| 'presets' \| 'presetsPlacement' \| 'timePickerProps' \| 'needConfirm'>` | \- | - | extends `Pick<DatePickerProps, 'value' \| 'defaultValue' \| 'disableDate' \| 'disableTime' \| 'enableTimePicker' \| 'firstDayOfWeek' \| 'format' \| 'mode' \| 'presets' \| 'presetsPlacement' \| 'timePickerProps' \| 'needConfirm'>` | N
onCellClick | Function | | Typescript:`(context: { date: Date, e: MouseEvent }) => void`<br/> | N
onChange | Function | | Typescript:`(value: DateValue, context: { dayjsValue?: Dayjs, e?: MouseEvent, trigger?: DatePickerTriggerSource }) => void`<br/> | N
onConfirm | Function | | Typescript:`(context: { date: Date, e: MouseEvent }) => void`<br/> | N
Expand Down
8 changes: 5 additions & 3 deletions src/date-picker/date-picker.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ borderless | Boolean | false | 无边框模式 | N
clearable | Boolean | false | 是否显示清除按钮 | N
defaultTime | String | '00:00:00' | 时间选择器默认值,当 value/defaultValue 未设置值时有效 | N
disableDate | Object / Array / Function | - | 禁用日期,示例:['A', 'B'] 表示日期 A 和日期 B 会被禁用。`{ from: 'A', to: 'B' }` 表示在 A 到 B 之间的日期会被禁用。`{ before: 'A', after: 'B' }` 表示在 A 之前和在 B 之后的日期都会被禁用。其中 A = '2021-01-01',B = '2021-02-01'。值类型为 Function 则表示返回值为 true 的日期会被禁用。TS 类型:`DisableDate` `type DisableDate = Array<DateValue> \| DisableDateObj \| ((date: DateValue) => boolean)` `interface DisableDateObj { from?: string; to?: string; before?: string; after?: string }`。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
disabled | Boolean | undefined | 是否禁用组件 | N
disableTime | Function | - | 禁用时间项的配置函数,仅在日期时间选择器中可用。TS 类型:`(time: Date) => Partial<{ hour: Array<number>, minute: Array<number>, second: Array<number>, millisecond: Array<number> }>` | N
disabled | Boolean | - | 是否禁用组件 | N
enableTimePicker | Boolean | false | 是否显示时间选择 | N
firstDayOfWeek | Number | 7 | 第一天从星期几开始。可选项:1/2/3/4/5/6/7 | N
format | String | 'YYYY-MM-DD' | 仅用于格式化日期显示的格式,不影响日期值。注意和 `valueType` 的区别,`valueType`会直接决定日期值 `value` 的格式。全局配置默认为:'YYYY-MM-DD',[详细文档](https://day.js.org/docs/en/display/format) | N
Expand Down Expand Up @@ -55,7 +56,8 @@ cancelRangeSelectLimit | Boolean | false | 默认的日期选择交互是根据
clearable | Boolean | false | 是否显示清除按钮 | N
defaultTime | Array | ["00:00:00", "23:59:59"] | 时间选择器默认值,当 value/defaultValue 未设置值时有效。TS 类型:`string[]` | N
disableDate | Object / Array / Function | - | 禁用日期,示例:['A', 'B'] 表示日期 A 和日期 B 会被禁用。{ from: 'A', to: 'B' } 表示在 A 到 B 之间的日期会被禁用。{ before: 'A', after: 'B' } 表示在 A 之前和在 B 之后的日期都会被禁用。其中 A = '2021-01-01',B = '2021-02-01'。值类型为 Function 则表示返回值为 true 的日期会被禁用。TS 类型:`DisableRangeDate` `type DisableRangeDate = Array<DateValue> \| DisableDateObj \| ((context: { date: DateRangeValue; partial: DateRangePickerPartial }) => boolean)` `interface DisableDateObj { from?: string; to?: string; before?: string; after?: string }` `type DateRangePickerPartial = 'start' \| 'end'`。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
disabled | Boolean | undefined | 是否禁用组件 | N
disableTime | Function | - | 禁用时间项的配置函数,仅在日期区间选择器中开启时间展示时可用。TS 类型:`(times: Array<Date \| null>, context: { partial: DateRangePickerPartial }) => Partial<{ hour: Array<number>, minute: Array<number>, second: Array<number> }>` | N
disabled | Boolean | - | 是否禁用组件 | N
enableTimePicker | Boolean | false | 是否显示时间选择 | N
firstDayOfWeek | Number | - | 第一天从星期几开始。可选项:1/2/3/4/5/6/7 | N
format | String | - | 用于格式化日期,[详细文档](https://day.js.org/docs/en/display/format) | N
Expand Down Expand Up @@ -93,7 +95,7 @@ onPresetClick | Function | | TS 类型:`(context: { preset: PresetDate, e: Mo
className | String | - | 类名 | N
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
defaultTime | String | '00:00:00' | 时间选择器默认值,当 value/defaultValue 未设置值时有效 | N
`Pick<DatePickerProps, 'value' \| 'defaultValue' \| 'disableDate' \| 'enableTimePicker' \| 'firstDayOfWeek' \| 'format' \| 'mode' \| 'presets' \| 'presetsPlacement' \| 'timePickerProps' \| 'needConfirm'>` | \- | - | 继承 `Pick<DatePickerProps, 'value' \| 'defaultValue' \| 'disableDate' \| 'enableTimePicker' \| 'firstDayOfWeek' \| 'format' \| 'mode' \| 'presets' \| 'presetsPlacement' \| 'timePickerProps' \| 'needConfirm'>` 中的全部属性 | N
`Pick<DatePickerProps, 'value' \| 'defaultValue' \| 'disableDate' \| 'disableTime' \| 'enableTimePicker' \| 'firstDayOfWeek' \| 'format' \| 'mode' \| 'presets' \| 'presetsPlacement' \| 'timePickerProps' \| 'needConfirm'>` | \- | - | 继承 `Pick<DatePickerProps, 'value' \| 'defaultValue' \| 'disableDate' \| 'disableTime' \| 'enableTimePicker' \| 'firstDayOfWeek' \| 'format' \| 'mode' \| 'presets' \| 'presetsPlacement' \| 'timePickerProps' \| 'needConfirm'>` 中的全部属性 | N
onCellClick | Function | | TS 类型:`(context: { date: Date, e: MouseEvent }) => void`<br/>点击日期单元格时触发 | N
onChange | Function | | TS 类型:`(value: DateValue, context: { dayjsValue?: Dayjs, e?: MouseEvent, trigger?: DatePickerTriggerSource }) => void`<br/>选中值发生变化时触发。参数 `context.trigger` 表示触发当前事件的来源,不同的模式触发来源也会不同 | N
onConfirm | Function | | TS 类型:`(context: { date: Date, e: MouseEvent }) => void`<br/>如果存在“确定”按钮,则点击“确定”按钮时触发 | N
Expand Down
1 change: 1 addition & 0 deletions src/date-picker/panel/PanelContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export default function PanelContent(props: PanelContentProps) {
<div className={`${panelName}-time-viewer`}>{time || defaultTime}</div>
<TimePickerPanel
key={partial}
position={partial}
format={timeFormat}
value={time || defaultTime}
onChange={onTimePickerChange}
Expand Down
24 changes: 21 additions & 3 deletions src/date-picker/panel/RangePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { forwardRef } from 'react';
import classNames from 'classnames';
import isFunction from 'lodash/isFunction';
import useConfig from '../../hooks/useConfig';
import { StyledProps } from '../../common';
import PanelContent from './PanelContent';
Expand All @@ -8,6 +9,7 @@ import { getDefaultFormat, parseToDayjs } from '../../_common/js/date-picker/for
import useTableData from '../hooks/useTableData';
import useDisableDate from '../hooks/useDisableDate';
import useDefaultProps from '../../hooks/useDefaultProps';
import { parseToDateTime } from '../utils';

import type { TdDateRangePickerProps } from '../type';
import type { TdTimePickerProps } from '../../time-picker';
Expand Down Expand Up @@ -62,10 +64,11 @@ const RangePanel = forwardRef<HTMLDivElement, RangePanelProps>((originalProps, r
month,
time = [],
panelPreselection,
onClick,
onConfirmClick,
onPresetClick,
cancelRangeSelectLimit,
onClick,
onConfirmClick,
disableTime,
} = props;

const { format } = getDefaultFormat({
Expand All @@ -88,6 +91,18 @@ const RangePanel = forwardRef<HTMLDivElement, RangePanelProps>((originalProps, r
: undefined,
});

const disableTimeOptions: TdTimePickerProps['disableTime'] = (h, m, s, ms) => {
if (!isFunction(disableTime)) {
return {};
}

const [startTime, endTime] = value || [];

return disableTime([parseToDateTime(startTime, format), parseToDateTime(endTime, format, [h, m, s, ms])], {
partial: activeIndex === 0 ? 'start' : 'end',
});
};

const [startYear, endYear] = year;
const [startMonth, endMonth] = month;

Expand Down Expand Up @@ -128,7 +143,10 @@ const RangePanel = forwardRef<HTMLDivElement, RangePanelProps>((originalProps, r

popupVisible: props.popupVisible,
enableTimePicker: props.enableTimePicker,
timePickerProps: props.timePickerProps,
timePickerProps: {
disableTime: disableTimeOptions,
...props.timePickerProps,
},
onMonthChange: props.onMonthChange,
onYearChange: props.onYearChange,
onJumperClick: props.onJumperClick,
Expand Down
18 changes: 16 additions & 2 deletions src/date-picker/panel/SinglePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import React, { forwardRef } from 'react';
import classNames from 'classnames';
import isFunction from 'lodash/isFunction';
import useConfig from '../../hooks/useConfig';
import { StyledProps } from '../../common';
import PanelContent from './PanelContent';
import ExtraContent from './ExtraContent';
import { TdDatePickerProps } from '../type';
import type { DateValue, TdDatePickerProps } from '../type';
import type { TdTimePickerProps } from '../../time-picker';
import { getDefaultFormat, parseToDayjs } from '../../_common/js/date-picker/format';
import useTableData from '../hooks/useTableData';
import useDisableDate from '../hooks/useDisableDate';
import useDefaultProps from '../../hooks/useDefaultProps';
import { parseToDateTime } from '../utils';

export interface SinglePanelProps extends TdDatePickerProps, StyledProps {
year?: number;
Expand Down Expand Up @@ -47,6 +49,7 @@ const SinglePanel = forwardRef<HTMLDivElement, SinglePanelProps>((originalProps,
year,
month,
onPanelClick,
disableTime,
} = props;

const { format } = getDefaultFormat({
Expand All @@ -57,6 +60,14 @@ const SinglePanel = forwardRef<HTMLDivElement, SinglePanelProps>((originalProps,

const disableDateOptions = useDisableDate({ disableDate: props.disableDate, mode: props.mode, format });

const disableTimeOptions: TdTimePickerProps['disableTime'] = (h: number, m: number, s: number, ms: number) => {
if (!isFunction(disableTime) || !value) {
return {};
}

return disableTime(parseToDateTime(value as DateValue, format, [h, m, s, ms]));
};

const tableData = useTableData({
value,
year,
Expand All @@ -79,7 +90,10 @@ const SinglePanel = forwardRef<HTMLDivElement, SinglePanelProps>((originalProps,
popupVisible: props.popupVisible,
multiple: props.multiple,
time: props.time,
timePickerProps: props.timePickerProps,
timePickerProps: {
disableTime: disableTimeOptions,
...props.timePickerProps,
},
enableTimePicker: props.enableTimePicker,
onMonthChange: props.onMonthChange,
onYearChange: props.onYearChange,
Expand Down
14 changes: 14 additions & 0 deletions src/date-picker/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export interface TdDatePickerProps {
* 禁用日期,示例:['A', 'B'] 表示日期 A 和日期 B 会被禁用。`{ from: 'A', to: 'B' }` 表示在 A 到 B 之间的日期会被禁用。`{ before: 'A', after: 'B' }` 表示在 A 之前和在 B 之后的日期都会被禁用。其中 A = '2021-01-01',B = '2021-02-01'。值类型为 Function 则表示返回值为 true 的日期会被禁用
*/
disableDate?: DisableDate;
/**
* 禁用时间项的配置函数,仅在日期时间选择器中可用
*/
disableTime?: (
time: Date,
) => Partial<{ hour: Array<number>; minute: Array<number>; second: Array<number>; millisecond: Array<number> }>;
/**
* 是否禁用组件
*/
Expand Down Expand Up @@ -191,6 +197,13 @@ export interface TdDateRangePickerProps {
* 禁用日期,示例:['A', 'B'] 表示日期 A 和日期 B 会被禁用。{ from: 'A', to: 'B' } 表示在 A 到 B 之间的日期会被禁用。{ before: 'A', after: 'B' } 表示在 A 之前和在 B 之后的日期都会被禁用。其中 A = '2021-01-01',B = '2021-02-01'。值类型为 Function 则表示返回值为 true 的日期会被禁用
*/
disableDate?: DisableRangeDate;
/**
* 禁用时间项的配置函数,仅在日期区间选择器中开启时间展示时可用
*/
disableTime?: (
times: Array<Date | null>,
context: { partial: DateRangePickerPartial },
) => Partial<{ hour: Array<number>; minute: Array<number>; second: Array<number> }>;
/**
* 是否禁用组件
*/
Expand Down Expand Up @@ -344,6 +357,7 @@ export interface TdDatePickerPanelProps
| 'value'
| 'defaultValue'
| 'disableDate'
| 'disableTime'
| 'enableTimePicker'
| 'firstDayOfWeek'
| 'format'
Expand Down
Loading