{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "calendar", "title": "Calendar", "description": "Day picker surface styled with Stitch buttons, radii, and range treatments.", "dependencies": ["react-day-picker@^10.0.1"], "registryDependencies": [ "askyourpolicy/ss-registry/stitch-base", "askyourpolicy/ss-registry/button" ], "files": [ { "path": "src/components/ui/calendar.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n DayPicker,\n getDefaultClassNames,\n type ChevronProps,\n type DayButton,\n type Locale,\n type RootProps,\n type WeekNumberProps,\n} from \"react-day-picker\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button, buttonVariants } from \"@/components/ui/button\";\nimport {\n CaretLeftIcon as ChevronLeftIcon,\n CaretRightIcon as ChevronRightIcon,\n CaretDownIcon as ChevronDownIcon,\n} from \"@phosphor-icons/react\";\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n captionLayout = \"label\",\n buttonVariant = \"ghost\",\n locale,\n formatters,\n components,\n ...props\n}: React.ComponentProps & {\n buttonVariant?: React.ComponentProps[\"variant\"];\n}) {\n const defaultClassNames = getDefaultClassNames();\n // Declaring these inline would hand DayPicker a new component type on every render, which unmounts\n // and rebuilds every day button on selection. That detaches the button under the pointer, and a\n // calendar inside a popover is then dismissed as if the click had landed outside it.\n const dayPickerComponents = React.useMemo(\n () => ({\n Root: CalendarRoot,\n Chevron: CalendarChevron,\n DayButton: (props: React.ComponentProps) => (\n \n ),\n WeekNumber: CalendarWeekNumber,\n ...components,\n }),\n [components, locale],\n );\n\n return (\n svg]:rotate-180`,\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n className,\n )}\n captionLayout={captionLayout}\n locale={locale}\n formatters={{\n formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: \"short\" }),\n ...formatters,\n }}\n classNames={{\n root: cn(\"w-fit\", defaultClassNames.root),\n months: cn(\"relative flex flex-col gap-4 md:flex-row\", defaultClassNames.months),\n month: cn(\"flex w-full flex-col gap-4\", defaultClassNames.month),\n nav: cn(\n \"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1\",\n defaultClassNames.nav,\n ),\n button_previous: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n defaultClassNames.button_previous,\n ),\n button_next: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n defaultClassNames.button_next,\n ),\n month_caption: cn(\n \"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)\",\n defaultClassNames.month_caption,\n ),\n dropdowns: cn(\n \"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium\",\n defaultClassNames.dropdowns,\n ),\n dropdown_root: cn(\"relative rounded-(--cell-radius)\", defaultClassNames.dropdown_root),\n dropdown: cn(\"absolute inset-0 bg-popover opacity-0\", defaultClassNames.dropdown),\n caption_label: cn(\n \"font-medium select-none\",\n captionLayout === \"label\"\n ? \"text-sm\"\n : \"flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground\",\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 \"flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none\",\n defaultClassNames.weekday,\n ),\n week: cn(\"mt-2 flex w-full\", defaultClassNames.week),\n week_number_header: cn(\"w-(--cell-size) select-none\", defaultClassNames.week_number_header),\n week_number: cn(\n \"text-[0.8rem] text-muted-foreground select-none\",\n defaultClassNames.week_number,\n ),\n day: cn(\n \"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)\",\n props.showWeekNumber\n ? \"[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)\"\n : \"[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)\",\n defaultClassNames.day,\n ),\n range_start: cn(\n \"relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted\",\n defaultClassNames.range_start,\n ),\n range_middle: cn(\"rounded-none\", defaultClassNames.range_middle),\n range_end: cn(\n \"relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted\",\n defaultClassNames.range_end,\n ),\n today: cn(\n \"rounded-(--cell-radius) bg-muted text-foreground 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(\"text-muted-foreground opacity-50\", defaultClassNames.disabled),\n hidden: cn(\"invisible\", defaultClassNames.hidden),\n ...classNames,\n }}\n components={dayPickerComponents}\n {...props}\n />\n );\n}\n\nfunction CalendarRoot({ className, rootRef, ...props }: RootProps) {\n return
;\n}\n\nfunction CalendarChevron({ className, orientation, ...props }: ChevronProps) {\n if (orientation === \"left\") {\n return ;\n }\n\n if (orientation === \"right\") {\n return ;\n }\n\n return ;\n}\n\nfunction CalendarWeekNumber({ children, ...props }: WeekNumberProps) {\n return (\n \n
\n {children}\n
\n \n );\n}\n\nfunction CalendarDayButton({\n className,\n day,\n modifiers,\n locale,\n ...props\n}: React.ComponentProps & { locale?: Partial }) {\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" } ], "docs": "Wraps react-day-picker, so mode, selected, and numberOfMonths come straight from its API. react-day-picker is pinned to 10.x because its class name keys change between majors.", "type": "registry:ui" }