@catalystsoftware/ui 1.0.0
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/README.md +7 -0
- package/components/catalyst-ui/buttons/burger.tsx +207 -0
- package/components/catalyst-ui/core/data-display/timeline.tsx +210 -0
- package/components/catalyst-ui/core/feedback/alert.tsx +491 -0
- package/components/catalyst-ui/core/feedback/spinner-1.tsx +65 -0
- package/components/catalyst-ui/core/feedback/toast.tsx +1857 -0
- package/components/catalyst-ui/core/navigation/menu.tsx +164 -0
- package/components/catalyst-ui/forms/toggle-class.tsx +176 -0
- package/components/catalyst-ui/hooks/use-copy-to-clipboard.tsx +419 -0
- package/components/catalyst-ui/hooks/use-counter.tsx +13 -0
- package/components/catalyst-ui/hooks/use-event-listener.tsx +23 -0
- package/components/catalyst-ui/hooks/use-export-markdown.tsx +47 -0
- package/components/catalyst-ui/hooks/use-focus.tsx +17 -0
- package/components/catalyst-ui/hooks/use-interval.tsx +23 -0
- package/components/catalyst-ui/hooks/use-is-client.tsx +16 -0
- package/components/catalyst-ui/hooks/use-media-query.tsx +19 -0
- package/components/catalyst-ui/hooks/use-mobile.tsx +19 -0
- package/components/catalyst-ui/hooks/use-resize-observer.tsx +81 -0
- package/components/catalyst-ui/hooks/use-timeout.tsx +21 -0
- package/components/catalyst-ui/hooks/use-timer.tsx +209 -0
- package/components/catalyst-ui/hooks/use-toggle.tsx +12 -0
- package/components/catalyst-ui/media/image.tsx +13 -0
- package/components/catalyst-ui/overlays/dual-sidebar.tsx +4142 -0
- package/components/catalyst-ui/overlays/sidebar-original.tsx +726 -0
- package/components/catalyst-ui/primitives/accordion.tsx +250 -0
- package/components/catalyst-ui/primitives/alert-dialog.tsx +126 -0
- package/components/catalyst-ui/primitives/aspect-ratio.tsx +9 -0
- package/components/catalyst-ui/primitives/avatar.tsx +296 -0
- package/components/catalyst-ui/primitives/badge.tsx +57 -0
- package/components/catalyst-ui/primitives/breadcrumb.tsx +101 -0
- package/components/catalyst-ui/primitives/button.tsx +265 -0
- package/components/catalyst-ui/primitives/calendar-v4.tsx +208 -0
- package/components/catalyst-ui/primitives/calendar.tsx +295 -0
- package/components/catalyst-ui/primitives/card.tsx +618 -0
- package/components/catalyst-ui/primitives/carousel.tsx +238 -0
- package/components/catalyst-ui/primitives/chart.tsx +347 -0
- package/components/catalyst-ui/primitives/checkbox.tsx +225 -0
- package/components/catalyst-ui/primitives/collapsible.tsx +212 -0
- package/components/catalyst-ui/primitives/command.tsx +393 -0
- package/components/catalyst-ui/primitives/context-menu.tsx +236 -0
- package/components/catalyst-ui/primitives/dialog.tsx +471 -0
- package/components/catalyst-ui/primitives/drawer.tsx +761 -0
- package/components/catalyst-ui/primitives/dropdown-menu.tsx +290 -0
- package/components/catalyst-ui/primitives/empty.tsx +104 -0
- package/components/catalyst-ui/primitives/field.tsx +244 -0
- package/components/catalyst-ui/primitives/hover-card.tsx +124 -0
- package/components/catalyst-ui/primitives/input-otp.tsx +76 -0
- package/components/catalyst-ui/primitives/input.tsx +64 -0
- package/components/catalyst-ui/primitives/item.tsx +196 -0
- package/components/catalyst-ui/primitives/kbd.tsx +75 -0
- package/components/catalyst-ui/primitives/label.tsx +24 -0
- package/components/catalyst-ui/primitives/navigation-menu.tsx +150 -0
- package/components/catalyst-ui/primitives/pagination.tsx +198 -0
- package/components/catalyst-ui/primitives/popover.tsx +232 -0
- package/components/catalyst-ui/primitives/progress.tsx +34 -0
- package/components/catalyst-ui/primitives/radio-group.tsx +43 -0
- package/components/catalyst-ui/primitives/resizable.tsx +56 -0
- package/components/catalyst-ui/primitives/select.tsx +155 -0
- package/components/catalyst-ui/primitives/separator.tsx +74 -0
- package/components/catalyst-ui/primitives/sheet.tsx +126 -0
- package/components/catalyst-ui/primitives/skeleton.tsx +15 -0
- package/components/catalyst-ui/primitives/slider.tsx +27 -0
- package/components/catalyst-ui/primitives/switch.tsx +187 -0
- package/components/catalyst-ui/primitives/tabs.tsx +335 -0
- package/components/catalyst-ui/primitives/textarea.tsx +24 -0
- package/components/catalyst-ui/primitives/toggle-group.tsx +55 -0
- package/components/catalyst-ui/primitives/toggle.tsx +42 -0
- package/components/catalyst-ui/primitives/tooltip.tsx +116 -0
- package/components/catalyst-ui/utils/basic-auth.tsx +40 -0
- package/components/catalyst-ui/utils/context-storage.tsx +19 -0
- package/components/catalyst-ui/utils/cors-middleware.tsx +71 -0
- package/components/catalyst-ui/utils/deferred-content.tsx +595 -0
- package/components/catalyst-ui/utils/honeypot-middleware.tsx +38 -0
- package/components/catalyst-ui/utils/incId.tsx +75 -0
- package/components/catalyst-ui/utils/jwk-auth.tsx +36 -0
- package/components/catalyst-ui/utils/request-id.tsx +14 -0
- package/components/catalyst-ui/utils/secure-headers.tsx +37 -0
- package/components/catalyst-ui/utils/server-timing.tsx +23 -0
- package/components/catalyst-ui/utils/utils.ts +43 -0
- package/components/catalyst-ui/utils/with-cookie.tsx +43 -0
- package/components/catalyst-ui/x/accordian-x.tsx +428 -0
- package/components/catalyst-ui/x/alert-x.tsx +413 -0
- package/components/catalyst-ui/x/animated-text-x.tsx +2242 -0
- package/components/catalyst-ui/x/avatar-x.tsx +515 -0
- package/components/catalyst-ui/x/badge-x.tsx +670 -0
- package/components/catalyst-ui/x/button-X.tsx +2857 -0
- package/components/catalyst-ui/x/button-group-x.tsx +847 -0
- package/components/catalyst-ui/x/calendar-x.tsx +1910 -0
- package/components/catalyst-ui/x/card-x.tsx +2597 -0
- package/components/catalyst-ui/x/checkbox-x.tsx +656 -0
- package/components/catalyst-ui/x/collapsible-x.tsx +1360 -0
- package/components/catalyst-ui/x/combobox-x.tsx +911 -0
- package/components/catalyst-ui/x/data-table-x.tsx +1753 -0
- package/components/catalyst-ui/x/date-picker-x.tsx +648 -0
- package/components/catalyst-ui/x/dialog-x.tsx +659 -0
- package/components/catalyst-ui/x/dropdown-menu-x.tsx +612 -0
- package/components/catalyst-ui/x/hover-card-x.tsx +375 -0
- package/components/catalyst-ui/x/icon-x.tsx +840 -0
- package/components/catalyst-ui/x/input-mask-x.tsx +981 -0
- package/components/catalyst-ui/x/input-otp-x.tsx +659 -0
- package/components/catalyst-ui/x/loader-x.tsx +1757 -0
- package/components/catalyst-ui/x/pagination-x.tsx +622 -0
- package/components/catalyst-ui/x/popover-x.tsx +744 -0
- package/components/catalyst-ui/x/radio-group-x.tsx +499 -0
- package/components/catalyst-ui/x/select-x.tsx +1127 -0
- package/components/catalyst-ui/x/sheet-x.tsx +668 -0
- package/components/catalyst-ui/x/switch-x.tsx +681 -0
- package/components/catalyst-ui/x/table-x.tsx +574 -0
- package/components/catalyst-ui/x/tabs-x.tsx +839 -0
- package/components/catalyst-ui/x/textarea-x.tsx +1263 -0
- package/components/catalyst-ui/x/tooltip-x.tsx +396 -0
- package/components/catalyst-ui/x/tracker-x.tsx +560 -0
- package/data/bg-data.tsx +901 -0
- package/data/buttons-data.tsx +2327 -0
- package/data/charts-data.tsx +102 -0
- package/data/chat-data.tsx +83 -0
- package/data/code-data.tsx +1040 -0
- package/data/comboboxes-data.tsx +1843 -0
- package/data/command-data.tsx +1381 -0
- package/data/core-data.tsx +15953 -0
- package/data/crm-data.tsx +47 -0
- package/data/data.tsx +159 -0
- package/data/date-and-time-data.tsx +554 -0
- package/data/dependencies.tsx +7 -0
- package/data/ecommerce-data.tsx +1387 -0
- package/data/forms-data.tsx +7890 -0
- package/data/hooks-data.tsx +5487 -0
- package/data/index.ts +34 -0
- package/data/inputs-data.tsx +557 -0
- package/data/interactive-data.tsx +5394 -0
- package/data/lofi-data.tsx +18295 -0
- package/data/marketing-data.tsx +2546 -0
- package/data/media-data.tsx +1510 -0
- package/data/motion-data.tsx +5801 -0
- package/data/overlay-data.tsx +4136 -0
- package/data/pdf-data.tsx +124 -0
- package/data/pos-data.tsx +213 -0
- package/data/postcss.config.js +6 -0
- package/data/primitive-data.tsx +5170 -0
- package/data/prompt-data.tsx +1226 -0
- package/data/requiredLibs.ts +4 -0
- package/data/sandbox-data.tsx +1 -0
- package/data/sidebars-data.tsx +5421 -0
- package/data/stacks-data.tsx +32 -0
- package/data/table-data.tsx +706 -0
- package/data/tailwind.config.js +3830 -0
- package/data/tailwind.config.ngin.js +3830 -0
- package/data/tailwind.css +431 -0
- package/data/tools-data.tsx +6910 -0
- package/data/typography-data.tsx +2050 -0
- package/data/utils-data.tsx +6500 -0
- package/data/x-data.tsx +1171 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30245 -0
- package/dist/index.js.map +362 -0
- package/package.json +50 -0
|
@@ -0,0 +1,1910 @@
|
|
|
1
|
+
import { eachYearOfInterval, startOfYear, endOfYear, eachMonthOfInterval, isBefore, isAfter, addDays, subDays, startOfMonth, subMonths, endOfMonth, addMonths, subYears } from "date-fns";
|
|
2
|
+
import { enUS, hi } from 'react-day-picker/locale'
|
|
3
|
+
import { formatDateRange } from "little-date";
|
|
4
|
+
import { format } from 'date-fns'
|
|
5
|
+
import { PlusIcon, CalendarIcon, ClockIcon, ChevronDownIcon, CircleCheckIcon } from "lucide-react";
|
|
6
|
+
import { useState, useId, useEffect, useRef } from "react";
|
|
7
|
+
import { type DateRange } from 'react-day-picker'
|
|
8
|
+
import { SelectValue, Input, Select, ScrollArea, Collapsible, Card, CardTitle, CardDescription, cn, Label, CardHeader, CardAction, CardContent, CardFooter, CalendarDayButton, Button, SelectTrigger, SelectContent, SelectItem, CollapsibleTrigger, CollapsibleContent, Calendar, calendarNavButtonVariants, calendarDayButtonVariants, calendarVariants } from "~/components/catalyst-ui";
|
|
9
|
+
import type { CaptionLabelProps, MonthGridProps } from 'react-day-picker'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ★ ━━━━ ☆ ━━━━ CalendarX ━━━━ ☆ ━━━━ ★
|
|
13
|
+
* type CalendarXType =
|
|
14
|
+
* | 'default' | 'MultiMonth' | 'WeekNumber' // Basic Variants
|
|
15
|
+
* | 'RangeSingleMonth' | 'RangeCalendarMultiMonth' | 'RangeWithMinimumDays' // Range Selection
|
|
16
|
+
* | 'MultiSelect' | 'CustomSelectDay' | 'CustomRangeSelect' // Selection Types
|
|
17
|
+
* | 'DisableDay' | 'DisabledWeekends' // Disabled Dates
|
|
18
|
+
* | 'WithMonthYearDropdown' | 'RightYearMonth' | 'LeftYearMonth' // Navigation & Layout
|
|
19
|
+
* | 'WithTodayMonthButton' | 'VariableSize' | 'WithAdvanceSelection'
|
|
20
|
+
* | 'WithDateInput' | 'WithTimeInput' // Input Integration
|
|
21
|
+
* | 'WithPresets' | 'WithRangePresets' // Quick Presets
|
|
22
|
+
* | 'CalendarAppointmentBooking' | 'EventList' | 'Pricing' | 'Localization' // Specialized
|
|
23
|
+
*
|
|
24
|
+
* ★ ━━━━ ☆ ━━━━ USAGE ━━━━ ☆ ━━━━ ★
|
|
25
|
+
*
|
|
26
|
+
* ★ ━━━━━━━━━ Basic Calendar Variants ━━━━━━━━━ ★
|
|
27
|
+
* ```jsx
|
|
28
|
+
* // Default Calendar - Single date selection
|
|
29
|
+
* <CalendarX
|
|
30
|
+
* calendar="default"
|
|
31
|
+
* date={singleDate}
|
|
32
|
+
* setDate={setSingleDate}
|
|
33
|
+
* />
|
|
34
|
+
*
|
|
35
|
+
* // Multi Month - Display multiple months
|
|
36
|
+
* <CalendarX
|
|
37
|
+
* calendar="MultiMonth"
|
|
38
|
+
* date={singleDate}
|
|
39
|
+
* setDate={setSingleDate}
|
|
40
|
+
* numOfMonths={2}
|
|
41
|
+
* />
|
|
42
|
+
*
|
|
43
|
+
* // With Week Numbers - Show week numbers
|
|
44
|
+
* <CalendarX
|
|
45
|
+
* calendar="WeekNumber"
|
|
46
|
+
* date={singleDate}
|
|
47
|
+
* setDate={setSingleDate}
|
|
48
|
+
* />
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* ★ ━━━━━━━━━ Range Selection ━━━━━━━━━ ★
|
|
52
|
+
* ```jsx
|
|
53
|
+
* // Single Month Range - Select a date range
|
|
54
|
+
* <CalendarX
|
|
55
|
+
* calendar="RangeSingleMonth"
|
|
56
|
+
* dateRange={rangeDate}
|
|
57
|
+
* setDateRange={setRangeDate}
|
|
58
|
+
* />
|
|
59
|
+
*
|
|
60
|
+
* // Multi Month Range - Range across months
|
|
61
|
+
* <CalendarX
|
|
62
|
+
* calendar="RangeCalendarMultiMonth"
|
|
63
|
+
* dateRange={rangeDate}
|
|
64
|
+
* setDateRange={setRangeDate}
|
|
65
|
+
* numOfMonths={2}
|
|
66
|
+
* />
|
|
67
|
+
*
|
|
68
|
+
* // Minimum Days Range - Minimum 5 days selection
|
|
69
|
+
* <CalendarX
|
|
70
|
+
* calendar="RangeWithMinimumDays"
|
|
71
|
+
* dateRange={rangeDate}
|
|
72
|
+
* setDateRange={setRangeDate}
|
|
73
|
+
* min={5}
|
|
74
|
+
* />
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* ★ ━━━━━━━━━ Selection Types ━━━━━━━━━ ★
|
|
78
|
+
* ```jsx
|
|
79
|
+
* // Multiple Selection - Select up to 5 dates
|
|
80
|
+
* <CalendarX
|
|
81
|
+
* calendar="MultiSelect"
|
|
82
|
+
* dates={multiDates}
|
|
83
|
+
* setDates={setMultiDates}
|
|
84
|
+
* />
|
|
85
|
+
*
|
|
86
|
+
* // Custom Day Selection - Custom styling for selected days
|
|
87
|
+
* <CalendarX
|
|
88
|
+
* calendar="CustomSelectDay"
|
|
89
|
+
* date={singleDate}
|
|
90
|
+
* setDate={setSingleDate}
|
|
91
|
+
* />
|
|
92
|
+
*
|
|
93
|
+
* // Custom Range Selection - Styled range with start/end markers
|
|
94
|
+
* <CalendarX
|
|
95
|
+
* calendar="CustomRangeSelect"
|
|
96
|
+
* dateRange={rangeDate}
|
|
97
|
+
* setDateRange={setRangeDate}
|
|
98
|
+
* />
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* ★ ━━━━━━━━━ Disabled Dates ━━━━━━━━━ ★
|
|
102
|
+
* ```jsx
|
|
103
|
+
* // Disable Past Dates - Dates before specific date are disabled
|
|
104
|
+
* <CalendarX
|
|
105
|
+
* calendar="DisableDay"
|
|
106
|
+
* date={singleDate}
|
|
107
|
+
* setDate={setSingleDate}
|
|
108
|
+
* before={new Date(2025, 5, 12)}
|
|
109
|
+
* />
|
|
110
|
+
*
|
|
111
|
+
* // Disable Weekends - Saturdays and Sundays disabled
|
|
112
|
+
* <CalendarX
|
|
113
|
+
* calendar="DisabledWeekends"
|
|
114
|
+
* dateRange={rangeDate}
|
|
115
|
+
* setDateRange={setRangeDate}
|
|
116
|
+
* />
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* ★ ━━━━━━━━━ Navigation & Layout ━━━━━━━━━ ★
|
|
120
|
+
* ```jsx
|
|
121
|
+
* // With Dropdowns - Month/Year dropdown selection
|
|
122
|
+
* <CalendarX
|
|
123
|
+
* calendar="WithMonthYearDropdown"
|
|
124
|
+
* date={singleDate}
|
|
125
|
+
* setDate={setSingleDate}
|
|
126
|
+
* />
|
|
127
|
+
*
|
|
128
|
+
* // Right Navigation - Navigation on the right side
|
|
129
|
+
* <CalendarX
|
|
130
|
+
* calendar="RightYearMonth"
|
|
131
|
+
* date={singleDate}
|
|
132
|
+
* setDate={setSingleDate}
|
|
133
|
+
* />
|
|
134
|
+
*
|
|
135
|
+
* // Left Navigation - Navigation on the left side
|
|
136
|
+
* <CalendarX
|
|
137
|
+
* calendar="LeftYearMonth"
|
|
138
|
+
* date={singleDate}
|
|
139
|
+
* setDate={setSingleDate}
|
|
140
|
+
* />
|
|
141
|
+
*
|
|
142
|
+
* // With Today Button - Quick navigation to today
|
|
143
|
+
* <CalendarX
|
|
144
|
+
* calendar="WithTodayMonthButton"
|
|
145
|
+
* date={singleDate}
|
|
146
|
+
* setDate={setSingleDate}
|
|
147
|
+
* month={month}
|
|
148
|
+
* setMonth={setMonth}
|
|
149
|
+
* />
|
|
150
|
+
*
|
|
151
|
+
* // Variable Size - Custom cell sizes
|
|
152
|
+
* <CalendarX
|
|
153
|
+
* calendar="VariableSize"
|
|
154
|
+
* date={singleDate}
|
|
155
|
+
* setDate={setSingleDate}
|
|
156
|
+
* />
|
|
157
|
+
*
|
|
158
|
+
* // Advanced Selection - Year/month grid view
|
|
159
|
+
* <CalendarX
|
|
160
|
+
* calendar="WithAdvanceSelection"
|
|
161
|
+
* date={singleDate}
|
|
162
|
+
* setDate={setSingleDate}
|
|
163
|
+
* />
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* ★ ━━━━━━━━━ Input Integration ━━━━━━━━━ ★
|
|
167
|
+
* ```jsx
|
|
168
|
+
* // With Date Input - Type or pick a date
|
|
169
|
+
* <CalendarX calendar="WithDateInput" />
|
|
170
|
+
*
|
|
171
|
+
* // With Time Input - Select date and time
|
|
172
|
+
* <CalendarX
|
|
173
|
+
* calendar="WithTimeInput"
|
|
174
|
+
* date={singleDate}
|
|
175
|
+
* setDate={setSingleDate}
|
|
176
|
+
* time={time}
|
|
177
|
+
* setTime={setTime}
|
|
178
|
+
* />
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* ★ ━━━━━━━━━ Quick Presets ━━━━━━━━━ ★
|
|
182
|
+
* ```jsx
|
|
183
|
+
* // Date Presets - Quick date selections
|
|
184
|
+
* <CalendarX
|
|
185
|
+
* calendar="WithPresets"
|
|
186
|
+
* date={singleDate}
|
|
187
|
+
* setDate={setSingleDate}
|
|
188
|
+
* />
|
|
189
|
+
*
|
|
190
|
+
* // Range Presets - Common range selections
|
|
191
|
+
* <CalendarX calendar="WithRangePresets" />
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* ★ ━━━━━━━━━ Specialized Calendars ━━━━━━━━━ ★
|
|
195
|
+
* ```jsx
|
|
196
|
+
* // Appointment Booking - Book appointments with time slots
|
|
197
|
+
* <CalendarX
|
|
198
|
+
* calendar="CalendarAppointmentBooking"
|
|
199
|
+
* date={singleDate}
|
|
200
|
+
* setDate={setSingleDate}
|
|
201
|
+
* selectedTime={selectedTime}
|
|
202
|
+
* setSelectedTime={setSelectedTime}
|
|
203
|
+
* />
|
|
204
|
+
*
|
|
205
|
+
* // Event List Calendar - View events for selected date
|
|
206
|
+
* <CalendarX
|
|
207
|
+
* calendar="EventList"
|
|
208
|
+
* date={singleDate}
|
|
209
|
+
* setDate={setSingleDate}
|
|
210
|
+
* />
|
|
211
|
+
*
|
|
212
|
+
* // Pricing Calendar - Dynamic pricing per date
|
|
213
|
+
* <CalendarX
|
|
214
|
+
* calendar="Pricing"
|
|
215
|
+
* date={singleDate}
|
|
216
|
+
* setDate={setSingleDate}
|
|
217
|
+
* />
|
|
218
|
+
*
|
|
219
|
+
* // Localization - Hindi and English support
|
|
220
|
+
* <CalendarX
|
|
221
|
+
* calendar="Localization"
|
|
222
|
+
* dateRange={rangeDate}
|
|
223
|
+
* setDateRange={setRangeDate}
|
|
224
|
+
* locale={locale}
|
|
225
|
+
* setLocale={setLocale}
|
|
226
|
+
* />
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* ★ ━━━━━━━━━ Props ━━━━━━━━━ ★
|
|
230
|
+
*
|
|
231
|
+
* CalendarX Props:
|
|
232
|
+
* - `calendar`: CalendarXType - Specifies which variant to use
|
|
233
|
+
* - `date`: Date | undefined - Selected single date (for single selection variants)
|
|
234
|
+
* - `setDate`: (date: Date | undefined) => void - Single date change callback
|
|
235
|
+
* - `dateRange`: { from: Date; to: Date } - Date range object (for range variants)
|
|
236
|
+
* - `setDateRange`: (range: { from: Date; to: Date }) => void - Range change callback
|
|
237
|
+
* - `dates`: Date[] - Array of selected dates (for multi-select variants)
|
|
238
|
+
* - `setDates`: (dates: Date[]) => void - Multi-date change callback
|
|
239
|
+
* - `numOfMonths`: number - Number of months to display
|
|
240
|
+
* - `min`: number - Minimum number of days required (for range variants)
|
|
241
|
+
* - `before`: Date - Disable dates before this date
|
|
242
|
+
* - `month`: Date - Current month view
|
|
243
|
+
* - `setMonth`: (month: Date) => void - Month change callback
|
|
244
|
+
* - `time`: string - Selected time string (for time input variants)
|
|
245
|
+
* - `setTime`: (time: string) => void - Time change callback
|
|
246
|
+
* - `selectedTime`: string - Selected appointment time (for booking variants)
|
|
247
|
+
* - `setSelectedTime`: (time: string) => void - Appointment time callback
|
|
248
|
+
* - `locale`: 'en' | 'hi' - Language locale (for localization variants)
|
|
249
|
+
* - `setLocale`: (locale: 'en' | 'hi') => void - Locale change callback
|
|
250
|
+
* - `className`: string - Additional CSS classes
|
|
251
|
+
*
|
|
252
|
+
* ★ ━━━━━━━━━ State Setup Examples ━━━━━━━━━ ★
|
|
253
|
+
* ```typescript
|
|
254
|
+
* // Single date selection
|
|
255
|
+
* const [singleDate, setSingleDate] = useState<Date | undefined>(new Date(2025, 5, 15));
|
|
256
|
+
*
|
|
257
|
+
* // Date range selection
|
|
258
|
+
* const [rangeDate, setRangeDate] = useState({
|
|
259
|
+
* from: new Date(2025, 5, 4),
|
|
260
|
+
* to: new Date(2025, 5, 17)
|
|
261
|
+
* });
|
|
262
|
+
*
|
|
263
|
+
* // Multiple date selection
|
|
264
|
+
* const [multiDates, setMultiDates] = useState<Date[]>([
|
|
265
|
+
* new Date(2025, 5, 12),
|
|
266
|
+
* new Date(2025, 5, 17),
|
|
267
|
+
* new Date(2025, 5, 20)
|
|
268
|
+
* ]);
|
|
269
|
+
*
|
|
270
|
+
* // Time selection
|
|
271
|
+
* const [time, setTime] = useState('12:00:00');
|
|
272
|
+
*
|
|
273
|
+
* // Locale selection
|
|
274
|
+
* const [locale, setLocale] = useState<'en' | 'hi'>('en');
|
|
275
|
+
* ```
|
|
276
|
+
*
|
|
277
|
+
* ★ ━━━━━━━━━ Basic Usage ━━━━━━━━━ ★
|
|
278
|
+
* ```javascript
|
|
279
|
+
* import { useState } from "react";
|
|
280
|
+
* import { CalendarX } from "./CalendarX";
|
|
281
|
+
*
|
|
282
|
+
* function App() {
|
|
283
|
+
* const [date, setDate] = useState<Date | undefined>(new Date());
|
|
284
|
+
*
|
|
285
|
+
* // Default calendar
|
|
286
|
+
* return <CalendarX date={date} setDate={setDate} />;
|
|
287
|
+
*
|
|
288
|
+
* // Specific variant
|
|
289
|
+
* return <CalendarX calendar="MultiMonth" date={date} setDate={setDate} />;
|
|
290
|
+
* }
|
|
291
|
+
* ```
|
|
292
|
+
*
|
|
293
|
+
*/
|
|
294
|
+
|
|
295
|
+
export function CalendarX({ calendar = 'default', children, ...props }: any) {
|
|
296
|
+
switch (calendar) {
|
|
297
|
+
case 'MultiMonth': // Multi month calendar
|
|
298
|
+
return <MultiMonth {...props} />
|
|
299
|
+
case 'RangeSingleMonth': // Single month calendar with range selection
|
|
300
|
+
return <RangeSingleMonth {...props} />
|
|
301
|
+
case 'RangeCalendarMultiMonth': // Multi month calendar with range selection
|
|
302
|
+
return <RangeCalendarMultiMonth {...props} />
|
|
303
|
+
case 'RangeWithMinimumDays': // Minimum 5 days selection
|
|
304
|
+
return <RangeWithMinimumDays {...props} />
|
|
305
|
+
case 'DisableDay': // Disabled day calendar
|
|
306
|
+
return <DisableDay {...props} />
|
|
307
|
+
case 'DisabledWeekends': // Disabled weekend calendar
|
|
308
|
+
return <DisabledWeekends {...props} />
|
|
309
|
+
case 'Localization': // Localize calendar
|
|
310
|
+
return <Localization {...props} />
|
|
311
|
+
case 'WithMonthYearDropdown': // Month and year dropdown calendar
|
|
312
|
+
return <WithMonthYearDropdown {...props} />
|
|
313
|
+
case 'VariableSize': // Variable size calendar
|
|
314
|
+
return <VariableSize {...props} />
|
|
315
|
+
case 'EventList': // Calendar with event list
|
|
316
|
+
return <EventList {...props} />
|
|
317
|
+
case 'MultiSelect': // Multi day select calendar
|
|
318
|
+
return <MultiSelect {...props} />
|
|
319
|
+
case 'CustomSelectDay': // Custom day select calendar
|
|
320
|
+
return <CustomSelectDay {...props} />
|
|
321
|
+
case 'CustomRangeSelect': // Custom range selection calendar
|
|
322
|
+
return <CustomRangeSelect {...props} />
|
|
323
|
+
case 'RightYearMonth': // Right side month year navigation calendar
|
|
324
|
+
return <RightYearMonth {...props} />
|
|
325
|
+
case 'LeftYearMonth': // Left side month year navigation calendar
|
|
326
|
+
return <LeftYearMonth {...props} />
|
|
327
|
+
case 'WeekNumber': // Week number calendar
|
|
328
|
+
return <WeekNumber {...props} />
|
|
329
|
+
case 'WithTodayMonthButton': // Calendar with today button
|
|
330
|
+
return <WithTodayMonthButton {...props} />
|
|
331
|
+
case 'WithDateInput': // Calendar with date input
|
|
332
|
+
return <WithDateInput {...props} />
|
|
333
|
+
case 'WithTimeInput': // Calendar with time input
|
|
334
|
+
return <WithTimeInput {...props} />
|
|
335
|
+
case 'WithAdvanceSelection': // Calendar with advance selection
|
|
336
|
+
return <WithAdvanceSelection {...props} />
|
|
337
|
+
case 'WithPresets': // Calendar with presets
|
|
338
|
+
return <WithPresets {...props} />
|
|
339
|
+
case 'WithRangePresets': // Range calendar with presets
|
|
340
|
+
return <WithRangePresets {...props} />
|
|
341
|
+
case 'CalendarAppointmentBooking': // Appointment calendar
|
|
342
|
+
return <AppointmentBooking {...props} />
|
|
343
|
+
case 'Pricing': // Calendar with pricing
|
|
344
|
+
return <Pricing {...props} />
|
|
345
|
+
default:
|
|
346
|
+
<DefaultCalendar {...props} />
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function DefaultCalendar({ date = new Date(), setDate, className, variant, size }) {
|
|
352
|
+
return (
|
|
353
|
+
<div>
|
|
354
|
+
<Calendar mode='single' defaultMonth={date} selected={date} onSelect={setDate} className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border', className)} />
|
|
355
|
+
</div>
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
const MultiMonth = ({ date = new Date(), setDate, numOfMonths = 2, className, variant, size }) => {
|
|
359
|
+
return (
|
|
360
|
+
<div>
|
|
361
|
+
<Calendar
|
|
362
|
+
mode='single'
|
|
363
|
+
defaultMonth={date}
|
|
364
|
+
numberOfMonths={numOfMonths}
|
|
365
|
+
selected={date}
|
|
366
|
+
onSelect={setDate}
|
|
367
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border', className)}
|
|
368
|
+
/>
|
|
369
|
+
</div>
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
const RangeSingleMonth = ({
|
|
373
|
+
dateRange = {
|
|
374
|
+
from: new Date(2025, 5, 4),
|
|
375
|
+
to: new Date(2025, 5, 17)
|
|
376
|
+
},
|
|
377
|
+
setDateRange,
|
|
378
|
+
className, variant, size
|
|
379
|
+
}) => {
|
|
380
|
+
return (
|
|
381
|
+
<div>
|
|
382
|
+
<Calendar
|
|
383
|
+
mode='range'
|
|
384
|
+
selected={dateRange}
|
|
385
|
+
defaultMonth={dateRange?.from}
|
|
386
|
+
onSelect={setDateRange}
|
|
387
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border', className)}
|
|
388
|
+
/>
|
|
389
|
+
<p className='text-muted-foreground mt-3 text-center text-xs' role='region'>
|
|
390
|
+
|
|
391
|
+
</p>
|
|
392
|
+
</div>
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
const RangeCalendarMultiMonth = ({
|
|
396
|
+
dateRange = {
|
|
397
|
+
from: new Date(2025, 5, 4),
|
|
398
|
+
to: new Date(2025, 5, 17)
|
|
399
|
+
}, setDateRange,
|
|
400
|
+
numOfMonths = 2, className, variant, size
|
|
401
|
+
}) => {
|
|
402
|
+
return (
|
|
403
|
+
<div>
|
|
404
|
+
<Calendar
|
|
405
|
+
mode='range'
|
|
406
|
+
defaultMonth={dateRange?.from}
|
|
407
|
+
selected={dateRange}
|
|
408
|
+
onSelect={setDateRange}
|
|
409
|
+
numberOfMonths={numOfMonths}
|
|
410
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border', className)}
|
|
411
|
+
/>
|
|
412
|
+
</div>
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
const RangeWithMinimumDays = ({
|
|
416
|
+
dateRange = {
|
|
417
|
+
from: new Date(2025, 5, 4),
|
|
418
|
+
to: new Date(2025, 5, 17)
|
|
419
|
+
}, setDateRange,
|
|
420
|
+
numOfMonths = 2,
|
|
421
|
+
min = 5,
|
|
422
|
+
className, variant, size
|
|
423
|
+
}) => {
|
|
424
|
+
|
|
425
|
+
return (
|
|
426
|
+
<div>
|
|
427
|
+
<Calendar
|
|
428
|
+
mode='range'
|
|
429
|
+
defaultMonth={dateRange?.from}
|
|
430
|
+
selected={dateRange}
|
|
431
|
+
onSelect={setDateRange}
|
|
432
|
+
numberOfMonths={numOfMonths}
|
|
433
|
+
min={min}
|
|
434
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border', className)}
|
|
435
|
+
/>
|
|
436
|
+
</div>
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
const DisableDay = ({ date = new Date(2025, 5, 18), setDate, className, variant, size, before = new Date(2025, 5, 12) }) => {
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
<div>
|
|
443
|
+
<Calendar
|
|
444
|
+
mode='single'
|
|
445
|
+
defaultMonth={date}
|
|
446
|
+
selected={date}
|
|
447
|
+
onSelect={setDate}
|
|
448
|
+
disabled={{
|
|
449
|
+
before: before
|
|
450
|
+
}}
|
|
451
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border', className)}
|
|
452
|
+
/>
|
|
453
|
+
</div>
|
|
454
|
+
)
|
|
455
|
+
}
|
|
456
|
+
const DisabledWeekends = ({ dateRange = {
|
|
457
|
+
from: new Date(2025, 5, 4),
|
|
458
|
+
to: new Date(2025, 5, 17)
|
|
459
|
+
}, setDateRange,
|
|
460
|
+
className, variant, size, dayOfWeek = [0, 6] }) => {
|
|
461
|
+
return (
|
|
462
|
+
<div>
|
|
463
|
+
<Calendar
|
|
464
|
+
mode='range'
|
|
465
|
+
defaultMonth={dateRange?.from}
|
|
466
|
+
selected={dateRange}
|
|
467
|
+
onSelect={setDateRange}
|
|
468
|
+
disabled={{ dayOfWeek: dayOfWeek }}
|
|
469
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border', className)}
|
|
470
|
+
excludeDisabled
|
|
471
|
+
/>
|
|
472
|
+
</div>
|
|
473
|
+
)
|
|
474
|
+
}
|
|
475
|
+
const localizedStrings = {
|
|
476
|
+
en: {
|
|
477
|
+
title: 'Book an appointment',
|
|
478
|
+
description: 'Select the dates for your appointment'
|
|
479
|
+
},
|
|
480
|
+
hi: {
|
|
481
|
+
title: 'अपॉइंटमेंट बुक करें',
|
|
482
|
+
description: 'अपनी अपॉइंटमेंट के लिए तारीखें चुनें'
|
|
483
|
+
}
|
|
484
|
+
} as const
|
|
485
|
+
function Localization({
|
|
486
|
+
dateRange = {
|
|
487
|
+
from: new Date(2025, 5, 4),
|
|
488
|
+
to: new Date(2025, 5, 17)
|
|
489
|
+
}, setDateRange,
|
|
490
|
+
className, variant, size,
|
|
491
|
+
locale = 'en',
|
|
492
|
+
setLocale
|
|
493
|
+
}) {
|
|
494
|
+
return (
|
|
495
|
+
<div>
|
|
496
|
+
<Card className='w-xxs shadow-none'>
|
|
497
|
+
<CardHeader className='border border-border-b'>
|
|
498
|
+
<CardTitle>{localizedStrings[locale].title}</CardTitle>
|
|
499
|
+
<CardDescription>{localizedStrings[locale].description}</CardDescription>
|
|
500
|
+
<CardAction>
|
|
501
|
+
<Select value={locale} onValueChange={value => setLocale(value as keyof typeof localizedStrings)}>
|
|
502
|
+
<SelectTrigger className='w-[100px]' aria-label='Select language'>
|
|
503
|
+
<SelectValue placeholder='Language' />
|
|
504
|
+
</SelectTrigger>
|
|
505
|
+
<SelectContent align='end'>
|
|
506
|
+
<SelectItem value='hi'>Hindi</SelectItem>
|
|
507
|
+
<SelectItem value='en'>English</SelectItem>
|
|
508
|
+
</SelectContent>
|
|
509
|
+
</Select>
|
|
510
|
+
</CardAction>
|
|
511
|
+
</CardHeader>
|
|
512
|
+
<CardContent>
|
|
513
|
+
<Calendar
|
|
514
|
+
mode='range'
|
|
515
|
+
selected={dateRange}
|
|
516
|
+
onSelect={setDateRange}
|
|
517
|
+
defaultMonth={dateRange?.from}
|
|
518
|
+
locale={locale === 'hi' ? hi : enUS}
|
|
519
|
+
numerals={locale === 'hi' ? 'deva' : 'latn'}
|
|
520
|
+
className={cn(calendarVariants({ variant, size }), 'w-full bg-transparent p-0', className)}
|
|
521
|
+
buttonVariant='outline'
|
|
522
|
+
/>
|
|
523
|
+
</CardContent>
|
|
524
|
+
</Card>
|
|
525
|
+
</div>
|
|
526
|
+
)
|
|
527
|
+
}
|
|
528
|
+
const WithMonthYearDropdown = ({ date = new Date(), setDate, className, variant, size }) => {
|
|
529
|
+
return (
|
|
530
|
+
<div>
|
|
531
|
+
<Calendar
|
|
532
|
+
mode='single'
|
|
533
|
+
selected={date}
|
|
534
|
+
onSelect={setDate}
|
|
535
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-md border border-border', className)}
|
|
536
|
+
captionLayout='dropdown'
|
|
537
|
+
/>
|
|
538
|
+
</div>
|
|
539
|
+
)
|
|
540
|
+
}
|
|
541
|
+
const VariableSize = ({ date = new Date(), setDate, className, variant, size }) => {
|
|
542
|
+
return (
|
|
543
|
+
<div>
|
|
544
|
+
<Calendar
|
|
545
|
+
mode='single'
|
|
546
|
+
selected={date}
|
|
547
|
+
onSelect={setDate}
|
|
548
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border [--cell-size:--spacing(11)] md:[--cell-size:--spacing(13)]', className)}
|
|
549
|
+
/>
|
|
550
|
+
<p className='text-muted-foreground mt-3 text-center text-xs' role='region'>
|
|
551
|
+
|
|
552
|
+
</p>
|
|
553
|
+
</div>
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
const EventList = ({ date = new Date(), setDate, className, variant, size, events = [
|
|
557
|
+
{
|
|
558
|
+
title: 'Team Sync Meeting',
|
|
559
|
+
from: '2025-06-12T09:00:00',
|
|
560
|
+
to: '2025-06-12T10:00:00'
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
title: 'Design Review',
|
|
564
|
+
from: '2025-06-12T11:30:00',
|
|
565
|
+
to: '2025-06-12T12:30:00'
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
title: 'Client Presentation',
|
|
569
|
+
from: '2025-06-12T14:00:00',
|
|
570
|
+
to: '2025-06-12T15:00:00'
|
|
571
|
+
}
|
|
572
|
+
] }) => {
|
|
573
|
+
return (
|
|
574
|
+
<div>
|
|
575
|
+
<Card className='w-2xs py-4'>
|
|
576
|
+
<CardContent className='px-4'>
|
|
577
|
+
<Calendar
|
|
578
|
+
mode='single'
|
|
579
|
+
selected={date}
|
|
580
|
+
onSelect={setDate}
|
|
581
|
+
className={cn(calendarVariants({ variant, size }), 'w-full bg-transparent p-0', className)}
|
|
582
|
+
required
|
|
583
|
+
/>
|
|
584
|
+
</CardContent>
|
|
585
|
+
<CardFooter className='flex flex-col items-start gap-3 border border-border-t px-4 !pt-4'>
|
|
586
|
+
<div className='flex w-full items-center justify-between px-1'>
|
|
587
|
+
<div className='text-sm font-medium'>
|
|
588
|
+
{date?.toLocaleDateString('en-US', {
|
|
589
|
+
day: 'numeric',
|
|
590
|
+
month: 'long',
|
|
591
|
+
year: 'numeric'
|
|
592
|
+
})}
|
|
593
|
+
</div>
|
|
594
|
+
<Button variant='ghost' size='icon' className='size-6' title='Add Event'>
|
|
595
|
+
<PlusIcon />
|
|
596
|
+
<span className='sr-only'>Add Event</span>
|
|
597
|
+
</Button>
|
|
598
|
+
</div>
|
|
599
|
+
<div className='flex w-full flex-col gap-2'>
|
|
600
|
+
{events.map(event => (
|
|
601
|
+
<div
|
|
602
|
+
key={event.title}
|
|
603
|
+
className='bg-muted after:bg-primary/70 relative rounded-md p-2 pl-6 text-sm after:absolute after:inset-y-2 after:left-2 after:w-1 after:rounded-full'
|
|
604
|
+
>
|
|
605
|
+
<div className='font-medium'>{event.title}</div>
|
|
606
|
+
<div className='text-muted-foreground text-xs'>
|
|
607
|
+
{formatDateRange(new Date(event.from), new Date(event.to))}
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
))}
|
|
611
|
+
</div>
|
|
612
|
+
</CardFooter>
|
|
613
|
+
</Card>
|
|
614
|
+
|
|
615
|
+
</div>
|
|
616
|
+
)
|
|
617
|
+
}
|
|
618
|
+
const MultiSelect = ({ dates = [new Date(2025, 5, 12), new Date(2025, 5, 17)], setDates, className, variant, size }) => {
|
|
619
|
+
return (
|
|
620
|
+
<div>
|
|
621
|
+
<Calendar
|
|
622
|
+
mode='multiple'
|
|
623
|
+
required selected={dates}
|
|
624
|
+
onSelect={setDates} max={5}
|
|
625
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border', className)}
|
|
626
|
+
/>
|
|
627
|
+
</div>
|
|
628
|
+
)
|
|
629
|
+
}
|
|
630
|
+
const CustomSelectDay = ({ date = new Date(), setDate, className, variant, size }) => {
|
|
631
|
+
return (
|
|
632
|
+
<div>
|
|
633
|
+
<Calendar
|
|
634
|
+
mode='single'
|
|
635
|
+
selected={date}
|
|
636
|
+
onSelect={setDate}
|
|
637
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border', className)}
|
|
638
|
+
classNames={{
|
|
639
|
+
day_button:
|
|
640
|
+
'rounded-full! data-[selected-single=true]:bg-sky-600! data-[selected-single=true]:text-white! data-[selected-single=true]:dark:bg-sky-400! data-[selected-single=true]:group-data-[focused=true]/day:ring-sky-600/20 data-[selected-single=true]:dark:group-data-[focused=true]/day:ring-sky-400/40',
|
|
641
|
+
today: 'rounded-full! bg-accent!'
|
|
642
|
+
}}
|
|
643
|
+
/>
|
|
644
|
+
</div>
|
|
645
|
+
)
|
|
646
|
+
}
|
|
647
|
+
const CustomRangeSelect = ({
|
|
648
|
+
dateRange = {
|
|
649
|
+
from: new Date(2025, 5, 4),
|
|
650
|
+
to: new Date(2025, 5, 17)
|
|
651
|
+
},
|
|
652
|
+
setDateRange,
|
|
653
|
+
className, variant, size
|
|
654
|
+
}) => {
|
|
655
|
+
return (
|
|
656
|
+
<div>
|
|
657
|
+
<Calendar
|
|
658
|
+
mode='range'
|
|
659
|
+
defaultMonth={dateRange?.from}
|
|
660
|
+
selected={dateRange}
|
|
661
|
+
onSelect={setDateRange}
|
|
662
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-md border border-border', className)}
|
|
663
|
+
classNames={{
|
|
664
|
+
range_start: 'bg-sky-600/20 dark:bg-sky-400/10 rounded-l-full',
|
|
665
|
+
range_end: 'bg-sky-600/20 dark:bg-sky-400/10 rounded-r-full',
|
|
666
|
+
day_button:
|
|
667
|
+
'data-[range-end=true]:rounded-full! data-[range-start=true]:rounded-full! data-[range-start=true]:bg-sky-600! data-[range-start=true]:text-white! data-[range-start=true]:dark:bg-sky-400! data-[range-start=true]:group-data-[focused=true]/day:ring-sky-600/20 data-[range-start=true]:dark:group-data-[focused=true]/day:ring-sky-400/40 data-[range-end=true]:bg-sky-600! data-[range-end=true]:text-white! data-[range-end=true]:dark:bg-sky-400! data-[range-end=true]:group-data-[focused=true]/day:ring-sky-600/20 data-[range-end=true]:dark:group-data-[focused=true]/day:ring-sky-400/40 data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-sky-600/20 data-[range-middle=true]:dark:bg-sky-400/10 hover:rounded-full',
|
|
668
|
+
today:
|
|
669
|
+
'data-[selected=true]:rounded-l-none! rounded-full bg-accent! data-[selected=true]:bg-sky-600/20! dark:data-[selected=true]:bg-sky-400/10! [&_button[data-range-middle=true]]:bg-transparent!'
|
|
670
|
+
}}
|
|
671
|
+
/>
|
|
672
|
+
</div>
|
|
673
|
+
)
|
|
674
|
+
}
|
|
675
|
+
const RightYearMonth = ({ date = new Date(), setDate, className, variant, size }) => {
|
|
676
|
+
return (
|
|
677
|
+
<div>
|
|
678
|
+
<Calendar
|
|
679
|
+
mode='single'
|
|
680
|
+
selected={date}
|
|
681
|
+
defaultMonth={date}
|
|
682
|
+
onSelect={setDate}
|
|
683
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-md border border-border', className)}
|
|
684
|
+
classNames={{
|
|
685
|
+
month_caption: 'flex items-center h-8 justify-start',
|
|
686
|
+
nav: 'flex justify-end absolute w-full items-center'
|
|
687
|
+
}}
|
|
688
|
+
/>
|
|
689
|
+
</div>
|
|
690
|
+
)
|
|
691
|
+
}
|
|
692
|
+
const LeftYearMonth = ({ date = new Date(), setDate, className, variant, size }) => {
|
|
693
|
+
return (
|
|
694
|
+
<div>
|
|
695
|
+
<Calendar
|
|
696
|
+
mode='single'
|
|
697
|
+
selected={date}
|
|
698
|
+
defaultMonth={date}
|
|
699
|
+
onSelect={setDate}
|
|
700
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-md border border-border', className)}
|
|
701
|
+
classNames={{
|
|
702
|
+
month_caption: 'flex items-center h-8 justify-end',
|
|
703
|
+
nav: 'flex justify-start absolute w-full items-center'
|
|
704
|
+
}}
|
|
705
|
+
/>
|
|
706
|
+
</div>
|
|
707
|
+
)
|
|
708
|
+
}
|
|
709
|
+
const WeekNumber = ({ date = new Date(), setDate, className, variant, size }) => {
|
|
710
|
+
return (
|
|
711
|
+
<div>
|
|
712
|
+
<Calendar
|
|
713
|
+
mode='single'
|
|
714
|
+
defaultMonth={date}
|
|
715
|
+
selected={date}
|
|
716
|
+
onSelect={setDate}
|
|
717
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border shadow-sm', className)}
|
|
718
|
+
showWeekNumber
|
|
719
|
+
/>
|
|
720
|
+
</div>
|
|
721
|
+
)
|
|
722
|
+
}
|
|
723
|
+
const WithTodayMonthButton = ({
|
|
724
|
+
date = new Date(),
|
|
725
|
+
setDate,
|
|
726
|
+
month = new Date(),
|
|
727
|
+
setMonth,
|
|
728
|
+
className,
|
|
729
|
+
variant,
|
|
730
|
+
size
|
|
731
|
+
}) => {
|
|
732
|
+
return (
|
|
733
|
+
<div>
|
|
734
|
+
<Card>
|
|
735
|
+
<CardHeader>
|
|
736
|
+
<CardTitle>Book the show</CardTitle>
|
|
737
|
+
<CardDescription>Find a date</CardDescription>
|
|
738
|
+
<CardAction>
|
|
739
|
+
<Button
|
|
740
|
+
size='sm'
|
|
741
|
+
variant='outline'
|
|
742
|
+
onClick={() => {
|
|
743
|
+
setMonth(new Date())
|
|
744
|
+
setDate(new Date())
|
|
745
|
+
}}
|
|
746
|
+
>
|
|
747
|
+
Today
|
|
748
|
+
</Button>
|
|
749
|
+
</CardAction>
|
|
750
|
+
</CardHeader>
|
|
751
|
+
<CardContent>
|
|
752
|
+
<Calendar
|
|
753
|
+
mode='single'
|
|
754
|
+
month={month}
|
|
755
|
+
onMonthChange={setMonth}
|
|
756
|
+
selected={date}
|
|
757
|
+
onSelect={setDate}
|
|
758
|
+
className=''
|
|
759
|
+
className={cn(calendarVariants({ variant, size }), 'bg-transparent p-0', className)}
|
|
760
|
+
/>
|
|
761
|
+
</CardContent>
|
|
762
|
+
</Card>
|
|
763
|
+
|
|
764
|
+
</div>
|
|
765
|
+
)
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const WithDateInput = ({ today = new Date(), className, variant, size }) => {
|
|
769
|
+
const id = useId()
|
|
770
|
+
const [month, setMonth] = useState(today)
|
|
771
|
+
const [date, setDate] = useState<Date | undefined>(today)
|
|
772
|
+
const [inputValue, setInputValue] = useState('')
|
|
773
|
+
|
|
774
|
+
const handleDayPickerSelect = (date: Date | undefined) => {
|
|
775
|
+
if (!date) {
|
|
776
|
+
setInputValue('')
|
|
777
|
+
setDate(undefined)
|
|
778
|
+
} else {
|
|
779
|
+
setDate(date)
|
|
780
|
+
setMonth(date)
|
|
781
|
+
setInputValue(format(date, 'yyyy-MM-dd'))
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
786
|
+
const value = e.target.value
|
|
787
|
+
|
|
788
|
+
setInputValue(value)
|
|
789
|
+
|
|
790
|
+
if (value) {
|
|
791
|
+
const parsedDate = new Date(value)
|
|
792
|
+
|
|
793
|
+
setDate(parsedDate)
|
|
794
|
+
setMonth(parsedDate)
|
|
795
|
+
} else {
|
|
796
|
+
setDate(undefined)
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
useEffect(() => {
|
|
801
|
+
setInputValue(format(today, 'yyyy-MM-dd'))
|
|
802
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
803
|
+
}, [])
|
|
804
|
+
|
|
805
|
+
return (
|
|
806
|
+
<div>
|
|
807
|
+
<Card className='gap-5 py-5'>
|
|
808
|
+
<CardHeader className='flex items-center border border-border-b px-3 !pb-3'>
|
|
809
|
+
<Label htmlFor={id} className='shrink-0 text-xs'>
|
|
810
|
+
Enter date
|
|
811
|
+
</Label>
|
|
812
|
+
<div className='relative grow'>
|
|
813
|
+
<Input
|
|
814
|
+
id={id}
|
|
815
|
+
type='date'
|
|
816
|
+
value={inputValue}
|
|
817
|
+
onChange={handleInputChange}
|
|
818
|
+
className='peer appearance-none pl-9 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none'
|
|
819
|
+
aria-label='Select date'
|
|
820
|
+
/>
|
|
821
|
+
<div className='text-muted-foreground/80 pointer-events-none absolute inset-y-0 left-0 flex items-center justify-center pl-3 peer-disabled:opacity-50'>
|
|
822
|
+
<CalendarIcon size={16} aria-hidden='true' />
|
|
823
|
+
</div>
|
|
824
|
+
</div>
|
|
825
|
+
</CardHeader>
|
|
826
|
+
<CardContent className='px-5'>
|
|
827
|
+
<Calendar
|
|
828
|
+
mode='single'
|
|
829
|
+
selected={date}
|
|
830
|
+
onSelect={handleDayPickerSelect}
|
|
831
|
+
month={month}
|
|
832
|
+
onMonthChange={setMonth}
|
|
833
|
+
className={cn(calendarVariants({ variant, size }), 'bg-transparent p-0', className)}
|
|
834
|
+
/>
|
|
835
|
+
</CardContent>
|
|
836
|
+
</Card>
|
|
837
|
+
|
|
838
|
+
</div>
|
|
839
|
+
)
|
|
840
|
+
}
|
|
841
|
+
const WithTimeInput = ({ date = new Date(), setDate, time = '12:00:00', setTime, className, variant, size }) => {
|
|
842
|
+
const id = useId()
|
|
843
|
+
|
|
844
|
+
return (
|
|
845
|
+
<div>
|
|
846
|
+
<Card className='gap-5 py-5'>
|
|
847
|
+
<CardHeader className='flex items-center border border-border-b px-3 !pb-3'>
|
|
848
|
+
<Label htmlFor={id} className='text-xs'>
|
|
849
|
+
Enter time
|
|
850
|
+
</Label>
|
|
851
|
+
<div className='relative grow'>
|
|
852
|
+
<Input
|
|
853
|
+
id={id}
|
|
854
|
+
type='time'
|
|
855
|
+
step='1'
|
|
856
|
+
value={time}
|
|
857
|
+
onChange={setTime}
|
|
858
|
+
className='peer appearance-none pl-9 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none'
|
|
859
|
+
/>
|
|
860
|
+
<div className='text-muted-foreground/80 pointer-events-none absolute inset-y-0 left-0 flex items-center justify-center pl-3 peer-disabled:opacity-50'>
|
|
861
|
+
<ClockIcon size={16} aria-hidden='true' />
|
|
862
|
+
</div>
|
|
863
|
+
</div>
|
|
864
|
+
</CardHeader>
|
|
865
|
+
<CardContent className='px-5'>
|
|
866
|
+
<Calendar
|
|
867
|
+
mode='single'
|
|
868
|
+
selected={date}
|
|
869
|
+
onSelect={setDate}
|
|
870
|
+
className={cn(calendarVariants({ variant, size }), 'bg-transparent p-0', className)}
|
|
871
|
+
/>
|
|
872
|
+
</CardContent>
|
|
873
|
+
</Card>
|
|
874
|
+
<p className='text-muted-foreground mt-4 text-center text-xs' role='region'>
|
|
875
|
+
|
|
876
|
+
</p>
|
|
877
|
+
</div>
|
|
878
|
+
)
|
|
879
|
+
}
|
|
880
|
+
const WithAdvanceSelection = ({ today = new Date(), startDate = new Date(1980, 6), endDate = new Date(2030, 6), className, variant, size }) => {
|
|
881
|
+
const [month, setMonth] = useState(today)
|
|
882
|
+
const [date, setDate] = useState<Date | undefined>(today)
|
|
883
|
+
const [isYearView, setIsYearView] = useState(false)
|
|
884
|
+
|
|
885
|
+
const years = eachYearOfInterval({
|
|
886
|
+
start: startOfYear(startDate),
|
|
887
|
+
end: endOfYear(endDate)
|
|
888
|
+
})
|
|
889
|
+
|
|
890
|
+
return (
|
|
891
|
+
<div>
|
|
892
|
+
<Calendar
|
|
893
|
+
mode='single'
|
|
894
|
+
selected={date}
|
|
895
|
+
onSelect={setDate}
|
|
896
|
+
month={month}
|
|
897
|
+
onMonthChange={setMonth}
|
|
898
|
+
defaultMonth={new Date()}
|
|
899
|
+
startMonth={startDate}
|
|
900
|
+
endMonth={endDate}
|
|
901
|
+
className={cn(calendarVariants({ variant, size }), 'overflow-hidden rounded-md border border-border p-2', className)}
|
|
902
|
+
classNames={{
|
|
903
|
+
month_caption: 'ml-2.5 mr-20 justify-start',
|
|
904
|
+
nav: 'flex absolute w-fit right-0 items-center'
|
|
905
|
+
}}
|
|
906
|
+
components={{
|
|
907
|
+
CaptionLabel: (props: CaptionLabelProps) => (
|
|
908
|
+
<CaptionLabel isYearView={isYearView} setIsYearView={setIsYearView} {...props} />
|
|
909
|
+
),
|
|
910
|
+
MonthGrid: (props: MonthGridProps) => {
|
|
911
|
+
return (
|
|
912
|
+
<MonthGrid
|
|
913
|
+
className={props.className}
|
|
914
|
+
isYearView={isYearView}
|
|
915
|
+
setIsYearView={setIsYearView}
|
|
916
|
+
startDate={startDate}
|
|
917
|
+
endDate={endDate}
|
|
918
|
+
years={years}
|
|
919
|
+
currentYear={month.getFullYear()}
|
|
920
|
+
currentMonth={month.getMonth()}
|
|
921
|
+
onMonthSelect={(selectedMonth: Date) => {
|
|
922
|
+
setMonth(selectedMonth)
|
|
923
|
+
setIsYearView(false)
|
|
924
|
+
}}
|
|
925
|
+
>
|
|
926
|
+
{props.children}
|
|
927
|
+
</MonthGrid>
|
|
928
|
+
)
|
|
929
|
+
}
|
|
930
|
+
}}
|
|
931
|
+
/>
|
|
932
|
+
|
|
933
|
+
</div>
|
|
934
|
+
)
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function MonthGrid({
|
|
938
|
+
className,
|
|
939
|
+
children,
|
|
940
|
+
isYearView,
|
|
941
|
+
startDate,
|
|
942
|
+
endDate,
|
|
943
|
+
years,
|
|
944
|
+
currentYear,
|
|
945
|
+
currentMonth,
|
|
946
|
+
onMonthSelect
|
|
947
|
+
}: {
|
|
948
|
+
className?: string
|
|
949
|
+
children: React.ReactNode
|
|
950
|
+
isYearView: boolean
|
|
951
|
+
setIsYearView: React.Dispatch<React.SetStateAction<boolean>>
|
|
952
|
+
startDate: Date
|
|
953
|
+
endDate: Date
|
|
954
|
+
years: Date[]
|
|
955
|
+
currentYear: number
|
|
956
|
+
currentMonth: number
|
|
957
|
+
onMonthSelect: (date: Date) => void
|
|
958
|
+
}) {
|
|
959
|
+
const currentYearRef = useRef<HTMLDivElement>(null)
|
|
960
|
+
const currentMonthButtonRef = useRef<HTMLButtonElement>(null)
|
|
961
|
+
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
|
962
|
+
|
|
963
|
+
useEffect(() => {
|
|
964
|
+
if (isYearView && currentYearRef.current && scrollAreaRef.current) {
|
|
965
|
+
const viewport = scrollAreaRef.current.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement
|
|
966
|
+
|
|
967
|
+
if (viewport) {
|
|
968
|
+
const yearTop = currentYearRef.current.offsetTop
|
|
969
|
+
|
|
970
|
+
viewport.scrollTop = yearTop
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
setTimeout(() => {
|
|
974
|
+
currentMonthButtonRef.current?.focus()
|
|
975
|
+
}, 100)
|
|
976
|
+
}
|
|
977
|
+
}, [isYearView])
|
|
978
|
+
|
|
979
|
+
return (
|
|
980
|
+
<div className='relative'>
|
|
981
|
+
<table className={className}>{children}</table>
|
|
982
|
+
{isYearView && (
|
|
983
|
+
<div className='bg-background absolute inset-0 z-20 -mx-2 -mb-2'>
|
|
984
|
+
<ScrollArea ref={scrollAreaRef} className='h-full'>
|
|
985
|
+
{years.map(year => {
|
|
986
|
+
const months = eachMonthOfInterval({
|
|
987
|
+
start: startOfYear(year),
|
|
988
|
+
end: endOfYear(year)
|
|
989
|
+
})
|
|
990
|
+
|
|
991
|
+
const isCurrentYear = year.getFullYear() === currentYear
|
|
992
|
+
|
|
993
|
+
return (
|
|
994
|
+
<div key={year.getFullYear()} ref={isCurrentYear ? currentYearRef : undefined}>
|
|
995
|
+
<CollapsibleYear title={year.getFullYear().toString()} open={isCurrentYear}>
|
|
996
|
+
<div className='grid grid-cols-3 gap-2'>
|
|
997
|
+
{months.map(month => {
|
|
998
|
+
const isDisabled = isBefore(month, startDate) || isAfter(month, endDate)
|
|
999
|
+
const isCurrentMonth = month.getMonth() === currentMonth && year.getFullYear() === currentYear
|
|
1000
|
+
|
|
1001
|
+
return (
|
|
1002
|
+
<Button
|
|
1003
|
+
key={month.getTime()}
|
|
1004
|
+
ref={isCurrentMonth ? currentMonthButtonRef : undefined}
|
|
1005
|
+
variant={isCurrentMonth ? 'default' : 'outline'}
|
|
1006
|
+
size='sm'
|
|
1007
|
+
className='h-7'
|
|
1008
|
+
disabled={isDisabled}
|
|
1009
|
+
onClick={() => onMonthSelect(month)}
|
|
1010
|
+
>
|
|
1011
|
+
{format(month, 'MMM')}
|
|
1012
|
+
</Button>
|
|
1013
|
+
)
|
|
1014
|
+
})}
|
|
1015
|
+
</div>
|
|
1016
|
+
</CollapsibleYear>
|
|
1017
|
+
</div>
|
|
1018
|
+
)
|
|
1019
|
+
})}
|
|
1020
|
+
</ScrollArea>
|
|
1021
|
+
</div>
|
|
1022
|
+
)}
|
|
1023
|
+
</div>
|
|
1024
|
+
)
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function CaptionLabel({
|
|
1028
|
+
children,
|
|
1029
|
+
isYearView,
|
|
1030
|
+
setIsYearView
|
|
1031
|
+
}: {
|
|
1032
|
+
isYearView: boolean
|
|
1033
|
+
setIsYearView: React.Dispatch<React.SetStateAction<boolean>>
|
|
1034
|
+
} & React.HTMLAttributes<HTMLSpanElement>) {
|
|
1035
|
+
return (
|
|
1036
|
+
<Button
|
|
1037
|
+
className='data-[state=open]:text-muted-foreground/80 -ms-2 flex items-center gap-2 text-sm font-medium hover:bg-transparent [&[data-state=open]>svg]:rotate-180'
|
|
1038
|
+
variant='ghost'
|
|
1039
|
+
size='sm'
|
|
1040
|
+
onClick={() => setIsYearView(prev => !prev)}
|
|
1041
|
+
data-state={isYearView ? 'open' : 'closed'}
|
|
1042
|
+
>
|
|
1043
|
+
{children}
|
|
1044
|
+
<ChevronDownIcon
|
|
1045
|
+
className='text-muted-foreground/80 shrink-0 transition-transform duration-200'
|
|
1046
|
+
aria-hidden='true'
|
|
1047
|
+
/>
|
|
1048
|
+
</Button>
|
|
1049
|
+
)
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function CollapsibleYear({ title, children, open }: { title: string; children: React.ReactNode; open?: boolean }) {
|
|
1053
|
+
return (
|
|
1054
|
+
<Collapsible className='border border-border-t px-2 py-1.5' defaultOpen={open}>
|
|
1055
|
+
<CollapsibleTrigger asChild>
|
|
1056
|
+
<Button
|
|
1057
|
+
className='flex w-full justify-start gap-2 text-sm font-medium hover:bg-transparent [&[data-state=open]>svg]:rotate-180'
|
|
1058
|
+
variant='ghost'
|
|
1059
|
+
size='sm'
|
|
1060
|
+
>
|
|
1061
|
+
<ChevronDownIcon
|
|
1062
|
+
className='text-muted-foreground/80 shrink-0 transition-transform duration-200'
|
|
1063
|
+
aria-hidden='true'
|
|
1064
|
+
/>
|
|
1065
|
+
{title}
|
|
1066
|
+
</Button>
|
|
1067
|
+
</CollapsibleTrigger>
|
|
1068
|
+
<CollapsibleContent className='data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden px-3 py-1 text-sm transition-all'>
|
|
1069
|
+
{children}
|
|
1070
|
+
</CollapsibleContent>
|
|
1071
|
+
</Collapsible>
|
|
1072
|
+
)
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const WithPresets = ({
|
|
1076
|
+
date = new Date(),
|
|
1077
|
+
setDate,
|
|
1078
|
+
className,
|
|
1079
|
+
variant,
|
|
1080
|
+
size,
|
|
1081
|
+
presets = [
|
|
1082
|
+
{ label: 'Today', value: 0 },
|
|
1083
|
+
{ label: 'Yesterday', value: -1 },
|
|
1084
|
+
{ label: 'Tomorrow', value: 1 },
|
|
1085
|
+
{ label: 'In 3 days', value: 3 },
|
|
1086
|
+
{ label: 'In a week', value: 7 },
|
|
1087
|
+
{ label: 'In 2 weeks', value: 14 }
|
|
1088
|
+
] }) => {
|
|
1089
|
+
return (
|
|
1090
|
+
<div>
|
|
1091
|
+
<Card className='max-w-xs py-4'>
|
|
1092
|
+
<CardContent className='px-4'>
|
|
1093
|
+
<Calendar
|
|
1094
|
+
mode='single'
|
|
1095
|
+
selected={date}
|
|
1096
|
+
onSelect={setDate}
|
|
1097
|
+
defaultMonth={date}
|
|
1098
|
+
className={cn(calendarVariants({ variant, size }), 'w-full bg-transparent p-0', className)}
|
|
1099
|
+
/>
|
|
1100
|
+
</CardContent>
|
|
1101
|
+
<CardFooter className='flex flex-wrap gap-2 border border-border-t px-4 !pt-4'>
|
|
1102
|
+
{presets.map(preset => (
|
|
1103
|
+
<Button
|
|
1104
|
+
key={preset.value}
|
|
1105
|
+
variant='outline'
|
|
1106
|
+
size='sm'
|
|
1107
|
+
className='flex-1'
|
|
1108
|
+
onClick={() => {
|
|
1109
|
+
const newDate = addDays(new Date(), preset.value)
|
|
1110
|
+
|
|
1111
|
+
setDate(newDate)
|
|
1112
|
+
}}
|
|
1113
|
+
>
|
|
1114
|
+
{preset.label}
|
|
1115
|
+
</Button>
|
|
1116
|
+
))}
|
|
1117
|
+
</CardFooter>
|
|
1118
|
+
</Card>
|
|
1119
|
+
|
|
1120
|
+
</div>
|
|
1121
|
+
)
|
|
1122
|
+
}
|
|
1123
|
+
const WithRangePresets = ({ today = new Date(), className, variant, size }) => {
|
|
1124
|
+
|
|
1125
|
+
const yesterday = {
|
|
1126
|
+
from: subDays(today, 1),
|
|
1127
|
+
to: subDays(today, 1)
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const tomorrow = {
|
|
1131
|
+
from: today,
|
|
1132
|
+
to: addDays(today, 1)
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
const last7Days = {
|
|
1136
|
+
from: subDays(today, 6),
|
|
1137
|
+
to: today
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
const next7Days = {
|
|
1141
|
+
from: addDays(today, 1),
|
|
1142
|
+
to: addDays(today, 7)
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
const last30Days = {
|
|
1146
|
+
from: subDays(today, 29),
|
|
1147
|
+
to: today
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
const monthToDate = {
|
|
1151
|
+
from: startOfMonth(today),
|
|
1152
|
+
to: today
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
const lastMonth = {
|
|
1156
|
+
from: startOfMonth(subMonths(today, 1)),
|
|
1157
|
+
to: endOfMonth(subMonths(today, 1))
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const nextMonth = {
|
|
1161
|
+
from: startOfMonth(addMonths(today, 1)),
|
|
1162
|
+
to: endOfMonth(addMonths(today, 1))
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const yearToDate = {
|
|
1166
|
+
from: startOfYear(today),
|
|
1167
|
+
to: today
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const lastYear = {
|
|
1171
|
+
from: startOfYear(subYears(today, 1)),
|
|
1172
|
+
to: endOfYear(subYears(today, 1))
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
const [month, setMonth] = useState(today)
|
|
1176
|
+
const [date, setDate] = useState<DateRange | undefined>(last7Days)
|
|
1177
|
+
|
|
1178
|
+
return (
|
|
1179
|
+
<div>
|
|
1180
|
+
<Card className='max-w-xs py-4'>
|
|
1181
|
+
<CardContent className='px-4'>
|
|
1182
|
+
<Calendar
|
|
1183
|
+
mode='range'
|
|
1184
|
+
selected={date}
|
|
1185
|
+
onSelect={newDate => {
|
|
1186
|
+
if (newDate) {
|
|
1187
|
+
setDate(newDate)
|
|
1188
|
+
}
|
|
1189
|
+
}}
|
|
1190
|
+
month={month}
|
|
1191
|
+
onMonthChange={setMonth}
|
|
1192
|
+
className={cn(calendarVariants({ variant, size }), 'w-full bg-transparent p-0', className)}
|
|
1193
|
+
/>
|
|
1194
|
+
</CardContent>
|
|
1195
|
+
<CardFooter className='flex flex-wrap gap-2 border border-border-t px-4 !pt-4'>
|
|
1196
|
+
<Button
|
|
1197
|
+
variant='outline'
|
|
1198
|
+
size='sm'
|
|
1199
|
+
onClick={() => {
|
|
1200
|
+
setDate({
|
|
1201
|
+
from: today,
|
|
1202
|
+
to: today
|
|
1203
|
+
})
|
|
1204
|
+
setMonth(today)
|
|
1205
|
+
}}
|
|
1206
|
+
>
|
|
1207
|
+
Today
|
|
1208
|
+
</Button>
|
|
1209
|
+
<Button
|
|
1210
|
+
variant='outline'
|
|
1211
|
+
size='sm'
|
|
1212
|
+
onClick={() => {
|
|
1213
|
+
setDate(yesterday)
|
|
1214
|
+
setMonth(yesterday.to)
|
|
1215
|
+
}}
|
|
1216
|
+
>
|
|
1217
|
+
Yesterday
|
|
1218
|
+
</Button>
|
|
1219
|
+
<Button
|
|
1220
|
+
variant='outline'
|
|
1221
|
+
size='sm'
|
|
1222
|
+
onClick={() => {
|
|
1223
|
+
setDate(tomorrow)
|
|
1224
|
+
setMonth(tomorrow.to)
|
|
1225
|
+
}}
|
|
1226
|
+
>
|
|
1227
|
+
Tomorrow
|
|
1228
|
+
</Button>
|
|
1229
|
+
<Button
|
|
1230
|
+
variant='outline'
|
|
1231
|
+
size='sm'
|
|
1232
|
+
onClick={() => {
|
|
1233
|
+
setDate(last7Days)
|
|
1234
|
+
setMonth(last7Days.to)
|
|
1235
|
+
}}
|
|
1236
|
+
>
|
|
1237
|
+
Last 7 days
|
|
1238
|
+
</Button>
|
|
1239
|
+
<Button
|
|
1240
|
+
variant='outline'
|
|
1241
|
+
size='sm'
|
|
1242
|
+
onClick={() => {
|
|
1243
|
+
setDate(next7Days)
|
|
1244
|
+
setMonth(next7Days.to)
|
|
1245
|
+
}}
|
|
1246
|
+
>
|
|
1247
|
+
Next 7 days
|
|
1248
|
+
</Button>
|
|
1249
|
+
<Button
|
|
1250
|
+
variant='outline'
|
|
1251
|
+
size='sm'
|
|
1252
|
+
onClick={() => {
|
|
1253
|
+
setDate(last30Days)
|
|
1254
|
+
setMonth(last30Days.to)
|
|
1255
|
+
}}
|
|
1256
|
+
>
|
|
1257
|
+
Last 30 days
|
|
1258
|
+
</Button>
|
|
1259
|
+
<Button
|
|
1260
|
+
variant='outline'
|
|
1261
|
+
size='sm'
|
|
1262
|
+
onClick={() => {
|
|
1263
|
+
setDate(monthToDate)
|
|
1264
|
+
setMonth(monthToDate.to)
|
|
1265
|
+
}}
|
|
1266
|
+
>
|
|
1267
|
+
Month to date
|
|
1268
|
+
</Button>
|
|
1269
|
+
<Button
|
|
1270
|
+
variant='outline'
|
|
1271
|
+
size='sm'
|
|
1272
|
+
onClick={() => {
|
|
1273
|
+
setDate(lastMonth)
|
|
1274
|
+
setMonth(lastMonth.to)
|
|
1275
|
+
}}
|
|
1276
|
+
>
|
|
1277
|
+
Last month
|
|
1278
|
+
</Button>
|
|
1279
|
+
<Button
|
|
1280
|
+
variant='outline'
|
|
1281
|
+
size='sm'
|
|
1282
|
+
onClick={() => {
|
|
1283
|
+
setDate(nextMonth)
|
|
1284
|
+
setMonth(nextMonth.to)
|
|
1285
|
+
}}
|
|
1286
|
+
>
|
|
1287
|
+
Next month
|
|
1288
|
+
</Button>
|
|
1289
|
+
<Button
|
|
1290
|
+
variant='outline'
|
|
1291
|
+
size='sm'
|
|
1292
|
+
onClick={() => {
|
|
1293
|
+
setDate(yearToDate)
|
|
1294
|
+
setMonth(yearToDate.to)
|
|
1295
|
+
}}
|
|
1296
|
+
>
|
|
1297
|
+
Year to date
|
|
1298
|
+
</Button>
|
|
1299
|
+
<Button
|
|
1300
|
+
variant='outline'
|
|
1301
|
+
size='sm'
|
|
1302
|
+
onClick={() => {
|
|
1303
|
+
setDate(lastYear)
|
|
1304
|
+
setMonth(lastYear.to)
|
|
1305
|
+
}}
|
|
1306
|
+
>
|
|
1307
|
+
Last year
|
|
1308
|
+
</Button>
|
|
1309
|
+
</CardFooter>
|
|
1310
|
+
</Card>
|
|
1311
|
+
|
|
1312
|
+
</div>
|
|
1313
|
+
)
|
|
1314
|
+
}
|
|
1315
|
+
const AppointmentBooking = ({
|
|
1316
|
+
date = new Date(),
|
|
1317
|
+
setDate,
|
|
1318
|
+
selectedTime = '10:00',
|
|
1319
|
+
setSelectedTime,
|
|
1320
|
+
className,
|
|
1321
|
+
variant,
|
|
1322
|
+
size,
|
|
1323
|
+
timeSlots = Array.from({ length: 37 }, (_, i) => {
|
|
1324
|
+
const totalMinutes = i * 15
|
|
1325
|
+
const hour = Math.floor(totalMinutes / 60) + 9
|
|
1326
|
+
const minute = totalMinutes % 60
|
|
1327
|
+
|
|
1328
|
+
return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`
|
|
1329
|
+
}),
|
|
1330
|
+
bookedDates = Array.from({ length: 3 }, (_, i) => new Date(2025, 5, 17 + i)),
|
|
1331
|
+
|
|
1332
|
+
}) => {
|
|
1333
|
+
|
|
1334
|
+
return (
|
|
1335
|
+
<div>
|
|
1336
|
+
<Card className='gap-0 p-0'>
|
|
1337
|
+
<CardHeader className='flex h-max justify-center border border-border-b !p-4'>
|
|
1338
|
+
<CardTitle>Book your appointment</CardTitle>
|
|
1339
|
+
</CardHeader>
|
|
1340
|
+
<CardContent className='relative p-0 md:pr-48'>
|
|
1341
|
+
<div className='p-6'>
|
|
1342
|
+
<Calendar
|
|
1343
|
+
mode='single'
|
|
1344
|
+
selected={date}
|
|
1345
|
+
onSelect={setDate}
|
|
1346
|
+
defaultMonth={date}
|
|
1347
|
+
disabled={bookedDates}
|
|
1348
|
+
showOutsideDays={false}
|
|
1349
|
+
modifiers={{
|
|
1350
|
+
booked: bookedDates
|
|
1351
|
+
}}
|
|
1352
|
+
modifiersClassNames={{
|
|
1353
|
+
booked: '[&>button]:line-through opacity-100'
|
|
1354
|
+
}}
|
|
1355
|
+
className={cn(calendarVariants({ variant, size }), 'bg-transparent p-0 [--cell-size:--spacing(10)]', className)}
|
|
1356
|
+
formatters={{
|
|
1357
|
+
formatWeekdayName: date => {
|
|
1358
|
+
return date.toLocaleString('en-US', { weekday: 'short' })
|
|
1359
|
+
}
|
|
1360
|
+
}}
|
|
1361
|
+
/>
|
|
1362
|
+
</div>
|
|
1363
|
+
<div className='inset-y-0 right-0 flex w-full flex-col gap-4 border border-border-t max-md:h-60 md:absolute md:w-48 md:border border-border-t-0 md:border border-border-l'>
|
|
1364
|
+
<ScrollArea className='h-full'>
|
|
1365
|
+
<div className='flex flex-col gap-2 p-6'>
|
|
1366
|
+
{timeSlots.map(time => (
|
|
1367
|
+
<Button
|
|
1368
|
+
key={time}
|
|
1369
|
+
variant={selectedTime === time ? 'default' : 'outline'}
|
|
1370
|
+
onClick={() => setSelectedTime(time)}
|
|
1371
|
+
className='w-full shadow-none'
|
|
1372
|
+
>
|
|
1373
|
+
{time}
|
|
1374
|
+
</Button>
|
|
1375
|
+
))}
|
|
1376
|
+
</div>
|
|
1377
|
+
</ScrollArea>
|
|
1378
|
+
</div>
|
|
1379
|
+
</CardContent>
|
|
1380
|
+
<CardFooter className='flex flex-col gap-4 border border-border-t px-6 !py-5 md:flex-row'>
|
|
1381
|
+
<div className='flex items-center gap-2 text-sm'>
|
|
1382
|
+
{date && selectedTime ? (
|
|
1383
|
+
<>
|
|
1384
|
+
<CircleCheckIcon className='size-5 stroke-green-600 dark:stroke-green-400' />
|
|
1385
|
+
<span>
|
|
1386
|
+
Your meeting is booked for{' '}
|
|
1387
|
+
<span className='font-medium'>
|
|
1388
|
+
{' '}
|
|
1389
|
+
{date?.toLocaleDateString('en-US', {
|
|
1390
|
+
weekday: 'long',
|
|
1391
|
+
day: 'numeric',
|
|
1392
|
+
month: 'long'
|
|
1393
|
+
})}{' '}
|
|
1394
|
+
</span>
|
|
1395
|
+
at <span className='font-medium'>{selectedTime}</span>.
|
|
1396
|
+
</span>
|
|
1397
|
+
</>
|
|
1398
|
+
) : (
|
|
1399
|
+
<>Select a date and time for your meeting.</>
|
|
1400
|
+
)}
|
|
1401
|
+
</div>
|
|
1402
|
+
<Button disabled={!date || !selectedTime} className='w-full md:ml-auto md:w-auto' variant='outline'>
|
|
1403
|
+
Continue
|
|
1404
|
+
</Button>
|
|
1405
|
+
</CardFooter>
|
|
1406
|
+
</Card>
|
|
1407
|
+
|
|
1408
|
+
</div>
|
|
1409
|
+
)
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
|
|
1413
|
+
const Pricing = ({ date = new Date(), setDate, className, variant, size, getPrice = null }) => {
|
|
1414
|
+
|
|
1415
|
+
function getPriceForDate(date: Date) {
|
|
1416
|
+
const seed = date.getFullYear() * 10000 + (date.getMonth() + 1) * 100 + date.getDate()
|
|
1417
|
+
|
|
1418
|
+
const val = (seed * 9301 + 49297) % 233280
|
|
1419
|
+
|
|
1420
|
+
return Math.floor(50 + (val / 233280) * 200)
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
return (
|
|
1424
|
+
<div>
|
|
1425
|
+
<Calendar
|
|
1426
|
+
mode='single'
|
|
1427
|
+
selected={date}
|
|
1428
|
+
onSelect={setDate}
|
|
1429
|
+
showOutsideDays={false}
|
|
1430
|
+
className={cn(calendarVariants({ variant, size }), 'rounded-lg border border-border [--cell-size:--spacing(12)]', className)}
|
|
1431
|
+
components={{
|
|
1432
|
+
DayButton: ({ children, modifiers, day, ...props }) => {
|
|
1433
|
+
const price = getPrice ? getPrice() : getPriceForDate(day.date)
|
|
1434
|
+
const isGreen = price < 100
|
|
1435
|
+
|
|
1436
|
+
return (
|
|
1437
|
+
<CalendarDayButton day={day} modifiers={modifiers} {...props}>
|
|
1438
|
+
{children}
|
|
1439
|
+
{!modifiers.outside && (
|
|
1440
|
+
<span className={isGreen ? 'text-green-600 dark:text-green-400' : ''}>${price}</span>
|
|
1441
|
+
)}
|
|
1442
|
+
</CalendarDayButton>
|
|
1443
|
+
)
|
|
1444
|
+
}
|
|
1445
|
+
}}
|
|
1446
|
+
disabled={{ before: new Date() }}
|
|
1447
|
+
/>
|
|
1448
|
+
|
|
1449
|
+
</div>
|
|
1450
|
+
)
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
export function CalendarXDemo() {
|
|
1456
|
+
const [singleDate, setSingleDate] = useState<Date | undefined>(new Date(2025, 5, 15));
|
|
1457
|
+
const [rangeDate, setRangeDate] = useState({
|
|
1458
|
+
from: new Date(2025, 5, 4),
|
|
1459
|
+
to: new Date(2025, 5, 17)
|
|
1460
|
+
});
|
|
1461
|
+
const [multiDates, setMultiDates] = useState<Date[]>([
|
|
1462
|
+
new Date(2025, 5, 12),
|
|
1463
|
+
new Date(2025, 5, 17),
|
|
1464
|
+
new Date(2025, 5, 20)
|
|
1465
|
+
]);
|
|
1466
|
+
const [locale, setLocale] = useState<'en' | 'hi'>('en');
|
|
1467
|
+
const [time, setTime] = useState('12:00:00');
|
|
1468
|
+
const [selectedTime, setSelectedTime] = useState('10:00');
|
|
1469
|
+
const [month, setMonth] = useState(new Date(2025, 5));
|
|
1470
|
+
|
|
1471
|
+
return (
|
|
1472
|
+
<div className="p-8 max-w-7xl mx-auto bg-background">
|
|
1473
|
+
<header className="mb-12">
|
|
1474
|
+
<h1 className="text-4xl font-bold mb-2">CalendarX Demo</h1>
|
|
1475
|
+
<p className="text-muted-foreground">
|
|
1476
|
+
A comprehensive collection of calendar variants built with Catalyst UI and React Day Picker
|
|
1477
|
+
</p>
|
|
1478
|
+
</header>
|
|
1479
|
+
|
|
1480
|
+
<div className="space-y-12">
|
|
1481
|
+
{/* Section 1: Basic Calendar Variants */}
|
|
1482
|
+
<section>
|
|
1483
|
+
<h2 className="text-2xl font-semibold mb-6 pb-2 border-b">Basic Calendar Variants</h2>
|
|
1484
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
1485
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1486
|
+
<h3 className="font-medium">Default Calendar</h3>
|
|
1487
|
+
<p className="text-sm text-muted-foreground mb-2">Single date selection</p>
|
|
1488
|
+
<CalendarX
|
|
1489
|
+
calendar="default"
|
|
1490
|
+
date={singleDate}
|
|
1491
|
+
setDate={setSingleDate}
|
|
1492
|
+
/>
|
|
1493
|
+
</div>
|
|
1494
|
+
|
|
1495
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1496
|
+
<h3 className="font-medium">Multi Month</h3>
|
|
1497
|
+
<p className="text-sm text-muted-foreground mb-2">Display multiple months</p>
|
|
1498
|
+
<CalendarX
|
|
1499
|
+
calendar="MultiMonth"
|
|
1500
|
+
date={singleDate}
|
|
1501
|
+
setDate={setSingleDate}
|
|
1502
|
+
numOfMonths={2}
|
|
1503
|
+
/>
|
|
1504
|
+
</div>
|
|
1505
|
+
|
|
1506
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1507
|
+
<h3 className="font-medium">With Week Numbers</h3>
|
|
1508
|
+
<p className="text-sm text-muted-foreground mb-2">Show week numbers</p>
|
|
1509
|
+
<CalendarX
|
|
1510
|
+
calendar="WeekNumber"
|
|
1511
|
+
date={singleDate}
|
|
1512
|
+
setDate={setSingleDate}
|
|
1513
|
+
/>
|
|
1514
|
+
</div>
|
|
1515
|
+
</div>
|
|
1516
|
+
</section>
|
|
1517
|
+
|
|
1518
|
+
{/* Section 2: Range Selection */}
|
|
1519
|
+
<section>
|
|
1520
|
+
<h2 className="text-2xl font-semibold mb-6 pb-2 border-b">Range Selection</h2>
|
|
1521
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
1522
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1523
|
+
<h3 className="font-medium">Single Month Range</h3>
|
|
1524
|
+
<p className="text-sm text-muted-foreground mb-2">Select a date range</p>
|
|
1525
|
+
<CalendarX
|
|
1526
|
+
calendar="RangeSingleMonth"
|
|
1527
|
+
dateRange={rangeDate}
|
|
1528
|
+
setDateRange={setRangeDate}
|
|
1529
|
+
/>
|
|
1530
|
+
</div>
|
|
1531
|
+
|
|
1532
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1533
|
+
<h3 className="font-medium">Multi Month Range</h3>
|
|
1534
|
+
<p className="text-sm text-muted-foreground mb-2">Range across months</p>
|
|
1535
|
+
<CalendarX
|
|
1536
|
+
calendar="RangeCalendarMultiMonth"
|
|
1537
|
+
dateRange={rangeDate}
|
|
1538
|
+
setDateRange={setRangeDate}
|
|
1539
|
+
numOfMonths={2}
|
|
1540
|
+
/>
|
|
1541
|
+
</div>
|
|
1542
|
+
|
|
1543
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1544
|
+
<h3 className="font-medium">Minimum Days Range</h3>
|
|
1545
|
+
<p className="text-sm text-muted-foreground mb-2">Minimum 5 days selection</p>
|
|
1546
|
+
<CalendarX
|
|
1547
|
+
calendar="RangeWithMinimumDays"
|
|
1548
|
+
dateRange={rangeDate}
|
|
1549
|
+
setDateRange={setRangeDate}
|
|
1550
|
+
min={5}
|
|
1551
|
+
/>
|
|
1552
|
+
</div>
|
|
1553
|
+
</div>
|
|
1554
|
+
</section>
|
|
1555
|
+
|
|
1556
|
+
{/* Section 3: Selection Types */}
|
|
1557
|
+
<section>
|
|
1558
|
+
<h2 className="text-2xl font-semibold mb-6 pb-2 border-b">Selection Types</h2>
|
|
1559
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
1560
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1561
|
+
<h3 className="font-medium">Multiple Selection</h3>
|
|
1562
|
+
<p className="text-sm text-muted-foreground mb-2">Select up to 5 dates</p>
|
|
1563
|
+
<CalendarX
|
|
1564
|
+
calendar="MultiSelect"
|
|
1565
|
+
dates={multiDates}
|
|
1566
|
+
setDates={setMultiDates}
|
|
1567
|
+
/>
|
|
1568
|
+
</div>
|
|
1569
|
+
|
|
1570
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1571
|
+
<h3 className="font-medium">Custom Day Selection</h3>
|
|
1572
|
+
<p className="text-sm text-muted-foreground mb-2">Custom styling for selected days</p>
|
|
1573
|
+
<CalendarX
|
|
1574
|
+
calendar="CustomSelectDay"
|
|
1575
|
+
date={singleDate}
|
|
1576
|
+
setDate={setSingleDate}
|
|
1577
|
+
/>
|
|
1578
|
+
</div>
|
|
1579
|
+
|
|
1580
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1581
|
+
<h3 className="font-medium">Custom Range Selection</h3>
|
|
1582
|
+
<p className="text-sm text-muted-foreground mb-2">Styled range with start/end markers</p>
|
|
1583
|
+
<CalendarX
|
|
1584
|
+
calendar="CustomRangeSelect"
|
|
1585
|
+
dateRange={rangeDate}
|
|
1586
|
+
setDateRange={setRangeDate}
|
|
1587
|
+
/>
|
|
1588
|
+
</div>
|
|
1589
|
+
</div>
|
|
1590
|
+
</section>
|
|
1591
|
+
|
|
1592
|
+
{/* Section 4: Disabled Dates */}
|
|
1593
|
+
<section>
|
|
1594
|
+
<h2 className="text-2xl font-semibold mb-6 pb-2 border-b">Disabled Dates</h2>
|
|
1595
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
1596
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1597
|
+
<h3 className="font-medium">Disable Past Dates</h3>
|
|
1598
|
+
<p className="text-sm text-muted-foreground mb-2">Dates before June 12, 2025 are disabled</p>
|
|
1599
|
+
<CalendarX
|
|
1600
|
+
calendar="DisableDay"
|
|
1601
|
+
date={singleDate}
|
|
1602
|
+
setDate={setSingleDate}
|
|
1603
|
+
before={new Date(2025, 5, 12)}
|
|
1604
|
+
/>
|
|
1605
|
+
</div>
|
|
1606
|
+
|
|
1607
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1608
|
+
<h3 className="font-medium">Disable Weekends</h3>
|
|
1609
|
+
<p className="text-sm text-muted-foreground mb-2">Saturdays and Sundays disabled</p>
|
|
1610
|
+
<CalendarX
|
|
1611
|
+
calendar="DisabledWeekends"
|
|
1612
|
+
dateRange={rangeDate}
|
|
1613
|
+
setDateRange={setRangeDate}
|
|
1614
|
+
/>
|
|
1615
|
+
</div>
|
|
1616
|
+
</div>
|
|
1617
|
+
</section>
|
|
1618
|
+
|
|
1619
|
+
{/* Section 5: Navigation & Layout */}
|
|
1620
|
+
<section>
|
|
1621
|
+
<h2 className="text-2xl font-semibold mb-6 pb-2 border-b">Navigation & Layout</h2>
|
|
1622
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
1623
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1624
|
+
<h3 className="font-medium">With Dropdowns</h3>
|
|
1625
|
+
<p className="text-sm text-muted-foreground mb-2">Month/Year dropdown selection</p>
|
|
1626
|
+
<CalendarX
|
|
1627
|
+
calendar="WithMonthYearDropdown"
|
|
1628
|
+
date={singleDate}
|
|
1629
|
+
setDate={setSingleDate}
|
|
1630
|
+
/>
|
|
1631
|
+
</div>
|
|
1632
|
+
|
|
1633
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1634
|
+
<h3 className="font-medium">Right Navigation</h3>
|
|
1635
|
+
<p className="text-sm text-muted-foreground mb-2">Navigation on the right side</p>
|
|
1636
|
+
<CalendarX
|
|
1637
|
+
calendar="RightYearMonth"
|
|
1638
|
+
date={singleDate}
|
|
1639
|
+
setDate={setSingleDate}
|
|
1640
|
+
/>
|
|
1641
|
+
</div>
|
|
1642
|
+
|
|
1643
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1644
|
+
<h3 className="font-medium">Left Navigation</h3>
|
|
1645
|
+
<p className="text-sm text-muted-foreground mb-2">Navigation on the left side</p>
|
|
1646
|
+
<CalendarX
|
|
1647
|
+
calendar="LeftYearMonth"
|
|
1648
|
+
date={singleDate}
|
|
1649
|
+
setDate={setSingleDate}
|
|
1650
|
+
/>
|
|
1651
|
+
</div>
|
|
1652
|
+
|
|
1653
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1654
|
+
<h3 className="font-medium">With Today Button</h3>
|
|
1655
|
+
<p className="text-sm text-muted-foreground mb-2">Quick navigation to today</p>
|
|
1656
|
+
<CalendarX
|
|
1657
|
+
calendar="WithTodayMonthButton"
|
|
1658
|
+
date={singleDate}
|
|
1659
|
+
setDate={setSingleDate}
|
|
1660
|
+
month={month}
|
|
1661
|
+
setMonth={setMonth}
|
|
1662
|
+
/>
|
|
1663
|
+
</div>
|
|
1664
|
+
|
|
1665
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1666
|
+
<h3 className="font-medium">Variable Size</h3>
|
|
1667
|
+
<p className="text-sm text-muted-foreground mb-2">Custom cell sizes</p>
|
|
1668
|
+
<CalendarX
|
|
1669
|
+
calendar="VariableSize"
|
|
1670
|
+
date={singleDate}
|
|
1671
|
+
setDate={setSingleDate}
|
|
1672
|
+
/>
|
|
1673
|
+
</div>
|
|
1674
|
+
|
|
1675
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1676
|
+
<h3 className="font-medium">Advanced Selection</h3>
|
|
1677
|
+
<p className="text-sm text-muted-foreground mb-2">Year/month grid view</p>
|
|
1678
|
+
<CalendarX
|
|
1679
|
+
calendar="WithAdvanceSelection"
|
|
1680
|
+
date={singleDate}
|
|
1681
|
+
setDate={setSingleDate}
|
|
1682
|
+
/>
|
|
1683
|
+
</div>
|
|
1684
|
+
</div>
|
|
1685
|
+
</section>
|
|
1686
|
+
|
|
1687
|
+
{/* Section 6: Input Integration */}
|
|
1688
|
+
<section>
|
|
1689
|
+
<h2 className="text-2xl font-semibold mb-6 pb-2 border-b">Input Integration</h2>
|
|
1690
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
1691
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1692
|
+
<h3 className="font-medium">With Date Input</h3>
|
|
1693
|
+
<p className="text-sm text-muted-foreground mb-2">Type or pick a date</p>
|
|
1694
|
+
<CalendarX
|
|
1695
|
+
calendar="WithDateInput"
|
|
1696
|
+
/>
|
|
1697
|
+
</div>
|
|
1698
|
+
|
|
1699
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1700
|
+
<h3 className="font-medium">With Time Input</h3>
|
|
1701
|
+
<p className="text-sm text-muted-foreground mb-2">Select date and time</p>
|
|
1702
|
+
<CalendarX
|
|
1703
|
+
calendar="WithTimeInput"
|
|
1704
|
+
date={singleDate}
|
|
1705
|
+
setDate={setSingleDate}
|
|
1706
|
+
time={time}
|
|
1707
|
+
setTime={setTime}
|
|
1708
|
+
/>
|
|
1709
|
+
</div>
|
|
1710
|
+
</div>
|
|
1711
|
+
</section>
|
|
1712
|
+
|
|
1713
|
+
{/* Section 7: Presets */}
|
|
1714
|
+
<section>
|
|
1715
|
+
<h2 className="text-2xl font-semibold mb-6 pb-2 border-b">Quick Presets</h2>
|
|
1716
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
1717
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1718
|
+
<h3 className="font-medium">Date Presets</h3>
|
|
1719
|
+
<p className="text-sm text-muted-foreground mb-2">Quick date selections</p>
|
|
1720
|
+
<CalendarX
|
|
1721
|
+
calendar="WithPresets"
|
|
1722
|
+
date={singleDate}
|
|
1723
|
+
setDate={setSingleDate}
|
|
1724
|
+
/>
|
|
1725
|
+
</div>
|
|
1726
|
+
|
|
1727
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1728
|
+
<h3 className="font-medium">Range Presets</h3>
|
|
1729
|
+
<p className="text-sm text-muted-foreground mb-2">Common range selections</p>
|
|
1730
|
+
<CalendarX
|
|
1731
|
+
calendar="WithRangePresets"
|
|
1732
|
+
/>
|
|
1733
|
+
</div>
|
|
1734
|
+
</div>
|
|
1735
|
+
</section>
|
|
1736
|
+
|
|
1737
|
+
{/* Section 8: Specialized Calendars */}
|
|
1738
|
+
<section>
|
|
1739
|
+
<h2 className="text-2xl font-semibold mb-6 pb-2 border-b">Specialized Calendars</h2>
|
|
1740
|
+
<div className="grid grid-cols-1 gap-6">
|
|
1741
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1742
|
+
<h3 className="font-medium">Appointment Booking</h3>
|
|
1743
|
+
<p className="text-sm text-muted-foreground mb-2">Book appointments with time slots</p>
|
|
1744
|
+
<CalendarX
|
|
1745
|
+
calendar="CalendarAppointmentBooking"
|
|
1746
|
+
date={singleDate}
|
|
1747
|
+
setDate={setSingleDate}
|
|
1748
|
+
selectedTime={selectedTime}
|
|
1749
|
+
setSelectedTime={setSelectedTime}
|
|
1750
|
+
/>
|
|
1751
|
+
</div>
|
|
1752
|
+
|
|
1753
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1754
|
+
<h3 className="font-medium">Event List Calendar</h3>
|
|
1755
|
+
<p className="text-sm text-muted-foreground mb-2">View events for selected date</p>
|
|
1756
|
+
<CalendarX
|
|
1757
|
+
calendar="EventList"
|
|
1758
|
+
date={singleDate}
|
|
1759
|
+
setDate={setSingleDate}
|
|
1760
|
+
/>
|
|
1761
|
+
</div>
|
|
1762
|
+
|
|
1763
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1764
|
+
<h3 className="font-medium">Pricing Calendar</h3>
|
|
1765
|
+
<p className="text-sm text-muted-foreground mb-2">Dynamic pricing per date</p>
|
|
1766
|
+
<CalendarX
|
|
1767
|
+
calendar="Pricing"
|
|
1768
|
+
date={singleDate}
|
|
1769
|
+
setDate={setSingleDate}
|
|
1770
|
+
/>
|
|
1771
|
+
</div>
|
|
1772
|
+
|
|
1773
|
+
<div className="space-y-4 p-4 border rounded-lg">
|
|
1774
|
+
<h3 className="font-medium">Localization</h3>
|
|
1775
|
+
<p className="text-sm text-muted-foreground mb-2">Hindi and English support</p>
|
|
1776
|
+
<CalendarX
|
|
1777
|
+
calendar="Localization"
|
|
1778
|
+
dateRange={rangeDate}
|
|
1779
|
+
setDateRange={setRangeDate}
|
|
1780
|
+
locale={locale}
|
|
1781
|
+
setLocale={setLocale}
|
|
1782
|
+
/>
|
|
1783
|
+
</div>
|
|
1784
|
+
</div>
|
|
1785
|
+
</section>
|
|
1786
|
+
|
|
1787
|
+
{/* Section 9: Usage Examples */}
|
|
1788
|
+
<section>
|
|
1789
|
+
<h2 className="text-2xl font-semibold mb-6 pb-2 border-b">Usage Examples</h2>
|
|
1790
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
1791
|
+
<div className="p-6 border rounded-lg bg-card">
|
|
1792
|
+
<h3 className="font-medium mb-4">Booking System Example</h3>
|
|
1793
|
+
<div className="space-y-4">
|
|
1794
|
+
<div className="flex items-center justify-between">
|
|
1795
|
+
<div>
|
|
1796
|
+
<h4 className="font-medium">Select Check-in Date</h4>
|
|
1797
|
+
<p className="text-sm text-muted-foreground">Choose your arrival date</p>
|
|
1798
|
+
</div>
|
|
1799
|
+
<CalendarX
|
|
1800
|
+
calendar="DisableDay"
|
|
1801
|
+
date={singleDate}
|
|
1802
|
+
setDate={setSingleDate}
|
|
1803
|
+
before={new Date()}
|
|
1804
|
+
className="w-[280px]"
|
|
1805
|
+
/>
|
|
1806
|
+
</div>
|
|
1807
|
+
<div className="flex items-center justify-between">
|
|
1808
|
+
<div>
|
|
1809
|
+
<h4 className="font-medium">Select Check-out Date</h4>
|
|
1810
|
+
<p className="text-sm text-muted-foreground">Choose your departure date</p>
|
|
1811
|
+
</div>
|
|
1812
|
+
<CalendarX
|
|
1813
|
+
calendar="RangeSingleMonth"
|
|
1814
|
+
dateRange={rangeDate}
|
|
1815
|
+
setDateRange={setRangeDate}
|
|
1816
|
+
className="w-[280px]"
|
|
1817
|
+
/>
|
|
1818
|
+
</div>
|
|
1819
|
+
</div>
|
|
1820
|
+
</div>
|
|
1821
|
+
|
|
1822
|
+
<div className="p-6 border rounded-lg bg-card">
|
|
1823
|
+
<h3 className="font-medium mb-4">Team Planning Example</h3>
|
|
1824
|
+
<div className="space-y-4">
|
|
1825
|
+
<div className="flex items-center gap-4">
|
|
1826
|
+
<div className="flex-1">
|
|
1827
|
+
<h4 className="font-medium">Available Dates</h4>
|
|
1828
|
+
<p className="text-sm text-muted-foreground">Select available dates for team meetings</p>
|
|
1829
|
+
<CalendarX
|
|
1830
|
+
calendar="MultiSelect"
|
|
1831
|
+
dates={multiDates}
|
|
1832
|
+
setDates={setMultiDates}
|
|
1833
|
+
className="mt-2"
|
|
1834
|
+
/>
|
|
1835
|
+
</div>
|
|
1836
|
+
<div className="flex-1">
|
|
1837
|
+
<h4 className="font-medium">Meeting Duration</h4>
|
|
1838
|
+
<CalendarX
|
|
1839
|
+
calendar="WithTimeInput"
|
|
1840
|
+
date={singleDate}
|
|
1841
|
+
setDate={setSingleDate}
|
|
1842
|
+
time={time}
|
|
1843
|
+
setTime={setTime}
|
|
1844
|
+
/>
|
|
1845
|
+
</div>
|
|
1846
|
+
</div>
|
|
1847
|
+
</div>
|
|
1848
|
+
</div>
|
|
1849
|
+
</div>
|
|
1850
|
+
</section>
|
|
1851
|
+
|
|
1852
|
+
{/* Section 10: Usage Instructions */}
|
|
1853
|
+
<section className="mt-12 p-6 border rounded-lg bg-muted/30">
|
|
1854
|
+
<h2 className="text-2xl font-semibold mb-4">Usage</h2>
|
|
1855
|
+
<div className="space-y-4">
|
|
1856
|
+
<div>
|
|
1857
|
+
<h3 className="font-medium mb-2">Basic Usage</h3>
|
|
1858
|
+
<pre className="bg-muted p-3 rounded text-sm overflow-x-auto">
|
|
1859
|
+
{`import { useState } from "react";
|
|
1860
|
+
import { CalendarX } from "./CalendarX";
|
|
1861
|
+
|
|
1862
|
+
function App() {
|
|
1863
|
+
const [date, setDate] = useState<Date | undefined>(new Date());
|
|
1864
|
+
|
|
1865
|
+
// Default calendar
|
|
1866
|
+
return <CalendarX date={date} setDate={setDate} />;
|
|
1867
|
+
|
|
1868
|
+
// Specific variant
|
|
1869
|
+
return <CalendarX calendar="MultiMonth" date={date} setDate={setDate} />;
|
|
1870
|
+
}`}
|
|
1871
|
+
</pre>
|
|
1872
|
+
</div>
|
|
1873
|
+
|
|
1874
|
+
<div>
|
|
1875
|
+
<h3 className="font-medium mb-2">Common Props</h3>
|
|
1876
|
+
<ul className="list-disc pl-5 space-y-1 text-sm">
|
|
1877
|
+
<li><code>calendar</code>: Specifies which variant to use</li>
|
|
1878
|
+
<li><code>date</code>/<code>setDate</code>: For single date selection</li>
|
|
1879
|
+
<li><code>dateRange</code>/<code>setDateRange</code>: For range selection</li>
|
|
1880
|
+
<li><code>dates</code>/<code>setDates</code>: For multiple date selection</li>
|
|
1881
|
+
<li><code>numOfMonths</code>: Number of months to display</li>
|
|
1882
|
+
<li><code>className</code>: Additional CSS classes</li>
|
|
1883
|
+
<li><code>variant</code>/<code>size</code>: Catalyst UI variant/size props</li>
|
|
1884
|
+
</ul>
|
|
1885
|
+
</div>
|
|
1886
|
+
|
|
1887
|
+
<div>
|
|
1888
|
+
<h3 className="font-medium mb-2">Range Selection Example</h3>
|
|
1889
|
+
<pre className="bg-muted p-3 rounded text-sm overflow-x-auto">
|
|
1890
|
+
{`const [rangeDate, setRangeDate] = useState({
|
|
1891
|
+
from: new Date(2025, 5, 4),
|
|
1892
|
+
to: new Date(2025, 5, 17)
|
|
1893
|
+
});
|
|
1894
|
+
|
|
1895
|
+
return (
|
|
1896
|
+
<CalendarX
|
|
1897
|
+
calendar="RangeSingleMonth"
|
|
1898
|
+
dateRange={rangeDate}
|
|
1899
|
+
setDateRange={setRangeDate}
|
|
1900
|
+
/>
|
|
1901
|
+
);`}
|
|
1902
|
+
</pre>
|
|
1903
|
+
</div>
|
|
1904
|
+
</div>
|
|
1905
|
+
</section>
|
|
1906
|
+
</div>
|
|
1907
|
+
</div>
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
|