diff --git a/src/time-picker/TimePicker.tsx b/src/time-picker/TimePicker.tsx index 25b2d6796..0305243a1 100644 --- a/src/time-picker/TimePicker.tsx +++ b/src/time-picker/TimePicker.tsx @@ -1,4 +1,4 @@ -import React, { useState, Ref, useEffect } from 'react'; +import React, { useState, Ref, useCallback } from 'react'; import classNames from 'classnames'; import dayjs from 'dayjs'; import customParseFormat from 'dayjs/plugin/customParseFormat'; @@ -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, { TimePickerPanelProps } from './panel/TimePickerPanel'; import { useTimePickerTextConfig } from './hooks/useTimePickerTextConfig'; import { formatInputValue, validateInputValue } from '../_common/js/time-picker/utils'; @@ -49,6 +49,7 @@ const TimePicker = forwardRefWithStatics( onFocus = noop, onOpen = noop, onInput = noop, + onPick = noop, } = props; const [value, onChange] = useControlled(props, 'value', props.onChange); @@ -65,8 +66,16 @@ const TimePicker = forwardRefWithStatics( [`${classPrefix}-is-focused`]: isPanelShowed, }); + const effectVisibleCurrentValue = useCallback( + (visible: boolean) => { + setPanelShow(visible); + setCurrentValue(visible ? value ?? '' : ''); + }, + [setPanelShow, setCurrentValue, value], + ); + const handleShowPopup = (visible: boolean, context: { e: React.MouseEvent }) => { - setPanelShow(visible); + effectVisibleCurrentValue(visible); visible ? onOpen(context) : onClose(context); // trigger on-open and on-close }; @@ -97,12 +106,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 (
@@ -134,10 +144,11 @@ const TimePicker = forwardRefWithStatics( isFooterDisplay={true} isShowPanel={isPanelShowed} disableTime={disableTime} - onChange={setCurrentValue} + onChange={handlePanelChange} onPick={props.onPick} hideDisabledTime={hideDisabledTime} handleConfirmClick={handleClickConfirm} + presets={props.presets} /> } /> diff --git a/src/time-picker/TimeRangePicker.tsx b/src/time-picker/TimeRangePicker.tsx index 3c49318a6..29de6cd7d 100644 --- a/src/time-picker/TimeRangePicker.tsx +++ b/src/time-picker/TimeRangePicker.tsx @@ -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'; import TimePickerPanel from './panel/TimePickerPanel'; import { useTimePickerTextConfig } from './hooks/useTimePickerTextConfig'; @@ -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 = (originalProps) => { const props = useDefaultProps(originalProps, timeRangePickerDefaultProps); @@ -41,6 +44,7 @@ const TimeRangePicker: FC = (originalProps) => { onInput = noop, style, className, + presets, } = props; const [value, onChange] = useControlled(props, 'value', props.onChange); @@ -59,26 +63,48 @@ const TimeRangePicker: FC = (originalProps) => { [`${classPrefix}-is-focused`]: isPanelShowed, }); - const handleShowPopup = (visible: boolean) => { + const handleShowPopup: RangeInputPopupProps['onPopupVisibleChange'] = (visible: boolean, { 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 }) => { @@ -99,7 +125,7 @@ const TimeRangePicker: FC = (originalProps) => { { e, position }: { e: React.FocusEvent; position: RangeInputPosition }, ) => { setCurrentValue(inputVal); - onInput({ value, e, position: position as TimeRangePickerPartial }); + onInput({ value, e, position: handlePositionTrans(position) }); }; const handleClickConfirm = () => { @@ -112,11 +138,12 @@ const TimeRangePicker: FC = (originalProps) => { value: TimeRangeValue, { e, position }: { e: React.FocusEvent; 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]); @@ -136,7 +163,7 @@ const TimeRangePicker: FC = (originalProps) => { ...props.popupProps, }} onInputChange={handleInputChange} - inputValue={isPanelShowed ? currentValue : value ?? defaultArrVal} + inputValue={isPanelShowed ? currentValue : value ?? TIME_PICKER_EMPTY} rangeInputProps={{ size, clearable, @@ -166,6 +193,8 @@ const TimeRangePicker: FC = (originalProps) => { onChange={handleTimeChange} handleConfirmClick={handleClickConfirm} position={currentPanelIdx === 0 ? 'start' : 'end'} + activeIndex={currentPanelIdx} + presets={presets} /> } /> diff --git a/src/time-picker/panel/TimePickerPanel.tsx b/src/time-picker/panel/TimePickerPanel.tsx index a7a77d327..d22de5b1d 100644 --- a/src/time-picker/panel/TimePickerPanel.tsx +++ b/src/time-picker/panel/TimePickerPanel.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useMemo, useState, MouseEvent } from 'react'; +import React, { FC, useEffect, useMemo, useState, useCallback } from 'react'; import dayjs from 'dayjs'; import SinglePanel, { SinglePanelProps } from './SinglePanel'; @@ -7,13 +7,20 @@ import Button from '../../button'; import { useTimePickerTextConfig } from '../hooks/useTimePickerTextConfig'; import { DEFAULT_STEPS, DEFAULT_FORMAT } from '../../_common/js/time-picker/const'; +import { TimePickerProps } from '../TimePicker'; +import { TimeRangePickerProps } from '../TimeRangePicker'; +import log from '../../_common/js/log'; export interface TimePickerPanelProps extends SinglePanelProps { isShowPanel?: boolean; isFooterDisplay?: boolean; // 是否展示footer handleConfirmClick?: (defaultValue: dayjs.Dayjs | string) => void; + presets?: TimePickerProps['presets'] | TimeRangePickerProps['presets']; + activeIndex?: number; } +type PresetValue = TimePickerPanelProps['presets'][keyof TimePickerPanelProps['presets']]; + const TimePickerPanel: FC = (props) => { const { format = DEFAULT_FORMAT, @@ -23,6 +30,7 @@ const TimePickerPanel: FC = (props) => { onChange, value, isShowPanel = true, + presets = null, } = props; const [triggerScroll, toggleTriggerScroll] = useState(false); // 触发滚动 const { classPrefix } = useConfig(); @@ -30,7 +38,7 @@ const TimePickerPanel: FC = (props) => { const TEXT_CONFIG = useTimePickerTextConfig(); const panelClassName = `${classPrefix}-time-picker__panel`; - const showNowTimeBtn = !!steps.filter((v) => v > 1).length; + const showNowTimeBtn = !!steps.filter((v) => Number(v) > 1).length; const defaultValue = useMemo(() => { const formattedValue = dayjs(value, format); @@ -45,9 +53,52 @@ const TimePickerPanel: FC = (props) => { if (isShowPanel) toggleTriggerScroll(true); }, [isShowPanel]); - const handleOnChange = (v: string, e: MouseEvent) => { - props.onChange(v); - props.onPick?.(v, { e }); + const resetTriggerScroll = useCallback(() => { + toggleTriggerScroll(false); + }, [toggleTriggerScroll]); + + const handlePresetClick = (presetValue: PresetValue) => { + const presetVal = typeof presetValue === 'function' ? presetValue() : presetValue; + if (typeof props.activeIndex === 'number') { + if (Array.isArray(presetVal)) { + props.onChange?.(presetVal[props.activeIndex]); + } else { + log.error('TimePicker', `preset: ${presets} 预设值必须是数组!`); + } + } else { + props.onChange?.(presetVal); + } + }; + + const renderFooter = () => { + if (presets) { + return Object.keys(presets).map((preset) => ( + + )); + } + + return !showNowTimeBtn ? ( + + ) : null; }; return ( @@ -55,33 +106,29 @@ const TimePickerPanel: FC = (props) => {
toggleTriggerScroll(false)} + resetTriggerScroll={resetTriggerScroll} />
{isFooterDisplay ? (
- {!showNowTimeBtn ? ( - - ) : null} + {renderFooter()}
) : null}