Skip to content

Commit d27a31e

Browse files
committed
feat: optimize the DatePicker component
1 parent 5136a47 commit d27a31e

14 files changed

+391
-142
lines changed

Diff for: packages/bui-core/src/DatePicker/DatePicker.tsx

+38-18
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import React, { forwardRef, useEffect, useState } from 'react';
22
import clsx from 'clsx';
33
import { useValue } from '@bifrostui/utils';
4-
import { DatePickerProps, DatePickerType } from './DatePicker.types';
5-
import Picker, { IPickerOptionItem } from '../Picker';
4+
import {
5+
DatePickerProps,
6+
DatePickerType,
7+
DatePickerOption,
8+
} from './DatePicker.types';
9+
import Picker from '../Picker';
10+
import { useLocaleText } from '../locales';
611

712
const MIN_DATE = new Date(new Date().getFullYear() - 10, 0, 1);
813
const MAX_DATE = new Date(new Date().getFullYear() + 10, 11, 31);
@@ -58,7 +63,9 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, ref) => {
5863
views = DEFAULT_PICKER,
5964
minDate: propMinDate = MIN_DATE,
6065
maxDate: propMaxDate = MAX_DATE,
66+
showUnit = false,
6167
formatter,
68+
filter,
6269
disableDateTimeView,
6370
dateTimeStep,
6471
onConfirm,
@@ -67,8 +74,9 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, ref) => {
6774
...others
6875
} = props;
6976

70-
const [options, setOptions] = useState<IPickerOptionItem[][]>([]);
77+
const [options, setOptions] = useState<DatePickerOption[][]>([]);
7178
const [pickerValue, setPickerValue] = useState<(string | number)[]>([]);
79+
const datePickerText = useLocaleText('datePicker');
7280

7381
const formatDate = (date: Date | null) => {
7482
let formattedDate = date;
@@ -142,6 +150,23 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, ref) => {
142150
onChange?.(e, { type: views[columnIndex], value: current });
143151
};
144152

153+
const formatOption = (type: DatePickerType, value: number) => {
154+
let label = padZero(value);
155+
156+
if (formatter) {
157+
return formatter(type, {
158+
value,
159+
label,
160+
});
161+
}
162+
163+
if (showUnit) {
164+
label += datePickerText[type];
165+
}
166+
167+
return { value, label };
168+
};
169+
145170
const generateOptions = (
146171
min: number,
147172
max: number,
@@ -155,23 +180,11 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, ref) => {
155180
(_, index) => {
156181
const value = index * step + min;
157182

158-
const option = {
159-
value: padZero(value),
160-
label: padZero(value),
161-
};
162-
163183
if (value <= getDateTypeValue(currentDate, type)) {
164184
valueIndex = index;
165185
}
166186

167-
if (formatter) {
168-
return formatter(type, {
169-
value: option.value,
170-
label: option.label,
171-
});
172-
}
173-
174-
return option;
187+
return formatOption(type, value);
175188
},
176189
);
177190

