Skip to content

Commit

Permalink
feat(DatePicker): support disableTime api (#3226)
Browse files Browse the repository at this point in the history
* feat(datepicker): support disableTime api

* test(datepicker): add disableTime test

* fix(datepicker): fix value DateValue type

---------

Co-authored-by: wū yāng <[email protected]>
  • Loading branch information
HaixingOoO and uyarn authored Nov 28, 2024
1 parent b5b9dbf commit b5227cb
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 30 deletions.
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

0 comments on commit b5227cb

Please sign in to comment.