ui
ui copied to clipboard
Feature request: Time picker
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?
That looks very nice, does the time picker has to go with date picker? can it be separate like mantine.dev time picker?
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
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.
Do you already have this on repo/codesandbox ser?
I got caught up on a separate project - I will share a repo/sandbox by end of the weekend!
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: @.***>
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
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.
+1 To this... I need it for my project now
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
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!
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!!
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 nothing related to a date picker - it was the new CLI which is why I shared my own implementation for now
@uncvrd it looks wonderful. going to dive in. Thank you!
How to use in form ?
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.
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
this is breaking when used with forms :( @zacwellmer
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".
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
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?
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:
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.
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 😅
Found this component . Was simple and solved the purpose , It uses Shadcn.
@hsuanyi-chou thanks for the credit ❤️
@liamlows is it possible to provide full implementation? with the imports, calendar and input files?
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