Skip to content

Commit 42f853b

Browse files
authored
chore: optimize disabledDate logic (#191)
co-author <[email protected]>
1 parent 2db525e commit 42f853b

File tree

9 files changed

+259
-21
lines changed

9 files changed

+259
-21
lines changed

examples/disabledDate.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React from 'react';
2+
import type { Moment } from 'moment';
3+
import moment from 'moment';
4+
import Picker from '../src/Picker';
5+
import momentGenerateConfig from '../src/generate/moment';
6+
import enUS from '../src/locale/en_US';
7+
import '../assets/index.less';
8+
9+
export default () => {
10+
const [value, setValue] = React.useState<Moment | null>(undefined);
11+
12+
const onSelect = (newValue: Moment) => {
13+
console.log('Select:', newValue);
14+
};
15+
16+
const onChange = (newValue: Moment | null, formatString?: string) => {
17+
console.log('Change:', newValue, formatString);
18+
setValue(newValue);
19+
};
20+
21+
function disabledDateBeforeToday(current: Moment) {
22+
return current <= moment().endOf('day');
23+
}
24+
25+
function disabledDateAfterToday(current: Moment) {
26+
return current >= moment().endOf('day');
27+
}
28+
29+
function disabledDateAfterTodayAndBeforeLastYear(current) {
30+
return current >= moment().startOf('day') || current < moment().subtract(1, 'years');
31+
}
32+
33+
const sharedProps = {
34+
generateConfig: momentGenerateConfig,
35+
value,
36+
onSelect,
37+
onChange,
38+
};
39+
40+
return (
41+
<div style={{ paddingBottom: '20px' }}>
42+
<h1>Value: {value ? value.format('YYYY-MM-DD HH:mm:ss') : 'null'}</h1>
43+
<h2>Date Mode</h2>
44+
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
45+
<div style={{ margin: '0 8px' }}>
46+
<h3>Before Today</h3>
47+
<Picker<Moment> {...sharedProps} disabledDate={disabledDateBeforeToday} locale={enUS} />
48+
</div>
49+
<div style={{ margin: '0 8px' }}>
50+
<h3>After Today</h3>
51+
<Picker<Moment> {...sharedProps} disabledDate={disabledDateAfterToday} locale={enUS} />
52+
</div>
53+
<div style={{ margin: '0 8px' }}>
54+
<h3>After Today or Before last year</h3>
55+
<Picker<Moment>
56+
{...sharedProps}
57+
disabledDate={disabledDateAfterTodayAndBeforeLastYear}
58+
locale={enUS}
59+
/>
60+
</div>
61+
</div>
62+
</div>
63+
);
64+
};

src/PanelContext.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import type { OnSelect } from './interface';
2+
import type { OnSelect, PanelMode } from './interface';
33

44
export type ContextOperationRefProps = {
55
onKeyDown?: (e: React.KeyboardEvent<HTMLElement>) => boolean;
@@ -18,6 +18,7 @@ export type PanelContextProps = {
1818
onSelect?: OnSelect<any>;
1919
hideRanges?: boolean;
2020
open?: boolean;
21+
mode?: PanelMode;
2122

2223
/** Only used for TimePicker and this is a deprecated prop */
2324
defaultOpenValue?: any;

src/Picker.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,12 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
358358
};
359359
}
360360

361+
const [hoverValue, onEnter, onLeave] = useHoverValue(text, {
362+
formatList,
363+
generateConfig,
364+
locale,
365+
});
366+
361367
// ============================= Panel =============================
362368
const panelProps = {
363369
// Remove `picker` & `format` here since TimePicker is little different with other panel
@@ -384,6 +390,11 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
384390
setSelectedValue(date);
385391
}}
386392
direction={direction}
393+
onPanelChange={(viewDate, mode) => {
394+
const { onPanelChange } = props;
395+
onLeave(true);
396+
onPanelChange?.(viewDate, mode);
397+
}}
387398
/>
388399
);
389400

@@ -446,12 +457,6 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
446457
};
447458
const popupPlacement = direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
448459

