-
-
Save mjbalcueva/1fbcb1be9ef68a82c14d778b686a04fa to your computer and use it in GitHub Desktop.
"use client" | |
import * as React from "react" | |
import { buttonVariants } from "@/components/ui/button" | |
import { ScrollArea } from "@/components/ui/scroll-area" | |
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" | |
import { cn } from "@/lib/utils" | |
import { ChevronLeft, ChevronRight } from "lucide-react" | |
import { DayPicker, DropdownProps } from "react-day-picker" | |
export type CalendarProps = React.ComponentProps<typeof DayPicker> | |
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) { | |
return ( | |
<DayPicker | |
showOutsideDays={showOutsideDays} | |
className={cn("p-3", className)} | |
classNames={{ | |
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0", | |
month: "space-y-4", | |
caption: "flex justify-center pt-1 relative items-center", | |
caption_label: "text-sm font-medium", | |
caption_dropdowns: "flex justify-center gap-1", | |
nav: "space-x-1 flex items-center", | |
nav_button: cn( | |
buttonVariants({ variant: "outline" }), | |
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100" | |
), | |
nav_button_previous: "absolute left-1", | |
nav_button_next: "absolute right-1", | |
table: "w-full border-collapse space-y-1", | |
head_row: "flex", | |
head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]", | |
row: "flex w-full mt-2", | |
cell: "text-center text-sm p-0 relative [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20", | |
day: cn(buttonVariants({ variant: "ghost" }), "h-9 w-9 p-0 font-normal aria-selected:opacity-100"), | |
day_selected: | |
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", | |
day_today: "bg-accent text-accent-foreground", | |
day_outside: "text-muted-foreground opacity-50", | |
day_disabled: "text-muted-foreground opacity-50", | |
day_range_middle: "aria-selected:bg-accent aria-selected:text-accent-foreground", | |
day_hidden: "invisible", | |
...classNames, | |
}} | |
components={{ | |
Dropdown: ({ value, onChange, children, ...props }: DropdownProps) => { | |
const options = React.Children.toArray(children) as React.ReactElement<React.HTMLProps<HTMLOptionElement>>[] | |
const selected = options.find((child) => child.props.value === value) | |
const handleChange = (value: string) => { | |
const changeEvent = { | |
target: { value }, | |
} as React.ChangeEvent<HTMLSelectElement> | |
onChange?.(changeEvent) | |
} | |
return ( | |
<Select | |
value={value?.toString()} | |
onValueChange={(value) => { | |
handleChange(value) | |
}} | |
> | |
<SelectTrigger className="pr-1.5 focus:ring-0"> | |
<SelectValue>{selected?.props?.children}</SelectValue> | |
</SelectTrigger> | |
<SelectContent position="popper"> | |
<ScrollArea className="h-80"> | |
{options.map((option, id: number) => ( | |
<SelectItem key={`${option.props.value}-${id}`} value={option.props.value?.toString() ?? ""}> | |
{option.props.children} | |
</SelectItem> | |
))} | |
</ScrollArea> | |
</SelectContent> | |
</Select> | |
) | |
}, | |
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />, | |
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />, | |
}} | |
{...props} | |
/> | |
) | |
} | |
Calendar.displayName = "Calendar" | |
export { Calendar } |
/* add this snippet in your globals.css file */ | |
.rdp-vhidden { | |
@apply hidden; | |
} |
"use client" | |
import * as React from "react" | |
import { Button } from "@/components/ui/button" | |
import { Calendar } from "@/components/ui/calendar" | |
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" | |
import { cn } from "@/lib/utils" | |
import { format } from "date-fns" | |
import { CalendarIcon } from "lucide-react" | |
export function SampleDatePicker() { | |
const [date, setDate] = React.useState<Date>() | |
return ( | |
<Popover> | |
<PopoverTrigger asChild> | |
<Button | |
variant={"outline"} | |
className={cn("w-[240px] justify-start text-left font-normal", !date && "text-muted-foreground")} | |
> | |
<CalendarIcon className="mr-2 h-4 w-4" /> | |
{date ? format(date, "PPP") : <span>Pick a date</span>} | |
</Button> | |
</PopoverTrigger> | |
<PopoverContent align="start" className=" w-auto p-0"> | |
<Calendar | |
mode="single" | |
captionLayout="dropdown-buttons" | |
selected={date} | |
onSelect={setDate} | |
fromYear={1960} | |
toYear={2030} | |
/> | |
</PopoverContent> | |
</Popover> | |
) | |
} |
This is awesome, thanks a lot mark @mjbalcueva
Thanks @mjbalcueva 🔥🔥
Thank you!
When I add captionLayout="dropdown" and fromYear and toYear to my Calendar component it always looks as if there is a double render/glitch when the calendar is opening. Do you perhaps know why?
Having the same issue. Adding dropwodn or dropdown-buttons causes a double render.
Thank you, it helps me a lot!
Great job!
Thank you very much! Looks great!
@mjbalcueva man you're the best bro. Saved me a ton of time! Can't express in words how much grateful I am. God bless you.
Since you like helping others, I wanna share something I've done: a Combobox with an auto-complete feature.
If you want to check it out and share with the community, here it is:
shadcn-ui/ui#2577 (comment)
@mjbalcueva man you're the best bro. Saved me a ton of time! Can't express in words how much grateful I am. God bless you. Since you like helping others, I wanna share something I've done: a Combobox with an auto-complete feature. If you want to check it out and share with the community, here it is: shadcn-ui/ui#2577 (comment)
Hi man, does your combobox work with multi-select like headless ui?
awesome thanks
@mjbalcueva thank you , this is so good !
thanks for sharing this
@mjbalcueva thank you for this. Has anyone used this in a modal?? Having some weird behaviors on the dropdowns
Using this component, after selecting current date selectedValue = undefined. To solve this issue add to calendar prop required
according https://react-day-picker.js.org/basics/selecting-days
A small change, instead of adding to the CSS file
.rdp-vhidden {
@apply hidden;
}
Apply the Tailwind selector directly in the component declaration:
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
month: "space-y-4",
//...
vhidden: "vhidden hidden", // Add this line
//..
}}
@jramiresbrito Try adding this classname caption_dropdowns: "flex justify-center gap-x-2"
for fixing the dropdowns in a row. If you put the code in a live example, I can try to help with the popover issue.
@jramiresbrito Try adding this classname
caption_dropdowns: "flex justify-center gap-x-2"
for fixing the dropdowns in a row. If you put the code in a live example, I can try to help with the popover issue.
Thanks for replying. I went for an easier solution by just adding classes instead of modifying the component broadly
Thank you!
Thank you for sharing this
Thanks a lot
@mjbalcueva 👏 great work. but I'm always wondering why shadcn-ui does not merges this kind of changes quickly?
@liuhe2020
Hey man, do you know how I can close the modal when the user selects a date, instead of having to click outside again?
onSelect={field.onChange}
I suppose I need to change this but I am not sure how.
here is how you make the datepicker close after selection of date
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
return (
<Popover open={isCalendarOpen} onOpenChange={setIsCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"overflow-hidden dark:text-white",
!date && "text-muted-foreground",
className
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? (
<span>
{window.innerWidth > 1024
? format(date, "PPP")
: format(date, "d MMM")}
</span>
) : (
<span className="hidden sm:block">Pick a date</span>
)}
<ChevronsUpDown className="sm:ml-2 h-4 w-4 shrink-0 opacity-50 " />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={(e) => {
setDate(e);
setIsCalendarOpen(false);
}}
initialFocus
captionLayout="dropdown-buttons"
fromYear={1990}
toYear={2025}
/>
</PopoverContent>
</Popover>
one logic i want my datepicker to have is after i have selected a date at some point and it closes.when i again clicked on it and it opens i want the date to point to the pervious date that i have selected already instead of the default initial state date?how do i implement this any ideas would be apperciated.
here is my implmentation:
export default function MainContent() {
const [date, setDate] = useState<Date | undefined>(new Date());
return (
<div className="w-full mt-24 absolute pr-1 sm:pr-4 ">
<DatePicker
className="w-[100px] sm:w-1/4 lg:w-48"
date={date}
setDate={setDate}
/>
</div>
);
}
type DatePickerProps = {
className: string;
date: Date | undefined;
setDate: React.Dispatch<React.SetStateAction<Date | undefined>>;
};
const DatePicker = ({ className, date, setDate }: DatePickerProps) => {
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
return (
<Popover open={isCalendarOpen} onOpenChange={setIsCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"overflow-hidden dark:text-white",
!date && "text-muted-foreground",
className
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? (
<span>
{window.innerWidth > 1024
? format(date, "PPP")
: format(date, "d MMM")}
</span>
) : (
<span className="hidden sm:block">Pick a date</span>
)}
<ChevronsUpDown className="sm:ml-2 h-4 w-4 shrink-0 opacity-50 " />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={(e) => {
setDate(e);
setIsCalendarOpen(false);
}}
initialFocus
captionLayout="dropdown-buttons"
fromYear={1990}
toYear={2025}
/>
</PopoverContent>
</Popover>
);
};
Thank you man!
This saved me big time!!! Thank you so much!
one logic i want my datepicker to have is after i have selected a date at some point and it closes.when i again clicked on it and it opens i want the date to point to the pervious date that i have selected already instead of the default initial state date?how do i implement this any ideas would be apperciated.
here is how i have come up to implement it by following from the docs and it works as expected.
export default function MainContent() {
const [date, setDate] = useState<Date | undefined>(new Date());
return (
<div className="w-full mt-24 absolute pr-1 sm:pr-4 ">
<DatePicker
className="w-[100px] sm:w-1/4 lg:w-48"
date={date}
setDate={setDate}
/>
</div>
);
}
type DatePickerProps = {
className: string;
date: Date | undefined;
setDate: React.Dispatch<React.SetStateAction<Date | undefined>>;
};
const DatePicker = ({ className, date, setDate }: DatePickerProps) => {
const [ month, setMonth ] = useState<Date | undefined>(new Date());
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
return (
<Popover open={isCalendarOpen} onOpenChange={setIsCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"overflow-hidden dark:text-white",
!date && "text-muted-foreground",
className
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? (
<span>
{window.innerWidth > 1024
? format(updateDatePart(month as Date,date), "PPP")
: format(updateDatePart(month as Date,date), "d MMM")}
</span>
) : (
<span className="hidden sm:block">Pick a date</span>
)}
<ChevronsUpDown className="sm:ml-2 h-4 w-4 shrink-0 opacity-50 " />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
month={month}
onMonthChange={(month) => setMonth(month)}
selected={date}
onSelect={(e) => {
setDate(e);
setIsCalendarOpen(false);
}}
initialFocus
captionLayout="dropdown-buttons"
fromYear={1990}
toYear={2025}
/>
</PopoverContent>
</Popover>
);
};
lib/utils.tsx
import { format, parse, setDate } from "date-fns"
export const updateDatePart = (month: Date, newDay: Date): Date => {
return setDate(month, newDay.getDate());
};
@ErhanArda no setting defaultMonth={value}
does not allow you to programatically control the month it just sets a default month which means every time it opens it is set to that default month read here, but if you want to programatically control the month, you have to set it like i have put above.you can read here more.
if it does not work for you please check your code so that it matches mine, or create a sandbox and i will check it.it works fine for me and hell yea it should for you to.
thanks @everyone for the positive feedback overall. If you guys are interested, I've made a custom password input component -> here