Skip to content

Commit a0b8809

Browse files
uyarnHaixingOoOtdesign-bot
authored
feat(DatePicker): add range and panelActiveDate API (#4139)
* feat(DatePicker): add range and panelActiveDate API * feat(datepicker): datePicker and DateRangePicker support range and panelActiveDate api * test(snap): test snap update * chore: stash changelog [ci skip] --------- Co-authored-by: HaixingOoO <974758671@qq.com> Co-authored-by: tdesign-bot <tdesign@tencent.com>
1 parent be18a3c commit a0b8809

17 files changed

Lines changed: 1161 additions & 48 deletions

File tree

packages/components/date-picker/DatePicker.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@ import React, { forwardRef, useCallback, useEffect } from 'react';
22

33
import classNames from 'classnames';
44
import dayjs from 'dayjs';
5-
import { isDate } from 'lodash-es';
5+
import { isArray, isDate } from 'lodash-es';
66

77
import { formatDate, formatTime, getDefaultFormat, parseToDayjs } from '@tdesign/common-js/date-picker/format';
8-
import { addMonth, covertToDate, extractTimeObj, isSame, subtractMonth } from '@tdesign/common-js/date-picker/utils';
8+
import {
9+
addMonth,
10+
covertToDate,
11+
extractTimeObj,
12+
isSame,
13+
subtractMonth,
14+
getRangeBounds,
15+
} from '@tdesign/common-js/date-picker/utils';
916

1017
import useConfig from '../hooks/useConfig';
1118
import useDefaultProps from '../hooks/useDefaultProps';
@@ -51,6 +58,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
5158
needConfirm,
5259
multiple,
5360
label,
61+
range,
5462
disableTime,
5563
onClear,
5664
onPick,
@@ -126,8 +134,16 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
126134
setInputValue(formatDate(dateValue, { format }));
127135

128136
if (popupVisible) {
129-
setYear(parseToDayjs(value as DateValue, format).year());
130-
setMonth(parseToDayjs(value as DateValue, format).month());
137+
if (((props.range && isArray(props.range)) || props.panelActiveDate) && !value) {
138+
const rangeBounds = getRangeBounds(props.range);
139+
const yearFromRange = rangeBounds.min?.getFullYear() ?? rangeBounds.max?.getFullYear();
140+
const monthFromRange = rangeBounds.min?.getMonth() ?? rangeBounds.max?.getMonth();
141+
setYear((props.panelActiveDate?.year ?? yearFromRange) as number);
142+
setMonth(props.panelActiveDate?.month ? Number(props.panelActiveDate?.month) - 1 : monthFromRange);
143+
} else {
144+
setYear(parseToDayjs(value as DateValue, format).year());
145+
setMonth(parseToDayjs(value as DateValue, format).month());
146+
}
131147
setTime(formatTime(value, format, timeFormat, defaultTime));
132148
} else {
133149
setIsHoverCell(false);
@@ -371,6 +387,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
371387
popupVisible,
372388
needConfirm,
373389
multiple,
390+
range,
374391
onCellClick,
375392
onCellMouseEnter,
376393
onCellMouseLeave,

packages/components/date-picker/DateRangePicker.tsx

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {
99
isValidDate,
1010
parseToDayjs,
1111
} from '@tdesign/common-js/date-picker/format';
12-
import { addMonth, extractTimeObj, subtractMonth } from '@tdesign/common-js/date-picker/utils';
12+
import { addMonth, extractTimeObj, getRangeBounds, subtractMonth } from '@tdesign/common-js/date-picker/utils';
1313
import log from '@tdesign/common-js/log/index';
14+
import { isArray, isFunction } from 'lodash-es';
1415
import useConfig from '../hooks/useConfig';
1516
import useDefaultProps from '../hooks/useDefaultProps';
1617
import useLatest from '../hooks/useLatest';
@@ -28,6 +29,7 @@ import type {
2829
TdDateRangePickerProps,
2930
DatePickerYearChangeTrigger,
3031
DatePickerMonthChangeTrigger,
32+
PickerDateRange,
3133
} from './type';
3234

3335
export interface DateRangePickerProps extends TdDateRangePickerProps, StyledProps {}
@@ -50,6 +52,8 @@ const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>((origin
5052
presetsPlacement,
5153
panelPreselection,
5254
cancelRangeSelectLimit,
55+
range,
56+
panelActiveDate,
5357
onPick,
5458
disableTime,
5559
needConfirm,
@@ -152,14 +156,80 @@ const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>((origin
152156

153157
// 空数据重置为当前年月
154158
if (!value.length) {
155-
const { year: defaultYear, month: defaultMonth } = initYearMonthTime({ value, mode, format, enableTimePicker });
156-
setYear(defaultYear);
157-
setMonth(defaultMonth);
159+
if ((range && isArray(range)) || panelActiveDate) {
160+
let startRange = range as PickerDateRange;
161+
let endRange = range as PickerDateRange;
162+
if (isArray(range)) {
163+
const [first, second] = range;
164+
if (isArray(first) || isFunction(first) || isArray(second) || isFunction(second)) {
165+
startRange = first as PickerDateRange;
166+
endRange = second as PickerDateRange;
167+
}
168+
}
169+
170+
const startRangeBounds = getRangeBounds(startRange);
171+
const endRangeBounds = getRangeBounds(endRange);
172+
173+
const startYearFromRange = startRangeBounds.min?.getFullYear() ?? startRangeBounds.max?.getFullYear();
174+
const startMonthFromRange = startRangeBounds.min?.getMonth() ?? startRangeBounds.max?.getMonth();
175+
176+
const endYearFromRange = endRangeBounds.min?.getFullYear() ?? endRangeBounds.max?.getFullYear();
177+
const endMonthFromRange = endRangeBounds.min?.getMonth() ?? endRangeBounds.max?.getMonth();
178+
179+
let startPanelActiveDate = panelActiveDate as any;
180+
let endPanelActiveDate = panelActiveDate as any;
181+
if (isArray(panelActiveDate)) {
182+
[startPanelActiveDate, endPanelActiveDate] = panelActiveDate;
183+
}
184+
185+
const leftYear = (startPanelActiveDate?.year ?? startYearFromRange) as number;
186+
const leftMonth = startPanelActiveDate?.month ? Number(startPanelActiveDate?.month) - 1 : startMonthFromRange;
187+
const rightYear = (endPanelActiveDate?.year ?? endYearFromRange) as number;
188+
const rightMonth = endPanelActiveDate?.month ? Number(endPanelActiveDate?.month) - 1 : endMonthFromRange;
189+
190+
// 获取默认值作为兜底
191+
const { year: defaultYear, month: defaultMonth } = initYearMonthTime({
192+
value,
193+
mode,
194+
format,
195+
enableTimePicker,
196+
});
197+
198+
const nextYear = [leftYear ?? defaultYear[0], rightYear ?? defaultYear[1]];
199+
const nextMonth = [leftMonth ?? defaultMonth[0], rightMonth ?? defaultMonth[1]];
200+
201+
// 修正:如果左右面板年月完全一致且未显式指定不同范围,则偏移面板
202+
if (nextYear[0] === nextYear[1] && nextMonth[0] === nextMonth[1] && !enableTimePicker) {
203+
if (startRange === endRange) {
204+
if (mode === 'year') nextYear[1] += 10;
205+
else if (mode === 'month' || mode === 'quarter') nextYear[1] += 1;
206+
else nextMonth[1] += 1;
207+
}
208+
}
209+
setYear(nextYear);
210+
setMonth(nextMonth);
211+
} else {
212+
const { year: defaultYear, month: defaultMonth } = initYearMonthTime({
213+
value,
214+
mode,
215+
format,
216+
enableTimePicker,
217+
});
218+
219+
setYear(defaultYear);
220+
setMonth(defaultMonth);
221+
}
158222
} else if (value.length === 2 && (!enableTimePicker || isSwitchTimeMode)) {
159223
handleSyncPanelValue(value);
160224
} else {
161-
setYear(value.map((v: string) => parseToDayjs(v, format).year()));
162-
setMonth(value.map((v: string) => parseToDayjs(v, format).month()));
225+
let nextYear = value.map((v: string) => parseToDayjs(v, format).year());
226+
if (nextYear.length === 1) nextYear = [nextYear[0], nextYear[0]];
227+
228+
let nextMonth = value.map((v: string) => parseToDayjs(v, format).month());
229+
if (nextMonth.length === 1) nextMonth = [nextMonth[0], Math.min(nextMonth[0] + 1, 11)];
230+
231+
setYear(nextYear);
232+
setMonth(nextMonth);
163233
}
164234
} else {
165235
setActiveIndex(0);
@@ -451,6 +521,7 @@ const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>((origin
451521
panelPreselection,
452522
year,
453523
month,
524+
range,
454525
mode,
455526
format,
456527
presets,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from 'react';
2+
import { DatePicker, DateRangePicker, Space } from 'tdesign-react';
3+
import dayjs from 'dayjs';
4+
5+
export default function DatePickerRangeDemo() {
6+
// 仅允许今天到未来 90 天(返回 true 表示可选)
7+
const rangeFn = (d: Date) => {
8+
const now = dayjs().startOf('day');
9+
const target = dayjs(d).startOf('day');
10+
const diff = target.diff(now, 'day');
11+
return diff >= 0 && diff <= 90;
12+
};
13+
14+
return (
15+
<Space direction="vertical">
16+
<DatePicker
17+
range={['2000-01-01', '2010-12-31']}
18+
panelActiveDate={{
19+
year: 2005,
20+
month: 10,
21+
}}
22+
placeholder="2000-2010之间的日期"
23+
/>
24+
<DateRangePicker
25+
range={['2000-01-01', '2010-12-31']}
26+
panelActiveDate={{
27+
year: 2005,
28+
month: 10,
29+
}}
30+
placeholder="2000-2010之间的日期"
31+
/>
32+
33+
{/* 2026开始,不限制结束 */}
34+
<DatePicker
35+
range={['2026-01-01', null]}
36+
panelActiveDate={{
37+
year: 2005,
38+
month: 10,
39+
}}
40+
placeholder="2026开始,不限制结束"
41+
/>
42+
<DateRangePicker
43+
range={['2026-01-01', null]}
44+
panelActiveDate={{
45+
year: 2005,
46+
month: 10,
47+
}}
48+
placeholder="2026开始,不限制结束"
49+
/>
50+
51+
{/* 2026开始,不限制结束 */}
52+
<DatePicker range={['2026-01-01', null]} placeholder="2026开始,不限制结束" />
53+
<DateRangePicker range={['2026-01-01', null]} placeholder="2026开始,不限制结束" />
54+
55+
{/* 未来 90 天 */}
56+
<DatePicker range={rangeFn} placeholder="未来 90 天" />
57+
<DateRangePicker range={rangeFn} placeholder="未来 90 天" />
58+
</Space>
59+
);
60+
}

0 commit comments

Comments
 (0)