@@ -182,10 +195,12 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, ref) => {
182195
disableDateTimeView?.[type] &&
183196
typeof disableDateTimeView[type] === 'function'
184197
) {
185-
const disabledOptions = disableDateTimeView[type](optionsArray);
198+
const disabledOptionsValue = disableDateTimeView[type](
199+
optionsArray.map((i) => i.value),
200+
);
186201

187202
return optionsArray.map((option) => {
188-
if (disabledOptions.includes(option)) {
203+
if (disabledOptionsValue.includes(option.value)) {
189204
return {
190205
...option,
191206
disabled: true,
@@ -196,6 +211,10 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, ref) => {
196211
});
197212
}
198213

214+
if (filter) {
215+
return filter(type, optionsArray);
216+
}
217+
199218
return optionsArray;
200219
};
201220

@@ -289,6 +308,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, ref) => {
289308
return (
290309
<Picker
291310
{...others}
311+
data-selected={currentDate ? currentDate.getTime() : ''}
292312
className={clsx('bui-date-picker', className)}
293313
ref={ref}
294314
options={options}

Diff for: packages/bui-core/src/DatePicker/DatePicker.types.ts

+35-15
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export enum DatePickerType {
1111
SECOND = 'second',
1212
}
1313

14+
export type DatePickerOption = IPickerOptionItem & {
15+
value: number;
16+
};
17+
1418
export type DatePickerProps<
1519
D extends React.ElementType = 'div',
1620
P = Omit<
@@ -20,29 +24,45 @@ export type DatePickerProps<
2024
> = OverrideProps<
2125
{
2226
props: P & {
23-
defaultValue?: Date; // 默认选中的值,当组件非受控时使用
24-
value?: Date; // 选中的值,当组件受控时使用
25-
views?: DatePickerType[]; // 日期选择器类型
26-
minDate?: Date; // 可选择的最小日期
27-
maxDate?: Date; // 可选择的最大日期
27+
// 默认选中的值,当组件非受控时使用
28+
defaultValue?: Date;
29+
// 选中的值,当组件受控时使用
30+
value?: Date;
31+
// 日期选择器类型
32+
views?: DatePickerType[];
33+
// 可选择的最小日期
34+
minDate?: Date;
35+
// 可选择的最大日期
36+
maxDate?: Date;
37+
// 是否展示选择器的单位,年/月/日/时/分/秒
38+
showUnit?: boolean;
39+
// 禁止选择的日期
2840
disableDateTimeView?: Partial<{
29-
[key in DatePickerType]: (
30-
options: IPickerOptionItem[],
31-
) => IPickerOptionItem[];
32-
}>; // 禁止选择的日期
41+
[key in DatePickerType]: (options: number[]) => number[];
42+
}>;
43+
// 时间间隔,设置递增步长
3344
dateTimeStep?: Partial<{
3445
[key in DatePickerType]: number;
35-
}>; // 时间间隔,设置递增步长
46+
}>;
47+
// 对选项进行格式化处理
3648
formatter?: (
3749
type: DatePickerType,
38-
option: IPickerOptionItem,
39-
) => IPickerOptionItem; // 对选项进行格式化处理
40-
onConfirm?: (e: React.SyntheticEvent, { value }: { value: Date }) => void; // 点击确定按钮时触发
41-
onClose?: (e: React.SyntheticEvent, { value }: { value: Date }) => void; // 确定和取消时都触发
50+
option: DatePickerOption,
51+
) => DatePickerOption;
52+
// 对选项进行过滤
53+
filter?: (
54+
type: DatePickerType,
55+
options: DatePickerOption[],
56+
) => DatePickerOption[];
57+
// 点击确定按钮时触发
58+
onConfirm?: (e: React.SyntheticEvent, { value }: { value: Date }) => void;
59+
// 确定和取消时都触发
60+
onClose?: (e: React.SyntheticEvent, { value }: { value: Date }) => void;
61+
// 选项改变时触发
4262
onChange?: (
4363
e: React.SyntheticEvent,
4464
{ value }: { type: DatePickerType; value: Date },
45-
) => void; // 选项改变时触发
65+
) => void;
4666
};
4767
defaultComponent: D;
4868
},

Diff for: packages/bui-core/src/DatePicker/__tests__/DatePicker.test.tsx

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { act, fireEvent, isConformant, render } from 'testing';
3-
import DatePicker, { DatePickerType } from '..';
3+
import DatePicker, { DatePickerType, DatePickerOption } from '..';
44

55
const rootClass = 'bui-date-picker';
66
const currentDate = new Date(2025, 1, 26);
@@ -178,8 +178,8 @@ describe('DatePicker', () => {
178178

179179
it('should render with disabled options', () => {
180180
const disableDateTimeView = {
181-
[DatePickerType.YEAR]: (options) =>
182-
options.filter((option) => Number(option.value) === 2025),
181+
[DatePickerType.YEAR]: (values) =>
182+
values.filter((value) => value === 2025),
183183
};
184184
render(<DatePicker open disableDateTimeView={disableDateTimeView} />);
185185
const hiddenOption = document.querySelector(
@@ -313,6 +313,34 @@ describe('DatePicker', () => {
313313
);
314314
});
315315

316+
it('should render with showUnit', () => {
317+
render(<DatePicker open showUnit minDate={minDate} maxDate={maxDate} />);
318+
const panels = document.querySelectorAll('.bui-picker-panel');
319+
const options = panels[0].querySelectorAll('.bui-picker-panel-option');
320+
expect(panels.length).toBe(3);
321+
expect(options[0].textContent).toBe('2020年');
322+
});
323+
324+
it('should render with filter', () => {
325+
render(
326+
<DatePicker
327+
open
328+
views={[DatePickerType.HOUR]}
329+
filter={(type: DatePickerType, options: DatePickerOption[]) => {
330+
switch (type) {
331+
case DatePickerType.HOUR:
332+
return options.filter((option) => option.value % 2 === 0);
333+
default:
334+
return options;
335+
}
336+
}}
337+
/>,
338+
);
339+
const panels = document.querySelectorAll('.bui-picker-panel');
340+
const options = panels[0].querySelectorAll('.bui-picker-panel-option');
341+
expect(options[1].textContent).toBe('02');
342+
});
343+
316344
it('show throw error when views is invalid', () => {
317345
expect(() => {
318346
render(<DatePicker open views={['invalid' as any]} />);

0 commit comments

Comments
 (0)