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): support week and year multiple #3264

Merged
merged 19 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/date-picker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import useDefaultProps from '../hooks/useDefaultProps';
import useLatest from '../hooks/useLatest';
import useUpdateEffect from '../hooks/useUpdateEffect';
import type { TagInputRemoveContext } from '../tag-input';
import { useLocaleReceiver } from '../locale/LocalReceiver';

export interface DatePickerProps extends TdDatePickerProps, StyledProps {}

Expand Down Expand Up @@ -64,6 +65,7 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
setCacheValue,
} = useSingle(props);

const [local] = useLocaleReceiver('datePicker');
const { format, timeFormat, valueType } = getDefaultFormat({
mode,
format: props.format,
Expand Down Expand Up @@ -241,20 +243,28 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((originalProps, r
}, []);

function processDate(date: Date) {
const isSameDate = (value as DateMultipleValue).some((val) => isSame(dayjs(val).toDate(), date));
let isSameDate: boolean;
const currentValue = (value || []) as DateMultipleValue;
if (mode !== 'week')
isSameDate = currentValue.some((val) =>
isSame(parseToDayjs(val, format).toDate(), date, mode, local.dayjsLocale),
);
else {
isSameDate = currentValue.some((val) => val === dayjs(date).locale(local.dayjsLocale).format(format));
}
let currentDate: DateMultipleValue;

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

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

const onTagRemoveClick = (ctx: TagInputRemoveContext) => {
Expand Down
44 changes: 36 additions & 8 deletions src/date-picker/_example/multiple.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,52 @@
import React, { useState } from 'react';
import { DatePicker } from 'tdesign-react';
import { DatePicker, Space } 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 [dateValue, setDateValue] = useState<DateMultipleValue>(['2024-10-01', '2024-10-24']);
const [weekValue, setWeekValue] = useState<DateMultipleValue>(['2024-50周', '2024-51周']);
const [yearValue, setYearValue] = useState<DateMultipleValue>(['2022', '2023', '2024']);

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

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

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

return (
<div>
<Space direction="vertical">
<DatePicker
value={dateValue}
placeholder="可清除、可输入的日期选择器"
onChange={handleDateChange}
clearable
multiple
/>
<DatePicker
value={weekValue}
placeholder="可清除、可输入的日期选择器"
onChange={handleWeekChange}
clearable
multiple
mode="week"
/>
<DatePicker
value={defaultValue}
value={yearValue}
placeholder="可清除、可输入的日期选择器"
onChange={handleChange}
onChange={handleYearChange}
clearable
multiple
mode="year"
/>
</div>
</Space>
);
}
26 changes: 22 additions & 4 deletions src/date-picker/base/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ import React, { useMemo } from 'react';
import classNames from 'classnames';
import dayjs from 'dayjs';
import isoWeek from 'dayjs/plugin/isoWeek';

import type { Dayjs } from 'dayjs';
import { useLocaleReceiver } from '../../locale/LocalReceiver';
import useConfig from '../../hooks/useConfig';
import DatePickerCell from './Cell';
import { TdDatePickerProps } from '../type';

import { SinglePanelProps } from '../panel/SinglePanel';
import { PanelContentProps } from '../panel/PanelContent';
import { parseToDayjs } from '../../_common/js/date-picker/format';

import type { DateMultipleValue, DateRangeValue, DateValue, TdDatePickerProps } from '../type';

dayjs.extend(isoWeek);

export interface DatePickerTableProps
extends Pick<TdDatePickerProps, 'mode' | 'firstDayOfWeek' | 'format'>,
extends Pick<TdDatePickerProps, 'mode' | 'firstDayOfWeek' | 'format' | 'multiple'>,
Pick<SinglePanelProps, 'onCellClick' | 'onCellMouseEnter' | 'onCellMouseLeave'>,
Pick<PanelContentProps, 'value'> {
data?: Array<any>;
Expand Down Expand Up @@ -60,7 +64,7 @@ const DatePickerTable = (props: DatePickerTableProps) => {
}, [mode, value, format]);

// 高亮周区间
const weekRowClass = (value, targetDayjs) => {
const weekRowClass = (value: DateValue | DateRangeValue, targetDayjs: Dayjs) => {
if (mode !== 'week' || !value) return {};

if (Array.isArray(value)) {
Expand Down Expand Up @@ -90,6 +94,20 @@ const DatePickerTable = (props: DatePickerTableProps) => {
};
};

// multiple
const multipleWeekRowClass = (value: DateMultipleValue, targetDayjs: Dayjs) => {
if (mode !== 'week' || (Array.isArray(value) && !value.length)) return {};
const isSomeYearWeek = value
.map((v) => parseToDayjs(v, format))
.some((item) => item.isoWeek() === targetDayjs.isoWeek() && item.isoWeekYear() === targetDayjs.isoWeekYear());

return {
[`${classPrefix}-date-picker__table-${mode}-row--active`]: isSomeYearWeek,
};
};

const activeRowCss = props.multiple ? multipleWeekRowClass : weekRowClass;

return (
<div className={`${classPrefix}-date-picker__table`} onMouseLeave={(e) => onCellMouseLeave?.({ e })}>
<table>
Expand All @@ -107,7 +125,7 @@ const DatePickerTable = (props: DatePickerTableProps) => {
<tr
key={i}
className={classNames(`${classPrefix}-date-picker__table-${mode}-row`, {
...weekRowClass(value, row[0].dayjsObj),
...activeRowCss(value, row[0].dayjsObj),
})}
>
{row.map((col: any, j: number) => (
Expand Down
8 changes: 4 additions & 4 deletions src/date-picker/hooks/useSingleValue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ export default function useSingleValue(props: TdDatePickerProps) {
}

const [time, setTime] = useState(() =>
formatTime(props.multiple ? value[0] : value, format, timeFormat, props.defaultTime),
formatTime(props.multiple ? value?.[0] : value, format, timeFormat, props.defaultTime),
);
const [month, setMonth] = useState<number>(() => parseToDayjs(props.multiple ? value[0] : value, format).month());
const [year, setYear] = useState<number>(() => parseToDayjs(props.multiple ? value[0] : value, format).year());
const [cacheValue, setCacheValue] = useState(() => formatDate(props.multiple ? value[0] : value, { format })); // 缓存选中值,panel 点击时更改
const [month, setMonth] = useState<number>(() => parseToDayjs(props.multiple ? value?.[0] : value, format).month());
const [year, setYear] = useState<number>(() => parseToDayjs(props.multiple ? value?.[0] : value, format).year());
const [cacheValue, setCacheValue] = useState(() => formatDate(props.multiple ? value?.[0] : value, { format })); // 缓存选中值,panel 点击时更改

// 输入框响应 value 变化
useEffect(() => {
Expand Down
3 changes: 2 additions & 1 deletion src/date-picker/panel/PanelContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface PanelContentProps {
timePickerProps: SinglePanelProps['timePickerProps'];
firstDayOfWeek: SinglePanelProps['firstDayOfWeek'];
time: SinglePanelProps['time'];

multiple?: SinglePanelProps['multiple'];
popupVisible?: boolean;
tableData: any[];
onMonthChange: SinglePanelProps['onMonthChange'] | RangePanelProps['onMonthChange'];
Expand Down Expand Up @@ -104,6 +104,7 @@ export default function PanelContent(props: PanelContentProps) {
time={time}
format={format}
firstDayOfWeek={firstDayOfWeek}
multiple={props.multiple}
onCellClick={(date: Date, { e }) => onCellClick?.(date, { e, partial })}
onCellMouseEnter={(date: Date) => onCellMouseEnter?.(date, { partial })}
onCellMouseLeave={onCellMouseLeave}
Expand Down
2 changes: 1 addition & 1 deletion src/select-input/useMultiple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ export default function useMultiple(props: SelectInputProps) {

const getTags = () => {
if (!(value instanceof Array)) {
if (['', null, undefined].includes(value as any)) return [];
return isObject(value) ? [value[iKeys.label]] : [value];
}
return value.map((item: SelectInputValue) => (isObject(item) ? item[iKeys.label] : item));
};
const tags = getTags();

const tPlaceholder = !tags || !tags.length ? props.placeholder : '';

const onTagInputChange = (val: TagInputValue, context: SelectInputChangeContext) => {
Expand Down
1 change: 1 addition & 0 deletions src/tag-input/useTagList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default function useTagList(props: TagInputProps) {

const renderLabel = ({ displayNode, label }: { displayNode: ReactNode; label: ReactNode }) => {
const newList = minCollapsedNum ? tagValue.slice(0, minCollapsedNum) : tagValue;

const list = displayNode
? [<Fragment key="display-node">{displayNode}</Fragment>]
: newList?.map((item, index) => {
Expand Down
Loading
Loading