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): datepicker support multiple api #3199

Merged
merged 15 commits into from
Nov 27, 2024
2 changes: 1 addition & 1 deletion src/_common
67 changes: 60 additions & 7 deletions src/date-picker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import dayjs from 'dayjs';
import isDate from 'lodash/isDate';
import useConfig from '../hooks/useConfig';
import { StyledProps } from '../common';
import { TdDatePickerProps, PresetDate } from './type';
import { TdDatePickerProps, PresetDate, DateMultipleValue, DateValue } from './type';
import SelectInput from '../select-input';
import SinglePanel from './panel/SinglePanel';
import useSingle from './hooks/useSingle';
import { parseToDayjs, getDefaultFormat, formatTime, formatDate } from '../_common/js/date-picker/format';
import { subtractMonth, addMonth, extractTimeObj, covertToDate } from '../_common/js/date-picker/utils';
import { subtractMonth, addMonth, extractTimeObj, covertToDate, isSame } from '../_common/js/date-picker/utils';
import { datePickerDefaultProps } from './defaultProps';
import useDefaultProps from '../hooks/useDefaultProps';
import useLatest from '../hooks/useLatest';
import useUpdateEffect from '../hooks/useUpdateEffect';
import type { TagInputRemoveContext } from '../tag-input';

export interface DatePickerProps extends TdDatePickerProps, StyledProps {}

Expand All @@ -35,6 +36,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
timePickerProps,
presetsPlacement,
needConfirm,
multiple,
onPick,
} = props;

Expand All @@ -43,6 +45,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
popupVisible,
inputProps,
popupProps,
tagInputProps,
value,
year,
month,
Expand All @@ -63,7 +66,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
mode,
format: props.format,
valueType: props.valueType,
enableTimePicker,
enableTimePicker: multiple ? false : enableTimePicker,
});

const onTriggerNeedConfirm = useLatest(() => {
Expand All @@ -90,6 +93,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
}, [popupVisible]);

