@vendure/dashboard 3.3.6-master-202507030835 → 3.3.6-master-202507031258

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/package.json +26 -39
  2. package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +7 -7
  3. package/src/app/routes/_authenticated/_customers/components/customer-address-card.tsx +3 -8
  4. package/src/lib/components/data-table/data-table-bulk-actions.tsx +9 -3
  5. package/src/lib/components/data-table/data-table.tsx +3 -2
  6. package/src/lib/components/shared/translatable-form-field.tsx +2 -1
  7. package/src/lib/components/ui/accordion.tsx +45 -50
  8. package/src/lib/components/ui/alert-dialog.tsx +93 -122
  9. package/src/lib/components/ui/alert.tsx +48 -54
  10. package/src/lib/components/ui/badge.tsx +29 -37
  11. package/src/lib/components/ui/breadcrumb.tsx +82 -89
  12. package/src/lib/components/ui/button.tsx +51 -52
  13. package/src/lib/components/ui/calendar.tsx +435 -196
  14. package/src/lib/components/ui/card.tsx +33 -78
  15. package/src/lib/components/ui/checkbox.tsx +23 -28
  16. package/src/lib/components/ui/collapsible.tsx +2 -0
  17. package/src/lib/components/ui/command.tsx +114 -159
  18. package/src/lib/components/ui/dialog.tsx +90 -115
  19. package/src/lib/components/ui/dropdown-menu.tsx +170 -207
  20. package/src/lib/components/ui/form.tsx +114 -138
  21. package/src/lib/components/ui/hover-card.tsx +26 -32
  22. package/src/lib/components/ui/input.tsx +15 -17
  23. package/src/lib/components/ui/label.tsx +16 -19
  24. package/src/lib/components/ui/pagination.tsx +87 -108
  25. package/src/lib/components/ui/popover.tsx +28 -36
  26. package/src/lib/components/ui/scroll-area.tsx +40 -48
  27. package/src/lib/components/ui/separator.tsx +20 -22
  28. package/src/lib/components/ui/sheet.tsx +91 -110
  29. package/src/lib/components/ui/sidebar.tsx +622 -652
  30. package/src/lib/components/ui/skeleton.tsx +10 -10
  31. package/src/lib/components/ui/sonner.tsx +11 -7
  32. package/src/lib/components/ui/switch.tsx +22 -27
  33. package/src/lib/components/ui/table.tsx +64 -96
  34. package/src/lib/components/ui/tabs.tsx +38 -56
  35. package/src/lib/components/ui/textarea.tsx +14 -14
  36. package/src/lib/components/ui/tooltip.tsx +37 -45
  37. package/src/lib/framework/page/detail-page.tsx +26 -20
  38. package/src/lib/graphql/graphql-env.d.ts +1 -1
  39. package/src/lib/components/ui/aspect-ratio.tsx +0 -9
  40. package/src/lib/components/ui/avatar.tsx +0 -53
  41. package/src/lib/components/ui/carousel.tsx +0 -241
  42. package/src/lib/components/ui/chart.tsx +0 -351
  43. package/src/lib/components/ui/context-menu.tsx +0 -252
  44. package/src/lib/components/ui/drawer.tsx +0 -133
  45. package/src/lib/components/ui/input-otp.tsx +0 -77
  46. package/src/lib/components/ui/menubar.tsx +0 -274
  47. package/src/lib/components/ui/navigation-menu.tsx +0 -168
  48. package/src/lib/components/ui/progress.tsx +0 -29
  49. package/src/lib/components/ui/radio-group.tsx +0 -45
  50. package/src/lib/components/ui/resizable.tsx +0 -54
  51. package/src/lib/components/ui/slider.tsx +0 -63
  52. package/src/lib/components/ui/toggle-group.tsx +0 -73
  53. package/src/lib/components/ui/toggle.tsx +0 -45
