From db5a81850b18dcb923e41eb7a8314a14dbffa105 Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Wed, 7 Aug 2024 02:01:39 -0400 Subject: [PATCH] fix input value on reset, enable single-picker input editing --- src/single.tsx | 162 +++++++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 74 deletions(-) diff --git a/src/single.tsx b/src/single.tsx index 8c0e927..95fc1cb 100644 --- a/src/single.tsx +++ b/src/single.tsx @@ -1,4 +1,10 @@ -import React, { useEffect, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { Button, ButtonProps, @@ -45,10 +51,7 @@ interface SingleProps extends DatepickerProps { export type VariantProps = | { - propsConfigs?: PropsConfigs; - } - | { - triggerVariant: 'default'; + triggerVariant?: 'default'; propsConfigs?: PropsConfigs; } | { @@ -70,49 +73,41 @@ const DefaultConfigs: Required = { monthsToDisplay: 1, }; -const defaultProps = { - defaultIsOpen: false, - closeOnSelect: true, - triggerVariant: 'default' as const, -}; - -export const SingleDatepicker: React.FC = (props) => { - const mergedProps = { ...defaultProps, ...props }; - const { - date: selectedDate, - name, - disabled, - onDateChange, - id, - minDate, - maxDate, - configs, - usePortal, - portalRef, - disabledDates, - defaultIsOpen, - triggerVariant, - propsConfigs, - closeOnSelect, - children, - } = mergedProps; - +export const SingleDatepicker: React.FC = ({ + date: selectedDate, + name, + disabled, + onDateChange, + id, + minDate, + maxDate, + configs, + usePortal, + portalRef, + disabledDates, + defaultIsOpen = false, + closeOnSelect = true, + children, + ...restProps +}) => { const [dateInView, setDateInView] = useState(selectedDate); const [offset, setOffset] = useState(0); + const internalUpdate = useRef(false); const { onOpen, onClose, isOpen } = useDisclosure({ defaultIsOpen }); const Icon = - mergedProps.triggerVariant === 'input' && mergedProps.triggerIcon ? ( - mergedProps.triggerIcon - ) : ( - - ); + restProps.triggerVariant === 'input' + ? restProps?.triggerIcon ?? + : null; - const datepickerConfigs = { - ...DefaultConfigs, - ...configs, - }; + const datepickerConfigs = useMemo( + () => ({ + ...DefaultConfigs, + ...configs, + }), + [configs] + ); const [tempInput, setInputVal] = useState( selectedDate ? format(selectedDate, datepickerConfigs.dateFormat) : '' @@ -125,37 +120,54 @@ export const SingleDatepicker: React.FC = (props) => { }; // dayzed utils - const handleOnDateSelected: OnDateSelected = ({ selectable, date }) => { - if (!selectable) return; - if (date instanceof Date && !isNaN(date.getTime())) { - onDateChange(date); - if (closeOnSelect) onClose(); - return; - } - }; + const handleOnDateSelected: OnDateSelected = useCallback( + ({ selectable, date }) => { + if (!selectable) return; + if (date instanceof Date && !isNaN(date.getTime())) { + internalUpdate.current = true; + onDateChange(date); + setInputVal(date ? format(date, datepickerConfigs.dateFormat) : ''); + if (closeOnSelect) onClose(); + return; + } + }, + [closeOnSelect, datepickerConfigs.dateFormat, onClose, onDateChange] + ); - const handleInputChange = (event: React.ChangeEvent) => { - setInputVal(event.target.value); - const newDate = parse( - event.target.value, - datepickerConfigs.dateFormat, - new Date() - ); - if (!(newDate instanceof Date && !isNaN(newDate.getTime()))) { - return; - } - const isDisabled = disabledDates?.has(startOfDay(newDate).getTime()); - if (isDisabled) return; - onDateChange(newDate); - }; + const handleInputChange = useCallback( + (event: React.ChangeEvent) => { + internalUpdate.current = true; + setInputVal(event.target.value); + const newDate = parse( + event.target.value, + datepickerConfigs.dateFormat, + new Date() + ); + if (!(newDate instanceof Date && !isNaN(newDate.getTime()))) { + return; + } + const isDisabled = disabledDates?.has(startOfDay(newDate).getTime()); + if (isDisabled) return; + onDateChange(newDate); + setDateInView(newDate); + }, + [datepickerConfigs.dateFormat, disabledDates, onDateChange] + ); const PopoverContentWrapper = usePortal ? Portal : React.Fragment; useEffect(() => { - if (selectedDate) { - setInputVal(format(selectedDate, datepickerConfigs.dateFormat)); + if (internalUpdate.current) { + internalUpdate.current = false; + return; } - }, [selectedDate, datepickerConfigs.dateFormat]); + setInputVal( + typeof selectedDate !== 'undefined' + ? format(selectedDate, datepickerConfigs.dateFormat) + : '' + ); + setDateInView(selectedDate); + }, [datepickerConfigs.dateFormat, selectedDate]); return ( = (props) => { onClose={onPopoverClose} isLazy > - {!children && triggerVariant === 'default' ? ( + {!children && (restProps.triggerVariant ?? 'default') === 'default' ? ( ) : null} - {!children && triggerVariant === 'input' ? ( + {!children && restProps.triggerVariant === 'input' ? ( = (props) => { value={tempInput} onChange={handleInputChange} paddingRight={'2.5rem'} - {...propsConfigs?.inputProps} + {...restProps.propsConfigs?.inputProps} /> @@ -217,7 +229,7 @@ export const SingleDatepicker: React.FC = (props) => { type="button" disabled={disabled} padding={'8px'} - {...propsConfigs?.triggerIconBtnProps} + {...restProps.propsConfigs?.triggerIconBtnProps} > {Icon} @@ -230,9 +242,11 @@ export const SingleDatepicker: React.FC = (props) => { > - + = (props) => { firstDayOfWeek: datepickerConfigs.firstDayOfWeek, }} configs={datepickerConfigs} - propsConfigs={propsConfigs} + propsConfigs={restProps.propsConfigs} disabledDates={disabledDates} />