| 1 |
import * as React from "react" |
| 2 |
import { DayPicker, getDefaultClassNames } from "react-day-picker"; |
| 3 |
|
| 4 |
import { cn } from "@/lib/utils" |
| 5 |
import { Button, buttonVariants } from "@/components/ui/button" |
| 6 |
import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from "lucide-react" |
| 7 |
|
| 8 |
function Calendar({ |
| 9 |
className, |
| 10 |
classNames, |
| 11 |
showOutsideDays = true, |
| 12 |
captionLayout = "label", |
| 13 |
buttonVariant = "ghost", |
| 14 |
locale, |
| 15 |
formatters, |
| 16 |
components, |
| 17 |
...props |
| 18 |
}) { |
| 19 |
const defaultClassNames = getDefaultClassNames() |
| 20 |
|
| 21 |
return ( |
| 22 |
<DayPicker |
| 23 |
showOutsideDays={showOutsideDays} |
| 24 |
className={cn( |
| 25 |
"disco:group/calendar disco:bg-background disco:p-2 disco:[--cell-radius:var(--radius-md)] disco:[--cell-size:--spacing(7)] disco:in-data-[slot=card-content]:bg-transparent disco:in-data-[slot=popover-content]:bg-transparent", |
| 26 |
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, |
| 27 |
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, |
| 28 |
className |
| 29 |
)} |
| 30 |
captionLayout={captionLayout} |
| 31 |
locale={locale} |
| 32 |
formatters={{ |
| 33 |
formatMonthDropdown: (date) => |
| 34 |
date.toLocaleString(locale?.code, { month: "short" }), |
| 35 |
...formatters, |
| 36 |
}} |
| 37 |
classNames={{ |
| 38 |
root: cn("disco:w-fit", defaultClassNames.root), |
| 39 |
months: cn( |
| 40 |
"disco:relative disco:flex disco:flex-col disco:gap-4 disco:md:flex-row", |
| 41 |
defaultClassNames.months |
| 42 |
), |
| 43 |
month: cn( |
| 44 |
"disco:flex disco:w-full disco:flex-col disco:gap-4", |
| 45 |
defaultClassNames.month |
| 46 |
), |
| 47 |
nav: cn( |
| 48 |
"disco:absolute disco:inset-x-0 disco:top-0 disco:flex disco:w-full disco:items-center disco:justify-between disco:gap-1", |
| 49 |
defaultClassNames.nav |
| 50 |
), |
| 51 |
button_previous: cn( |
| 52 |
buttonVariants({ variant: buttonVariant }), |
| 53 |
"disco:size-(--cell-size) disco:p-0 disco:select-none disco:aria-disabled:opacity-50", |
| 54 |
defaultClassNames.button_previous |
| 55 |
), |
| 56 |
button_next: cn( |
| 57 |
buttonVariants({ variant: buttonVariant }), |
| 58 |
"disco:size-(--cell-size) disco:p-0 disco:select-none disco:aria-disabled:opacity-50", |
| 59 |
defaultClassNames.button_next |
| 60 |
), |
| 61 |
month_caption: cn( |
| 62 |
"disco:flex disco:h-(--cell-size) disco:w-full disco:items-center disco:justify-center disco:px-(--cell-size)", |
| 63 |
defaultClassNames.month_caption |
| 64 |
), |
| 65 |
dropdowns: cn( |
| 66 |
"disco:flex disco:h-(--cell-size) disco:w-full disco:items-center disco:justify-center disco:gap-1.5 disco:text-sm disco:font-medium", |
| 67 |
defaultClassNames.dropdowns |
| 68 |
), |
| 69 |
dropdown_root: cn( |
| 70 |
"disco:relative disco:rounded-(--cell-radius)", |
| 71 |
defaultClassNames.dropdown_root |
| 72 |
), |
| 73 |
dropdown: cn( |
| 74 |
"disco:absolute disco:inset-0 disco:bg-popover disco:opacity-0", |
| 75 |
defaultClassNames.dropdown |
| 76 |
), |
| 77 |
caption_label: cn("disco:font-medium disco:select-none", captionLayout === "label" |
| 78 |
? "disco:text-sm" |
| 79 |
: "disco:flex disco:items-center disco:gap-1 disco:rounded-(--cell-radius) disco:text-sm disco:[&>svg]:size-3.5 disco:[&>svg]:text-muted-foreground", defaultClassNames.caption_label), |
| 80 |
table: "disco:w-full disco:border-collapse", |
| 81 |
weekdays: cn("disco:flex", defaultClassNames.weekdays), |
| 82 |
weekday: cn( |
| 83 |
"disco:flex-1 disco:rounded-(--cell-radius) disco:text-[0.8rem] disco:font-normal disco:text-muted-foreground disco:select-none", |
| 84 |
defaultClassNames.weekday |
| 85 |
), |
| 86 |
week: cn("disco:mt-2 disco:flex disco:w-full", defaultClassNames.week), |
| 87 |
week_number_header: cn( |
| 88 |
"disco:w-(--cell-size) disco:select-none", |
| 89 |
defaultClassNames.week_number_header |
| 90 |
), |
| 91 |
week_number: cn( |
| 92 |
"disco:text-[0.8rem] disco:text-muted-foreground disco:select-none", |
| 93 |
defaultClassNames.week_number |
| 94 |
), |
| 95 |
day: cn( |
| 96 |
"disco:group/day disco:relative disco:aspect-square disco:h-full disco:w-full disco:rounded-(--cell-radius) disco:p-0 disco:text-center disco:select-none disco:[&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)", |
| 97 |
props.showWeekNumber |
| 98 |
? "disco:[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)" |
| 99 |
: "disco:[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)", |
| 100 |
defaultClassNames.day |
| 101 |
), |
| 102 |
range_start: cn( |
| 103 |
"disco:relative disco:isolate disco:z-0 disco:rounded-l-(--cell-radius) disco:bg-muted disco:after:absolute disco:after:inset-y-0 disco:after:right-0 disco:after:w-4 disco:after:bg-muted", |
| 104 |
defaultClassNames.range_start |
| 105 |
), |
| 106 |
range_middle: cn("disco:rounded-none", defaultClassNames.range_middle), |
| 107 |
range_end: cn( |
| 108 |
"disco:relative disco:isolate disco:z-0 disco:rounded-r-(--cell-radius) disco:bg-muted disco:after:absolute disco:after:inset-y-0 disco:after:left-0 disco:after:w-4 disco:after:bg-muted", |
| 109 |
defaultClassNames.range_end |
| 110 |
), |
| 111 |
today: cn( |
| 112 |
"disco:rounded-(--cell-radius) disco:bg-muted disco:text-foreground disco:data-[selected=true]:rounded-none", |
| 113 |
defaultClassNames.today |
| 114 |
), |
| 115 |
outside: cn( |
| 116 |
"disco:text-muted-foreground disco:aria-selected:text-muted-foreground", |
| 117 |
defaultClassNames.outside |
| 118 |
), |
| 119 |
disabled: cn("disco:text-muted-foreground disco:opacity-50", defaultClassNames.disabled), |
| 120 |
hidden: cn("disco:invisible", defaultClassNames.hidden), |
| 121 |
...classNames, |
| 122 |
}} |
| 123 |
components={{ |
| 124 |
Root: ({ className, rootRef, ...props }) => { |
| 125 |
return (<div data-slot="calendar" ref={rootRef} className={cn(className)} {...props} />); |
| 126 |
}, |
| 127 |
Chevron: ({ className, orientation, ...props }) => { |
| 128 |
if (orientation === "left") { |
| 129 |
return (<ChevronLeftIcon className={cn("disco:size-4", className)} {...props} />); |
| 130 |
} |
| 131 |
|
| 132 |
if (orientation === "right") { |
| 133 |
return (<ChevronRightIcon className={cn("disco:size-4", className)} {...props} />); |
| 134 |
} |
| 135 |
|
| 136 |
return (<ChevronDownIcon className={cn("disco:size-4", className)} {...props} />); |
| 137 |
}, |
| 138 |
DayButton: ({ ...props }) => ( |
| 139 |
<CalendarDayButton locale={locale} {...props} /> |
| 140 |
), |
| 141 |
WeekNumber: ({ children, ...props }) => { |
| 142 |
return ( |
| 143 |
<td {...props}> |
| 144 |
<div |
| 145 |
className="disco:flex disco:size-(--cell-size) disco:items-center disco:justify-center disco:text-center"> |
| 146 |
{children} |
| 147 |
</div> |
| 148 |
</td> |
| 149 |
); |
| 150 |
}, |
| 151 |
...components, |
| 152 |
}} |
| 153 |
{...props} /> |
| 154 |
); |
| 155 |
} |
| 156 |
|
| 157 |
function CalendarDayButton({ |
| 158 |
className, |
| 159 |
day, |
| 160 |
modifiers, |
| 161 |
locale, |
| 162 |
...props |
| 163 |
}) { |
| 164 |
const defaultClassNames = getDefaultClassNames() |
| 165 |
|
| 166 |
const ref = React.useRef(null) |
| 167 |
React.useEffect(() => { |
| 168 |
if (modifiers.focused) ref.current?.focus() |
| 169 |
}, [modifiers.focused]) |
| 170 |
|
| 171 |
return ( |
| 172 |
<Button |
| 173 |
variant="ghost" |
| 174 |
size="icon" |
| 175 |
data-day={day.date.toLocaleDateString(locale?.code)} |
| 176 |
data-selected-single={ |
| 177 |
modifiers.selected && |
| 178 |
!modifiers.range_start && |
| 179 |
!modifiers.range_end && |
| 180 |
!modifiers.range_middle |
| 181 |
} |
| 182 |
data-range-start={modifiers.range_start} |
| 183 |
data-range-end={modifiers.range_end} |
| 184 |
data-range-middle={modifiers.range_middle} |
| 185 |
className={cn( |
| 186 |
"disco:relative disco:isolate disco:z-10 disco:flex disco:aspect-square disco:size-auto disco:w-full disco:min-w-(--cell-size) disco:flex-col disco:gap-1 disco:border-0 disco:leading-none disco:font-normal disco:group-data-[focused=true]/day:relative disco:group-data-[focused=true]/day:z-10 disco:group-data-[focused=true]/day:border-ring disco:group-data-[focused=true]/day:ring-[3px] disco:group-data-[focused=true]/day:ring-ring/50 disco:data-[range-end=true]:rounded-(--cell-radius) disco:data-[range-end=true]:rounded-r-(--cell-radius) disco:data-[range-end=true]:bg-primary disco:data-[range-end=true]:text-primary-foreground disco:data-[range-middle=true]:rounded-none disco:data-[range-middle=true]:bg-muted disco:data-[range-middle=true]:text-foreground disco:data-[range-start=true]:rounded-(--cell-radius) disco:data-[range-start=true]:rounded-l-(--cell-radius) disco:data-[range-start=true]:bg-primary disco:data-[range-start=true]:text-primary-foreground disco:data-[selected-single=true]:bg-primary disco:data-[selected-single=true]:text-primary-foreground disco:dark:hover:text-foreground disco:[&>span]:text-xs disco:[&>span]:opacity-70", |
| 187 |
defaultClassNames.day, |
| 188 |
className |
| 189 |
)} |
| 190 |
{...props} /> |
| 191 |
); |
| 192 |
} |
| 193 |
|
| 194 |
export { Calendar, CalendarDayButton } |
| 195 |
|