mirror of https://github.com/acid-info/lsd.git
fix: fix controlled versions of Calendar and DatePicker
This commit is contained in:
parent
03052f4004
commit
2f6f391735
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -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]
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue