From 2f6f391735cf336e92fa3e3fb1e9b51749627d2f Mon Sep 17 00:00:00 2001 From: Hossein Mehrabi Date: Wed, 22 Mar 2023 14:38:29 +0330 Subject: [PATCH] fix: fix controlled versions of Calendar and DatePicker --- .../components/Calendar/Calendar.stories.tsx | 14 +------ .../src/components/Calendar/Calendar.tsx | 39 +++++++++--------- .../src/components/DatePicker/DatePicker.tsx | 40 ++++++++++--------- packages/lsd-react/src/utils/date.utils.ts | 15 +++++++ packages/lsd-react/src/utils/useInput.ts | 8 +++- 5 files changed, 64 insertions(+), 52 deletions(-) create mode 100644 packages/lsd-react/src/utils/date.utils.ts diff --git a/packages/lsd-react/src/components/Calendar/Calendar.stories.tsx b/packages/lsd-react/src/components/Calendar/Calendar.stories.tsx index 7e45d38..48b8f08 100644 --- a/packages/lsd-react/src/components/Calendar/Calendar.stories.tsx +++ b/packages/lsd-react/src/components/Calendar/Calendar.stories.tsx @@ -21,12 +21,7 @@ export const Uncontrolled: Story = (arg) => { return (
- console.log(date?.toDateString())} - open={true} - handleRef={ref} - > + Calendar
@@ -38,12 +33,7 @@ export const Controlled: Story = (arg) => { return (
- console.log(date?.toDateString())} - open={true} - handleRef={ref} - > + Calendar
diff --git a/packages/lsd-react/src/components/Calendar/Calendar.tsx b/packages/lsd-react/src/components/Calendar/Calendar.tsx index 0a3f9b6..df53bc2 100644 --- a/packages/lsd-react/src/components/Calendar/Calendar.tsx +++ b/packages/lsd-react/src/components/Calendar/Calendar.tsx @@ -5,14 +5,15 @@ import { } from '@datepicker-react/hooks' import clsx from 'clsx' import React, { useEffect, useRef, useState } from 'react' +import { useClickAway } from 'react-use' +import { safeConvertDateToString } from '../../utils/date.utils' import { calendarClasses } from './Calendar.classes' import { CalendarContext } from './Calendar.context' import { Month } from './Month' -import { useClickAway } from 'react-use' export type CalendarProps = Omit< React.HTMLAttributes, - 'label' + 'label' | 'onChange' > & { open?: boolean disabled?: boolean @@ -28,7 +29,7 @@ export const Calendar: React.FC & { } = ({ open, handleRef, - value: _value, + value: valueProp, size = 'large', disabled = false, onChange, @@ -39,7 +40,7 @@ export const Calendar: React.FC & { const ref = useRef(null) const [style, setStyle] = useState({}) const [value, setValue] = useState( - _value ? new Date(_value) : null, + valueProp ? safeConvertDateToString(valueProp).date : null, ) useClickAway(ref, (event) => { @@ -49,23 +50,12 @@ export const Calendar: React.FC & { }) const handleDateChange = (data: OnDatesChangeProps) => { - if (typeof _value !== 'undefined') { - if (typeof onChange !== 'undefined') { - onChange(data.startDate ?? new Date()) - } - } else { - setValue(data.startDate) - } + if (typeof valueProp !== 'undefined') + return onChange?.(data.startDate ?? new Date()) + + setValue(data.startDate) } - useEffect(() => { - onDateFocus(_value ? new Date(_value) : new Date()) - }, [_value]) - - useEffect(() => { - onDateFocus(value ? new Date(value) : new Date()) - }, [value]) - const { activeMonths, isDateSelected, @@ -87,6 +77,17 @@ export const Calendar: React.FC & { numberOfMonths: 1, }) + useEffect(() => { + onDateFocus(value ? new Date(value) : new Date()) + }, [value]) + + useEffect(() => { + if (typeof valueProp === 'undefined') return + + const { date } = safeConvertDateToString(valueProp) + setValue(date) + }, [valueProp]) + const updateStyle = () => { const { width, height, top, left } = handleRef.current!.getBoundingClientRect() diff --git a/packages/lsd-react/src/components/DatePicker/DatePicker.tsx b/packages/lsd-react/src/components/DatePicker/DatePicker.tsx index cd55434..37c5372 100644 --- a/packages/lsd-react/src/components/DatePicker/DatePicker.tsx +++ b/packages/lsd-react/src/components/DatePicker/DatePicker.tsx @@ -1,5 +1,10 @@ import clsx from 'clsx' import React, { useRef, useState } from 'react' +import { + dateToISODateString, + removeDateTimezoneOffset, +} from '../../utils/date.utils' +import { useInput } from '../../utils/useInput' import { Calendar } from '../Calendar' import { DateField } from '../DateField' import { CalendarIcon } from '../Icons' @@ -18,7 +23,6 @@ export type DatePickerProps = Omit< withCalendar?: boolean supportingText?: string value?: string - onChange?: (value: string) => void defaultValue?: string placeholder?: string size?: 'large' | 'medium' @@ -27,24 +31,22 @@ export type DatePickerProps = Omit< export const DatePicker: React.FC & { classes: typeof datePickerClasses -} = ({ value: _value, onChange, withCalendar = true, ...props }) => { +} = ({ value: valueProp, onChange, withCalendar = true, ...props }) => { const ref = useRef(null) const [openCalendar, setOpenCalendar] = useState(false) - const [date, setDate] = useState(_value || '') - const handleDateFieldChange = (date: any) => { - const offset = new Date(date).getTimezoneOffset() - const formattedDate = new Date(date.getTime() - offset * 60 * 1000) - const value = formattedDate.toISOString().split('T')[0] + const input = useInput({ + value: valueProp, + defaultValue: '', + onChange, + getInput: () => + ref.current?.querySelector( + `input.${DateField.classes.input}`, + ) as HTMLInputElement, + }) - if (typeof _value !== 'undefined') { - if (typeof onChange !== 'undefined') { - onChange(value) - } - } else { - setDate(value) - } - } + const handleDateChange = (date: Date) => + input.setValue(dateToISODateString(removeDateTimezoneOffset(date))) return (
& { } onIconClick={() => setOpenCalendar((prev) => !prev)} - value={date} - onChange={(data) => setDate(data.target.value)} + value={input.value} + onChange={input.onChange} style={{ width: '310px' }} {...props} > {withCalendar && ( handleDateChange(date)} open={openCalendar} onClose={() => setOpenCalendar(false)} handleRef={ref} - value={date} + value={input.value} disabled={props.disabled} /> )} diff --git a/packages/lsd-react/src/utils/date.utils.ts b/packages/lsd-react/src/utils/date.utils.ts new file mode 100644 index 0000000..a5dba50 --- /dev/null +++ b/packages/lsd-react/src/utils/date.utils.ts @@ -0,0 +1,15 @@ +export const safeConvertDateToString = (value: string) => { + const date = new Date(value ?? undefined) + const isValid = !Number.isNaN(+date) + + return { + isValid, + date: isValid ? date : new Date(), + } +} + +export const removeDateTimezoneOffset = (date: Date) => + new Date(+date - date.getTimezoneOffset() * 60 * 1000) + +export const dateToISODateString = (date: Date) => + date.toISOString().split('T')[0] diff --git a/packages/lsd-react/src/utils/useInput.ts b/packages/lsd-react/src/utils/useInput.ts index fa60220..2e6db79 100644 --- a/packages/lsd-react/src/utils/useInput.ts +++ b/packages/lsd-react/src/utils/useInput.ts @@ -12,6 +12,7 @@ export type InputProps = { defaultValue: T onChange?: InputOnChangeType ref?: React.RefObject + getInput?: () => HTMLInputElement } export const useInput = ( @@ -42,9 +43,12 @@ export const useInput = ( } const setter = (value: InputValueType) => { - if (!props.ref?.current) return + const element = + props?.ref?.current ?? + (typeof props.getInput === 'function' && props.getInput()) + + if (!element) return - const element = props.ref.current const event = new Event('input', { bubbles: true }) Object.getOwnPropertyDescriptor(