449-
const [hoverValue, onEnter, onLeave] = useHoverValue(text, {
450-
formatList,
451-
generateConfig,
452-
locale,
453-
});
454-
455460
return (
456461
<PanelContext.Provider
457462
value={{

src/PickerPanel.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
344344
onViewDateChange: setViewDate,
345345
sourceMode,
346346
onPanelChange: onInternalPanelChange,
347-
disabledDate: mergedMode !== 'decade' ? disabledDate : undefined,
347+
disabledDate,
348348
};
349349
delete pickerProps.onChange;
350350
delete pickerProps.onSelect;
@@ -518,6 +518,7 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
518518
<PanelContext.Provider
519519
value={{
520520
...panelContext,
521+
mode: mergedMode,
521522
hideHeader: 'hideHeader' in props ? hideHeader : panelContext.hideHeader,
522523
hidePrevBtn: inRange && panelPosition === 'right',
523524
hideNextBtn: inRange && panelPosition === 'left',

src/RangePicker.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,13 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
815815
locale={locale}
816816
tabIndex={-1}
817817
onPanelChange={(date, newMode) => {
818+
// clear hover value when panel change
819+
if (mergedActivePickerIndex === 0) {
820+
onStartLeave(true);
821+
}
822+
if (mergedActivePickerIndex === 1) {
823+
onEndLeave(true);
824+
}
818825
triggerModesChange(
819826
updateValues(mergedModes, newMode, mergedActivePickerIndex),
820827
updateValues(selectedValue, date, mergedActivePickerIndex),

src/panels/DecadePanel/DecadeBody.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export type YearBodyProps<DateType> = {
1616

1717
function DecadeBody<DateType>(props: YearBodyProps<DateType>) {
1818
const DECADE_UNIT_DIFF_DES = DECADE_UNIT_DIFF - 1;
19-
const { prefixCls, viewDate, generateConfig, disabledDate } = props;
19+
const { prefixCls, viewDate, generateConfig } = props;
2020

2121
const cellPrefixCls = `${prefixCls}-cell`;
2222

@@ -35,13 +35,10 @@ function DecadeBody<DateType>(props: YearBodyProps<DateType>) {
3535
);
3636

3737
const getCellClassName = (date: DateType) => {
38-
const disabled = disabledDate && disabledDate(date);
39-
4038
const startDecadeNumber = generateConfig.getYear(date);
4139
const endDecadeNumber = startDecadeNumber + DECADE_UNIT_DIFF_DES;
4240

4341
return {
44-
[`${cellPrefixCls}-disabled`]: disabled,
4542
[`${cellPrefixCls}-in-view`]:
4643
startDecadeYear <= startDecadeNumber && endDecadeNumber <= endDecadeYear,
4744
[`${cellPrefixCls}-selected`]: startDecadeNumber === decadeYearNumber,

src/panels/PanelBody.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import PanelContext from '../PanelContext';
44
import type { GenerateConfig } from '../generate';
55
import { getLastDay } from '../utils/timeUtil';
66
import type { PanelMode } from '../interface';
7+
import { getCellDateDisabled } from '../utils/dateUtil';
78

89
export type PanelBodyProps<DateType> = {
910
prefixCls: string;
@@ -46,7 +47,7 @@ export default function PanelBody<DateType>({
4647
titleCell,
4748
headerCells,
4849
}: PanelBodyProps<DateType>) {
49-
const { onDateMouseEnter, onDateMouseLeave } = React.useContext(PanelContext);
50+
const { onDateMouseEnter, onDateMouseLeave, mode } = React.useContext(PanelContext);
5051

5152
const cellPrefixCls = `${prefixCls}-cell`;
5253

@@ -60,7 +61,12 @@ export default function PanelBody<DateType>({
6061
for (let j = 0; j < colNum; j += 1) {
6162
const offset = i * colNum + j;
6263
const currentDate = getCellDate(baseDate, offset);
63-
const disabled = disabledDate && disabledDate(currentDate);
64+
const disabled = getCellDateDisabled({
65+
cellDate: currentDate,
66+
mode,
67+
disabledDate,
68+
generateConfig,
69+
});
6470

6571
if (j === 0) {
6672
rowStartDate = currentDate;
@@ -78,8 +84,11 @@ export default function PanelBody<DateType>({
7884
title={title}
7985
className={classNames(cellPrefixCls, {
8086
[`${cellPrefixCls}-disabled`]: disabled,
81-
[`${cellPrefixCls}-start`]: getCellText(currentDate) === 1 || picker === 'year' && Number(title) % 10 === 0,
82-
[`${cellPrefixCls}-end`]: title === getLastDay(generateConfig, currentDate) || picker === 'year' && Number(title) % 10 === 9,
87+
[`${cellPrefixCls}-start`]:
88+
getCellText(currentDate) === 1 || (picker === 'year' && Number(title) % 10 === 0),
89+
[`${cellPrefixCls}-end`]:
90+
title === getLastDay(generateConfig, currentDate) ||
91+
(picker === 'year' && Number(title) % 10 === 9),
8392
...getCellClassName(currentDate),
8493
})}
8594
onClick={() => {

src/utils/dateUtil.ts

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { DECADE_UNIT_DIFF } from '../panels/DecadePanel/index';
2+
import type { PanelMode, NullableDateType, PickerMode, Locale, CustomFormat } from '../interface';
13
import type { GenerateConfig } from '../generate';
2-
import type { NullableDateType, PickerMode, Locale, CustomFormat } from '../interface';
34

45
export const WEEK_DAY_COUNT = 7;
56

@@ -228,3 +229,93 @@ export function parseValue<DateType>(
228229

229230
return generateConfig.locale.parse(locale.locale, value, formatList as string[]);
230231
}
232+
233+
// eslint-disable-next-line consistent-return
234+
export function getCellDateDisabled<DateType>({
235+
cellDate,
236+
mode,
237+
disabledDate,
238+
generateConfig,
239+
}: {
240+
cellDate: DateType;
241+
mode: Omit<PanelMode, 'time'>;
242+
generateConfig: GenerateConfig<DateType>;
243+
disabledDate?: (date: DateType) => boolean;
244+
}): boolean {
245+
if (!disabledDate) return false;
246+
// Whether cellDate is disabled in range
247+
const getDisabledFromRange = (
248+
currentMode: 'date' | 'month' | 'year',
249+
start: number,
250+
end: number,
251+
) => {
252+
let current = start;
253+
while (current <= end) {
254+
let date: DateType;
255+
switch (currentMode) {
256+
case 'date': {
257+
date = generateConfig.setDate(cellDate, current);
258+
if (!disabledDate(date)) {
259+
return false;
260+
}
261+
break;
262+
}
263+
case 'month': {
264+
date = generateConfig.setMonth(cellDate, current);
265+
if (
266+
!getCellDateDisabled({
267+
cellDate: date,
268+
mode: 'month',
269+
generateConfig,
270+
disabledDate,
271+
})
272+
) {
273+
return false;
274+
}
275+
break;
276+
}
277+
case 'year': {
278+
date = generateConfig.setYear(cellDate, current);
279+
if (
280+
!getCellDateDisabled({
281+
cellDate: date,
282+
mode: 'year',
283+
generateConfig,
284+
disabledDate,
285+
})
286+
) {
287+
return false;
288+
}
289+
break;
290+
}
291+
}
292+
current += 1;
293+
}
294+
return true;
295+
};
296+
switch (mode) {
297+
case 'date':
298+
case 'week': {
299+
return disabledDate(cellDate);
300+
}
301+
case 'month': {
302+
const startDate = 1;
303+
const endDate = generateConfig.getDate(generateConfig.getEndDate(cellDate));
304+
return getDisabledFromRange('date', startDate, endDate);
305+
}
306+
case 'quarter': {
307+
const startMonth = Math.floor(generateConfig.getMonth(cellDate) / 3) * 3;
308+
const endMonth = startMonth + 2;
309+
return getDisabledFromRange('month', startMonth, endMonth);
310+
}
311+
case 'year': {
312+
return getDisabledFromRange('month', 0, 11);
313+
}
314+
case 'decade': {
315+
const year = generateConfig.getYear(cellDate);
316+
const startYear = Math.floor(year / DECADE_UNIT_DIFF) * DECADE_UNIT_DIFF;
317+
const endYear = startYear + DECADE_UNIT_DIFF - 1;
318+
return getDisabledFromRange('year', startYear, endYear);
319+
}
320+
}
321+
}

0 commit comments

Comments
 (0)