theNorms commited on
Commit
8b2f6fa
·
verified ·
1 Parent(s): 5ee9fef

Upload project files

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