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

feature: timepicker support onPick and presets features #2902

Merged
merged 8 commits into from
May 16, 2024
24 changes: 16 additions & 8 deletions src/time-picker/TimePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, Ref, useEffect } from 'react';
import React, { useState, Ref } from 'react';
import classNames from 'classnames';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
Expand All @@ -12,7 +12,7 @@ import noop from '../_util/noop';

import SelectInput, { SelectInputProps, SelectInputValueChangeContext } from '../select-input';
import TimeRangePicker from './TimeRangePicker';
import TimePickerPanel from './panel/TimePickerPanel';
import TimePickerPanel, { type TimePickerPanelProps } from './panel/TimePickerPanel';

import { useTimePickerTextConfig } from './hooks/useTimePickerTextConfig';
import { formatInputValue, validateInputValue } from '../_common/js/time-picker/utils';
Expand Down Expand Up @@ -49,6 +49,7 @@ const TimePicker = forwardRefWithStatics(
onFocus = noop,
onOpen = noop,
onInput = noop,
onPick = noop,
} = props;

const [value, onChange] = useControlled(props, 'value', props.onChange);
Expand All @@ -65,8 +66,13 @@ const TimePicker = forwardRefWithStatics(
[`${classPrefix}-is-focused`]: isPanelShowed,
});

