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