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
70.67k stars 4.25k forks source link

Feature request: Time picker #255

Closed its-monotype closed 2 months ago

uncvrd commented 1 year ago

Hi everyone, as we wait for an official DateTime picker for the library, I decided to re-create this using React Aria's useDatePicker and have styled it using shadcn (using the Button and Popover components so it looks visually similar but also includes Time functionality.

See the demo below:

https://github.com/shadcn/ui/assets/25097099/0c04072f-b5a8-49b4-9116-6acf3c80ac38

Would this be of interest to any of you? I can move this to a repo/codesandbox.

I ran in to a "focus" problem with React Aria that requires two clicks to close the popover though (since they manually control event propagation and I guess Radix needs it to trigger the popover to close). As a workaround, I'm using another Aria hook called useInteractOutside to listen for clicks outside of the Popover and made the popover a controlled component. So maybe we call all work together to build this in to a better temporary solution?

jonnguyen12 commented 1 year ago

That looks very nice, does the time picker has to go with date picker? can it be separate like mantine.dev time picker?

uncvrd commented 1 year ago

Thanks! I ended up creating a separate component for a TimePicker too since having a Popover for just the time felt excessive. This is what it looks like. With the DateTime picker, you can select the granularity to day and at that point you have just a Date picker (hides the time picker input on the Popover) so that covers all three basis for me at least.

https://github.com/shadcn/ui/assets/25097099/2ef8ec4c-9433-480b-85e9-04f00e1d80fa

uncvrd commented 1 year ago

Lets see what they're planning for a release next week and if it isn't something related to a time picker, I'll spend some time extracting this out in to a separate repo for everyone to use/contribute to until then.

gtandes commented 1 year ago

Do you already have this on repo/codesandbox ser?

uncvrd commented 1 year ago

I got caught up on a separate project - I will share a repo/sandbox by end of the weekend!

gtandes commented 1 year ago

Thanks for the swift response ser! It'd be great if you can get it there sooner!

On Thu, Jun 15, 2023, 11:33 PM Jordan Lewallen @.***> wrote:

I got caught up on a separate project - I will share a sandbox by end of the weekend!

— Reply to this email directly, view it on GitHub https://github.com/shadcn/ui/issues/255#issuecomment-1593299762, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOAV32GR52ZCN7N76PZESMTXLMTMXANCNFSM6AAAAAAXQGF6DI . You are receiving this because you commented.Message ID: @.***>

uncvrd commented 1 year ago

Hi everyone - as promised here is a repository that demo's react-aria's useDatePicker using shadcn theming & components. I would like to highly encourage PR's on this repository as I'm sure this can be implemented better.

EDIT: I also don't use NextJS App Router in my projects so I just threw "use client" everywhere and hoped for the best lol

Hope it helps as a starting point :)

https://github.com/uncvrd/shadcn-ui-date-time-picker

jonnguyen12 commented 1 year ago

Hi everyone - as promised here is a repository that demo's react-aria's useDatePicker using shadcn theming & components. I would like to highly encourage PR's on this repository as I'm sure this can be implemented better.

Hope it helps as a starting point :)

https://github.com/uncvrd/shadcn-ui-date-time-picker

This is a nice surprise for the Father's Day. :) Well done.

dBianchii commented 1 year ago

+1 To this... I need it for my project now

dBianchii commented 1 year ago

Hi everyone - as promised here is a repository that demo's react-aria's useDatePicker using shadcn theming & components. I would like to highly encourage PR's on this repository as I'm sure this can be implemented better.

EDIT: I also don't use NextJS App Router in my projects so I just threw "use client" everywhere and hoped for the best lol

Hope it helps as a starting point :)

https://github.com/uncvrd/shadcn-ui-date-time-picker

@uncvrd Thats really cool. I am trying to use it. How do I pass in the date state into the date-time-picker ?

uncvrd commented 1 year ago

@dBianchii

so useDatePicker works with the @internationalized/date library, to handle a controlled state, I've been doing the following:

