fix: fix controlled versions of Calendar and DatePicker

This commit is contained in:
Hossein Mehrabi 2023-03-22 14:38:29 +03:30 committed by Jon
parent 03052f4004
commit 2f6f391735
5 changed files with 64 additions and 52 deletions

View File

@ -21,12 +21,7 @@ export const Uncontrolled: Story<CalendarProps> = (arg) => {
return (
<div ref={ref} style={{ width: '300px' }}>
<Calendar
{...arg}
handleDateFieldChange={(date) => console.log(date?.toDateString())}
open={true}
handleRef={ref}
>
<Calendar {...arg} open={true} handleRef={ref}>
Calendar
</Calendar>
</div>
@ -38,12 +33,7 @@ export const Controlled: Story<CalendarProps> = (arg) => {
return (
<div ref={ref} style={{ width: '300px' }}>
<Calendar
{...arg}
handleDateFieldChange={(date) => console.log(date?.toDateString())}
open={true}
handleRef={ref}
>
<Calendar {...arg} open={true} handleRef={ref}>
Calendar
</Calendar>
</div>

View File

@ -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<HTMLDivElement>,
'label'
'label' | 'onChange'
> & {
open?: boolean
disabled?: boolean
@ -28,7 +29,7 @@ export const Calendar: React.FC<CalendarProps> & {
} = ({
open,
handleRef,
value: _value,
value: valueProp,
size = 'large',
disabled = false,
onChange,
@ -39,7 +40,7 @@ export const Calendar: React.FC<CalendarProps> & {
const ref = useRef<HTMLDivElement>(null)
const [style, setStyle] = useState<React.CSSProperties>({})
const [value, setValue] = useState<Date | null>(
_value ? new Date(_value) : null,
valueProp ? safeConvertDateToString(valueProp).date : null,
)
useClickAway(ref, (event) => {
@ -49,23 +50,12 @@ export const Calendar: React.FC<CalendarProps> & {
})
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<CalendarProps> & {
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()

View File

@ -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<DatePickerProps> & {
classes: typeof datePickerClasses
} = ({ value: _value, onChange, withCalendar = true, ...props }) => {
} = ({ value: valueProp, onChange, withCalendar = true, ...props }) => {
const ref = useRef<HTMLDivElement>(null)
const [openCalendar, setOpenCalendar] = useState(false)
const [date, setDate] = useState<string>(_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 (
<div
@ -58,19 +60,19 @@ export const DatePicker: React.FC<DatePickerProps> & {
<DateField
icon={withCalendar && <CalendarIcon color="primary" />}
onIconClick={() => setOpenCalendar((prev) => !prev)}
value={date}
onChange={(data) => setDate(data.target.value)}
value={input.value}
onChange={input.onChange}
style={{ width: '310px' }}
{...props}
>
<Portal id="calendar">
{withCalendar && (
<Calendar
onChange={handleDateFieldChange}
onChange={(date) => handleDateChange(date)}
open={openCalendar}
onClose={() => setOpenCalendar(false)}
handleRef={ref}
value={date}
value={input.value}
disabled={props.disabled}
/>
)}

View File

@ -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]

View File

@ -12,6 +12,7 @@ export type InputProps<T extends InputValueType = InputValueType> = {
defaultValue: T
onChange?: InputOnChangeType
ref?: React.RefObject<HTMLInputElement>
getInput?: () => HTMLInputElement
}
export const useInput = <T extends InputValueType = InputValueType>(
@ -42,9 +43,12 @@ export const useInput = <T extends InputValueType = InputValueType>(
}
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(