useEffect(() => {
if (multiple) return;
// 面板展开重置数据
// Date valueType、week mode 、quarter mode nad empty string don't need to be parsed
const dateValue =
Expand All @@ -100,8 +104,8 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
setInputValue(formatDate(dateValue, { format }));

if (popupVisible) {
setYear(parseToDayjs(value, format).year());
setMonth(parseToDayjs(value, format).month());
setYear(parseToDayjs(value as DateValue, format).year());
setMonth(parseToDayjs(value as DateValue, format).month());
setTime(formatTime(value, format, timeFormat, defaultTime));
} else {
setIsHoverCell(false);
Expand All @@ -111,12 +115,14 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r

// 日期 hover
function onCellMouseEnter(date: Date) {
if (multiple) return;
setIsHoverCell(true);
setInputValue(formatDate(date, { format }));
}

// 日期 leave
function onCellMouseLeave() {
if (multiple) return;
setIsHoverCell(false);
setInputValue(formatDate(cacheValue, { format }));
}
Expand All @@ -133,6 +139,14 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
if (enableTimePicker) {
setCacheValue(formatDate(date, { format }));
} else {
if (multiple) {
const newDate = processDate(date);
onChange(newDate, {
dayjsValue: parseToDayjs(date, format),
trigger: 'pick',
});
return;
}
onChange(formatDate(date, { format, targetFormat: valueType }), {
dayjsValue: parseToDayjs(date, format),
trigger: 'pick',
Expand Down Expand Up @@ -224,21 +238,54 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
// eslint-disable-next-line
}, []);

function processDate(date: Date) {
const isSameDate = (value as DateMultipleValue).some((val) => isSame(dayjs(val).toDate(), date));
let currentDate: DateMultipleValue;

if (!isSameDate) {
currentDate = (value as DateMultipleValue).concat(formatDate(date, { format, targetFormat: valueType }));
} else {
currentDate = (value as DateMultipleValue).filter(
(val) =>
formatDate(val, { format, targetFormat: valueType }) !==
formatDate(date, { format, targetFormat: valueType }),
);
}

return currentDate.sort((a, b) => dayjs(a).valueOf() - dayjs(b).valueOf());
}

const onTagRemoveClick = (ctx: TagInputRemoveContext) => {
const removeDate = dayjs(ctx.item).toDate();
const newDate = processDate(removeDate);
onChange?.(newDate, {
dayjsValue: parseToDayjs(removeDate, format),
trigger: 'pick',
});
};

const onTagClearClick = ({ e }) => {
e.stopPropagation();
setPopupVisible(false);
onChange([], { dayjsValue: dayjs(), trigger: 'clear' });
};

const panelProps = {
value: cacheValue,
year,
month,
mode,
format,
presets,
time,
time: multiple ? false : time,
disableDate,
firstDayOfWeek,
timePickerProps,
enableTimePicker,
enableTimePicker: multiple ? false : enableTimePicker,
presetsPlacement,
popupVisible,
needConfirm,
multiple,
onCellClick,
onCellMouseEnter,
onCellMouseLeave,
Expand All @@ -263,6 +310,12 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
inputProps={inputProps}
popupVisible={popupVisible}
panel={<SinglePanel {...panelProps} />}
multiple={multiple}
tagInputProps={{
onRemove: onTagRemoveClick,
...tagInputProps,
}}
onClear={onTagClearClick}
/>
</div>
);
Expand Down
12 changes: 6 additions & 6 deletions src/date-picker/DatePickerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ const DatePickerPanel = forwardRef<HTMLDivElement, DatePickerPanelProps>((origin
if (year !== nextYear) {
props.onYearChange?.({
year: nextYear,
date: parseToDayjs(value, format).toDate(),
date: parseToDayjs(value as DateValue, format).toDate(),
trigger: trigger === 'current' ? 'today' : (`year-${triggerMap[trigger]}` as DatePickerYearChangeTrigger),
});
}

if (month !== nextMonth) {
props.onMonthChange?.({
month: nextMonth,
date: parseToDayjs(value, format).toDate(),
date: parseToDayjs(value as DateValue, format).toDate(),
trigger: trigger === 'current' ? 'today' : (`month-${triggerMap[trigger]}` as DatePickerMonthChangeTrigger),
});
}
Expand All @@ -117,7 +117,7 @@ const DatePickerPanel = forwardRef<HTMLDivElement, DatePickerPanelProps>((origin

props.onTimeChange?.({
time: val,
date: parseToDayjs(value, format).toDate(),
date: parseToDayjs(value as DateValue, format).toDate(),
trigger: 'time-hour',
});
}
Expand All @@ -129,7 +129,7 @@ const DatePickerPanel = forwardRef<HTMLDivElement, DatePickerPanelProps>((origin
trigger: 'confirm',
});

props.onConfirm?.({ date: dayjs(value).toDate(), e });
props.onConfirm?.({ date: dayjs(value as DateValue).toDate(), e });
}

// 预设
Expand All @@ -151,7 +151,7 @@ const DatePickerPanel = forwardRef<HTMLDivElement, DatePickerPanelProps>((origin

props.onYearChange?.({
year,
date: parseToDayjs(value, format).toDate(),
date: parseToDayjs(value as DateValue, format).toDate(),
trigger: 'year-select',
});
}
Expand All @@ -161,7 +161,7 @@ const DatePickerPanel = forwardRef<HTMLDivElement, DatePickerPanelProps>((origin

props.onMonthChange?.({
month,
date: parseToDayjs(value, format).toDate(),
date: parseToDayjs(value as DateValue, format).toDate(),
trigger: 'month-select',
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/date-picker/_example/date-time.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default function YearDatePicker() {
const [value, setValue] = useState<DateValue>('2022-02-02 12:11:11');
const [value2, setValue2] = useState<DateValue>('2022-02-02 am 12:11:11');

const handleChange: DatePickerProps['onChange'] = (value) => {
const handleChange: DatePickerProps['onChange'] = (value: DateValue) => {
console.log(value);
setValue(value);
};
Expand All @@ -18,7 +18,7 @@ export default function YearDatePicker() {
<DatePicker
enableTimePicker
value={value2}
onChange={(v) => setValue2(v)}
onChange={(v: DateValue) => setValue2(v)}
allowInput
clearable
format="YYYY-MM-DD a hh:mm:ss"
Expand Down
24 changes: 24 additions & 0 deletions src/date-picker/_example/multiple.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { useState } from 'react';
import { DatePicker } from 'tdesign-react';
import type { DatePickerProps, DateMultipleValue } from 'tdesign-react';

export default function YearDatePicker() {
const [defaultValue, setDefaultValue] = useState<DateMultipleValue>(['2000-01-04', '2000-01-03', '2000-01-05']);

const handleChange: DatePickerProps['onChange'] = (value: DateMultipleValue, context) => {
console.log('onChange:', value, context);
setDefaultValue(value);
};

return (
<div>
<DatePicker
value={defaultValue}
placeholder="可清除、可输入的日期选择器"
onChange={handleChange}
clearable
multiple
/>
</div>
);
}
4 changes: 2 additions & 2 deletions src/date-picker/base/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import classNames from 'classnames';
import { useLocaleReceiver } from '../../locale/LocalReceiver';
import Button from '../../button';
import useConfig from '../../hooks/useConfig';
import { TdDatePickerProps, TdDateRangePickerProps, DateValue } from '../type';
import { TdDatePickerProps, TdDateRangePickerProps, DateValue, DateMultipleValue } from '../type';

interface DatePickerFooterProps
extends Pick<TdDatePickerProps, 'enableTimePicker' | 'presetsPlacement' | 'needConfirm'> {
presets?: TdDatePickerProps['presets'] | TdDateRangePickerProps['presets'];
onPresetClick?: Function;
onConfirmClick?: Function;
selectedValue?: DateValue;
selectedValue?: DateValue | DateMultipleValue;
}

const DatePickerFooter = (props: DatePickerFooterProps) => {
Expand Down
16 changes: 10 additions & 6 deletions src/date-picker/date-picker.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,28 @@ 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 | - | make DatePicker to be disabled | N
disabled | Boolean | undefined | 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
inputProps | Object | - | Typescript:`InputProps`,[Input API Documents](./input?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
mode | String | date | options: year/quarter/month/week/date | N
multiple | Boolean | false | support multiple date,but not support being use together with range-picker、enableTimePicker and allowInput。Typescript:`boolean` | N
needConfirm | Boolean | true | whether a confirmation button needs to be clicked to complete the action in the date-time picker scenario, default is true | N
placeholder | String / Array | undefined | Typescript:`string` | N
popupProps | Object | - | Typescript:`PopupProps`,[Popup API Documents](./popup?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
prefixIcon | TElement | - | Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
presets | Object | - | Typescript:`PresetDate` `interface PresetDate { [name: string]: DateValue \| (() => DateValue) }`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
presetsPlacement | String | bottom | options: left/top/right/bottom | N
selectInputProps | Object | - | Typescript:`SelectInputProps`,[SelectInput API Documents](./select-input?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
size | String | medium | options: small/medium/large。Typescript:`SizeEnum`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
status | String | default | options: default/success/warning/error | N
suffixIcon | TElement | - | Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
timePickerProps | Object | - | Typescript:`TimePickerProps`,[TimePicker API Documents](./time-picker?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
tips | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
value | String / Number / Date | '' | Typescript:`DateValue` `type DateValue = string \| number \| Date`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
defaultValue | String / Number / Date | '' | uncontrolled property。Typescript:`DateValue` `type DateValue = string \| number \| Date`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
value | String / Number / Array / Date | '' | Typescript:`DateValue \| DateMultipleValue` ` type DateValue = string \| number \| Date ` ` type DateMultipleValue = Array<DateValue> `。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
defaultValue | String / Number / Array / Date | '' | uncontrolled property。Typescript:`DateValue \| DateMultipleValue` ` type DateValue = string \| number \| Date ` ` type DateMultipleValue = Array<DateValue> `。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
valueDisplay | TNode | - | `MouseEvent<SVGElement>`。Typescript:`string \| TNode<{ value: DateValue; displayValue?: DateValue }>`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
valueType | String | - | Typescript:`DatePickerValueType` `type DatePickerValueType = 'time-stamp' \| 'Date' \| 'YYYY' \| 'YYYY-MM' \| 'YYYY-MM-DD' \| 'YYYY-MM-DD HH' \| 'YYYY-MM-DD HH:mm' \| 'YYYY-MM-DD HH:mm:ss' \| 'YYYY-MM-DD HH:mm:ss:SSS'` `type ValueTypeEnum = DatePickerValueType`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
onBlur | Function | | Typescript:`(context: { value: DateValue; e: FocusEvent }) => void`<br/> | N
onChange | Function | | Typescript:`(value: DateValue, context: { dayjsValue?: Dayjs, trigger?: DatePickerTriggerSource }) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts)。<br/>`import { Dayjs } from 'dayjs'`<br/><br/>`type DatePickerTriggerSource = 'confirm' \| 'pick' \| 'enter' \| 'preset' \| 'clear'`<br/> | N
Expand All @@ -49,15 +52,16 @@ className | String | - | className of component | N
style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N
allowInput | Boolean | false | \- | N
borderless | Boolean | false | \- | N
cancelRangeSelectLimit | Boolean | false | The default date selection interaction is determined based on the order of dates clicked and will be restricted. For example, if a user first clicks on the start date input box and chooses a date, for instance, 2020-05-15, the interaction will automatically shift focus to the end date input box, waiting for the user to select the end time. At this point, the user can only select a date later than 2020-05-15 (previous dates will be grayed out and disabled, restricting the user's selection). When this value is set to `true`, this restriction is lifted. | N
cancelRangeSelectLimit | Boolean | false | The default date selection interaction is determined based on the order of dates clicked and will be restricted. For example, if a user first clicks on the start date input box and chooses a date, for instance, 2020-05-15, the interaction will automatically shift focus to the end date input box, waiting for the user to select the end time. At this point, the user can only select a date later than 2020-05-15 (previous dates will be grayed out and disabled, restricting the user's selection). When this value is set to `true`, this restriction is lifted | N
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 | - | \- | N
disabled | Boolean | undefined | \- | N
enableTimePicker | Boolean | false | \- | N
firstDayOfWeek | Number | - | options: 1/2/3/4/5/6/7 | N
format | String | - | \- | N
mode | String | date | options: year/quarter/month/week/date | N
needConfirm | Boolean | true | whether a confirmation button needs to be clicked to complete the action in the date-time range picker scenario, default is true | N
panelPreselection | Boolean | true | \- | N
placeholder | String / Array | - | Typescript:`string \| Array<string>` | N
popupProps | Object | - | Typescript:`PopupProps`,[Popup API Documents](./popup?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/date-picker/type.ts) | N
Expand Down Expand Up @@ -90,7 +94,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'>` | \- | - | extends `Pick<DatePickerProps, 'value' \| 'defaultValue' \| 'disableDate' \| 'enableTimePicker' \| 'firstDayOfWeek' \| 'format' \| 'mode' \| 'presets' \| 'presetsPlacement' \| 'timePickerProps'>` | 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
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
Loading