@k3-universe/react-kit 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/index.js +1303 -75
  2. package/dist/kit/builder/form/components/FormBuilder.d.ts +5 -1
  3. package/dist/kit/builder/form/components/FormBuilder.d.ts.map +1 -1
  4. package/dist/kit/builder/form/components/FormBuilderField.d.ts.map +1 -1
  5. package/dist/kit/builder/form/components/fields/DateTimePickerField.d.ts +4 -0
  6. package/dist/kit/builder/form/components/fields/DateTimePickerField.d.ts.map +1 -0
  7. package/dist/kit/builder/form/components/fields/DateTimeRangePickerField.d.ts +4 -0
  8. package/dist/kit/builder/form/components/fields/DateTimeRangePickerField.d.ts.map +1 -0
  9. package/dist/kit/builder/form/components/fields/TimePickerField.d.ts +4 -0
  10. package/dist/kit/builder/form/components/fields/TimePickerField.d.ts.map +1 -0
  11. package/dist/kit/builder/form/components/fields/TimeRangePickerField.d.ts +4 -0
  12. package/dist/kit/builder/form/components/fields/TimeRangePickerField.d.ts.map +1 -0
  13. package/dist/kit/builder/form/components/fields/index.d.ts +4 -0
  14. package/dist/kit/builder/form/components/fields/index.d.ts.map +1 -1
  15. package/dist/kit/components/datetimepicker/DateTimePicker.d.ts +32 -0
  16. package/dist/kit/components/datetimepicker/DateTimePicker.d.ts.map +1 -0
  17. package/dist/kit/components/datetimepicker/DateTimeRangePicker.d.ts +39 -0
  18. package/dist/kit/components/datetimepicker/DateTimeRangePicker.d.ts.map +1 -0
  19. package/dist/kit/components/datetimepicker/index.d.ts +5 -0
  20. package/dist/kit/components/datetimepicker/index.d.ts.map +1 -0
  21. package/dist/kit/components/timepicker/TimePicker.d.ts +26 -0
  22. package/dist/kit/components/timepicker/TimePicker.d.ts.map +1 -0
  23. package/dist/kit/components/timepicker/TimeRangePicker.d.ts +31 -0
  24. package/dist/kit/components/timepicker/TimeRangePicker.d.ts.map +1 -0
  25. package/dist/kit/components/timepicker/index.d.ts +5 -0
  26. package/dist/kit/components/timepicker/index.d.ts.map +1 -0
  27. package/dist/kit/themes/clean-slate.css +16 -0
  28. package/dist/kit/themes/default.css +16 -0
  29. package/dist/kit/themes/minimal-modern.css +16 -0
  30. package/dist/kit/themes/spotify.css +16 -0
  31. package/package.json +1 -1
  32. package/src/kit/builder/form/components/FormBuilder.tsx +17 -0
  33. package/src/kit/builder/form/components/FormBuilderField.tsx +48 -0
  34. package/src/kit/builder/form/components/fields/DateTimePickerField.tsx +33 -0
  35. package/src/kit/builder/form/components/fields/DateTimeRangePickerField.tsx +42 -0
  36. package/src/kit/builder/form/components/fields/TimePickerField.tsx +30 -0
  37. package/src/kit/builder/form/components/fields/TimeRangePickerField.tsx +37 -0
  38. package/src/kit/builder/form/components/fields/index.ts +4 -0
  39. package/src/kit/components/datetimepicker/DateTimePicker.tsx +314 -0
  40. package/src/kit/components/datetimepicker/DateTimeRangePicker.tsx +486 -0
  41. package/src/kit/components/datetimepicker/index.ts +3 -0
  42. package/src/kit/components/timepicker/TimePicker.tsx +311 -0
  43. package/src/kit/components/timepicker/TimeRangePicker.tsx +291 -0
  44. package/src/kit/components/timepicker/index.ts +3 -0
  45. package/src/stories/kit/builder/Form.DateTime.stories.tsx +66 -0
  46. package/src/stories/kit/builder/Form.Time.stories.tsx +64 -0
  47. package/src/stories/kit/components/TimePicker.stories.tsx +69 -0
  48. package/src/stories/kit/components/TimeRangePicker.stories.tsx +37 -0
