Skip to content

Instantly share code, notes, and snippets.

@mjbalcueva
Last active July 17, 2024 07:26
Show Gist options
  • Save mjbalcueva/1fbcb1be9ef68a82c14d778b686a04fa to your computer and use it in GitHub Desktop.
Save mjbalcueva/1fbcb1be9ef68a82c14d778b686a04fa to your computer and use it in GitHub Desktop.
shadcn ui calendar custom year and month dropdown
"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>
)
}
@aynuayex
Copy link

@Marco-Antonio-Rodrigues where does onChangeValue come?

@Maliksidk19
Copy link

Maliksidk19 commented Jul 16, 2024

@ErhanArda yes it works but when you select the year and month from the drop down it does not reflect immediately on the button here part

<Button>other code here
{value? formattedDate : <span>Pick a date </span>}
<Button>

until you select a date and the date picker closes, but mine does.

Instead of doing this you can simply close the popover in onDayClick, it means popover will only close when a date is selected and won't close on any other interaction

image

@aynuayex
Copy link

@Maliksidk19 I don't feel you, I mean that is the default behavior the date picker closes only on day selection and we are here taking about the input field not reflecting the year and month selection on selection before the picker is closed after selecting day.

@Maliksidk19
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment