@srcroot/ui 0.0.55 → 0.0.58
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 +151 -151
- package/dist/index.d.ts +0 -0
- package/dist/index.js +120 -93
- package/package.json +7 -2
- package/src/registry/analytics/google-analytics.tsx +36 -39
- package/src/registry/analytics/google-tag-manager.tsx +62 -65
- package/src/registry/analytics/meta-pixel.tsx +44 -47
- package/src/registry/analytics/microsoft-clarity.tsx +31 -34
- package/src/registry/analytics/tiktok-pixel.tsx +34 -37
- package/src/registry/lib/utils.ts +0 -0
- package/src/registry/themes/v3/blue.css +157 -157
- package/src/registry/themes/v3/glass.css +153 -153
- package/src/registry/themes/v3/gray.css +157 -157
- package/src/registry/themes/v3/green.css +157 -157
- package/src/registry/themes/v3/neutral.css +157 -157
- package/src/registry/themes/v3/orange.css +157 -157
- package/src/registry/themes/v3/rose.css +157 -157
- package/src/registry/themes/v3/slate.css +157 -157
- package/src/registry/themes/v3/stone.css +157 -157
- package/src/registry/themes/v3/violet.css +186 -186
- package/src/registry/themes/v3/zinc.css +157 -157
- package/src/registry/themes/v4/blue.css +184 -184
- package/src/registry/themes/v4/glass.css +180 -180
- package/src/registry/themes/v4/gray.css +184 -184
- package/src/registry/themes/v4/green.css +184 -184
- package/src/registry/themes/v4/neutral.css +184 -184
- package/src/registry/themes/v4/orange.css +184 -184
- package/src/registry/themes/v4/rose.css +184 -184
- package/src/registry/themes/v4/slate.css +184 -184
- package/src/registry/themes/v4/stone.css +184 -184
- package/src/registry/themes/v4/violet.css +184 -184
- package/src/registry/themes/v4/zinc.css +184 -184
- package/src/registry/ui/accordion.tsx +164 -165
- package/src/registry/ui/alert-dialog.tsx +213 -214
- package/src/registry/ui/alert.tsx +73 -76
- package/src/registry/ui/aspect-ratio.tsx +44 -47
- package/src/registry/ui/avatar.tsx +96 -97
- package/src/registry/ui/badge.tsx +52 -55
- package/src/registry/ui/breadcrumb.tsx +147 -150
- package/src/registry/ui/button-group.tsx +64 -67
- package/src/registry/ui/button.tsx +71 -72
- package/src/registry/ui/calendar.tsx +514 -515
- package/src/registry/ui/card.tsx +88 -91
- package/src/registry/ui/carousel.tsx +214 -214
- package/src/registry/ui/chart.tsx +373 -373
- package/src/registry/ui/chatbot.tsx +86 -13
- package/src/registry/ui/checkbox.tsx +93 -94
- package/src/registry/ui/collapsible.tsx +107 -108
- package/src/registry/ui/combobox.tsx +171 -171
- package/src/registry/ui/command.tsx +300 -300
- package/src/registry/ui/container.tsx +44 -47
- package/src/registry/ui/context-menu.tsx +221 -221
- package/src/registry/ui/date-picker.tsx +228 -228
- package/src/registry/ui/dialog.tsx +269 -270
- package/src/registry/ui/drawer.tsx +10 -4
- package/src/registry/ui/dropdown-menu.tsx +529 -530
- package/src/registry/ui/empty-state.tsx +0 -2
- package/src/registry/ui/file-upload.tsx +0 -0
- package/src/registry/ui/floating-dock.tsx +0 -0
- package/src/registry/ui/form-field.tsx +91 -94
- package/src/registry/ui/google-analytics.tsx +38 -0
- package/src/registry/ui/google-tag-manager.tsx +64 -0
- package/src/registry/ui/hover-card.tsx +223 -223
- package/src/registry/ui/image.tsx +144 -147
- package/src/registry/ui/input-group.tsx +82 -85
- package/src/registry/ui/input.tsx +125 -125
- package/src/registry/ui/kbd.tsx +60 -63
- package/src/registry/ui/label.tsx +36 -37
- package/src/registry/ui/loading-spinner.tsx +108 -111
- package/src/registry/ui/map.tsx +0 -0
- package/src/registry/ui/marquee.tsx +2 -0
- package/src/registry/ui/menubar.tsx +246 -246
- package/src/registry/ui/meta-pixel.tsx +46 -0
- package/src/registry/ui/microsoft-clarity.tsx +33 -0
- package/src/registry/ui/native-select.tsx +49 -52
- package/src/registry/ui/otp-input.tsx +163 -155
- package/src/registry/ui/pagination.tsx +149 -152
- package/src/registry/ui/patterns.tsx +28 -0
- package/src/registry/ui/popover.tsx +226 -227
- package/src/registry/ui/progress.tsx +51 -52
- package/src/registry/ui/radio.tsx +99 -102
- package/src/registry/ui/resizable.tsx +314 -314
- package/src/registry/ui/scroll-animation.tsx +45 -0
- package/src/registry/ui/scroll-area.tsx +121 -122
- package/src/registry/ui/scroll-to-top.tsx +0 -0
- package/src/registry/ui/search.tsx +162 -150
- package/src/registry/ui/select.tsx +292 -293
- package/src/registry/ui/separator.tsx +46 -47
- package/src/registry/ui/sheet.tsx +6 -3
- package/src/registry/ui/sidebar.tsx +628 -628
- package/src/registry/ui/skeleton.tsx +26 -29
- package/src/registry/ui/slider.tsx +196 -197
- package/src/registry/ui/slot.tsx +69 -72
- package/src/registry/ui/star-rating.tsx +146 -134
- package/src/registry/ui/switch.tsx +72 -73
- package/src/registry/ui/table-of-contents.tsx +96 -96
- package/src/registry/ui/table.tsx +138 -139
- package/src/registry/ui/tabs.tsx +124 -125
- package/src/registry/ui/text.tsx +61 -64
- package/src/registry/ui/textarea.tsx +41 -42
- package/src/registry/ui/theme-switcher.tsx +66 -66
- package/src/registry/ui/tiktok-pixel.tsx +36 -0
- package/src/registry/ui/toast.tsx +97 -98
- package/src/registry/ui/toggle-group.tsx +129 -129
- package/src/registry/ui/toggle.tsx +72 -72
- package/src/registry/ui/tooltip.tsx +143 -144
- package/src/registry/ui/whatsapp.tsx +0 -0
|
@@ -1,228 +1,228 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { addDays, startOfMonth, endOfMonth } from "date-fns"
|
|
5
|
-
import { Calendar } from "./calendar"
|
|
6
|
-
import { Popover, PopoverContent, PopoverTrigger } from "./popover"
|
|
7
|
-
import { Button } from "./button"
|
|
8
|
-
import { cn } from "@/lib/utils"
|
|
9
|
-
|
|
10
|
-
// Calendar icon
|
|
11
|
-
const CalendarIcon = () => (
|
|
12
|
-
<svg
|
|
13
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
-
width="16"
|
|
15
|
-
height="16"
|
|
16
|
-
viewBox="0 0 24 24"
|
|
17
|
-
fill="none"
|
|
18
|
-
stroke="currentColor"
|
|
19
|
-
strokeWidth="2"
|
|
20
|
-
strokeLinecap="round"
|
|
21
|
-
strokeLinejoin="round"
|
|
22
|
-
className="mr-2 h-4 w-4 opacity-50"
|
|
23
|
-
>
|
|
24
|
-
<rect width="18" height="18" x="3" y="4" rx="2" ry="2" />
|
|
25
|
-
<line x1="16" x2="16" y1="2" y2="6" />
|
|
26
|
-
<line x1="8" x2="8" y1="2" y2="6" />
|
|
27
|
-
<line x1="3" x2="21" y1="10" y2="10" />
|
|
28
|
-
</svg>
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
// Format helpers
|
|
32
|
-
const formatDate = (date: Date) => {
|
|
33
|
-
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const formatRange = (dates: Date[]) => {
|
|
37
|
-
if (dates.length === 0) return null
|
|
38
|
-
if (dates.length === 1) return formatDate(dates[0])
|
|
39
|
-
return `${formatDate(dates[0])} → ${formatDate(dates[1])}`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const formatMultiple = (dates: Date[]) => {
|
|
43
|
-
if (dates.length === 0) return null
|
|
44
|
-
return `${dates.length} date${dates.length > 1 ? 's' : ''} selected`
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// DatePicker Props
|
|
48
|
-
interface DatePickerBaseProps {
|
|
49
|
-
/** Placeholder text when no date selected */
|
|
50
|
-
placeholder?: string
|
|
51
|
-
/** Whether the picker is disabled */
|
|
52
|
-
disabled?: boolean
|
|
53
|
-
/** Custom class name for the trigger button */
|
|
54
|
-
className?: string
|
|
55
|
-
/** Number of months to display */
|
|
56
|
-
numberOfMonths?: 1 | 2
|
|
57
|
-
/** Calendar size */
|
|
58
|
-
size?: "xs" | "sm" | "default" | "md" | "lg"
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
interface DatePickerSingleProps extends DatePickerBaseProps {
|
|
62
|
-
mode?: "single"
|
|
63
|
-
selected?: Date
|
|
64
|
-
onSelect?: (date: Date | undefined) => void
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
interface DatePickerMultipleProps extends DatePickerBaseProps {
|
|
68
|
-
mode: "multiple"
|
|
69
|
-
selected?: Date[]
|
|
70
|
-
onSelect?: (dates: Date[]) => void
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
interface DatePickerRangeProps extends DatePickerBaseProps {
|
|
74
|
-
mode: "range"
|
|
75
|
-
selected?: Date[]
|
|
76
|
-
onSelect?: (dates: Date[]) => void
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
type DatePickerProps = DatePickerSingleProps | DatePickerMultipleProps | DatePickerRangeProps
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* DatePicker - A complete date picker component
|
|
83
|
-
*
|
|
84
|
-
* Combines Calendar + Popover for a ready-to-use date selection experience.
|
|
85
|
-
* Supports single, multiple, and range selection modes.
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* // Single date
|
|
89
|
-
* <DatePicker selected={date} onSelect={setDate} />
|
|
90
|
-
*
|
|
91
|
-
* // Multiple dates
|
|
92
|
-
* <DatePicker mode="multiple" selected={dates} onSelect={setDates} />
|
|
93
|
-
*
|
|
94
|
-
* // Date range with dual months
|
|
95
|
-
* <DatePicker mode="range" numberOfMonths={2} selected={range} onSelect={setRange} />
|
|
96
|
-
*/
|
|
97
|
-
const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
|
|
98
|
-
({
|
|
99
|
-
mode = "single",
|
|
100
|
-
selected,
|
|
101
|
-
onSelect,
|
|
102
|
-
placeholder,
|
|
103
|
-
disabled = false,
|
|
104
|
-
className,
|
|
105
|
-
numberOfMonths = 1,
|
|
106
|
-
size = "default",
|
|
107
|
-
...props
|
|
108
|
-
}, ref) => {
|
|
109
|
-
const [open, setOpen] = React.useState(false)
|
|
110
|
-
|
|
111
|
-
// Determine display text
|
|
112
|
-
const getDisplayText = () => {
|
|
113
|
-
if (mode === "single") {
|
|
114
|
-
return selected ? formatDate(selected as Date) : null
|
|
115
|
-
} else if (mode === "multiple") {
|
|
116
|
-
const dates = (selected as Date[]) || []
|
|
117
|
-
return dates.length > 0 ? formatMultiple(dates) : null
|
|
118
|
-
} else {
|
|
119
|
-
const dates = (selected as Date[]) || []
|
|
120
|
-
return dates.length > 0 ? formatRange(dates) : null
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const displayText = getDisplayText()
|
|
125
|
-
const defaultPlaceholder = mode === "single" ? "Pick a date"
|
|
126
|
-
: mode === "multiple" ? "Pick dates"
|
|
127
|
-
: "Pick a date range"
|
|
128
|
-
|
|
129
|
-
// Presets for Range Mode
|
|
130
|
-
const presets = [
|
|
131
|
-
{
|
|
132
|
-
label: "Last 7 Days",
|
|
133
|
-
getValue: () => {
|
|
134
|
-
const today = new Date()
|
|
135
|
-
return [addDays(today, -7), today]
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
label: "Last 30 Days",
|
|
140
|
-
getValue: () => {
|
|
141
|
-
const today = new Date()
|
|
142
|
-
return [addDays(today, -30), today]
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
label: "This Month",
|
|
147
|
-
getValue: () => {
|
|
148
|
-
const today = new Date()
|
|
149
|
-
return [startOfMonth(today), endOfMonth(today)]
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
// Handle selection
|
|
155
|
-
const handleSelect = (value: any) => {
|
|
156
|
-
if (mode === "single") {
|
|
157
|
-
(onSelect as ((date: Date | undefined) => void))?.(value)
|
|
158
|
-
setOpen(false) // Close on single selection
|
|
159
|
-
} else if (mode === "multiple") {
|
|
160
|
-
(onSelect as ((dates: Date[]) => void))?.(value || [])
|
|
161
|
-
} else {
|
|
162
|
-
const dates = value || []
|
|
163
|
-
; (onSelect as ((dates: Date[]) => void))?.(dates)
|
|
164
|
-
// Do not close automatically for range to allow adjustment
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const handlePresetSelect = (preset: { getValue: () => Date[] }) => {
|
|
169
|
-
if (mode === "range") {
|
|
170
|
-
const dates = preset.getValue()
|
|
171
|
-
; (onSelect as ((dates: Date[]) => void))?.(dates)
|
|
172
|
-
// Update internal state if uncontrolled (not covered here fully but ensures trigger updates if parent consumes correctly)
|
|
173
|
-
// For this component to be fully controlled, parent must pass `selected`.
|
|
174
|
-
// If we want it to close on preset select:
|
|
175
|
-
setOpen(false)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return (
|
|
180
|
-
<Popover open={open} onOpenChange={setOpen}>
|
|
181
|
-
<PopoverTrigger asChild>
|
|
182
|
-
<Button
|
|
183
|
-
ref={ref}
|
|
184
|
-
variant="outline"
|
|
185
|
-
disabled={disabled}
|
|
186
|
-
className={cn(
|
|
187
|
-
"w-[260px] justify-start text-left font-normal",
|
|
188
|
-
!displayText && "text-muted-foreground",
|
|
189
|
-
className
|
|
190
|
-
)}
|
|
191
|
-
>
|
|
192
|
-
<CalendarIcon />
|
|
193
|
-
{displayText || <span>{placeholder || defaultPlaceholder}</span>}
|
|
194
|
-
</Button>
|
|
195
|
-
</PopoverTrigger>
|
|
196
|
-
<PopoverContent className="w-auto p-0" align="end">
|
|
197
|
-
<div className="flex">
|
|
198
|
-
{mode === "range" && (
|
|
199
|
-
<div className="border-r p-2 space-y-1 w-[140px]">
|
|
200
|
-
{presets.map((preset) => (
|
|
201
|
-
<Button
|
|
202
|
-
key={preset.label}
|
|
203
|
-
variant="ghost"
|
|
204
|
-
className="w-full justify-start font-normal"
|
|
205
|
-
onClick={() => handlePresetSelect(preset)}
|
|
206
|
-
>
|
|
207
|
-
{preset.label}
|
|
208
|
-
</Button>
|
|
209
|
-
))}
|
|
210
|
-
</div>
|
|
211
|
-
)}
|
|
212
|
-
<Calendar
|
|
213
|
-
mode={mode}
|
|
214
|
-
numberOfMonths={numberOfMonths}
|
|
215
|
-
size={size}
|
|
216
|
-
selected={selected}
|
|
217
|
-
onSelect={handleSelect}
|
|
218
|
-
className="rounded-md border-0 shadow-none"
|
|
219
|
-
/>
|
|
220
|
-
</div>
|
|
221
|
-
</PopoverContent>
|
|
222
|
-
</Popover>
|
|
223
|
-
)
|
|
224
|
-
}
|
|
225
|
-
)
|
|
226
|
-
DatePicker.displayName = "DatePicker"
|
|
227
|
-
|
|
228
|
-
export { DatePicker, type DatePickerProps }
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { addDays, startOfMonth, endOfMonth } from "date-fns"
|
|
5
|
+
import { Calendar } from "./calendar"
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover"
|
|
7
|
+
import { Button } from "./button"
|
|
8
|
+
import { cn } from "@/lib/utils"
|
|
9
|
+
|
|
10
|
+
// Calendar icon
|
|
11
|
+
const CalendarIcon = () => (
|
|
12
|
+
<svg
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
width="16"
|
|
15
|
+
height="16"
|
|
16
|
+
viewBox="0 0 24 24"
|
|
17
|
+
fill="none"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
strokeWidth="2"
|
|
20
|
+
strokeLinecap="round"
|
|
21
|
+
strokeLinejoin="round"
|
|
22
|
+
className="mr-2 h-4 w-4 opacity-50"
|
|
23
|
+
>
|
|
24
|
+
<rect width="18" height="18" x="3" y="4" rx="2" ry="2" />
|
|
25
|
+
<line x1="16" x2="16" y1="2" y2="6" />
|
|
26
|
+
<line x1="8" x2="8" y1="2" y2="6" />
|
|
27
|
+
<line x1="3" x2="21" y1="10" y2="10" />
|
|
28
|
+
</svg>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// Format helpers
|
|
32
|
+
const formatDate = (date: Date) => {
|
|
33
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const formatRange = (dates: Date[]) => {
|
|
37
|
+
if (dates.length === 0) return null
|
|
38
|
+
if (dates.length === 1) return formatDate(dates[0])
|
|
39
|
+
return `${formatDate(dates[0])} → ${formatDate(dates[1])}`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const formatMultiple = (dates: Date[]) => {
|
|
43
|
+
if (dates.length === 0) return null
|
|
44
|
+
return `${dates.length} date${dates.length > 1 ? 's' : ''} selected`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// DatePicker Props
|
|
48
|
+
interface DatePickerBaseProps {
|
|
49
|
+
/** Placeholder text when no date selected */
|
|
50
|
+
placeholder?: string
|
|
51
|
+
/** Whether the picker is disabled */
|
|
52
|
+
disabled?: boolean
|
|
53
|
+
/** Custom class name for the trigger button */
|
|
54
|
+
className?: string
|
|
55
|
+
/** Number of months to display */
|
|
56
|
+
numberOfMonths?: 1 | 2
|
|
57
|
+
/** Calendar size */
|
|
58
|
+
size?: "xs" | "sm" | "default" | "md" | "lg"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface DatePickerSingleProps extends DatePickerBaseProps {
|
|
62
|
+
mode?: "single"
|
|
63
|
+
selected?: Date
|
|
64
|
+
onSelect?: (date: Date | undefined) => void
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface DatePickerMultipleProps extends DatePickerBaseProps {
|
|
68
|
+
mode: "multiple"
|
|
69
|
+
selected?: Date[]
|
|
70
|
+
onSelect?: (dates: Date[]) => void
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface DatePickerRangeProps extends DatePickerBaseProps {
|
|
74
|
+
mode: "range"
|
|
75
|
+
selected?: Date[]
|
|
76
|
+
onSelect?: (dates: Date[]) => void
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type DatePickerProps = DatePickerSingleProps | DatePickerMultipleProps | DatePickerRangeProps
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* DatePicker - A complete date picker component
|
|
83
|
+
*
|
|
84
|
+
* Combines Calendar + Popover for a ready-to-use date selection experience.
|
|
85
|
+
* Supports single, multiple, and range selection modes.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* // Single date
|
|
89
|
+
* <DatePicker selected={date} onSelect={setDate} />
|
|
90
|
+
*
|
|
91
|
+
* // Multiple dates
|
|
92
|
+
* <DatePicker mode="multiple" selected={dates} onSelect={setDates} />
|
|
93
|
+
*
|
|
94
|
+
* // Date range with dual months
|
|
95
|
+
* <DatePicker mode="range" numberOfMonths={2} selected={range} onSelect={setRange} />
|
|
96
|
+
*/
|
|
97
|
+
const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
|
|
98
|
+
({
|
|
99
|
+
mode = "single",
|
|
100
|
+
selected,
|
|
101
|
+
onSelect,
|
|
102
|
+
placeholder,
|
|
103
|
+
disabled = false,
|
|
104
|
+
className,
|
|
105
|
+
numberOfMonths = 1,
|
|
106
|
+
size = "default",
|
|
107
|
+
...props
|
|
108
|
+
}, ref) => {
|
|
109
|
+
const [open, setOpen] = React.useState(false)
|
|
110
|
+
|
|
111
|
+
// Determine display text
|
|
112
|
+
const getDisplayText = () => {
|
|
113
|
+
if (mode === "single") {
|
|
114
|
+
return selected ? formatDate(selected as Date) : null
|
|
115
|
+
} else if (mode === "multiple") {
|
|
116
|
+
const dates = (selected as Date[]) || []
|
|
117
|
+
return dates.length > 0 ? formatMultiple(dates) : null
|
|
118
|
+
} else {
|
|
119
|
+
const dates = (selected as Date[]) || []
|
|
120
|
+
return dates.length > 0 ? formatRange(dates) : null
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const displayText = getDisplayText()
|
|
125
|
+
const defaultPlaceholder = mode === "single" ? "Pick a date"
|
|
126
|
+
: mode === "multiple" ? "Pick dates"
|
|
127
|
+
: "Pick a date range"
|
|
128
|
+
|
|
129
|
+
// Presets for Range Mode
|
|
130
|
+
const presets = [
|
|
131
|
+
{
|
|
132
|
+
label: "Last 7 Days",
|
|
133
|
+
getValue: () => {
|
|
134
|
+
const today = new Date()
|
|
135
|
+
return [addDays(today, -7), today]
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
label: "Last 30 Days",
|
|
140
|
+
getValue: () => {
|
|
141
|
+
const today = new Date()
|
|
142
|
+
return [addDays(today, -30), today]
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: "This Month",
|
|
147
|
+
getValue: () => {
|
|
148
|
+
const today = new Date()
|
|
149
|
+
return [startOfMonth(today), endOfMonth(today)]
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
// Handle selection
|
|
155
|
+
const handleSelect = (value: any) => {
|
|
156
|
+
if (mode === "single") {
|
|
157
|
+
(onSelect as ((date: Date | undefined) => void))?.(value)
|
|
158
|
+
setOpen(false) // Close on single selection
|
|
159
|
+
} else if (mode === "multiple") {
|
|
160
|
+
(onSelect as ((dates: Date[]) => void))?.(value || [])
|
|
161
|
+
} else {
|
|
162
|
+
const dates = value || []
|
|
163
|
+
; (onSelect as ((dates: Date[]) => void))?.(dates)
|
|
164
|
+
// Do not close automatically for range to allow adjustment
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const handlePresetSelect = (preset: { getValue: () => Date[] }) => {
|
|
169
|
+
if (mode === "range") {
|
|
170
|
+
const dates = preset.getValue()
|
|
171
|
+
; (onSelect as ((dates: Date[]) => void))?.(dates)
|
|
172
|
+
// Update internal state if uncontrolled (not covered here fully but ensures trigger updates if parent consumes correctly)
|
|
173
|
+
// For this component to be fully controlled, parent must pass `selected`.
|
|
174
|
+
// If we want it to close on preset select:
|
|
175
|
+
setOpen(false)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
181
|
+
<PopoverTrigger asChild>
|
|
182
|
+
<Button
|
|
183
|
+
ref={ref}
|
|
184
|
+
variant="outline"
|
|
185
|
+
disabled={disabled}
|
|
186
|
+
className={cn(
|
|
187
|
+
"w-[260px] justify-start text-left font-normal",
|
|
188
|
+
!displayText && "text-muted-foreground",
|
|
189
|
+
className
|
|
190
|
+
)}
|
|
191
|
+
>
|
|
192
|
+
<CalendarIcon />
|
|
193
|
+
{displayText || <span>{placeholder || defaultPlaceholder}</span>}
|
|
194
|
+
</Button>
|
|
195
|
+
</PopoverTrigger>
|
|
196
|
+
<PopoverContent className="w-auto p-0" align="end">
|
|
197
|
+
<div className="flex">
|
|
198
|
+
{mode === "range" && (
|
|
199
|
+
<div className="border-r p-2 space-y-1 w-[140px]">
|
|
200
|
+
{presets.map((preset) => (
|
|
201
|
+
<Button
|
|
202
|
+
key={preset.label}
|
|
203
|
+
variant="ghost"
|
|
204
|
+
className="w-full justify-start font-normal"
|
|
205
|
+
onClick={() => handlePresetSelect(preset)}
|
|
206
|
+
>
|
|
207
|
+
{preset.label}
|
|
208
|
+
</Button>
|
|
209
|
+
))}
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
<Calendar
|
|
213
|
+
mode={mode}
|
|
214
|
+
numberOfMonths={numberOfMonths}
|
|
215
|
+
size={size}
|
|
216
|
+
selected={selected}
|
|
217
|
+
onSelect={handleSelect}
|
|
218
|
+
className="rounded-md border-0 shadow-none"
|
|
219
|
+
/>
|
|
220
|
+
</div>
|
|
221
|
+
</PopoverContent>
|
|
222
|
+
</Popover>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
DatePicker.displayName = "DatePicker"
|
|
227
|
+
|
|
228
|
+
export { DatePicker, type DatePickerProps }
|