ui icon indicating copy to clipboard operation
ui copied to clipboard

Flexibility to type date in the datepicker

Open mohammedzamakhan opened this issue 1 year ago • 3 comments

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 avatar May 25 '23 03:05 mohammedzamakhan

@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>
  )
}

albbus-stack avatar May 25 '23 17:05 albbus-stack

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.

Phoenixmatrix avatar Sep 20 '23 01:09 Phoenixmatrix

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,
  }}
/>

mcometa avatar Jun 19 '24 10:06 mcometa

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.

shadcn avatar Jul 11 '24 23:07 shadcn

Commenting just to bump this again. It would be great to have this option for accessibility and simply for better UX

jonasjancarik avatar Jul 30 '24 19:07 jonasjancarik

Commeting too

diegourday avatar Aug 01 '24 17:08 diegourday

Supporting!

chrisrolling avatar Aug 03 '24 09:08 chrisrolling