const handleShowPopup = (visible: boolean, context: { e: React.MouseEvent<HTMLDivElement, MouseEvent> }) => {
const effectVisibleCurrentValue = (visible: boolean) => {
setPanelShow(visible);
setCurrentValue(visible ? value ?? '' : '');
};

const handleShowPopup = (visible: boolean, context: { e: React.MouseEvent<HTMLDivElement, MouseEvent> }) => {
effectVisibleCurrentValue(visible);
visible ? onOpen(context) : onClose(context); // trigger on-open and on-close
};

Expand Down Expand Up @@ -97,12 +103,13 @@ const TimePicker = forwardRefWithStatics(
const handleClickConfirm = () => {
const isValidTime = validateInputValue(currentValue, format);
if (isValidTime) onChange(currentValue);
setPanelShow(false);
effectVisibleCurrentValue(false);
};

useEffect(() => {
setCurrentValue(isPanelShowed ? value ?? '' : '');
}, [isPanelShowed, value]);
const handlePanelChange: TimePickerPanelProps['onChange'] = (v, ctx) => {
setCurrentValue(v);
onPick?.(v, ctx);
};

return (
<div className={classNames(name, className)} ref={ref} style={style}>
Expand Down Expand Up @@ -134,10 +141,11 @@ const TimePicker = forwardRefWithStatics(
isFooterDisplay={true}
isShowPanel={isPanelShowed}
disableTime={disableTime}
onChange={setCurrentValue}
onChange={handlePanelChange}
onPick={props.onPick}
hideDisabledTime={hideDisabledTime}
handleConfirmClick={handleClickConfirm}
presets={props.presets}
/>
}
/>
Expand Down
55 changes: 42 additions & 13 deletions src/time-picker/TimeRangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import React, { FC, useState, useEffect } from 'react';
import classNames from 'classnames';

import { TimeIcon as TdTimeIcon } from 'tdesign-icons-react';
import isArray from 'lodash/isArray';
import noop from '../_util/noop';
import useControlled from '../hooks/useControlled';
import useConfig from '../hooks/useConfig';
import useGlobalIcon from '../hooks/useGlobalIcon';
import { RangeInputPopup, RangeInputPosition } from '../range-input';
import { RangeInputPopup, RangeInputPopupProps, RangeInputPosition } from '../range-input';
ZWkang marked this conversation as resolved.
Show resolved Hide resolved
import TimePickerPanel from './panel/TimePickerPanel';

import { useTimePickerTextConfig } from './hooks/useTimePickerTextConfig';
Expand All @@ -20,7 +21,9 @@ import useDefaultProps from '../hooks/useDefaultProps';

export interface TimeRangePickerProps extends TdTimeRangePickerProps, StyledProps {}

const defaultArrVal = [undefined, undefined];
function handlePositionTrans(income: RangeInputPosition): TimeRangePickerPartial {
return income === 'first' ? 'start' : 'end';
}

const TimeRangePicker: FC<TimeRangePickerProps> = (originalProps) => {
const props = useDefaultProps<TimeRangePickerProps>(originalProps, timeRangePickerDefaultProps);
Expand All @@ -41,6 +44,7 @@ const TimeRangePicker: FC<TimeRangePickerProps> = (originalProps) => {
onInput = noop,
style,
className,
presets,
} = props;

const [value, onChange] = useControlled(props, 'value', props.onChange);
Expand All @@ -59,26 +63,48 @@ const TimeRangePicker: FC<TimeRangePickerProps> = (originalProps) => {
[`${classPrefix}-is-focused`]: isPanelShowed,
});

const handleShowPopup = (visible: boolean) => {
const handleShowPopup: RangeInputPopupProps['onPopupVisibleChange'] = (visible, { trigger }) => {
if (trigger === 'trigger-element-click') {
setPanelShow(true);
return;
}
setPanelShow(visible);
};

function handlePickerValue(pickValue: string | string[], currentValue: string[]) {
if (Array.isArray(pickValue)) return pickValue;
return currentPanelIdx === 0
? [pickValue, currentValue[1] ?? pickValue]
: [currentValue[0] ?? pickValue, pickValue];
}

const handleOnPick = (pickValue: string[], e: { e: React.MouseEvent }) => {
let context;
if (isArray(pickValue)) {
context = { e };
} else if (currentPanelIdx.value === 0) {
context = { e, position: 'start' as TimeRangePickerPartial };
} else {
context = { e, position: 'end' as TimeRangePickerPartial };
}
props.onPick?.(pickValue, context);
};

const handleClear = (context: { e: React.MouseEvent }) => {
const { e } = context;
e.stopPropagation();
onChange(undefined);
setCurrentValue(TIME_PICKER_EMPTY);
};

const handleClick = ({ position }: { position: 'first' | 'second' }) => {
setCurrentPanelIdx(position === 'first' ? 0 : 1);
};

const handleTimeChange = (newValue: string) => {
if (currentPanelIdx === 0) {
setCurrentValue([newValue, currentValue[1] ?? newValue]);
} else {
setCurrentValue([currentValue[0] ?? newValue, newValue]);
}
const handleTimeChange = (newValue: string | string[], context: { e: React.MouseEvent }) => {
const nextCurrentValue = handlePickerValue(newValue, currentValue);
setCurrentValue(nextCurrentValue);
handleOnPick(nextCurrentValue, context);
};

const handleInputBlur = (value: TimeRangeValue, { e }: { e: React.FocusEvent<HTMLInputElement> }) => {
Expand All @@ -99,7 +125,7 @@ const TimeRangePicker: FC<TimeRangePickerProps> = (originalProps) => {
{ e, position }: { e: React.FocusEvent<HTMLInputElement>; position: RangeInputPosition },
) => {
setCurrentValue(inputVal);
onInput({ value, e, position: position as TimeRangePickerPartial });
onInput({ value, e, position: handlePositionTrans(position) });
};

const handleClickConfirm = () => {
Expand All @@ -112,11 +138,12 @@ const TimeRangePicker: FC<TimeRangePickerProps> = (originalProps) => {
value: TimeRangeValue,
{ e, position }: { e: React.FocusEvent<HTMLInputElement>; position: RangeInputPosition },
) => {
onFocus({ value, e, position: position as TimeRangePickerPartial });
onFocus({ value, e, position: handlePositionTrans(position) });
};

useEffect(() => {
setCurrentValue(isPanelShowed ? value ?? TIME_PICKER_EMPTY : defaultArrVal);
// to fix the effect trigger before input blur
setCurrentValue(isPanelShowed ? value ?? TIME_PICKER_EMPTY : TIME_PICKER_EMPTY);
if (!isPanelShowed) setCurrentPanelIdx(undefined);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isPanelShowed]);
Expand All @@ -136,7 +163,7 @@ const TimeRangePicker: FC<TimeRangePickerProps> = (originalProps) => {
...props.popupProps,
}}
onInputChange={handleInputChange}
inputValue={isPanelShowed ? currentValue : value ?? defaultArrVal}
inputValue={isPanelShowed ? currentValue : value ?? TIME_PICKER_EMPTY}
rangeInputProps={{
size,
clearable,
Expand Down Expand Up @@ -166,6 +193,8 @@ const TimeRangePicker: FC<TimeRangePickerProps> = (originalProps) => {
onChange={handleTimeChange}
handleConfirmClick={handleClickConfirm}
position={currentPanelIdx === 0 ? 'start' : 'end'}
activeIndex={currentPanelIdx}
presets={presets}
/>
}
/>
Expand Down
19 changes: 18 additions & 1 deletion src/time-picker/_example/hms.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,29 @@ export default function BasicTimePicker() {
const [value, setValue] = useState('12:00:00');

const handleValueChange = (v) => {
console.log('change', v);
setValue(v);
};

const handleOnPick = (v, context) => {
console.log('onPick', v, context);
};

return <TimePicker value={value} onChange={handleValueChange} onPick={handleOnPick} />;
const handleClose = () => {
console.log('close');
};

const handleOpen = () => {
console.log('open');
};

return (
<TimePicker
value={value}
onChange={handleValueChange}
onPick={handleOnPick}
onClose={handleClose}
onOpen={handleOpen}
/>
);
}
33 changes: 33 additions & 0 deletions src/time-picker/_example/presets.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useState } from 'react';
import { TimePicker, Space } from 'tdesign-react';

const { TimeRangePicker } = TimePicker;

export default function RangeTimePicker() {
const [time1, setTime1] = useState('20:22');
const [time2, setTime2] = useState(['00:00:00', '23:59:59']);
return (
<Space direction="vertical">
<TimePicker
value={time1}
onChange={setTime1}
presets={{
上午十一点: '11:00:00',
}}
clearable
/>

<TimeRangePicker
value={time2}
onChange={setTime2}
style={{ marginTop: '20px' }}
clearable
format="HH:mm:ss"
allow-input
presets={{
下午: ['13:00:00', '18:00:00'],
}}
/>
</Space>
);
}
4 changes: 2 additions & 2 deletions src/time-picker/panel/SinglePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const SinglePanel: FC<SinglePanelProps> = (props) => {
const maskRef = useRef(null);

const dayjsValue = useMemo(() => {
const isStepsSet = !!steps.filter((v) => v > 1).length;
const isStepsSet = !!steps.filter((v) => Number(v) > 1).length;

if (value) return dayjs(value, format);

Expand Down Expand Up @@ -292,7 +292,7 @@ const SinglePanel: FC<SinglePanelProps> = (props) => {
const updateTimeScrollPos = useCallback(
(isAutoScroll = false) => {
const behavior = value && !isAutoScroll ? 'smooth' : 'auto';
const isStepsSet = !!steps.filter((v) => v > 1).length;
const isStepsSet = !!steps.filter((v) => Number(v) > 1).length;

cols.forEach((col: EPickerCols, idx: number) => {
if (!isStepsSet || (isStepsSet && value)) {
Expand Down
Loading
Loading