shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
73.27k stars 4.49k forks source link

[feat]: Keyboard input for Date Picker #5220

Open vwkd opened 3 weeks ago

vwkd commented 3 weeks ago

Feature description

It would be great if the Date Picker and Date Range Picker could support keyboard input like the Date Picker and Date Range Picker of Bits UI.

It's often easier to type out a far away date instead of clicking multiple times through the UI.

Affected component/components

Date Picker, Date Range Picker

Additional Context

Similar reported issues that were since closed as stale:

546

2229

4171

Before submitting

carlos-lopez-SaRa commented 1 week ago

I had to make my own with react-imask and my version just feels kinda wrong, I don't have enough time to make a good date input (couldn't screenshot the blinking cursor): Screenshot 2024-10-21 201551

export const DateTimeInput = (props: DateTimeInputProps) => {
  const {
    value,
    showTimeInput = false,
    calendarMinDate = set(new Date(), { year: 1900 }),
    calendarMaxDate = new Date(),
    onChange,
  } = props;

  const minDate = set(calendarMinDate, dateResetTime);

  const maxDate = set(calendarMaxDate, dateResetTime);

  const dateFormat = showTimeInput ? localeDateTimeFormat : localeDateFormat;

  const mask = showTimeInput ? localeDateTimeMask : localeDateMask;

  return (
    <Popover>
      <div className="flex w-full items-center gap-2">
        <IMaskShadCNInput
          mask={mask}
          value={value && isValid(value) ? format(value, dateFormat) : ''}
          onAccept={(val, mask) => {
            if (val === '') {
              onChange();
              return;
            }

            if (!mask.masked.isComplete) {
              return;
            }

            if (!isMatch(val, dateFormat)) {
              toast.error('Invalid date format', {
                description: `Please enter a valid date in the format ${dateFormat}`,
              });
              onChange();
              return;
            }

            const parsedDate = parse(val, dateFormat, new Date());
            onChange(parsedDate);
          }}
          placeholder={`Pick a date${showTimeInput ? 'time' : ''}`}
        />

        <PopoverTrigger asChild>
          <Button
            className={cn(
              'pl-3 text-left font-normal',
              !value && 'text-secondary-foreground',
            )}
          >
            <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
          </Button>
        </PopoverTrigger>
      </div>

      <PopoverContent className="w-auto p-0" align="start">
        <Calendar
          mode="single"
          required
          fromDate={minDate}
          toDate={maxDate}
          captionLayout="dropdown"
          selected={value}
          onSelect={(selectedDate) => {
            if (!selectedDate) {
              onChange();
              return;
            }

            const date = set(selectedDate, {
              hours: value?.getHours() ?? 0,
              minutes: value?.getMinutes() ?? 0,
            });

            onChange(date);
          }}
          disabled={(date) =>
            (minDate && isBefore(date, minDate)) ||
            (maxDate && isAfter(date, maxDate)) ||
            false
          }
          initialFocus
        />
        {showTimeInput && (
          <Input
            type="time"
            className="mt-2"
            // take locale date time string in format that the input expects (24hr time)
            value={value && isValid(value) ? format(value, timeFormat) : ''}
            // take hours and minutes and update our Date object then change date object to our new value
            onChange={(selectedTime) => {
              if (!selectedTime.target.value) {
                return;
              }

              const currentTime = parse(
                selectedTime.target.value,
                timeFormat,
                value ?? new Date(),
              );

              onChange(currentTime);
            }}
          />
        )}
      </PopoverContent>
    </Popover>
  );
};
import { RefAttributes } from 'react';
import { IMaskInput, IMaskMixin } from 'react-imask';

import { Input } from './ui/input';

export const IMaskShadCNInput = IMaskMixin(({ inputRef, ...props }) => (
  <Input {...props} ref={inputRef as RefAttributes<HTMLInputElement>['ref']} />
)) as typeof IMaskInput;
huybuidac commented 4 days ago

@vwkd

https://shadcn-datetime-picker-pro.vercel.app/?path=/story/datetimepicker--date-time-input-picker-in-form

https://github.com/user-attachments/assets/1a14076d-cff7-4068-af10-d61a2ff9284b