@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.
- package/package.json +26 -39
- package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +7 -7
- package/src/app/routes/_authenticated/_customers/components/customer-address-card.tsx +3 -8
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +9 -3
- package/src/lib/components/data-table/data-table.tsx +3 -2
- package/src/lib/components/shared/translatable-form-field.tsx +2 -1
- package/src/lib/components/ui/accordion.tsx +45 -50
- package/src/lib/components/ui/alert-dialog.tsx +93 -122
- package/src/lib/components/ui/alert.tsx +48 -54
- package/src/lib/components/ui/badge.tsx +29 -37
- package/src/lib/components/ui/breadcrumb.tsx +82 -89
- package/src/lib/components/ui/button.tsx +51 -52
- package/src/lib/components/ui/calendar.tsx +435 -196
- package/src/lib/components/ui/card.tsx +33 -78
- package/src/lib/components/ui/checkbox.tsx +23 -28
- package/src/lib/components/ui/collapsible.tsx +2 -0
- package/src/lib/components/ui/command.tsx +114 -159
- package/src/lib/components/ui/dialog.tsx +90 -115
- package/src/lib/components/ui/dropdown-menu.tsx +170 -207
- package/src/lib/components/ui/form.tsx +114 -138
- package/src/lib/components/ui/hover-card.tsx +26 -32
- package/src/lib/components/ui/input.tsx +15 -17
- package/src/lib/components/ui/label.tsx +16 -19
- package/src/lib/components/ui/pagination.tsx +87 -108
- package/src/lib/components/ui/popover.tsx +28 -36
- package/src/lib/components/ui/scroll-area.tsx +40 -48
- package/src/lib/components/ui/separator.tsx +20 -22
- package/src/lib/components/ui/sheet.tsx +91 -110
- package/src/lib/components/ui/sidebar.tsx +622 -652
- package/src/lib/components/ui/skeleton.tsx +10 -10
- package/src/lib/components/ui/sonner.tsx +11 -7
- package/src/lib/components/ui/switch.tsx +22 -27
- package/src/lib/components/ui/table.tsx +64 -96
- package/src/lib/components/ui/tabs.tsx +38 -56
- package/src/lib/components/ui/textarea.tsx +14 -14
- package/src/lib/components/ui/tooltip.tsx +37 -45
- package/src/lib/framework/page/detail-page.tsx +26 -20
- package/src/lib/graphql/graphql-env.d.ts +1 -1
- package/src/lib/components/ui/aspect-ratio.tsx +0 -9
- package/src/lib/components/ui/avatar.tsx +0 -53
- package/src/lib/components/ui/carousel.tsx +0 -241
- package/src/lib/components/ui/chart.tsx +0 -351
- package/src/lib/components/ui/context-menu.tsx +0 -252
- package/src/lib/components/ui/drawer.tsx +0 -133
- package/src/lib/components/ui/input-otp.tsx +0 -77
- package/src/lib/components/ui/menubar.tsx +0 -274
- package/src/lib/components/ui/navigation-menu.tsx +0 -168
- package/src/lib/components/ui/progress.tsx +0 -29
- package/src/lib/components/ui/radio-group.tsx +0 -45
- package/src/lib/components/ui/resizable.tsx +0 -54
- package/src/lib/components/ui/slider.tsx +0 -63
- package/src/lib/components/ui/toggle-group.tsx +0 -73
- package/src/lib/components/ui/toggle.tsx +0 -45
|
@@ -1,208 +1,447 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
+
const isNextDisabled = (() => {
|
|
253
|
+
if (navView === 'years') {
|
|
141
254
|
return (
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
150
|
-
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
447
|
+
export { Calendar };
|