@@ -1,208 +1,447 @@
1
- import * as React from "react"
2
- import {
3
- ChevronDownIcon,
4
- ChevronLeftIcon,
5
- ChevronRightIcon,
6
- } from "lucide-react"
7
- import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
1
+ 'use client';
8
2
 
9
- import { cn } from "@/vdb/lib/utils"
10
- import { Button, buttonVariants } from "@/vdb/components/ui/button"
3
+ // A custom calendar that is compatible
4
+ // with react-day-picker v9 from https://date-picker.luca-felix.com/
11
5
 
6
+ import { Button, buttonVariants } from '@/vdb/components/ui/button.js';
7
+ import { cn } from '@/vdb/lib/utils.js';
8
+ import { differenceInCalendarDays } from 'date-fns';
9
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
10
+ import * as React from 'react';
11
+ import { DayPicker, labelNext, labelPrevious, useDayPicker, type DayPickerProps } from 'react-day-picker';
12
+
13
+ export type CalendarProps = DayPickerProps & {
14
+ /**
15
+ * In the year view, the number of years to display at once.
16
+ * @default 12
17
+ */
18
+ yearRange?: number;
19
+
20
+ /**
21
+ * Wether to show the year switcher in the caption.
22
+ * @default true
23
+ */
24
+ showYearSwitcher?: boolean;
25
+
26
+ monthsClassName?: string;
27
+ monthCaptionClassName?: string;
28
+ weekdaysClassName?: string;
29
+ weekdayClassName?: string;
30
+ monthClassName?: string;
31
+ captionClassName?: string;
32
+ captionLabelClassName?: string;
33
+ buttonNextClassName?: string;
34
+ buttonPreviousClassName?: string;
35
+ navClassName?: string;
36
+ monthGridClassName?: string;
37
+ weekClassName?: string;
38
+ dayClassName?: string;
39
+ dayButtonClassName?: string;
40
+ rangeStartClassName?: string;
41
+ rangeEndClassName?: string;
42
+ selectedClassName?: string;
43
+ todayClassName?: string;
44
+ outsideClassName?: string;
45
+ disabledClassName?: string;
46
+ rangeMiddleClassName?: string;
47
+ hiddenClassName?: string;
48
+ };
49
+
50
+ type NavView = 'days' | 'years';
51
+
52
+ /**
53
+ * A custom calendar component built on top of react-day-picker.
54
+ * @param props The props for the calendar.
55
+ * @default yearRange 12
56
+ * @returns
57
+ */
12
58
  function Calendar({
13
- className,
14
- classNames,
15
- showOutsideDays = true,
16
- captionLayout = "label",
17
- buttonVariant = "ghost",
18
- formatters,
19
- components,
20
- ...props
21
- }: React.ComponentProps<typeof DayPicker> & {
22
- buttonVariant?: React.ComponentProps<typeof Button>["variant"]
59
+ className,
60
+ showOutsideDays = true,
61
+ showYearSwitcher = true,
62
+ yearRange = 12,
63
+ numberOfMonths,
64
+ ...props
65
+ }: CalendarProps) {
66
+ const [navView, setNavView] = React.useState<NavView>('days');
67
+ const [displayYears, setDisplayYears] = React.useState<{
68
+ from: number;
69
+ to: number;
70
+ }>(
71
+ React.useMemo(() => {
72
+ const currentYear = new Date().getFullYear();
73
+ return {
74
+ from: currentYear - Math.floor(yearRange / 2 - 1),
75
+ to: currentYear + Math.ceil(yearRange / 2),
76
+ };
77
+ }, [yearRange]),
78
+ );
79
+
80
+ const { onNextClick, onPrevClick, startMonth, endMonth } = props;
81
+
82
+ const columnsDisplayed = navView === 'years' ? 1 : numberOfMonths;
83
+
84
+ const _monthsClassName = cn('relative flex', props.monthsClassName);
85
+ const _monthCaptionClassName = cn(
86
+ 'relative mx-10 flex h-7 items-center justify-center',
87
+ props.monthCaptionClassName,
88
+ );
89
+ const _weekdaysClassName = cn('flex flex-row', props.weekdaysClassName);
90
+ const _weekdayClassName = cn('w-8 text-sm font-normal text-muted-foreground', props.weekdayClassName);
91
+ const _monthClassName = cn('w-full', props.monthClassName);
92
+ const _captionClassName = cn('relative flex items-center justify-center pt-1', props.captionClassName);
93
+ const _captionLabelClassName = cn('truncate text-sm font-medium', props.captionLabelClassName);
94
+ const buttonNavClassName = buttonVariants({
95
+ variant: 'outline',
96
+ className: 'absolute h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
97
+ });
98
+ const _buttonNextClassName = cn(buttonNavClassName, 'right-0', props.buttonNextClassName);
99
+ const _buttonPreviousClassName = cn(buttonNavClassName, 'left-0', props.buttonPreviousClassName);
100
+ const _navClassName = cn('flex items-start', props.navClassName);
101
+ const _monthGridClassName = cn('mx-auto mt-4', props.monthGridClassName);
102
+ const _weekClassName = cn('mt-2 flex w-max items-start', props.weekClassName);
103
+ const _dayClassName = cn(
104
+ 'flex size-8 flex-1 items-center justify-center p-0 text-sm',
105
+ props.dayClassName,
106
+ );
107
+ const _dayButtonClassName = cn(
108
+ buttonVariants({ variant: 'ghost' }),
109
+ 'size-8 rounded-md p-0 font-normal transition-none aria-selected:opacity-100',
110
+ props.dayButtonClassName,
111
+ );
112
+ const buttonRangeClassName =
113
+ 'bg-accent [&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary [&>button]:hover:text-primary-foreground';
114
+ const _rangeStartClassName = cn(
115
+ buttonRangeClassName,
116
+ 'day-range-start rounded-s-md',
117
+ props.rangeStartClassName,
118
+ );
119
+ const _rangeEndClassName = cn(
120
+ buttonRangeClassName,
121
+ 'day-range-end rounded-e-md',
122
+ props.rangeEndClassName,
123
+ );
124
+ const _rangeMiddleClassName = cn(
125
+ 'bg-accent !text-foreground [&>button]:bg-transparent [&>button]:!text-foreground [&>button]:hover:bg-transparent [&>button]:hover:!text-foreground',
126
+ props.rangeMiddleClassName,
127
+ );
128
+ const _selectedClassName = cn(
129
+ '[&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary [&>button]:hover:text-primary-foreground',
130
+ props.selectedClassName,
131
+ );
132
+ const _todayClassName = cn(
133
+ '[&>button]:bg-accent [&>button]:text-accent-foreground',
134
+ props.todayClassName,
135
+ );
136
+ const _outsideClassName = cn(
137
+ 'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
138
+ props.outsideClassName,
139
+ );
140
+ const _disabledClassName = cn('text-muted-foreground opacity-50', props.disabledClassName);
141
+ const _hiddenClassName = cn('invisible flex-1', props.hiddenClassName);
142
+
143
+ return (
144
+ <DayPicker
145
+ showOutsideDays={showOutsideDays}
146
+ className={cn('p-3', className)}
147
+ style={{
148
+ width: 248.8 * (columnsDisplayed ?? 1) + 'px',
149
+ }}
150
+ classNames={{
151
+ months: _monthsClassName,
152
+ month_caption: _monthCaptionClassName,
153
+ weekdays: _weekdaysClassName,
154
+ weekday: _weekdayClassName,
155
+ month: _monthClassName,
156
+ caption: _captionClassName,
157
+ caption_label: _captionLabelClassName,
158
+ button_next: _buttonNextClassName,
159
+ button_previous: _buttonPreviousClassName,
160
+ nav: _navClassName,
161
+ month_grid: _monthGridClassName,
162
+ week: _weekClassName,
163
+ day: _dayClassName,
164
+ day_button: _dayButtonClassName,
165
+ range_start: _rangeStartClassName,
166
+ range_middle: _rangeMiddleClassName,
167
+ range_end: _rangeEndClassName,
168
+ selected: _selectedClassName,
169
+ today: _todayClassName,
170
+ outside: _outsideClassName,
171
+ disabled: _disabledClassName,
172
+ hidden: _hiddenClassName,
173
+ }}
174
+ components={{
175
+ Chevron: ({ orientation }) => {
176
+ const Icon = orientation === 'left' ? ChevronLeft : ChevronRight;
177
+ return <Icon className="h-4 w-4" />;
178
+ },
179
+ Nav: ({ className }) => (
180
+ <Nav
181
+ className={className}
182
+ displayYears={displayYears}
183
+ navView={navView}
184
+ setDisplayYears={setDisplayYears}
185
+ startMonth={startMonth}
186
+ endMonth={endMonth}
187
+ onPrevClick={onPrevClick}
188
+ />
189
+ ),
190
+ CaptionLabel: props => (
191
+ <CaptionLabel
192
+ showYearSwitcher={showYearSwitcher}
193
+ navView={navView}
194
+ setNavView={setNavView}
195
+ displayYears={displayYears}
196
+ {...props}
197
+ />
198
+ ),
199
+ MonthGrid: ({ className, children, ...props }) => (
200
+ <MonthGrid
201
+ children={children}
202
+ className={className}
203
+ displayYears={displayYears}
204
+ startMonth={startMonth}
205
+ endMonth={endMonth}
206
+ navView={navView}
207
+ setNavView={setNavView}
208
+ {...props}
209
+ />
210
+ ),
211
+ }}
212
+ numberOfMonths={columnsDisplayed}
213
+ {...props}
214
+ />
215
+ );
216
+ }
217
+
218
+ Calendar.displayName = 'Calendar';
219
+
220
+ function Nav({
221
+ className,
222
+ navView,
223
+ startMonth,
224
+ endMonth,
225
+ displayYears,
226
+ setDisplayYears,
227
+ onPrevClick,
228
+ onNextClick,
229
+ }: {
230
+ className?: string;
231
+ navView: NavView;
232
+ startMonth?: Date;
233
+ endMonth?: Date;
234
+ displayYears: { from: number; to: number };
235
+ setDisplayYears: React.Dispatch<React.SetStateAction<{ from: number; to: number }>>;
236
+ onPrevClick?: (date: Date) => void;
237
+ onNextClick?: (date: Date) => void;
23
238
  }) {
24
- const defaultClassNames = getDefaultClassNames()
25
-
26
- return (
27
- <DayPicker
28
- showOutsideDays={showOutsideDays}
29
- className={cn(
30
- "bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
31
- String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
32
- String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
33
- className
34
- )}
35
- captionLayout={captionLayout}
36
- formatters={{
37
- formatMonthDropdown: (date) =>
38
- date.toLocaleString("default", { month: "short" }),
39
- ...formatters,
40
- }}
41
- classNames={{
42
- root: cn("w-fit", defaultClassNames.root),
43
- months: cn(
44
- "flex gap-4 flex-col md:flex-row relative",
45
- defaultClassNames.months
46
- ),
47
- month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
48
- nav: cn(
49
- "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
50
- defaultClassNames.nav
51
- ),
52
- button_previous: cn(
53
- buttonVariants({ variant: buttonVariant }),
54
- "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
55
- defaultClassNames.button_previous
56
- ),
57
- button_next: cn(
58
- buttonVariants({ variant: buttonVariant }),
59
- "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
60
- defaultClassNames.button_next
61
- ),
62
- month_caption: cn(
63
- "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
64
- defaultClassNames.month_caption
65
- ),
66
- dropdowns: cn(
67
- "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
68
- defaultClassNames.dropdowns
69
- ),
70
- dropdown_root: cn(
71
- "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
72
- defaultClassNames.dropdown_root
73
- ),
74
- dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown),
75
- caption_label: cn(
76
- "select-none font-medium",
77
- captionLayout === "label"
78
- ? "text-sm"
79
- : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
80
- defaultClassNames.caption_label
81
- ),
82
- table: "w-full border-collapse",
83
- weekdays: cn("flex", defaultClassNames.weekdays),
84
- weekday: cn(
85
- "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
86
- defaultClassNames.weekday
87
- ),
88
- week: cn("flex w-full mt-2", defaultClassNames.week),
89
- week_number_header: cn(
90
- "select-none w-(--cell-size)",
91
- defaultClassNames.week_number_header
92
- ),
93
- week_number: cn(
94
- "text-[0.8rem] select-none text-muted-foreground",
95
- defaultClassNames.week_number
96
- ),
97
- day: cn(
98
- "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",
99
- defaultClassNames.day
100
- ),
101
- range_start: cn(
102
- "rounded-l-md bg-accent",
103
- defaultClassNames.range_start
104
- ),
105
- range_middle: cn("rounded-none", defaultClassNames.range_middle),
106
- range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
107
- today: cn(
108
- "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
109
- defaultClassNames.today
110
- ),
111
- outside: cn(
112
- "text-muted-foreground aria-selected:text-muted-foreground",
113
- defaultClassNames.outside
114
- ),
115
- disabled: cn(
116
- "text-muted-foreground opacity-50",
117
- defaultClassNames.disabled
118
- ),
119
- hidden: cn("invisible", defaultClassNames.hidden),
120
- ...classNames,
121
- }}
122
- components={{
123
- Root: ({ className, rootRef, ...props }) => {
124
- return (
125
- <div
126
- data-slot="calendar"
127
- ref={rootRef}
128
- className={cn(className)}
129
- {...props}
130
- />
131
- )
132
- },
133
- Chevron: ({ className, orientation, ...props }) => {
134
- if (orientation === "left") {
239
+ const { nextMonth, previousMonth, goToMonth } = useDayPicker();
240
+
241
+ const isPreviousDisabled = (() => {
242
+ if (navView === 'years') {
135
243
  return (
136
- <ChevronLeftIcon className={cn("size-4", className)} {...props} />
137
- )
138
- }
244
+ (startMonth &&
245
+ differenceInCalendarDays(new Date(displayYears.from - 1, 0, 1), startMonth) < 0) ||
246
+ (endMonth && differenceInCalendarDays(new Date(displayYears.from - 1, 0, 1), endMonth) > 0)
247
+ );
248
+ }
249
+ return !previousMonth;
250
+ })();
139
251
 
140
- if (orientation === "right") {
252
+ const isNextDisabled = (() => {
253
+ if (navView === 'years') {
141
254
  return (
142
- <ChevronRightIcon
143
- className={cn("size-4", className)}
255
+ (startMonth &&
256
+ differenceInCalendarDays(new Date(displayYears.to + 1, 0, 1), startMonth) < 0) ||
257
+ (endMonth && differenceInCalendarDays(new Date(displayYears.to + 1, 0, 1), endMonth) > 0)
258
+ );
259
+ }
260
+ return !nextMonth;
261
+ })();
262
+
263
+ const handlePreviousClick = React.useCallback(() => {
264
+ if (!previousMonth) return;
265
+ if (navView === 'years') {
266
+ setDisplayYears(prev => ({
267
+ from: prev.from - (prev.to - prev.from + 1),
268
+ to: prev.to - (prev.to - prev.from + 1),
269
+ }));
270
+ onPrevClick?.(new Date(displayYears.from - (displayYears.to - displayYears.from), 0, 1));
271
+ return;
272
+ }
273
+ goToMonth(previousMonth);
274
+ onPrevClick?.(previousMonth);
275
+ }, [previousMonth, goToMonth]);
276
+
277
+ const handleNextClick = React.useCallback(() => {
278
+ if (!nextMonth) return;
279
+ if (navView === 'years') {
280
+ setDisplayYears(prev => ({
281
+ from: prev.from + (prev.to - prev.from + 1),
282
+ to: prev.to + (prev.to - prev.from + 1),
283
+ }));
284
+ onNextClick?.(new Date(displayYears.from + (displayYears.to - displayYears.from), 0, 1));
285
+ return;
286
+ }
287
+ goToMonth(nextMonth);
288
+ onNextClick?.(nextMonth);
289
+ }, [goToMonth, nextMonth]);
290
+ return (
291
+ <nav className={cn('flex items-center', className)}>
292
+ <Button
293
+ variant="outline"
294
+ className="absolute left-0 h-7 w-7 bg-transparent p-0 opacity-80 hover:opacity-100"
295
+ type="button"
296
+ tabIndex={isPreviousDisabled ? undefined : -1}
297
+ disabled={isPreviousDisabled}
298
+ aria-label={
299
+ navView === 'years'
300
+ ? `Go to the previous ${displayYears.to - displayYears.from + 1} years`
301
+ : labelPrevious(previousMonth)
302
+ }
303
+ onClick={handlePreviousClick}
304
+ >
305
+ <ChevronLeft className="h-4 w-4" />
306
+ </Button>
307
+
308
+ <Button
309
+ variant="outline"
310
+ className="absolute right-0 h-7 w-7 bg-transparent p-0 opacity-80 hover:opacity-100"
311
+ type="button"
312
+ tabIndex={isNextDisabled ? undefined : -1}
313
+ disabled={isNextDisabled}
314
+ aria-label={
315
+ navView === 'years'
316
+ ? `Go to the next ${displayYears.to - displayYears.from + 1} years`
317
+ : labelNext(nextMonth)
318
+ }
319
+ onClick={handleNextClick}
320
+ >
321
+ <ChevronRight className="h-4 w-4" />
322
+ </Button>
323
+ </nav>
324
+ );
325
+ }
326
+
327
+ function CaptionLabel({
328
+ children,
329
+ showYearSwitcher,
330
+ navView,
331
+ setNavView,
332
+ displayYears,
333
+ ...props
334
+ }: {
335
+ showYearSwitcher?: boolean;
336
+ navView: NavView;
337
+ setNavView: React.Dispatch<React.SetStateAction<NavView>>;
338
+ displayYears: { from: number; to: number };
339
+ } & React.HTMLAttributes<HTMLSpanElement>) {
340
+ if (!showYearSwitcher) return <span {...props}>{children}</span>;
341
+ return (
342
+ <Button
343
+ className="h-7 w-full truncate text-sm font-medium"
344
+ variant="ghost"
345
+ size="sm"
346
+ onClick={() => setNavView(prev => (prev === 'days' ? 'years' : 'days'))}
347
+ >
348
+ {navView === 'days' ? children : displayYears.from + ' - ' + displayYears.to}
349
+ </Button>
350
+ );
351
+ }
352
+
353
+ function MonthGrid({
354
+ className,
355
+ children,
356
+ displayYears,
357
+ startMonth,
358
+ endMonth,
359
+ navView,
360
+ setNavView,
361
+ ...props
362
+ }: {
363
+ className?: string;
364
+ children: React.ReactNode;
365
+ displayYears: { from: number; to: number };
366
+ startMonth?: Date;
367
+ endMonth?: Date;
368
+ navView: NavView;
369
+ setNavView: React.Dispatch<React.SetStateAction<NavView>>;
370
+ } & React.TableHTMLAttributes<HTMLTableElement>) {
371
+ if (navView === 'years') {
372
+ return (
373
+ <YearGrid
374
+ displayYears={displayYears}
375
+ startMonth={startMonth}
376
+ endMonth={endMonth}
377
+ setNavView={setNavView}
378
+ navView={navView}
379
+ className={className}
144
380
  {...props}
145
- />
146
- )
147
- }
148
-
149
- return (
150
- <ChevronDownIcon className={cn("size-4", className)} {...props} />
151
- )
152
- },
153
- DayButton: CalendarDayButton,
154
- WeekNumber: ({ children, ...props }) => {
155
- return (
156
- <td {...props}>
157
- <div className="flex size-(--cell-size) items-center justify-center text-center">
158
- {children}
159
- </div>
160
- </td>
161
- )
162
- },
163
- ...components,
164
- }}
165
- {...props}
166
- />
167
- )
381
+ />
382
+ );
383
+ }
384
+ return (
385
+ <table className={className} {...props}>
386
+ {children}
387
+ </table>
388
+ );
168
389
  }
