@vendure/dashboard 3.2.2 → 3.2.4

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 (92) hide show
  1. package/dist/plugin/utils/ast-utils.d.ts +10 -0
  2. package/dist/plugin/utils/ast-utils.js +96 -0
  3. package/dist/plugin/utils/ast-utils.spec.d.ts +1 -0
  4. package/dist/plugin/utils/ast-utils.spec.js +120 -0
  5. package/dist/plugin/{config-loader.d.ts → utils/config-loader.d.ts} +22 -8
  6. package/dist/plugin/utils/config-loader.js +325 -0
  7. package/dist/plugin/{schema-generator.d.ts → utils/schema-generator.d.ts} +5 -0
  8. package/dist/plugin/{schema-generator.js → utils/schema-generator.js} +6 -0
  9. package/dist/plugin/{ui-config.js → utils/ui-config.js} +2 -2
  10. package/dist/plugin/vite-plugin-admin-api-schema.js +2 -2
  11. package/dist/plugin/vite-plugin-config-loader.d.ts +2 -3
  12. package/dist/plugin/vite-plugin-config-loader.js +18 -9
  13. package/dist/plugin/vite-plugin-dashboard-metadata.js +12 -14
  14. package/dist/plugin/vite-plugin-gql-tada.js +2 -2
  15. package/dist/plugin/vite-plugin-ui-config.js +3 -2
  16. package/package.json +8 -6
  17. package/src/app/app-providers.tsx +8 -8
  18. package/src/app/main.tsx +1 -1
  19. package/src/app/routes/_authenticated/_assets/assets.graphql.ts +26 -0
  20. package/src/app/routes/_authenticated/_assets/assets.tsx +2 -2
  21. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +156 -0
  22. package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +104 -0
  23. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +228 -0
  24. package/src/app/routes/_authenticated/_orders/components/money-gross-net.tsx +18 -0
  25. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +2 -1
  26. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +38 -0
  27. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +53 -0
  28. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +8 -49
  29. package/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx +65 -0
  30. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +187 -1
  31. package/src/app/routes/_authenticated/_orders/orders.tsx +39 -18
  32. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +31 -9
  33. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +418 -0
  34. package/src/app/routes/_authenticated/_products/products.tsx +1 -1
  35. package/src/app/routes/_authenticated.tsx +12 -1
  36. package/src/lib/components/data-table/add-filter-menu.tsx +61 -0
  37. package/src/lib/components/data-table/data-table-column-header.tsx +0 -13
  38. package/src/lib/components/data-table/data-table-filter-badge.tsx +75 -0
  39. package/src/lib/components/data-table/data-table-filter-dialog.tsx +27 -28
  40. package/src/lib/components/data-table/data-table-types.ts +1 -0
  41. package/src/lib/components/data-table/data-table-view-options.tsx +72 -23
  42. package/src/lib/components/data-table/data-table.tsx +23 -24
  43. package/src/lib/components/data-table/filters/data-table-boolean-filter.tsx +57 -0
  44. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +93 -0
  45. package/src/lib/components/data-table/filters/data-table-id-filter.tsx +58 -0
  46. package/src/lib/components/data-table/filters/data-table-number-filter.tsx +119 -0
  47. package/src/lib/components/data-table/filters/data-table-string-filter.tsx +62 -0
  48. package/src/lib/components/data-table/human-readable-operator.tsx +65 -0
  49. package/src/lib/components/layout/nav-user.tsx +4 -4
  50. package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +93 -0
  51. package/src/lib/components/shared/{asset-gallery.tsx → asset/asset-gallery.tsx} +51 -20
  52. package/src/lib/components/shared/{asset-picker-dialog.tsx → asset/asset-picker-dialog.tsx} +1 -1
  53. package/src/lib/components/shared/{asset-preview-dialog.tsx → asset/asset-preview-dialog.tsx} +1 -7
  54. package/src/lib/components/shared/asset/asset-preview-selector.tsx +34 -0
  55. package/src/lib/components/shared/asset/asset-preview.tsx +128 -0
  56. package/src/lib/components/shared/asset/asset-properties.tsx +46 -0
  57. package/src/lib/components/shared/{focal-point-control.tsx → asset/focal-point-control.tsx} +1 -1
  58. package/src/lib/components/shared/custom-fields-form.tsx +4 -3
  59. package/src/lib/components/shared/customer-selector.tsx +13 -14
  60. package/src/lib/components/shared/detail-page-button.tsx +2 -2
  61. package/src/lib/components/shared/entity-assets.tsx +3 -3
  62. package/src/lib/components/shared/navigation-confirmation.tsx +39 -0
  63. package/src/lib/components/shared/paginated-list-data-table.tsx +9 -1
  64. package/src/lib/components/shared/product-variant-selector.tsx +111 -0
  65. package/src/lib/components/shared/vendure-image.tsx +1 -1
  66. package/src/lib/components/ui/calendar.tsx +508 -63
  67. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +113 -3
  68. package/src/lib/framework/document-introspection/get-document-structure.ts +70 -11
  69. package/src/lib/framework/form-engine/use-generated-form.tsx +8 -7
  70. package/src/lib/framework/layout-engine/page-layout.tsx +4 -0
  71. package/src/lib/framework/page/list-page.tsx +23 -4
  72. package/src/lib/framework/page/use-detail-page.ts +1 -0
  73. package/src/lib/graphql/fragments.tsx +8 -0
  74. package/src/lib/index.ts +5 -5
  75. package/src/lib/providers/auth.tsx +12 -9
  76. package/src/lib/providers/channel-provider.tsx +1 -0
  77. package/src/lib/providers/server-config.tsx +7 -1
  78. package/src/lib/providers/user-settings.tsx +24 -0
  79. package/vite/utils/ast-utils.spec.ts +128 -0
  80. package/vite/utils/ast-utils.ts +119 -0
  81. package/vite/utils/config-loader.ts +410 -0
  82. package/vite/{schema-generator.ts → utils/schema-generator.ts} +7 -1
  83. package/vite/{ui-config.ts → utils/ui-config.ts} +2 -2
  84. package/vite/vite-plugin-admin-api-schema.ts +2 -2
  85. package/vite/vite-plugin-config-loader.ts +25 -13
  86. package/vite/vite-plugin-dashboard-metadata.ts +19 -15
  87. package/vite/vite-plugin-gql-tada.ts +2 -2
  88. package/vite/vite-plugin-ui-config.ts +3 -2
  89. package/dist/plugin/config-loader.js +0 -141
  90. package/src/lib/components/shared/asset-preview.tsx +0 -345
  91. package/vite/config-loader.ts +0 -181
  92. /package/dist/plugin/{ui-config.d.ts → utils/ui-config.d.ts} +0 -0
