@refraction-ui/astro 0.15.1 → 0.15.2
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.
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
3
|
-
createDatePicker,
|
|
4
|
-
formatDate,
|
|
5
|
-
datePickerTriggerStyles,
|
|
6
|
-
datePickerDropdownStyles,
|
|
7
|
-
datePickerDayVariants,
|
|
8
|
-
datePickerTimeStyles,
|
|
9
|
-
datePickerTimeInputStyles,
|
|
10
|
-
type CalendarDay,
|
|
11
|
-
} from '../date-picker/index.ts'
|
|
12
2
|
import { cn } from '../shared/index.ts'
|
|
3
|
+
import { datePickerTriggerStyles } from '../date-picker/index.ts'
|
|
13
4
|
|
|
14
5
|
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
15
6
|
/** Selected date value (ISO string) */
|
|
@@ -20,7 +11,7 @@ interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
|
20
11
|
maxDate?: string
|
|
21
12
|
/** Show time selection inputs */
|
|
22
13
|
showTime?: boolean
|
|
23
|
-
/** Date format string */
|
|
14
|
+
/** Date format string (ignored by native input, but kept for interface compatibility) */
|
|
24
15
|
format?: string
|
|
25
16
|
/** Placeholder text */
|
|
26
17
|
placeholder?: string
|
|
@@ -34,365 +25,58 @@ const {
|
|
|
34
25
|
maxDate,
|
|
35
26
|
showTime = false,
|
|
36
27
|
format,
|
|
37
|
-
placeholder
|
|
28
|
+
placeholder,
|
|
38
29
|
disabled = false,
|
|
39
30
|
class: className,
|
|
40
31
|
...props
|
|
41
32
|
} = Astro.props
|
|
42
33
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const displayValue = api.formatDisplay()
|
|
57
|
-
const DAY_HEADERS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
|
|
58
|
-
const currentMonth = api.state.currentMonth
|
|
59
|
-
const monthLabel = currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
|
|
60
|
-
|
|
61
|
-
function getDayState(day: CalendarDay): 'default' | 'selected' | 'today' | 'disabled' | 'outside' {
|
|
62
|
-
if (day.isDisabled) return 'disabled'
|
|
63
|
-
if (day.isSelected) return 'selected'
|
|
64
|
-
if (day.isToday) return 'today'
|
|
65
|
-
if (!day.isCurrentMonth) return 'outside'
|
|
66
|
-
return 'default'
|
|
34
|
+
const sliceLen = showTime ? 16 : 10
|
|
35
|
+
|
|
36
|
+
// Format to YYYY-MM-DD or YYYY-MM-DDTHH:mm
|
|
37
|
+
function formatDateStr(isoStr?: string) {
|
|
38
|
+
if (!isoStr) return undefined
|
|
39
|
+
try {
|
|
40
|
+
const d = new Date(isoStr)
|
|
41
|
+
const offset = d.getTimezoneOffset() * 60000
|
|
42
|
+
const localDate = new Date(d.getTime() - offset)
|
|
43
|
+
return localDate.toISOString().slice(0, sliceLen)
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return undefined
|
|
46
|
+
}
|
|
67
47
|
}
|
|
48
|
+
|
|
49
|
+
const parsedValue = formatDateStr(value)
|
|
50
|
+
const parsedMin = formatDateStr(minDate)
|
|
51
|
+
const parsedMax = formatDateStr(maxDate)
|
|
68
52
|
---
|
|
69
53
|
|
|
70
|
-
<
|
|
54
|
+
<input
|
|
55
|
+
type={showTime ? "datetime-local" : "date"}
|
|
56
|
+
class={cn(datePickerTriggerStyles, "justify-start text-left font-normal w-[280px]", className)}
|
|
57
|
+
value={parsedValue}
|
|
58
|
+
min={parsedMin}
|
|
59
|
+
max={parsedMax}
|
|
60
|
+
disabled={disabled}
|
|
61
|
+
placeholder={placeholder}
|
|
71
62
|
data-rfr-datepicker
|
|
72
|
-
data-rfr-datepicker-value={value || ''}
|
|
73
|
-
data-rfr-datepicker-min={minDate || ''}
|
|
74
|
-
data-rfr-datepicker-max={maxDate || ''}
|
|
75
|
-
data-rfr-datepicker-show-time={showTime}
|
|
76
|
-
data-rfr-datepicker-format={format || (showTime ? 'YYYY-MM-DD HH:mm' : 'YYYY-MM-DD')}
|
|
77
|
-
data-rfr-datepicker-placeholder={placeholder}
|
|
78
|
-
data-rfr-datepicker-year={currentMonth.getFullYear()}
|
|
79
|
-
data-rfr-datepicker-month={currentMonth.getMonth()}
|
|
80
|
-
data-state="closed"
|
|
81
|
-
class={cn('relative inline-block', className)}
|
|
82
63
|
{...props}
|
|
83
|
-
|
|
84
|
-
<!-- Trigger button -->
|
|
85
|
-
<button
|
|
86
|
-
type="button"
|
|
87
|
-
data-rfr-datepicker-trigger
|
|
88
|
-
class={cn(datePickerTriggerStyles, disabled && 'opacity-50 cursor-not-allowed')}
|
|
89
|
-
aria-expanded="false"
|
|
90
|
-
aria-haspopup="dialog"
|
|
91
|
-
disabled={disabled}
|
|
92
|
-
>{displayValue}</button>
|
|
93
|
-
|
|
94
|
-
<!-- Dropdown (initially hidden) -->
|
|
95
|
-
<div
|
|
96
|
-
data-rfr-datepicker-dropdown
|
|
97
|
-
class={datePickerDropdownStyles}
|
|
98
|
-
role="dialog"
|
|
99
|
-
aria-modal="true"
|
|
100
|
-
hidden
|
|
101
|
-
>
|
|
102
|
-
<!-- Month navigation -->
|
|
103
|
-
<div class="flex items-center justify-between mb-3">
|
|
104
|
-
<button
|
|
105
|
-
type="button"
|
|
106
|
-
data-rfr-datepicker-prev
|
|
107
|
-
class="p-1 hover:bg-accent rounded cursor-pointer"
|
|
108
|
-
aria-label="Previous month"
|
|
109
|
-
>‹</button>
|
|
110
|
-
<span data-rfr-datepicker-label class="text-sm font-medium">{monthLabel}</span>
|
|
111
|
-
<button
|
|
112
|
-
type="button"
|
|
113
|
-
data-rfr-datepicker-next
|
|
114
|
-
class="p-1 hover:bg-accent rounded cursor-pointer"
|
|
115
|
-
aria-label="Next month"
|
|
116
|
-
>›</button>
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
<!-- Calendar grid -->
|
|
120
|
-
<div class="grid grid-cols-7 gap-0 text-center" role="grid">
|
|
121
|
-
{DAY_HEADERS.map((d) => (
|
|
122
|
-
<div class="text-xs text-muted-foreground py-1 font-medium">{d}</div>
|
|
123
|
-
))}
|
|
124
|
-
<div data-rfr-datepicker-grid class="contents">
|
|
125
|
-
{api.days.map((day) => {
|
|
126
|
-
const state = getDayState(day)
|
|
127
|
-
return (
|
|
128
|
-
<button
|
|
129
|
-
type="button"
|
|
130
|
-
class={datePickerDayVariants({ state })}
|
|
131
|
-
disabled={day.isDisabled}
|
|
132
|
-
data-rfr-datepicker-day
|
|
133
|
-
data-date={day.date.toISOString()}
|
|
134
|
-
aria-selected={day.isSelected}
|
|
135
|
-
aria-disabled={day.isDisabled}
|
|
136
|
-
>{day.date.getDate()}</button>
|
|
137
|
-
)
|
|
138
|
-
})}
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
<!-- Time inputs (optional) -->
|
|
143
|
-
{showTime && (
|
|
144
|
-
<div class={datePickerTimeStyles} data-rfr-datepicker-time>
|
|
145
|
-
<span class="text-sm text-muted-foreground">Time:</span>
|
|
146
|
-
<input
|
|
147
|
-
type="number"
|
|
148
|
-
min="0"
|
|
149
|
-
max="23"
|
|
150
|
-
value={parsedValue ? parsedValue.getHours() : 0}
|
|
151
|
-
class={datePickerTimeInputStyles}
|
|
152
|
-
aria-label="Hours"
|
|
153
|
-
data-rfr-datepicker-hours
|
|
154
|
-
/>
|
|
155
|
-
<span class="text-muted-foreground">:</span>
|
|
156
|
-
<input
|
|
157
|
-
type="number"
|
|
158
|
-
min="0"
|
|
159
|
-
max="59"
|
|
160
|
-
value={parsedValue ? parsedValue.getMinutes() : 0}
|
|
161
|
-
class={datePickerTimeInputStyles}
|
|
162
|
-
aria-label="Minutes"
|
|
163
|
-
data-rfr-datepicker-minutes
|
|
164
|
-
/>
|
|
165
|
-
</div>
|
|
166
|
-
)}
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
64
|
+
/>
|
|
169
65
|
|
|
170
66
|
<script>
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
'
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
function isSameDay(a: Date, b: Date): boolean {
|
|
182
|
-
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function padZero(n: number): string {
|
|
186
|
-
return n < 10 ? `0${n}` : `${n}`
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function formatDateStr(date: Date | null, fmt: string, showTime: boolean): string {
|
|
190
|
-
if (!date) return ''
|
|
191
|
-
let result = fmt
|
|
192
|
-
.replace('YYYY', String(date.getFullYear()))
|
|
193
|
-
.replace('MM', padZero(date.getMonth() + 1))
|
|
194
|
-
.replace('DD', padZero(date.getDate()))
|
|
195
|
-
if (showTime) {
|
|
196
|
-
result = result.replace('HH', padZero(date.getHours())).replace('mm', padZero(date.getMinutes()))
|
|
197
|
-
}
|
|
198
|
-
return result
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function isDateDisabled(date: Date, minStr: string, maxStr: string): boolean {
|
|
202
|
-
if (minStr) {
|
|
203
|
-
const min = new Date(minStr)
|
|
204
|
-
const startOfMin = new Date(min.getFullYear(), min.getMonth(), min.getDate())
|
|
205
|
-
if (date < startOfMin) return true
|
|
206
|
-
}
|
|
207
|
-
if (maxStr) {
|
|
208
|
-
const max = new Date(maxStr)
|
|
209
|
-
const endOfMax = new Date(max.getFullYear(), max.getMonth(), max.getDate(), 23, 59, 59, 999)
|
|
210
|
-
if (date > endOfMax) return true
|
|
211
|
-
}
|
|
212
|
-
return false
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function renderDatePickerGrid(root: HTMLElement) {
|
|
216
|
-
const year = parseInt(root.getAttribute('data-rfr-datepicker-year') || '0', 10)
|
|
217
|
-
const month = parseInt(root.getAttribute('data-rfr-datepicker-month') || '0', 10)
|
|
218
|
-
const valueStr = root.getAttribute('data-rfr-datepicker-value') || ''
|
|
219
|
-
const minStr = root.getAttribute('data-rfr-datepicker-min') || ''
|
|
220
|
-
const maxStr = root.getAttribute('data-rfr-datepicker-max') || ''
|
|
221
|
-
const selectedDate = valueStr ? new Date(valueStr) : null
|
|
222
|
-
const today = new Date()
|
|
223
|
-
|
|
224
|
-
const label = root.querySelector<HTMLElement>('[data-rfr-datepicker-label]')
|
|
225
|
-
if (label) label.textContent = `${MONTH_NAMES[month]} ${year}`
|
|
226
|
-
|
|
227
|
-
const grid = root.querySelector<HTMLElement>('[data-rfr-datepicker-grid]')
|
|
228
|
-
if (!grid) return
|
|
229
|
-
|
|
230
|
-
grid.innerHTML = ''
|
|
231
|
-
const first = new Date(year, month, 1)
|
|
232
|
-
const startDow = first.getDay()
|
|
233
|
-
const gridStart = new Date(year, month, 1 - startDow)
|
|
234
|
-
|
|
235
|
-
for (let i = 0; i < 42; i++) {
|
|
236
|
-
const date = new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + i)
|
|
237
|
-
const isToday = isSameDay(date, today)
|
|
238
|
-
const isSelected = selectedDate ? isSameDay(date, selectedDate) : false
|
|
239
|
-
const isCurrentMonth = date.getMonth() === month
|
|
240
|
-
const isDisabled = isDateDisabled(date, minStr, maxStr)
|
|
241
|
-
|
|
242
|
-
let variant = DAY_VARIANT_DEFAULT
|
|
243
|
-
if (isDisabled) variant = DAY_VARIANT_DISABLED
|
|
244
|
-
else if (isSelected) variant = DAY_VARIANT_SELECTED
|
|
245
|
-
else if (isToday) variant = DAY_VARIANT_TODAY
|
|
246
|
-
else if (!isCurrentMonth) variant = DAY_VARIANT_OUTSIDE
|
|
247
|
-
|
|
248
|
-
const btn = document.createElement('button')
|
|
249
|
-
btn.type = 'button'
|
|
250
|
-
btn.className = variant
|
|
251
|
-
btn.disabled = isDisabled
|
|
252
|
-
btn.setAttribute('data-rfr-datepicker-day', '')
|
|
253
|
-
btn.setAttribute('data-date', date.toISOString())
|
|
254
|
-
btn.setAttribute('aria-selected', String(isSelected))
|
|
255
|
-
btn.setAttribute('aria-disabled', String(isDisabled))
|
|
256
|
-
btn.textContent = String(date.getDate())
|
|
257
|
-
grid.appendChild(btn)
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function initDatePickers() {
|
|
262
|
-
document.querySelectorAll<HTMLElement>('[data-rfr-datepicker]').forEach((root) => {
|
|
263
|
-
if (root.hasAttribute('data-rfr-datepicker-init')) return
|
|
264
|
-
root.setAttribute('data-rfr-datepicker-init', '')
|
|
265
|
-
|
|
266
|
-
const trigger = root.querySelector<HTMLButtonElement>('[data-rfr-datepicker-trigger]')
|
|
267
|
-
const dropdown = root.querySelector<HTMLElement>('[data-rfr-datepicker-dropdown]')
|
|
268
|
-
const showTime = root.getAttribute('data-rfr-datepicker-show-time') === 'true'
|
|
269
|
-
const fmt = root.getAttribute('data-rfr-datepicker-format') || 'YYYY-MM-DD'
|
|
270
|
-
const placeholder = root.getAttribute('data-rfr-datepicker-placeholder') || 'Select date...'
|
|
271
|
-
|
|
272
|
-
let isOpen = false
|
|
273
|
-
|
|
274
|
-
function setOpen(open: boolean) {
|
|
275
|
-
isOpen = open
|
|
276
|
-
root.setAttribute('data-state', open ? 'open' : 'closed')
|
|
277
|
-
trigger?.setAttribute('aria-expanded', String(open))
|
|
278
|
-
if (dropdown) {
|
|
279
|
-
if (open) {
|
|
280
|
-
dropdown.removeAttribute('hidden')
|
|
281
|
-
renderDatePickerGrid(root)
|
|
282
|
-
} else {
|
|
283
|
-
dropdown.setAttribute('hidden', '')
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function updateTriggerLabel() {
|
|
289
|
-
const valueStr = root.getAttribute('data-rfr-datepicker-value') || ''
|
|
290
|
-
const date = valueStr ? new Date(valueStr) : null
|
|
291
|
-
if (trigger) {
|
|
292
|
-
trigger.textContent = date ? formatDateStr(date, fmt, showTime) : placeholder
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Toggle dropdown
|
|
297
|
-
trigger?.addEventListener('click', () => {
|
|
298
|
-
if (trigger.disabled) return
|
|
299
|
-
setOpen(!isOpen)
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
// Prev month
|
|
303
|
-
root.querySelector('[data-rfr-datepicker-prev]')?.addEventListener('click', () => {
|
|
304
|
-
const year = parseInt(root.getAttribute('data-rfr-datepicker-year') || '0', 10)
|
|
305
|
-
const month = parseInt(root.getAttribute('data-rfr-datepicker-month') || '0', 10)
|
|
306
|
-
const prev = new Date(year, month - 1, 1)
|
|
307
|
-
root.setAttribute('data-rfr-datepicker-year', String(prev.getFullYear()))
|
|
308
|
-
root.setAttribute('data-rfr-datepicker-month', String(prev.getMonth()))
|
|
309
|
-
renderDatePickerGrid(root)
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
// Next month
|
|
313
|
-
root.querySelector('[data-rfr-datepicker-next]')?.addEventListener('click', () => {
|
|
314
|
-
const year = parseInt(root.getAttribute('data-rfr-datepicker-year') || '0', 10)
|
|
315
|
-
const month = parseInt(root.getAttribute('data-rfr-datepicker-month') || '0', 10)
|
|
316
|
-
const next = new Date(year, month + 1, 1)
|
|
317
|
-
root.setAttribute('data-rfr-datepicker-year', String(next.getFullYear()))
|
|
318
|
-
root.setAttribute('data-rfr-datepicker-month', String(next.getMonth()))
|
|
319
|
-
renderDatePickerGrid(root)
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
// Day click (delegated on grid's parent)
|
|
323
|
-
const gridParent = root.querySelector('[role="grid"]')
|
|
324
|
-
gridParent?.addEventListener('click', (e) => {
|
|
325
|
-
const btn = (e.target as HTMLElement).closest<HTMLButtonElement>('[data-rfr-datepicker-day]')
|
|
326
|
-
if (!btn || btn.disabled) return
|
|
327
|
-
const dateStr = btn.getAttribute('data-date')
|
|
328
|
-
if (!dateStr) return
|
|
329
|
-
|
|
330
|
-
const selectedDate = new Date(dateStr)
|
|
331
|
-
// Preserve existing time if showTime
|
|
332
|
-
if (showTime) {
|
|
333
|
-
const hoursInput = root.querySelector<HTMLInputElement>('[data-rfr-datepicker-hours]')
|
|
334
|
-
const minutesInput = root.querySelector<HTMLInputElement>('[data-rfr-datepicker-minutes]')
|
|
335
|
-
if (hoursInput) selectedDate.setHours(parseInt(hoursInput.value, 10) || 0)
|
|
336
|
-
if (minutesInput) selectedDate.setMinutes(parseInt(minutesInput.value, 10) || 0)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
root.setAttribute('data-rfr-datepicker-value', selectedDate.toISOString())
|
|
340
|
-
updateTriggerLabel()
|
|
341
|
-
renderDatePickerGrid(root)
|
|
342
|
-
|
|
343
|
-
if (!showTime) {
|
|
344
|
-
setOpen(false)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
root.dispatchEvent(new CustomEvent('rfr-date-change', {
|
|
348
|
-
detail: { date: selectedDate, iso: selectedDate.toISOString() },
|
|
349
|
-
bubbles: true,
|
|
350
|
-
}))
|
|
351
|
-
})
|
|
352
|
-
|
|
353
|
-
// Time input changes
|
|
354
|
-
const hoursInput = root.querySelector<HTMLInputElement>('[data-rfr-datepicker-hours]')
|
|
355
|
-
const minutesInput = root.querySelector<HTMLInputElement>('[data-rfr-datepicker-minutes]')
|
|
356
|
-
|
|
357
|
-
function handleTimeChange() {
|
|
358
|
-
const valueStr = root.getAttribute('data-rfr-datepicker-value') || ''
|
|
359
|
-
const date = valueStr ? new Date(valueStr) : new Date()
|
|
360
|
-
if (hoursInput) {
|
|
361
|
-
const h = parseInt(hoursInput.value, 10)
|
|
362
|
-
if (!isNaN(h) && h >= 0 && h <= 23) date.setHours(h)
|
|
363
|
-
}
|
|
364
|
-
if (minutesInput) {
|
|
365
|
-
const m = parseInt(minutesInput.value, 10)
|
|
366
|
-
if (!isNaN(m) && m >= 0 && m <= 59) date.setMinutes(m)
|
|
367
|
-
}
|
|
368
|
-
root.setAttribute('data-rfr-datepicker-value', date.toISOString())
|
|
369
|
-
updateTriggerLabel()
|
|
370
|
-
root.dispatchEvent(new CustomEvent('rfr-date-change', {
|
|
67
|
+
document.querySelectorAll<HTMLInputElement>('[data-rfr-datepicker]').forEach((input) => {
|
|
68
|
+
if (input.hasAttribute('data-rfr-datepicker-init')) return
|
|
69
|
+
input.setAttribute('data-rfr-datepicker-init', '')
|
|
70
|
+
|
|
71
|
+
input.addEventListener('change', (e) => {
|
|
72
|
+
const target = e.target as HTMLInputElement
|
|
73
|
+
if (target.value) {
|
|
74
|
+
const date = new Date(target.value)
|
|
75
|
+
input.dispatchEvent(new CustomEvent('rfr-date-change', {
|
|
371
76
|
detail: { date, iso: date.toISOString() },
|
|
372
77
|
bubbles: true,
|
|
373
78
|
}))
|
|
374
79
|
}
|
|
375
|
-
|
|
376
|
-
hoursInput?.addEventListener('change', handleTimeChange)
|
|
377
|
-
minutesInput?.addEventListener('change', handleTimeChange)
|
|
378
|
-
|
|
379
|
-
// Close on outside click
|
|
380
|
-
document.addEventListener('mousedown', (e) => {
|
|
381
|
-
if (isOpen && !root.contains(e.target as Node)) {
|
|
382
|
-
setOpen(false)
|
|
383
|
-
}
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
// Close on Escape
|
|
387
|
-
document.addEventListener('keydown', (e) => {
|
|
388
|
-
if (isOpen && e.key === 'Escape') {
|
|
389
|
-
e.preventDefault()
|
|
390
|
-
setOpen(false)
|
|
391
|
-
}
|
|
392
|
-
})
|
|
393
80
|
})
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
initDatePickers()
|
|
397
|
-
document.addEventListener('astro:after-swap', initDatePickers)
|
|
81
|
+
})
|
|
398
82
|
</script>
|
|
@@ -4,7 +4,7 @@ export const datePickerTriggerStyles =
|
|
|
4
4
|
'inline-flex items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring cursor-pointer'
|
|
5
5
|
|
|
6
6
|
export const datePickerDropdownStyles =
|
|
7
|
-
'absolute z-50 mt-1 rounded-md border bg-popover p-4 text-popover-foreground shadow-md'
|
|
7
|
+
'absolute z-50 mt-1 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md'
|
|
8
8
|
|
|
9
9
|
export const datePickerGridStyles = 'w-full border-collapse'
|
|
10
10
|
|
|
@@ -24,6 +24,8 @@ export interface DatePickerProps {
|
|
|
24
24
|
defaultOpen?: boolean
|
|
25
25
|
/** Callback when open changes */
|
|
26
26
|
onOpenChange?: (open: boolean) => void
|
|
27
|
+
/** The currently displayed month (controlled) */
|
|
28
|
+
currentMonth?: Date
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface DatePickerState {
|
|
@@ -149,7 +151,7 @@ export function createDatePicker(props: DatePickerProps = {}): DatePickerAPI {
|
|
|
149
151
|
|
|
150
152
|
const today = new Date()
|
|
151
153
|
let internalOpen = controlledOpen ?? defaultOpen
|
|
152
|
-
let currentMonth = value ? startOfMonth(value) : startOfMonth(today)
|
|
154
|
+
let currentMonth = props.currentMonth ?? (value ? startOfMonth(value) : startOfMonth(today))
|
|
153
155
|
let view: DatePickerView = 'calendar'
|
|
154
156
|
const hours = value ? value.getHours() : 0
|
|
155
157
|
const minutes = value ? value.getMinutes() : 0
|