169
390
 
170
- function CalendarDayButton({
171
- className,
172
- day,
173
- modifiers,
174
- ...props
175
- }: React.ComponentProps<typeof DayButton>) {
176
- const defaultClassNames = getDefaultClassNames()
177
-
178
- const ref = React.useRef<HTMLButtonElement>(null)
179
- React.useEffect(() => {
180
- if (modifiers.focused) ref.current?.focus()
181
- }, [modifiers.focused])
182
-
183
- return (
184
- <Button
185
- ref={ref}
186
- variant="ghost"
187
- size="icon"
188
- data-day={day.date.toLocaleDateString()}
189
- data-selected-single={
190
- modifiers.selected &&
191
- !modifiers.range_start &&
192
- !modifiers.range_end &&
193
- !modifiers.range_middle
194
- }
195
- data-range-start={modifiers.range_start}
196
- data-range-end={modifiers.range_end}
197
- data-range-middle={modifiers.range_middle}
198
- className={cn(
199
- "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",
200
- defaultClassNames.day,
201
- className
202
- )}
203
- {...props}
204
- />
205
- )
391
+ function YearGrid({
392
+ className,
393
+ displayYears,
394
+ startMonth,
395
+ endMonth,
396
+ setNavView,
397
+ navView,
398
+ ...props
399
+ }: {
400
+ className?: string;
401
+ displayYears: { from: number; to: number };
402
+ startMonth?: Date;
403
+ endMonth?: Date;
404
+ setNavView: React.Dispatch<React.SetStateAction<NavView>>;
405
+ navView: NavView;
406
+ } & React.HTMLAttributes<HTMLDivElement>) {
407
+ const { goToMonth, selected } = useDayPicker();
408
+
409
+ return (
410
+ <div className={cn('grid grid-cols-4 gap-y-2', className)} {...props}>
411
+ {Array.from({ length: displayYears.to - displayYears.from + 1 }, (_, i) => {
412
+ const isBefore =
413
+ differenceInCalendarDays(new Date(displayYears.from + i, 11, 31), startMonth!) < 0;
414
+
415
+ const isAfter =
416
+ differenceInCalendarDays(new Date(displayYears.from + i, 0, 0), endMonth!) > 0;
417
+
418
+ const isDisabled = isBefore || isAfter;
419
+ return (
420
+ <Button
421
+ key={i}
422
+ className={cn(
423
+ 'h-7 w-full text-sm font-normal text-foreground',
424
+ displayYears.from + i === new Date().getFullYear() &&
425
+ 'bg-accent font-medium text-accent-foreground',
426
+ )}
427
+ variant="ghost"
428
+ onClick={() => {
429
+ setNavView('days');
430
+ goToMonth(
431
+ new Date(
432
+ displayYears.from + i,
433
+ (selected as Date | undefined)?.getMonth() ?? 0,
434
+ ),
435
+ );
436
+ }}
437
+ disabled={navView === 'years' ? isDisabled : undefined}
438
+ >
439
+ {displayYears.from + i}
440
+ </Button>
441
+ );
442
+ })}
443
+ </div>
444
+ );
206
445
  }
207
446
 
208
- export { Calendar, CalendarDayButton }
447
+ export { Calendar };