@@ -1,69 +1,514 @@
1
- import * as React from 'react';
2
- import { ChevronLeft, ChevronRight } from 'lucide-react';
3
- import { DayPicker } from 'react-day-picker';
1
+ "use client"
4
2
 
5
- import { cn } from '@/lib/utils.js';
6
- import { buttonVariants } from '@/components/ui/button.js';
3
+ // A custom calendar that is compatible
4
+ // with react-day-picker v9 from https://date-picker.luca-felix.com/
7
5
 
6
+ import { Button, buttonVariants } from "@/components/ui/button.js"
7
+ import { cn } from "@/lib/utils.js"
8
+ import { differenceInCalendarDays } from "date-fns"
9
+ import { ChevronLeft, ChevronRight } from "lucide-react"
10
+ import * as React from "react"
11
+ import {
12
+ DayPicker,
13
+ labelNext,
14
+ labelPrevious,
15
+ useDayPicker,
16
+ type DayPickerProps,
17
+ } from "react-day-picker"
18
+
19
+ export type CalendarProps = DayPickerProps & {
20
+ /**
21
+ * In the year view, the number of years to display at once.
22
+ * @default 12
23
+ */
24
+ yearRange?: number
25
+
26
+ /**
27
+ * Wether to show the year switcher in the caption.
28
+ * @default true
29
+ */
30
+ showYearSwitcher?: boolean
31
+
32
+ monthsClassName?: string
33
+ monthCaptionClassName?: string
34
+ weekdaysClassName?: string
35
+ weekdayClassName?: string
36
+ monthClassName?: string
37
+ captionClassName?: string
38
+ captionLabelClassName?: string
39
+ buttonNextClassName?: string
40
+ buttonPreviousClassName?: string
41
+ navClassName?: string
42
+ monthGridClassName?: string
43
+ weekClassName?: string
44
+ dayClassName?: string
45
+ dayButtonClassName?: string
46
+ rangeStartClassName?: string
47
+ rangeEndClassName?: string
48
+ selectedClassName?: string
49
+ todayClassName?: string
50
+ outsideClassName?: string
51
+ disabledClassName?: string
52
+ rangeMiddleClassName?: string
53
+ hiddenClassName?: string
54
+ }
55
+
56
+ type NavView = "days" | "years"
57
+
58
+ /**
59
+ * A custom calendar component built on top of react-day-picker.
60
+ * @param props The props for the calendar.
61
+ * @default yearRange 12
62
+ * @returns
63
+ */
8
64
  function Calendar({
9
- className,
10
- classNames,
11
- showOutsideDays = true,
12
- ...props
13
- }: React.ComponentProps<typeof DayPicker>) {
14
- return (
15
- <DayPicker
16
- showOutsideDays={showOutsideDays}
17
- className={cn('p-3', className)}
18
- classNames={{
19
- months: 'flex flex-col sm:flex-row gap-2',
20
- month: 'flex flex-col gap-4',
21
- caption: 'flex justify-center pt-1 relative items-center w-full',
22
- caption_label: 'text-sm font-medium',
23
- nav: 'flex items-center gap-1',
24
- nav_button: cn(
25
- buttonVariants({ variant: 'outline' }),
26
- 'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
27
- ),
28
- nav_button_previous: 'absolute left-1',
29
- nav_button_next: 'absolute right-1',
30
- table: 'w-full border-collapse space-x-1',
31
- head_row: 'flex',
32
- head_cell: 'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]',
33
- row: 'flex w-full mt-2',
34
- cell: cn(
35
- 'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md',
36
- props.mode === 'range'
37
- ? '[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md'
38
- : '[&:has([aria-selected])]:rounded-md',
39
- ),
40
- day: cn(
41
- buttonVariants({ variant: 'ghost' }),
42
- 'size-8 p-0 font-normal aria-selected:opacity-100',
43
- ),
44
- day_range_start:
45
- 'day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground',
46
- day_range_end: 'day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground',
47
- day_selected:
48
- 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
49
- day_today: 'bg-accent text-accent-foreground',
50
- day_outside: 'day-outside text-muted-foreground aria-selected:text-muted-foreground',
51
- day_disabled: 'text-muted-foreground opacity-50',
52
- day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
53
- day_hidden: 'invisible',
54
- ...classNames,
55
- }}
56
- components={{
57
- IconLeft: ({ className, ...props }) => (
58
- <ChevronLeft className={cn('size-4', className)} {...props} />
59
- ),
60
- IconRight: ({ className, ...props }) => (
61
- <ChevronRight className={cn('size-4', className)} {...props} />
62
- ),
63
- }}
65
+ className,
66
+ showOutsideDays = true,
67
+ showYearSwitcher = true,
68
+ yearRange = 12,
69
+ numberOfMonths,
70
+ ...props
71
+ }: CalendarProps) {
72
+ const [navView, setNavView] = React.useState<NavView>("days")
73
+ const [displayYears, setDisplayYears] = React.useState<{
74
+ from: number
75
+ to: number
76
+ }>(
77
+ React.useMemo(() => {
78
+ const currentYear = new Date().getFullYear()
79
+ return {
80
+ from: currentYear - Math.floor(yearRange / 2 - 1),
81
+ to: currentYear + Math.ceil(yearRange / 2),
82
+ }
83
+ }, [yearRange])
84
+ )
85
+
86
+ const { onNextClick, onPrevClick, startMonth, endMonth } = props
87
+
88
+ const columnsDisplayed = navView === "years" ? 1 : numberOfMonths
89
+
90
+ const _monthsClassName = cn("relative flex", props.monthsClassName)
91
+ const _monthCaptionClassName = cn(
92
+ "relative mx-10 flex h-7 items-center justify-center",
93
+ props.monthCaptionClassName
94
+ )
95
+ const _weekdaysClassName = cn("flex flex-row", props.weekdaysClassName)
96
+ const _weekdayClassName = cn(
97
+ "w-8 text-sm font-normal text-muted-foreground",
98
+ props.weekdayClassName
99
+ )
100
+ const _monthClassName = cn("w-full", props.monthClassName)
101
+ const _captionClassName = cn(
102
+ "relative flex items-center justify-center pt-1",
103
+ props.captionClassName
104
+ )
105
+ const _captionLabelClassName = cn(
106
+ "truncate text-sm font-medium",
107
+ props.captionLabelClassName
108
+ )
109
+ const buttonNavClassName = buttonVariants({
110
+ variant: "outline",
111
+ className:
112
+ "absolute h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
113
+ })
114
+ const _buttonNextClassName = cn(
115
+ buttonNavClassName,
116
+ "right-0",
117
+ props.buttonNextClassName
118
+ )
119
+ const _buttonPreviousClassName = cn(
120
+ buttonNavClassName,
121
+ "left-0",
122
+ props.buttonPreviousClassName
123
+ )
124
+ const _navClassName = cn("flex items-start", props.navClassName)
125
+ const _monthGridClassName = cn("mx-auto mt-4", props.monthGridClassName)
126
+ const _weekClassName = cn("mt-2 flex w-max items-start", props.weekClassName)
127
+ const _dayClassName = cn(
128
+ "flex size-8 flex-1 items-center justify-center p-0 text-sm",
129
+ props.dayClassName
130
+ )
131
+ const _dayButtonClassName = cn(
132
+ buttonVariants({ variant: "ghost" }),
133
+ "size-8 rounded-md p-0 font-normal transition-none aria-selected:opacity-100",
134
+ props.dayButtonClassName
135
+ )
136
+ const buttonRangeClassName =
137
+ "bg-accent [&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary [&>button]:hover:text-primary-foreground"
138
+ const _rangeStartClassName = cn(
139
+ buttonRangeClassName,
140
+ "day-range-start rounded-s-md",
141
+ props.rangeStartClassName
142
+ )
143
+ const _rangeEndClassName = cn(
144
+ buttonRangeClassName,
145
+ "day-range-end rounded-e-md",
146
+ props.rangeEndClassName
147
+ )
148
+ const _rangeMiddleClassName = cn(
149
+ "bg-accent !text-foreground [&>button]:bg-transparent [&>button]:!text-foreground [&>button]:hover:bg-transparent [&>button]:hover:!text-foreground",
150
+ props.rangeMiddleClassName
151
+ )
152
+ const _selectedClassName = cn(
153
+ "[&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary [&>button]:hover:text-primary-foreground",
154
+ props.selectedClassName
155
+ )
156
+ const _todayClassName = cn(
157
+ "[&>button]:bg-accent [&>button]:text-accent-foreground",
158
+ props.todayClassName
159
+ )
160
+ const _outsideClassName = cn(
161
+ "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
162
+ props.outsideClassName
163
+ )
164
+ const _disabledClassName = cn(
165
+ "text-muted-foreground opacity-50",
166
+ props.disabledClassName
167
+ )
168
+ const _hiddenClassName = cn("invisible flex-1", props.hiddenClassName)
169
+
170
+ return (
171
+ <DayPicker
172
+ showOutsideDays={showOutsideDays}
173
+ className={cn("p-3", className)}
174
+ style={{
175
+ width: 248.8 * (columnsDisplayed ?? 1) + "px",
176
+ }}
177
+ classNames={{
178
+ months: _monthsClassName,
179
+ month_caption: _monthCaptionClassName,
180
+ weekdays: _weekdaysClassName,
181
+ weekday: _weekdayClassName,
182
+ month: _monthClassName,
183
+ caption: _captionClassName,
184
+ caption_label: _captionLabelClassName,
185
+ button_next: _buttonNextClassName,
186
+ button_previous: _buttonPreviousClassName,
187
+ nav: _navClassName,
188
+ month_grid: _monthGridClassName,
189
+ week: _weekClassName,
190
+ day: _dayClassName,
191
+ day_button: _dayButtonClassName,
192
+ range_start: _rangeStartClassName,
193
+ range_middle: _rangeMiddleClassName,
194
+ range_end: _rangeEndClassName,
195
+ selected: _selectedClassName,
196
+ today: _todayClassName,
197
+ outside: _outsideClassName,
198
+ disabled: _disabledClassName,
199
+ hidden: _hiddenClassName,
200
+ }}
201
+ components={{
202
+ Chevron: ({ orientation }) => {
203
+ const Icon = orientation === "left" ? ChevronLeft : ChevronRight
204
+ return <Icon className="h-4 w-4" />
205
+ },
206
+ Nav: ({ className }) => (
207
+ <Nav
208
+ className={className}
209
+ displayYears={displayYears}
210
+ navView={navView}
211
+ setDisplayYears={setDisplayYears}
212
+ startMonth={startMonth}
213
+ endMonth={endMonth}
214
+ onPrevClick={onPrevClick}
215
+ />
216
+ ),
217
+ CaptionLabel: (props) => (
218
+ <CaptionLabel
219
+ showYearSwitcher={showYearSwitcher}
220
+ navView={navView}
221
+ setNavView={setNavView}
222
+ displayYears={displayYears}
223
+ {...props}
224
+ />
225
+ ),
226
+ MonthGrid: ({ className, children, ...props }) => (
227
+ <MonthGrid
228
+ children={children}
229
+ className={className}
230
+ displayYears={displayYears}
231
+ startMonth={startMonth}
232
+ endMonth={endMonth}
233
+ navView={navView}
234
+ setNavView={setNavView}
64
235
  {...props}
65
- />
66
- );
236
+ />
237
+ ),
238
+ }}
239
+ numberOfMonths={columnsDisplayed}
240
+ {...props}
241
+ />
242
+ )
243
+ }
244
+ Calendar.displayName = "Calendar"
245
+
246
+ function Nav({
247
+ className,
248
+ navView,
249
+ startMonth,
250
+ endMonth,
251
+ displayYears,
252
+ setDisplayYears,
253
+ onPrevClick,
254
+ onNextClick,
255
+ }: {
256
+ className?: string
257
+ navView: NavView
258
+ startMonth?: Date
259
+ endMonth?: Date
260
+ displayYears: { from: number; to: number }
261
+ setDisplayYears: React.Dispatch<
262
+ React.SetStateAction<{ from: number; to: number }>
263
+ >
264
+ onPrevClick?: (date: Date) => void
265
+ onNextClick?: (date: Date) => void
266
+ }) {
267
+ const { nextMonth, previousMonth, goToMonth } = useDayPicker()
268
+
269
+ const isPreviousDisabled = (() => {
270
+ if (navView === "years") {
271
+ return (
272
+ (startMonth &&
273
+ differenceInCalendarDays(
274
+ new Date(displayYears.from - 1, 0, 1),
275
+ startMonth
276
+ ) < 0) ||
277
+ (endMonth &&
278
+ differenceInCalendarDays(
279
+ new Date(displayYears.from - 1, 0, 1),
280
+ endMonth
281
+ ) > 0)
282
+ )
283
+ }
284
+ return !previousMonth
285
+ })()
286
+
287
+ const isNextDisabled = (() => {
288
+ if (navView === "years") {
289
+ return (
290
+ (startMonth &&
291
+ differenceInCalendarDays(
292
+ new Date(displayYears.to + 1, 0, 1),
293
+ startMonth
294
+ ) < 0) ||
295
+ (endMonth &&
296
+ differenceInCalendarDays(
297
+ new Date(displayYears.to + 1, 0, 1),
298
+ endMonth
299
+ ) > 0)
300
+ )
301
+ }
302
+ return !nextMonth
303
+ })()
304
+
305
+ const handlePreviousClick = React.useCallback(() => {
306
+ if (!previousMonth) return
307
+ if (navView === "years") {
308
+ setDisplayYears((prev) => ({
309
+ from: prev.from - (prev.to - prev.from + 1),
310
+ to: prev.to - (prev.to - prev.from + 1),
311
+ }))
312
+ onPrevClick?.(
313
+ new Date(
314
+ displayYears.from - (displayYears.to - displayYears.from),
315
+ 0,
316
+ 1
317
+ )
318
+ )
319
+ return
320
+ }
321
+ goToMonth(previousMonth)
322
+ onPrevClick?.(previousMonth)
323
+ }, [previousMonth, goToMonth])
324
+
325
+ const handleNextClick = React.useCallback(() => {
326
+ if (!nextMonth) return
327
+ if (navView === "years") {
328
+ setDisplayYears((prev) => ({
329
+ from: prev.from + (prev.to - prev.from + 1),
330
+ to: prev.to + (prev.to - prev.from + 1),
331
+ }))
332
+ onNextClick?.(
333
+ new Date(
334
+ displayYears.from + (displayYears.to - displayYears.from),
335
+ 0,
336
+ 1
337
+ )
338
+ )
339
+ return
340
+ }
341
+ goToMonth(nextMonth)
342
+ onNextClick?.(nextMonth)
343
+ }, [goToMonth, nextMonth])
344
+ return (
345
+ <nav className={cn("flex items-center", className)}>
346
+ <Button
347
+ variant="outline"
348
+ className="absolute left-0 h-7 w-7 bg-transparent p-0 opacity-80 hover:opacity-100"
349
+ type="button"
350
+ tabIndex={isPreviousDisabled ? undefined : -1}
351
+ disabled={isPreviousDisabled}
352
+ aria-label={
353
+ navView === "years"
354
+ ? `Go to the previous ${
355
+ displayYears.to - displayYears.from + 1
356
+ } years`
357
+ : labelPrevious(previousMonth)
358
+ }
359
+ onClick={handlePreviousClick}
360
+ >
361
+ <ChevronLeft className="h-4 w-4" />
362
+ </Button>
363
+
364
+ <Button
365
+ variant="outline"
366
+ className="absolute right-0 h-7 w-7 bg-transparent p-0 opacity-80 hover:opacity-100"
367
+ type="button"
368
+ tabIndex={isNextDisabled ? undefined : -1}
369
+ disabled={isNextDisabled}
370
+ aria-label={
371
+ navView === "years"
372
+ ? `Go to the next ${displayYears.to - displayYears.from + 1} years`
373
+ : labelNext(nextMonth)
374
+ }
375
+ onClick={handleNextClick}
376
+ >
377
+ <ChevronRight className="h-4 w-4" />
378
+ </Button>
379
+ </nav>
380
+ )
381
+ }
382
+
383
+ function CaptionLabel({
384
+ children,
385
+ showYearSwitcher,
386
+ navView,
387
+ setNavView,
388
+ displayYears,
389
+ ...props
390
+ }: {
391
+ showYearSwitcher?: boolean
392
+ navView: NavView
393
+ setNavView: React.Dispatch<React.SetStateAction<NavView>>
394
+ displayYears: { from: number; to: number }
395
+ } & React.HTMLAttributes<HTMLSpanElement>) {
396
+ if (!showYearSwitcher) return <span {...props}>{children}</span>
397
+ return (
398
+ <Button
399
+ className="h-7 w-full truncate text-sm font-medium"
400
+ variant="ghost"
401
+ size="sm"
402
+ onClick={() => setNavView((prev) => (prev === "days" ? "years" : "days"))}
403
+ >
404
+ {navView === "days"
405
+ ? children
406
+ : displayYears.from + " - " + displayYears.to}
407
+ </Button>
408
+ )
409
+ }
410
+
411
+ function MonthGrid({
412
+ className,
413
+ children,
414
+ displayYears,
415
+ startMonth,
416
+ endMonth,
417
+ navView,
418
+ setNavView,
419
+ ...props
420
+ }: {
421
+ className?: string
422
+ children: React.ReactNode
423
+ displayYears: { from: number; to: number }
424
+ startMonth?: Date
425
+ endMonth?: Date
426
+ navView: NavView
427
+ setNavView: React.Dispatch<React.SetStateAction<NavView>>
428
+ } & React.TableHTMLAttributes<HTMLTableElement>) {
429
+ if (navView === "years") {
430
+ return (
431
+ <YearGrid
432
+ displayYears={displayYears}
433
+ startMonth={startMonth}
434
+ endMonth={endMonth}
435
+ setNavView={setNavView}
436
+ navView={navView}
437
+ className={className}
438
+ {...props}
439
+ />
440
+ )
441
+ }
442
+ return (
443
+ <table className={className} {...props}>
444
+ {children}
445
+ </table>
446
+ )
447
+ }
448
+
449
+ function YearGrid({
450
+ className,
451
+ displayYears,
452
+ startMonth,
453
+ endMonth,
454
+ setNavView,
455
+ navView,
456
+ ...props
457
+ }: {
458
+ className?: string
459
+ displayYears: { from: number; to: number }
460
+ startMonth?: Date
461
+ endMonth?: Date
462
+ setNavView: React.Dispatch<React.SetStateAction<NavView>>
463
+ navView: NavView
464
+ } & React.HTMLAttributes<HTMLDivElement>) {
465
+ const { goToMonth, selected } = useDayPicker()
466
+
467
+ return (
468
+ <div className={cn("grid grid-cols-4 gap-y-2", className)} {...props}>
469
+ {Array.from(
470
+ { length: displayYears.to - displayYears.from + 1 },
471
+ (_, i) => {
472
+ const isBefore =
473
+ differenceInCalendarDays(
474
+ new Date(displayYears.from + i, 11, 31),
475
+ startMonth!
476
+ ) < 0
477
+
478
+ const isAfter =
479
+ differenceInCalendarDays(
480
+ new Date(displayYears.from + i, 0, 0),
481
+ endMonth!
482
+ ) > 0
483
+
484
+ const isDisabled = isBefore || isAfter
485
+ return (
486
+ <Button
487
+ key={i}
488
+ className={cn(
489
+ "h-7 w-full text-sm font-normal text-foreground",
490
+ displayYears.from + i === new Date().getFullYear() &&
491
+ "bg-accent font-medium text-accent-foreground"
492
+ )}
493
+ variant="ghost"
494
+ onClick={() => {
495
+ setNavView("days")
496
+ goToMonth(
497
+ new Date(
498
+ displayYears.from + i,
499
+ (selected as Date | undefined)?.getMonth() ?? 0
500
+ )
501
+ )
502
+ }}
503
+ disabled={navView === "years" ? isDisabled : undefined}
504
+ >
505
+ {displayYears.from + i}
506
+ </Button>
507
+ )
508
+ }
509
+ )}
510
+ </div>
511
+ )
67
512
  }
68
513
 
69
- export { Calendar };
514
+ export { Calendar }