<DateTimePicker
    value={!!field.value ? parseAbsolute(field.value.toISOString(), getLocalTimeZone()) : null}
    onChange={(date) => {
        field.onChange(!!date ? date.toDate(getLocalTimeZone()) : null);
    }}
    shouldCloseOnSelect={false}
/>

Not sure if this is the best approach, but since the date can be null if a user has only typed in a few of the MM/dd/YYYY values, I needed to do those ternary checks 🤷

See instructions on useDatePicker here, you'll see the @internationalized/date library being used in implementation further down the page, there's a couple options besides parseAbsolute: https://react-spectrum.adobe.com/react-aria/useDatePicker.html

Let me know if y'all have better implementations!

tika commented 1 year ago

This is really nice @uncvrd, thank you! I can't seem to figure out how to just disregard the timezone, so instead I've done this:

<DateTimePicker
  onBlur={field.onBlur}
  value={
    !!field.value
      ? parseAbsolute(field.value.toISOString(), "GMT")
      : null
  }
  onChange={(date) => {
    field.onChange(!!date ? date.toDate("GMT") : new Date());
  }}
  granularity="minute"
/>

Do you have any idea how I could instead just use parseDateTime from Adobe's localisation lib? I had a problem parsing the Date ISO string like this: parseDateTime(field.value.toISOString()).

Super helpful component!!

wasauce commented 1 year ago

Lets see what they're planning for a release next week and if it isn't something related to a time picker, I'll spend some time extracting this out in to a separate repo for everyone to use/contribute to until then.

What was released? I'm having trouble finding something related to a time picker.

uncvrd commented 1 year ago

@wasauce nothing related to a date picker - it was the new CLI which is why I shared my own implementation for now

wasauce commented 1 year ago

@uncvrd it looks wonderful. going to dive in. Thank you!

codingwithashu commented 1 year ago

How to use in form ?

danvoyce commented 11 months ago

For anyone like myself wanting a simple (interim) solution, you can use <Input type="time" /> and leverage the native browser UI. I actually much prefer this on mobile devices.

zacwellmer commented 11 months ago

Here's a simple one that re-uses shadcn's calendar component. I stole the time field from @uncvrd. https://gist.github.com/zacwellmer/316e38705b6534907c142af8ca21fa9d

https://github.com/shadcn-ui/ui/assets/9603276/5d74a539-96a0-498c-ac41-7157133913b5

statusunknown418 commented 10 months ago

