ui
ui copied to clipboard
Flexibility to type date in the datepicker
The current date picker doesn't allow manual entry of date to the date picker, which means, to enter my date of birth, it will take me more than a minute to select the date.
@mohammedzamakhan This is just a basic implementation that maybe covers all the states, you should be able to test this and refactor it for your usecase.
export function DatePickerDemo() {
const [stringDate, setStringDate] = React.useState<string>("")
const [date, setDate] = React.useState<Date>()
const [errorMessage, setErrorMessage] = React.useState<string>("")
return (
<Popover>
<div className="relative w-[280px]">
<Input
type="string"
value={stringDate}
onChange={(e) => {
setStringDate(e.target.value)
const parsedDate = new Date(e.target.value)
if (parsedDate.toString() === "Invalid Date") {
setErrorMessage("Invalid Date")
setDate(undefined)
} else {
setErrorMessage("")
setDate(parsedDate)
}
}}
/>
{errorMessage !== "" && (
<div className="absolute bottom-[-1.75rem] left-0 text-red-400 text-sm">
{errorMessage}
</div>
)}
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"font-normal absolute right-0 translate-y-[-50%] top-[50%] rounded-l-none",
!date && "text-muted-foreground"
)}
>
<CalendarIcon className="w-4 h-4" />
</Button>
</PopoverTrigger>
</div>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={(selectedDate) => {
if (!selectedDate) return
setDate(selectedDate)
setStringDate(format(selectedDate, "MM/dd/yyyy"))
setErrorMessage("")
}}
defaultMonth={date}
initialFocus
/>
</PopoverContent>
</Popover>
)
}
I was looking for this too. A datepicker and especially a date range picker where you can't type the date is a fairly inaccessible experience. React Aria has a blog post about date inputs and accessibility that captures this pretty well.
I probably can combine Radix/Shadcn alongside React-Aria's hook API to get my cake and eat it too, but having this built in would of course be much better.
This worked for me after several tries. I use Maskito for masking the dates, and date-fns for formatting. Just tweak according to your needs.
// InputWithDatePicker.tsx
import { CalendarIcon } from 'lucide-react'
import { useMaskito } from '@maskito/react'
import { cn } from '@/lib/utils'
import {
Popover,
PopoverContent,
PopoverTrigger,
Input,
Button,
Calendar,
CalendarProps,
} from '../'
import options from './mask'
export function InputWithDatePicker({
calendarProps,
inputProps,
}: {
inputProps?: any
calendarProps?: CalendarProps
}) {
const maskedInputRef = useMaskito({ options })
return (
<Popover>
<div className="relative w-full">
<Input
{...inputProps}
onInput={inputProps.onChange} // maskito-specific, must use onInput
ref={maskedInputRef}
/>
<PopoverTrigger asChild>
<Button
variant="ghost"
className={cn(
'absolute right-0 top-[50%] translate-y-[-50%] rounded-none px-2',
)}
>
<CalendarIcon className="h-4 w-4 text-dark-500" />
</Button>
</PopoverTrigger>
</div>
<PopoverContent className="w-auto p-0">
<Calendar {...calendarProps} />
</PopoverContent>
</Popover>
)
}
Usage:
<InputWithDatePicker
inputProps={{
...field,
onChange: (e: any) => {
form.setValue('dateOfBirth', e.target.value)
field.onChange(e.target.value)
form.trigger('dateOfBirth')
setDateValue(new Date(e.target.value))
},
className: cn(
form.formState.errors.dateOfBirth &&
'border-2 border-empathy-500 focus-visible:outline-1 focus-visible:outline-empathy-500',
),
disabled:
initialData?.payload?.disabled &&
initialData?.payload?.disabled?.includes('date_of_birth'),
}}
calendarProps={{
captionLayout: 'dropdown-buttons', // to enable dropdown for Month and Year selection
mode: 'single',
selected: dateValue,
onSelect: (date: any) => {
if (!date && !isValid(new Date(date))) return
form.setValue('dateOfBirth', format(date, 'yyyy-MM-dd'))
form.trigger('dateOfBirth')
field.onChange(format(date, 'yyyy-MM-dd'))
return date
},
disabled:
((date: Date) =>
date > new Date() || date < new Date('1900-01-01')) ||
(initialData?.payload?.disabled &&
initialData?.payload?.disabled?.includes(
'date_of_birth',
)),
fromYear: 1900,
toYear: new Date().getFullYear(),
month: dateValue, // this will display the current selected date from the input
initialFocus: true,
}}
/>
This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.
Commenting just to bump this again. It would be great to have this option for accessibility and simply for better UX
Commeting too
Supporting!