ui icon indicating copy to clipboard operation
ui copied to clipboard

Feature request: Time picker

Open its-monotype opened this issue 1 year ago • 36 comments

its-monotype avatar Apr 29 '23 12:04 its-monotype

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?

uncvrd avatar Jun 01 '23 21:06 uncvrd

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

jonnguyen12 avatar Jun 01 '23 23:06 jonnguyen12

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 avatar Jun 02 '23 02:06 uncvrd

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.

uncvrd avatar Jun 03 '23 19:06 uncvrd

Do you already have this on repo/codesandbox ser?

gtandes avatar Jun 15 '23 15:06 gtandes

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

uncvrd avatar Jun 15 '23 15:06 uncvrd

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: @.***>

gtandes avatar Jun 15 '23 15:06 gtandes

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 avatar Jun 19 '23 07:06 uncvrd

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.

jonnguyen12 avatar Jun 19 '23 07:06 jonnguyen12

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

dBianchii avatar Jun 20 '23 23:06 dBianchii

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 ?

dBianchii avatar Jun 21 '23 00:06 dBianchii

@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!

uncvrd avatar Jun 21 '23 05:06 uncvrd

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!!

tika avatar Jul 07 '23 17:07 tika

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.

wasauce avatar Jul 14 '23 15:07 wasauce

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

uncvrd avatar Jul 14 '23 17:07 uncvrd

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

wasauce avatar Jul 14 '23 18:07 wasauce

How to use in form ?

codingwithashu avatar Sep 11 '23 10:09 codingwithashu

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.

danvoyce avatar Oct 17 '23 19:10 danvoyce

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

zacwellmer avatar Oct 25 '23 14:10 zacwellmer

this is breaking when used with forms :( @zacwellmer

statusunknown418 avatar Nov 10 '23 16:11 statusunknown418

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".

stevegiorgi avatar Dec 10 '23 20:12 stevegiorgi

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

tiagobnobrega avatar Feb 08 '24 15:02 tiagobnobrega

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?

bpena707 avatar Feb 21 '24 17:02 bpena707

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.

Snailedlt avatar Feb 25 '24 16:02 Snailedlt

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 😅

liamlows avatar Feb 26 '24 21:02 liamlows

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

datetime-picker

hsuanyi-chou avatar Feb 27 '24 05:02 hsuanyi-chou

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

anasmohammed361 avatar Mar 03 '24 18:03 anasmohammed361

@hsuanyi-chou thanks for the credit ❤️

uncvrd avatar Mar 10 '24 03:03 uncvrd

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

heynish avatar Mar 13 '24 00:03 heynish

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

psiddharthdesign avatar Apr 22 '24 18:04 psiddharthdesign