{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "calendar", "title": "Calendar", "description": "A date picker calendar component.", "dependencies": [ "react-day-picker", "lucide-react" ], "registryDependencies": [ "button", "input", "label" ], "files": [ { "path": "registry/default/ui/calendar.tsx", "content": "'use client';\n\nimport {\n ChevronDownIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n} from 'lucide-react';\nimport * as React from 'react';\nimport {\n type DateRange,\n DayButton,\n DayPicker,\n getDefaultClassNames,\n} from 'react-day-picker';\n\nimport { Button, buttonVariants } from '@/registry/default/ui/button';\nimport { Input } from '@/registry/default/ui/input';\nimport { Label } from '@/registry/default/ui/label';\nimport { cn } from '@/lib/utils';\n\ntype TCalendarProps = React.ComponentProps & {\n buttonVariant?: React.ComponentProps['variant'];\n withTime?: boolean;\n};\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n captionLayout = 'label',\n buttonVariant = 'ghost',\n formatters,\n components,\n withTime,\n ...props\n}: TCalendarProps) {\n const defaultClassNames = getDefaultClassNames();\n\n const dayPicker = (\n svg]:rotate-180`,\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n !withTime && className\n )}\n captionLayout={captionLayout}\n formatters={{\n formatMonthDropdown: (date) =>\n date.toLocaleString('default', { month: 'short' }),\n ...formatters,\n }}\n classNames={{\n root: cn('w-fit', defaultClassNames.root),\n months: cn(\n 'flex gap-4 flex-col md:flex-row relative',\n defaultClassNames.months\n ),\n month: cn('flex flex-col w-full gap-4', defaultClassNames.month),\n nav: cn(\n 'flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between',\n defaultClassNames.nav\n ),\n button_previous: cn(\n buttonVariants({ variant: buttonVariant }),\n 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',\n defaultClassNames.button_previous\n ),\n button_next: cn(\n buttonVariants({ variant: buttonVariant }),\n 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',\n defaultClassNames.button_next\n ),\n month_caption: cn(\n 'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)',\n defaultClassNames.month_caption\n ),\n dropdowns: cn(\n 'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5',\n defaultClassNames.dropdowns\n ),\n dropdown_root: cn(\n 'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md',\n defaultClassNames.dropdown_root\n ),\n dropdown: cn('absolute inset-0 opacity-0', defaultClassNames.dropdown),\n caption_label: cn(\n 'select-none font-medium',\n captionLayout === 'label'\n ? 'text-sm'\n : 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5',\n defaultClassNames.caption_label\n ),\n month_grid: cn('w-full border-collapse', defaultClassNames.month_grid),\n weekdays: cn('flex', defaultClassNames.weekdays),\n weekday: cn(\n 'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none',\n defaultClassNames.weekday\n ),\n week: cn('flex w-full mt-2', defaultClassNames.week),\n week_number_header: cn(\n 'select-none w-(--cell-size)',\n defaultClassNames.week_number_header\n ),\n week_number: cn(\n 'text-[0.8rem] select-none text-muted-foreground',\n defaultClassNames.week_number\n ),\n day: cn(\n 'relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none',\n defaultClassNames.day\n ),\n range_start: cn(\n 'rounded-l-md bg-accent',\n defaultClassNames.range_start\n ),\n range_middle: cn('rounded-none', defaultClassNames.range_middle),\n range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end),\n today: cn(\n 'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',\n defaultClassNames.today\n ),\n outside: cn(\n 'text-muted-foreground aria-selected:text-muted-foreground',\n defaultClassNames.outside\n ),\n disabled: cn(\n 'text-muted-foreground opacity-50',\n defaultClassNames.disabled\n ),\n hidden: cn('invisible', defaultClassNames.hidden),\n ...classNames,\n }}\n components={{\n Root: ({ className, rootRef, ...props }) => (\n \n ),\n Chevron: ({ className, orientation, ...props }) => {\n if (orientation === 'left') {\n return (\n \n );\n }\n\n if (orientation === 'right') {\n return (\n \n );\n }\n\n return (\n \n );\n },\n DayButton: CalendarDayButton,\n WeekNumber: ({ children, ...props }) => (\n \n
\n {children}\n
\n \n ),\n ...components,\n }}\n {...props}\n />\n );\n\n if (!withTime) return dayPicker;\n\n const timeProps = props as {\n mode?: TCalendarProps['mode'];\n selected?: Date | DateRange;\n onSelect?: (date: Date | DateRange | undefined) => void;\n };\n\n return (\n \n {dayPicker}\n
\n \n
\n \n );\n}\n\nfunction toTimeString(date: Date | undefined) {\n if (!date) return '';\n const pad = (n: number) => String(n).padStart(2, '0');\n\n return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(\n date.getSeconds()\n )}`;\n}\n\nfunction applyTimeString(base: Date | undefined, time: string) {\n const parts = time.split(':').map(Number);\n const next = base ? new Date(base) : new Date();\n next.setHours(Number.isFinite(parts[0]) ? parts[0]! : 0);\n next.setMinutes(Number.isFinite(parts[1]) ? parts[1]! : 0);\n next.setSeconds(Number.isFinite(parts[2]) ? parts[2]! : 0);\n next.setMilliseconds(0);\n\n return next;\n}\n\ntype TCalendarTimePanelProps = {\n mode: TCalendarProps['mode'];\n selected: Date | DateRange | undefined;\n onSelect?: (date: Date | DateRange | undefined) => void;\n};\n\nfunction CalendarTimePanel({\n mode,\n selected,\n onSelect,\n}: TCalendarTimePanelProps) {\n const fromId = React.useId();\n const toId = React.useId();\n\n if (mode === 'range') {\n const range = (selected as DateRange | undefined) ?? {\n from: undefined,\n to: undefined,\n };\n\n return (\n
\n
\n \n \n onSelect?.({\n from: applyTimeString(range.from, e.target.value),\n to: range.to,\n })\n }\n />\n
\n
\n \n \n onSelect?.({\n from: range.from,\n to: applyTimeString(range.to, e.target.value),\n })\n }\n />\n
\n
\n );\n }\n\n const date = selected as Date | undefined;\n\n return (\n
\n \n onSelect?.(applyTimeString(date, e.target.value))}\n />\n
\n );\n}\n\nfunction CalendarDayButton({\n className,\n day,\n modifiers,\n ...props\n}: React.ComponentProps) {\n const defaultClassNames = getDefaultClassNames();\n\n const ref = React.useRef(null);\n React.useEffect(() => {\n if (modifiers.focused) ref.current?.focus();\n }, [modifiers.focused]);\n\n return (\n span]:text-xs [&>span]:opacity-70',\n defaultClassNames.day,\n className\n )}\n {...props}\n />\n );\n}\n\nexport { Calendar, CalendarDayButton };\n", "type": "registry:ui" } ], "type": "registry:ui" }