this is breaking when used with forms :( @zacwellmer

stevegiorgi commented 9 months ago

This works great until you set an onChange event -- then it begins to close before you've had a chance to fill in all values.

Example: I select a date, type in my hour, and begin to type in minutes, but it closes (loses focus) as soon as I've typed in a single number. If I wanted to type in "45", it only allows me to type in the first number before closing. I have to reopen it to type "5" and yet again to select "AM/PM".

tiagobnobrega commented 7 months ago

Reached this request while searching for this. Here's the implementation I settled. It uses timespace lib in case anyone is interested. Usage:


const form = useForm(...)

 <FormField
    control={form.control}
    {/*statTime field type is: Date */}
    name="startTime"
    render={({ field }) => (
      <FormItem className="w-1/3">
        <FormLabel>Label</FormLabel>
        <FormControl>
        {/*This is the component*/}
          <TimePicker onChange={field.onChange} value={field.value}>
            <TimePickerSegment segment={"hours"} />
            <TimePickerSeparator>:</TimePickerSeparator>
            <TimePickerSegment segment={"minutes"} />
          </TimePicker>
        </FormControl>
      </FormItem>
    )}
  />

Component Source:

//time-picker.tsx
import { type DateType, useTimescape } from "timescape/react";
import * as React from "react";
import {
  createContext,
  forwardRef,
  type HTMLAttributes,
  type ReactNode,
  useContext,
} from "react";
import { cn } from "@/lib/utils/style";
import type { Options } from "timescape";

export type TimePickerProps = HTMLAttributes<HTMLDivElement> & {
  value?: Date;
  onChange: (date?: Date) => void;
  children: ReactNode;
  options?: Omit<Options, "date" | "onChangeDate">;
};
type TimePickerContextValue = ReturnType<typeof useTimescape>;
const TimePickerContext = createContext<TimePickerContextValue | null>(null);

const useTimepickerContext = (): TimePickerContextValue => {
  const context = useContext(TimePickerContext);
  if (!context) {
    throw new Error(
      "Unable to access TimePickerContext. This component should be wrapped by a TimePicker component",
    );
  }
  return context;
};

const TimePicker = forwardRef<React.ElementRef<"div">, TimePickerProps>(
  ({ value, onChange, options, className, ...props }, ref) => {
    const timePickerContext = useTimescape({
      date: value,
      onChangeDate: onChange,
      ...options,
    });
    return (
      <TimePickerContext.Provider value={timePickerContext}>
        <div
          ref={ref}
          {...props}
          className={cn(
            "flex w-auto h-10 rounded-md border border-input bg-background px-3 py-1 text-sm",
            "ring-offset-background focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
            "disabled:cursor-not-allowed disabled:opacity-50",
            className,
          )}
        ></div>
      </TimePickerContext.Provider>
    );
  },
);
TimePicker.displayName = "TimePicker";

type TimePickerSegmentProps = Omit<
  HTMLAttributes<HTMLInputElement>,
  "value" | "onChange"
> & {
  segment: DateType;
  inputClassName?: string;
};

const TimePickerSegment = forwardRef<
  React.ElementRef<"input">,
  TimePickerSegmentProps
>(({ segment, inputClassName, className, ...props }, ref) => {
  const { getInputProps } = useTimepickerContext();
  const { ref: timePickerInputRef } = getInputProps(segment);
  return (
    <div
      className={cn(
        "rounded-md focus-within:bg-accent text-accent-foreground px-2 py-1",
      )}
    >
      <input
        ref={(node) => {
          if (typeof ref === "function") {
            ref(node);
          } else {
            if (ref) ref.current = node;
          }
          timePickerInputRef(node);
        }}
        {...props}
        className={cn(
          "tabular-nums caret-transparent",
          "bg-transparent ring-0 ring-offset-0 border-transparent focus-visible:border-transparent focus-visible:ring-0 outline-none",
          inputClassName,
        )}
      />
    </div>
  );
});
TimePickerSegment.displayName = "TimePickerSegment";

type TimePickerSeparatorProps = HTMLAttributes<HTMLSpanElement>;
const TimePickerSeparator = forwardRef<
  React.ElementRef<"span">,
  TimePickerSeparatorProps
>(({ className, ...props }, ref) => {
  return (
    <span ref={ref} {...props} className={cn("text-sm py-1", className)}></span>
  );
});
TimePickerSeparator.displayName = "TimePickerSeparator";

export { TimePicker, TimePickerSegment, TimePickerSeparator };

PS: This could also be used for date and time. Check out timescape docs: https://github.com/dan-lee/timescape

bpena707 commented 7 months ago

This is great. I have tried some sort of implementation like this but how would you store this into a Prisma? would it be a simple DateTime? and would MySQL accept this too?

Snailedlt commented 7 months ago

A time-picker would be great! In one of our projects we need a time-picker where we can pick a time-range (from-to) for customers to receive their order.

For now we're just using a select, with time options in the 3hr range like so: image

This way of doing it has some issues though. For example we need custom logic to return the correct type instead of a string, and it also doesn't look that good, so a separate TimePicker component would be really nice to have. Would also be cool to see how it could be integrated into the existing DatePicker.

liamlows commented 7 months ago

For those looking for a super easy drop in solution that surprisingly themes well to shadcn automatically, here is an example using the native time selector as @danvoyce mentioned using react hook form and modifying the original date object so form submission is easy:

<FormField
  control={form.control}
  name="date"
  render={({ field }) => (
    <FormItem>
      <FormLabel className="pr-4">Date</FormLabel>
      <Popover
        open={calendarOpen}
        onOpenChange={(open) => setCalendarOpen(open)}
      >
        <PopoverTrigger asChild>
          <FormControl>
            <Button
              variant="outline"
              className={cn(
                'w-full pl-3 text-left font-normal',
                !field.value && 'text-muted-foreground',
              )}
            >
              {field.value ? (
                `${field.value.toLocaleString([], {
                  year: 'numeric',
                  month: 'numeric',
                  day: 'numeric',
                  hour: '2-digit',
                  minute: '2-digit',
                })}`
              ) : (
                <span>Select Date</span>
              )}
              <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
            </Button>
          </FormControl>
        </PopoverTrigger>
        <PopoverContent>
          <Calendar
            className="p-0"
            mode="single"
            selected={field.value}
            onSelect={field.onChange}
            disabled={(date) => date > new Date() || date < new Date('1900-01-01')}
            initialFocus
          />
          <Input
            type="time"
            className="mt-2"
            // take locale date time string in format that the input expects (24hr time)
            value={field.value.toLocaleTimeString([], {
              hourCycle: 'h23',
              hour: '2-digit',
              minute: '2-digit',
            })}
            // take hours and minutes and update our Date object then change date object to our new value
            onChange={(selectedTime) => {
              const currentTime = field.value;
              currentTime.setHours(
                parseInt(selectedTime.target.value.split(':')[0]),
                parseInt(selectedTime.target.value.split(':')[1]),
                0,
              );
              field.onChange(currentTime);
            }}
          />
        </PopoverContent>
      </Popover>
      <FormMessage />
      <FormDescription>
        This is the date & time the assessment finished.
      </FormDescription>
    </FormItem>
  )}
/>

now that i think of it this could also be a variant element of the native Shadcn calendar too 😅

hsuanyi-chou commented 7 months ago

thanks to @uncvrd I've created a datetime picker by his repo and add usages.

datetime-picker

anasmohammed361 commented 7 months ago

Found this component . Was simple and solved the purpose , It uses Shadcn.

uncvrd commented 6 months ago

@hsuanyi-chou thanks for the credit ❤️

heynish commented 6 months ago

@liamlows is it possible to provide full implementation? with the imports, calendar and input files?

psiddharthdesign commented 5 months ago

Is there a way to convert this DateTimePicker value to ISO 8601 like : 2024-04-10 19:22:36.2213+00 ? I work with Supabase and Nextjs

anasmohammed361 commented 5 months ago

Is there a way to convert this DateTimePicker value to ISO 8601 like : 2024-04-10 19:22:36.2213+00 ? I work with Supabase and Nextjs

toISOString()

i think you can convert the Date object to your wish when assigning the value either on onChange or store the date as it is and converting it once before writing to your supabase db makes much more sense

bennobuilder commented 4 months ago

https://github.com/shadcn-ui/ui/assets/57860196/d5148eb6-9a69-4fcc-a191-bde5c64552b7

Here's my spin on a basic date/time picker using shadcn/ui, react-stately, and react-aria (first iteration):

https://github.com/dyndotart/monorepo/tree/79-add-basic-input-events/packages/ui/src/components/composed/DateTimePicker

Maliksidk19 commented 4 months ago

image

html default input time type is also pretty much good when used with shadcn Input

<Input type="time" className="w-max py-6" />

ilorez commented 4 months ago

thanks to @uncvrd I've created a datetime picker by his repo and add usages.

datetime-picker Thanks that was so fast and helpful <3

dubbajubba commented 3 months ago

image

html default input time type is also pretty much good when used with shadcn Input

<Input type="time" className="w-max py-6" />

I agree. This seems to be the easiest solution by far and still looks great!

bpena707 commented 3 months ago

i am using the component and it works fine when i input the time but I want it to display to the columns.tsx shadcn ui component using the data table in the format of HH:MMAM but instead it displays as HH:MM:SS in 24 hour format. how would you make it display? would it be a regular expression?

shadcn commented 2 months ago

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.

Snailedlt commented 2 months ago

Please keep this issue open. It would be a very useful feature

anthonysgro commented 2 months ago

agree, i think it would be a great feature and we should keep it open

anthonysgro commented 2 months ago

image

html default input time type is also pretty much good when used with shadcn Input

<Input type="time" className="w-max py-6" />

Just an FYI, that nice dropdown UI looks only supported on chrome. It looks worse on other browsers