@@ -0,0 +1,486 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Calendar as CalendarIcon } from 'lucide-react';
5
+ import { cn } from '../../../shadcn/lib/utils';
6
+ import { Button } from '../../../shadcn/ui/button';
7
+ import { Popover, PopoverContent, PopoverTrigger } from '../../../shadcn/ui/popover';
8
+ import { Calendar } from '../../../shadcn/ui/calendar';
9
+ import {
10
+ Select,
11
+ SelectContent,
12
+ SelectItem,
13
+ SelectTrigger,
14
+ SelectValue,
15
+ } from '../../../shadcn/ui/select';
16
+
17
+ export type TimePrecision = 'hour' | 'minute' | 'second';
18
+
19
+ export interface DateTimeRangeValue {
20
+ from?: Date | null;
21
+ to?: Date | null;
22
+ }
23
+
24
+ export interface DateTimeRangePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
25
+ value?: DateTimeRangeValue | null;
26
+ onChange?: (range: DateTimeRangeValue | null) => void;
27
+ placeholder?: string;
28
+ disabled?: boolean;
29
+ minDate?: Date;
30
+ maxDate?: Date;
31
+ disabledDates?: Array<Date | { from: Date; to: Date }>;
32
+ numberOfMonths?: number;
33
+ popoverSide?: 'top' | 'right' | 'bottom' | 'left';
34
+ // time config
35
+ timePrecision?: TimePrecision; // default 'minute'
36
+ hourCycle?: 12 | 24; // default 24
37
+ minuteStep?: number; // default 5
38
+ secondStep?: number; // default 5
39
+ buttonVariant?: React.ComponentProps<typeof Button>['variant'];
40
+ open?: boolean;
41
+ onOpenChange?: (open: boolean) => void;
42
+ showFooter?: boolean; // default true
43
+ cancelLabel?: string; // default 'Cancel'
44
+ applyLabel?: string; // default 'Apply'
45
+ clearLabel?: string; // default 'Clear'
46
+ contentClassName?: string; // custom classes for the inner content container
47
+ }
48
+
49
+ const pad2 = (n: number) => String(n).padStart(2, '0');
50
+ function startOfDay(d: Date) { return new Date(d.getFullYear(), d.getMonth(), d.getDate()); }
51
+ function sameDay(a: Date, b: Date) { return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); }
52
+ function isBefore(date: Date, min?: Date) { return !!(min && date < startOfDay(min)); }
53
+ function isAfter(date: Date, max?: Date) { return !!(max && date > startOfDay(max)); }
54
+ function inDisabled(date: Date, items?: Array<Date | { from: Date; to: Date }>) {
55
+ if (!items || items.length === 0) return false;
56
+ const d = startOfDay(date);
57
+ for (const it of items) {
58
+ if (it instanceof Date) {
59
+ if (sameDay(d, it)) return true;
60
+ } else if (it && 'from' in it && 'to' in it) {
61
+ const from = startOfDay(it.from);
62
+ const to = startOfDay(it.to);
63
+ if (d >= from && d <= to) return true;
64
+ }
65
+ }
66
+ return false;
67
+ }
68
+ function rangeContainsDisabled(from?: Date, to?: Date, items?: Array<Date | { from: Date; to: Date }>) {
69
+ if (!from || !to) return false;
70
+ if (!items || items.length === 0) return false;
71
+ const a = startOfDay(from); const b = startOfDay(to);
72
+ const start = a <= b ? a : b; const end = a <= b ? b : a;
73
+ let cur = start;
74
+ while (cur <= end) {
75
+ if (inDisabled(cur, items)) return true;
76
+ cur = new Date(cur.getFullYear(), cur.getMonth(), cur.getDate() + 1);
77
+ }
78
+ return false;
79
+ }
80
+
81
+ function TimeSelectors({
82
+ label,
83
+ value,
84
+ onChange,
85
+ precision,
86
+ hourCycle,
87
+ minuteStep,
88
+ secondStep,
89
+ disabled,
90
+ compact,
91
+ }: {
92
+ label: string;
93
+ value: Date | null | undefined;
94
+ onChange: (val: Date | null) => void;
95
+ precision: TimePrecision;
96
+ hourCycle: 12 | 24;
97
+ minuteStep: number;
98
+ secondStep: number;
99
+ disabled?: boolean;
100
+ compact?: boolean;
101
+ }) {
102
+ const hours = React.useMemo(() => (hourCycle === 12 ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 24 }, (_, i) => i)), [hourCycle]);
103
+ const minutes = React.useMemo(() => Array.from({ length: Math.ceil(60 / minuteStep) }, (_, i) => i * minuteStep), [minuteStep]);
104
+ const seconds = React.useMemo(() => Array.from({ length: Math.ceil(60 / secondStep) }, (_, i) => i * secondStep), [secondStep]);
105
+ const selectedHour = React.useMemo(() => {
106
+ if (!value) return hourCycle === 12 ? 12 : 0;
107
+ const h = value.getHours();
108
+ return hourCycle === 12 ? (h % 12 === 0 ? 12 : h % 12) : h;
109
+ }, [value, hourCycle]);
110
+ const selectedMinute = value?.getMinutes() ?? 0;
111
+ const selectedSecond = value?.getSeconds() ?? 0;
112
+ const selectedPeriod: 'AM' | 'PM' = value && value.getHours() >= 12 ? 'PM' : 'AM';
113
+
114
+ const setPart = (part: 'hour' | 'minute' | 'second' | 'period', v: number | 'AM' | 'PM') => {
115
+ const base = value
116
+ ? new Date(value)
117
+ : (() => {
118
+ const n = new Date();
119
+ return new Date(n.getFullYear(), n.getMonth(), n.getDate(), 0, 0, 0, 0);
120
+ })();
121
+ if (part === 'hour') {
122
+ let h = Number(v);
123
+ if (hourCycle === 12) {
124
+ const isPM = base.getHours() >= 12;
125
+ h = h % 12;
126
+ base.setHours(isPM ? (h === 12 ? 12 : h + 12) : (h === 12 ? 0 : h));
127
+ } else {
128
+ base.setHours(h);
129
+ }
130
+ } else if (part === 'minute') {
131
+ base.setMinutes(Number(v));
132
+ } else if (part === 'second') {
133
+ base.setSeconds(Number(v));
134
+ } else if (part === 'period' && (v === 'AM' || v === 'PM')) {
135
+ const curH = base.getHours();
136
+ const isAMNow = curH < 12;
137
+ if (v === 'AM' && !isAMNow) base.setHours(curH - 12);
138
+ if (v === 'PM' && isAMNow) base.setHours(curH + 12);
139
+ }
140
+ base.setMilliseconds(0);
141
+ onChange(base);
142
+ };
143
+
144
+ const widthClass = compact ? 'w-16' : 'w-24';
145
+ return (
146
+ <div className={compact ? '' : 'space-y-1'}>
147
+ {!compact && <div className="text-xs text-muted-foreground">{label}</div>}
148
+ <div className="flex items-end gap-2">
149
+ <div className={widthClass}>
150
+ {!compact && <div className="mb-1 block text-xs text-muted-foreground">Hour</div>}
151
+ <Select disabled={disabled} value={String(selectedHour)} onValueChange={(v) => setPart('hour', Number(v))}>
152
+ <SelectTrigger aria-label={`${label} hour`}>
153
+ <SelectValue />
154
+ </SelectTrigger>
155
+ <SelectContent>
156
+ {hours.map((h) => (
157
+ <SelectItem key={h} value={String(h)}>{hourCycle === 12 ? h : pad2(h)}</SelectItem>
158
+ ))}
159
+ </SelectContent>
160
+ </Select>
161
+ </div>
162
+ {(precision === 'minute' || precision === 'second') && (
163
+ <div className={widthClass}>
164
+ {!compact && <div className="mb-1 block text-xs text-muted-foreground">Minute</div>}
165
+ <Select disabled={disabled} value={String(selectedMinute - (selectedMinute % minuteStep))} onValueChange={(v) => setPart('minute', Number(v))}>
166
+ <SelectTrigger aria-label={`${label} minute`}>
167
+ <SelectValue />
168
+ </SelectTrigger>
169
+ <SelectContent>
170
+ {minutes.map((m) => (
171
+ <SelectItem key={m} value={String(m)}>{pad2(m)}</SelectItem>
172
+ ))}
173
+ </SelectContent>
174
+ </Select>
175
+ </div>
176
+ )}
177
+ {precision === 'second' && (
178
+ <div className={widthClass}>
179
+ {!compact && <div className="mb-1 block text-xs text-muted-foreground">Second</div>}
180
+ <Select disabled={disabled} value={String(selectedSecond - (selectedSecond % secondStep))} onValueChange={(v) => setPart('second', Number(v))}>
181
+ <SelectTrigger aria-label={`${label} second`}>
182
+ <SelectValue />
183
+ </SelectTrigger>
184
+ <SelectContent>
185
+ {seconds.map((s) => (
186
+ <SelectItem key={s} value={String(s)}>{pad2(s)}</SelectItem>
187
+ ))}
188
+ </SelectContent>
189
+ </Select>
190
+ </div>
191
+ )}
192
+ {hourCycle === 12 && (
193
+ <div className={widthClass}>
194
+ {!compact && <div className="mb-1 block text-xs text-muted-foreground">Period</div>}
195
+ <Select disabled={disabled} value={selectedPeriod} onValueChange={(v) => setPart('period', v as 'AM' | 'PM')}>
196
+ <SelectTrigger aria-label={`${label} period`}>
197
+ <SelectValue />
198
+ </SelectTrigger>
199
+ <SelectContent>
200
+ <SelectItem value="AM">AM</SelectItem>
201
+ <SelectItem value="PM">PM</SelectItem>
202
+ </SelectContent>
203
+ </Select>
204
+ </div>
205
+ )}
206
+ </div>
207
+ </div>
208
+ );
209
+ }
210
+
211
+ export function DateTimeRangePicker({
212
+ value,
213
+ onChange,
214
+ placeholder = 'Pick a date & time range',
215
+ disabled,
216
+ minDate,
217
+ maxDate,
218
+ disabledDates,
219
+ numberOfMonths = 2,
220
+ popoverSide,
221
+ timePrecision = 'minute',
222
+ hourCycle = 24,
223
+ minuteStep = 5,
224
+ secondStep = 5,
225
+ className,
226
+ buttonVariant = 'outline',
227
+ contentClassName,
228
+ ...props
229
+ }: DateTimeRangePickerProps) {
230
+ const [internalOpen, setInternalOpen] = React.useState(false);
231
+ const isOpen = typeof props.open === 'boolean' ? props.open : internalOpen;
232
+ const setOpen = (o: boolean) => (props.onOpenChange ? props.onOpenChange(o) : setInternalOpen(o));
233
+ const [draft, setDraft] = React.useState<DateTimeRangeValue | null>(value ?? null);
234
+
235
+ React.useEffect(() => {
236
+ if (isOpen) setDraft(value ?? null);
237
+ }, [isOpen, value]);
238
+
239
+ const fmtTime = React.useCallback((d?: Date | null) => {
240
+ if (!d) return '';
241
+ const h = d.getHours();
242
+ const m = d.getMinutes();
243
+ const s = d.getSeconds();
244
+ return hourCycle === 12
245
+ ? `${((h % 12) || 12)}:${pad2(m)}${timePrecision === 'second' ? `:${pad2(s)}` : ''} ${h >= 12 ? 'PM' : 'AM'}`
246
+ : `${pad2(h)}:${pad2(m)}${timePrecision === 'second' ? `:${pad2(s)}` : ''}`;
247
+ }, [hourCycle, timePrecision]);
248
+
249
+ const label = React.useMemo(() => {
250
+ const f = draft?.from ?? value?.from ?? null;
251
+ const t = draft?.to ?? value?.to ?? null;
252
+ if (f && t) {
253
+ const fd = f.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: '2-digit' });
254
+ const td = t.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: '2-digit' });
255
+ return `${fd} ${fmtTime(f)} – ${td} ${fmtTime(t)}`;
256
+ }
257
+ return placeholder;
258
+ }, [draft, value, placeholder, fmtTime]);
259
+
260
+ // Date inputs and parsing (DD / MM / YYYY)
261
+ const fmtDate = React.useCallback((d?: Date | null) => {
262
+ if (!d) return '';
263
+ const dd = String(d.getDate()).padStart(2, '0');
264
+ const mm = String(d.getMonth() + 1).padStart(2, '0');
265
+ const yyyy = d.getFullYear();
266
+ return `${dd} / ${mm} / ${yyyy}`;
267
+ }, []);
268
+ const [fromInput, setFromInput] = React.useState<string>(fmtDate(draft?.from ?? value?.from ?? null));
269
+ const [toInput, setToInput] = React.useState<string>(fmtDate(draft?.to ?? value?.to ?? null));
270
+ React.useEffect(() => {
271
+ if (isOpen) {
272
+ setFromInput(fmtDate(value?.from ?? null));
273
+ setToInput(fmtDate(value?.to ?? null));
274
+ }
275
+ }, [isOpen, value, fmtDate]);
276
+ const maskDate = (raw: string) => {
277
+ const digits = raw.replace(/\D/g, '').slice(0, 8);
278
+ const parts: string[] = [];
279
+ const dd = digits.slice(0, Math.min(2, digits.length)); if (dd) parts.push(dd);
280
+ const mm = digits.length > 2 ? digits.slice(2, Math.min(4, digits.length)) : ''; if (mm) parts.push(mm);
281
+ const yyyy = digits.length > 4 ? digits.slice(4) : ''; if (yyyy) parts.push(yyyy);
282
+ return parts.join(' / ');
283
+ };
284
+ const parseMasked = (masked: string): Date | undefined => {
285
+ const m = masked.match(/^(\d{1,2})\s*\/\s*(\d{1,2})\s*\/\s*(\d{4})$/);
286
+ if (!m) return undefined;
287
+ const dd = Number(m[1]); const mm = Number(m[2]); const yyyy = Number(m[3]);
288
+ if (mm < 1 || mm > 12) return undefined;
289
+ const lastDay = new Date(yyyy, mm, 0).getDate();
290
+ if (dd < 1 || dd > lastDay) return undefined;
291
+ const out = new Date(yyyy, mm - 1, dd);
292
+ if (isBefore(out, minDate) || isAfter(out, maxDate) || inDisabled(out, disabledDates)) return undefined;
293
+ return out;
294
+ };
295
+ const fromParsed = parseMasked(fromInput);
296
+ const toParsed = parseMasked(toInput);
297
+ const mergedFrom = React.useMemo(() => {
298
+ if (fromParsed) {
299
+ const base = draft?.from ?? value?.from ?? null;
300
+ if (base) {
301
+ return new Date(
302
+ fromParsed.getFullYear(),
303
+ fromParsed.getMonth(),
304
+ fromParsed.getDate(),
305
+ base.getHours(),
306
+ base.getMinutes(),
307
+ base.getSeconds()
308
+ );
309
+ }
310
+ return fromParsed;
311
+ }
312
+ return draft?.from ?? undefined;
313
+ }, [fromParsed, draft, value]);
314
+ const mergedTo = React.useMemo(() => {
315
+ if (toParsed) {
316
+ const base = draft?.to ?? value?.to ?? null;
317
+ if (base) {
318
+ return new Date(
319
+ toParsed.getFullYear(),
320
+ toParsed.getMonth(),
321
+ toParsed.getDate(),
322
+ base.getHours(),
323
+ base.getMinutes(),
324
+ base.getSeconds()
325
+ );
326
+ }
327
+ return toParsed;
328
+ }
329
+ return draft?.to ?? undefined;
330
+ }, [toParsed, draft, value]);
331
+ const invalidRange = !mergedFrom || !mergedTo || isBefore(mergedFrom, minDate) || isAfter(mergedTo, maxDate) || mergedFrom > mergedTo || rangeContainsDisabled(mergedFrom, mergedTo, disabledDates);
332
+
333
+ return (
334
+ <div className={cn('w-fit', className)} {...props}>
335
+ <Popover open={isOpen} onOpenChange={setOpen}>
336
+ <PopoverTrigger asChild>
337
+ <Button type="button" disabled={disabled} variant={buttonVariant} className={cn('w-[360px] justify-start text-left font-normal', !value && 'text-muted-foreground')}>
338
+ <CalendarIcon className="mr-2 h-4 w-4" />
339
+ {label}
340
+ </Button>
341
+ </PopoverTrigger>
342
+ <PopoverContent className="w-auto max-w-none p-4" align="start" side={popoverSide ?? 'bottom'} sideOffset={8}>
343
+ <div className={cn('w-fit min-w-0', contentClassName)}>
344
+ {/* Header row: date inputs with inline time selectors beside each */}
345
+ <div className="mb-3 rounded-md border border-input bg-background/50 px-3 py-2">
346
+ <div className="flex flex-wrap items-end gap-3">
347
+ <div className="flex items-end gap-2">
348
+ <input
349
+ type="text"
350
+ inputMode="numeric"
351
+ value={fromInput}
352
+ onChange={(e) => setFromInput(maskDate(e.target.value))}
353
+ onBlur={() => {
354
+ const p = parseMasked(fromInput);
355
+ if (p) {
356
+ const prev = draft?.from ?? null;
357
+ const withTime = new Date(
358
+ p.getFullYear(),
359
+ p.getMonth(),
360
+ p.getDate(),
361
+ prev ? prev.getHours() : 0,
362
+ prev ? prev.getMinutes() : 0,
363
+ prev ? prev.getSeconds() : 0
364
+ );
365
+ setDraft((d) => ({ ...(d ?? {}), from: withTime }));
366
+ }
367
+ }}
368
+ placeholder="dd/mm/yyyy"
369
+ className="h-9 w-40 rounded-md border bg-background px-3 text-sm shadow-xs outline-hidden"
370
+ />
371
+ <TimeSelectors
372
+ label="From"
373
+ compact
374
+ value={draft?.from ?? null}
375
+ onChange={(d) => setDraft((prev) => ({ ...(prev ?? {}), from: d }))}
376
+ precision={timePrecision}
377
+ hourCycle={hourCycle}
378
+ minuteStep={minuteStep}
379
+ secondStep={secondStep}
380
+ disabled={disabled}
381
+ />
382
+ </div>
383
+ <span className="text-muted-foreground">–</span>
384
+ <div className="flex items-end gap-2">
385
+ <input
386
+ type="text"
387
+ inputMode="numeric"
388
+ value={toInput}
389
+ onChange={(e) => setToInput(maskDate(e.target.value))}
390
+ onBlur={() => {
391
+ const p = parseMasked(toInput);
392
+ if (p) {
393
+ const prev = draft?.to ?? null;
394
+ const withTime = new Date(
395
+ p.getFullYear(),
396
+ p.getMonth(),
397
+ p.getDate(),
398
+ prev ? prev.getHours() : 0,
399
+ prev ? prev.getMinutes() : 0,
400
+ prev ? prev.getSeconds() : 0
401
+ );
402
+ setDraft((d) => ({ ...(d ?? {}), to: withTime }));
403
+ }
404
+ }}
405
+ placeholder="dd/mm/yyyy"
406
+ className="h-9 w-40 rounded-md border bg-background px-3 text-sm shadow-xs outline-hidden"
407
+ />
408
+ <TimeSelectors
409
+ label="To"
410
+ compact
411
+ value={draft?.to ?? null}
412
+ onChange={(d) => setDraft((prev) => ({ ...(prev ?? {}), to: d }))}
413
+ precision={timePrecision}
414
+ hourCycle={hourCycle}
415
+ minuteStep={minuteStep}
416
+ secondStep={secondStep}
417
+ disabled={disabled}
418
+ />
419
+ </div>
420
+ </div>
421
+ </div>
422
+
423
+ {/* Calendar */}
424
+ <Calendar
425
+ mode="range"
426
+ numberOfMonths={numberOfMonths}
427
+ selected={draft?.from && draft?.to ? { from: draft.from, to: draft.to } : undefined}
428
+ onSelect={(range) => {
429
+ if (disabled) return;
430
+ if (!range) { setDraft(null); return; }
431
+ const { from, to } = range as { from?: Date; to?: Date };
432
+ if (from && (isBefore(from, minDate) || isAfter(from, maxDate))) return;
433
+ if (to && (isBefore(to, minDate) || isAfter(to, maxDate))) return;
434
+ if (from && to && rangeContainsDisabled(from, to, disabledDates)) return;
435
+ const prevFrom = draft?.from ?? from;
436
+ const prevTo = draft?.to ?? to;
437
+ const nextFrom = from
438
+ ? new Date(from.getFullYear(), from.getMonth(), from.getDate(), prevFrom?.getHours?.() ?? 0, prevFrom?.getMinutes?.() ?? 0, prevFrom?.getSeconds?.() ?? 0)
439
+ : undefined;
440
+ const nextTo = to
441
+ ? new Date(to.getFullYear(), to.getMonth(), to.getDate(), prevTo?.getHours?.() ?? 0, prevTo?.getMinutes?.() ?? 0, prevTo?.getSeconds?.() ?? 0)
442
+ : undefined;
443
+ setDraft({ from: nextFrom ?? null, to: nextTo ?? null });
444
+ setFromInput(fmtDate(nextFrom ?? null));
445
+ setToInput(fmtDate(nextTo ?? null));
446
+ }}
447
+ defaultMonth={draft?.from ?? value?.from ?? new Date()}
448
+ disabled={(d) => isBefore(d, minDate) || isAfter(d, maxDate) || inDisabled(d, disabledDates)}
449
+ buttonVariant="ghost"
450
+ showOutsideDays
451
+ />
452
+ </div>
453
+ {(props.showFooter ?? true) && (
454
+ <div className="flex items-center justify-between gap-2 pt-3 mt-3 border-t">
455
+ <Button type="button" variant="outline" size="sm" onClick={() => setOpen(false)} disabled={disabled}>
456
+ {props.cancelLabel ?? 'Cancel'}
457
+ </Button>
458
+ <div className="flex gap-2">
459
+ <Button type="button" variant="ghost" size="sm" onClick={() => onChange?.(null)} disabled={disabled}>
460
+ {props.clearLabel ?? 'Clear'}
461
+ </Button>
462
+ <Button
463
+ type="button"
464
+ variant="default"
465
+ size="sm"
466
+ onClick={() => {
467
+ if (invalidRange || !mergedFrom || !mergedTo) return;
468
+ onChange?.({ from: mergedFrom, to: mergedTo });
469
+ setOpen(false);
470
+ }}
471
+ disabled={disabled || invalidRange}
472
+ >
473
+ {props.applyLabel ?? 'Apply'}
474
+ </Button>
475
+ </div>
476
+ </div>
477
+ )}
478
+ </PopoverContent>
479
+ </Popover>
480
+ </div>
481
+ );
482
+ }
483
+
484
+ DateTimeRangePicker.displayName = 'DateTimeRangePicker';
485
+
486
+ export default DateTimeRangePicker;
@@ -0,0 +1,3 @@
1
+ export { DateTimePicker } from './DateTimePicker'
2
+ export { DateTimeRangePicker } from './DateTimeRangePicker'
3
+ export default void 0