Skip to content

Commit

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

* chore(datepicker): adjustment props multiple

* chore(datepicker): update onRemove and onClear methods

* chore(datepicker): code clean

* chore(datepicker): code clean

* fix: fix type

* fix: fix type

* chore: update common

* chore: update snapshot

* chore: revert api change

* chore: revert api change

* chore: docs  update

* chore: type

* chore: code clean

---------

Co-authored-by: Uyarn <[email protected]>
  • Loading branch information
HaixingOoO and uyarn authored Nov 27, 2024
1 parent 533f124 commit e241722
Show file tree
Hide file tree
Showing 20 changed files with 308 additions and 68 deletions.
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

0 comments on commit e241722

Please sign in to comment.