acai-ui-library 0.1.16 → 0.1.17

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.
@@ -0,0 +1,653 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/components/date-time-picker.tsx
31
+ var date_time_picker_exports = {};
32
+ __export(date_time_picker_exports, {
33
+ DateTimePicker: () => DateTimePicker
34
+ });
35
+ module.exports = __toCommonJS(date_time_picker_exports);
36
+ var React4 = __toESM(require("react"), 1);
37
+ var import_lucide_react4 = require("lucide-react");
38
+
39
+ // src/lib/utils.ts
40
+ var import_clsx = require("clsx");
41
+ var import_tailwind_merge = require("tailwind-merge");
42
+ function cn(...inputs) {
43
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
44
+ }
45
+
46
+ // src/components/button.tsx
47
+ var React = __toESM(require("react"), 1);
48
+ var import_react_slot = require("@radix-ui/react-slot");
49
+ var import_class_variance_authority = require("class-variance-authority");
50
+ var import_lucide_react = require("lucide-react");
51
+ var import_jsx_runtime = require("react/jsx-runtime");
52
+ var buttonVariants = (0, import_class_variance_authority.cva)(
53
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:size-4",
54
+ {
55
+ variants: {
56
+ variant: {
57
+ default: "bg-primary text-primary-foreground hover:bg-primary-hover",
58
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive-hover",
59
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
60
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover",
61
+ ghost: "hover:bg-accent hover:text-accent-foreground",
62
+ link: "text-primary underline-offset-4 hover:underline",
63
+ success: "bg-success text-success-foreground hover:bg-success-hover"
64
+ },
65
+ size: {
66
+ xs: "h-7 px-2.5 text-xs",
67
+ sm: "h-9 px-3",
68
+ default: "h-10 px-4 py-2",
69
+ lg: "h-11 px-8",
70
+ icon: "h-10 w-10",
71
+ "icon-sm": "h-8 w-8"
72
+ },
73
+ shape: {
74
+ default: "rounded-md",
75
+ rounded: "rounded-lg",
76
+ pill: "rounded-full",
77
+ square: "rounded-none"
78
+ }
79
+ },
80
+ defaultVariants: {
81
+ variant: "default",
82
+ size: "default",
83
+ shape: "default"
84
+ }
85
+ }
86
+ );
87
+ var Button = React.forwardRef(
88
+ ({
89
+ className,
90
+ variant,
91
+ size,
92
+ shape,
93
+ asChild = false,
94
+ loading = false,
95
+ prefixIcon,
96
+ suffixIcon,
97
+ children,
98
+ disabled,
99
+ ...props
100
+ }, ref) => {
101
+ const Comp = asChild ? import_react_slot.Slot : "button";
102
+ const isDisabled = disabled || loading;
103
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
104
+ Comp,
105
+ {
106
+ className: cn(buttonVariants({ variant, size, shape, className })),
107
+ ref,
108
+ disabled: isDisabled,
109
+ ...props,
110
+ children: [
111
+ loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Loader2, { className: "animate-spin" }),
112
+ !loading && prefixIcon,
113
+ children,
114
+ suffixIcon
115
+ ]
116
+ }
117
+ );
118
+ }
119
+ );
120
+ Button.displayName = "Button";
121
+
122
+ // src/components/calendar.tsx
123
+ var import_react_day_picker = require("react-day-picker");
124
+ var import_lucide_react2 = require("lucide-react");
125
+ var import_jsx_runtime2 = require("react/jsx-runtime");
126
+ function Calendar({ className, classNames, showOutsideDays = true, captionLayout = "dropdown", ...props }) {
127
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
128
+ import_react_day_picker.DayPicker,
129
+ {
130
+ showOutsideDays,
131
+ className: cn("w-[260px] rounded-lg p-3", className),
132
+ classNames: {
133
+ months: "flex flex-col",
134
+ month: "space-y-3",
135
+ month_caption: "relative flex h-8 items-center justify-center",
136
+ caption_label: "text-sm font-medium hidden",
137
+ nav: "flex items-center",
138
+ button_previous: cn(
139
+ "absolute start-0 inline-flex size-7 items-center justify-center rounded-md bg-transparent p-0 text-muted-foreground hover:bg-[var(--hc-color-border-subtle)] hover:text-foreground transition-colors cursor-pointer border-none"
140
+ ),
141
+ button_next: cn(
142
+ "absolute end-0 inline-flex size-7 items-center justify-center rounded-md bg-transparent p-0 text-muted-foreground hover:bg-[var(--hc-color-border-subtle)] hover:text-foreground transition-colors cursor-pointer border-none"
143
+ ),
144
+ month_grid: "w-full border-collapse",
145
+ weekdays: "flex w-full",
146
+ weekday: "flex-1 h-8 flex items-center justify-center text-[11px] font-medium text-muted-foreground uppercase tracking-wider",
147
+ week: "flex w-full mt-1",
148
+ day: "flex-1 h-8 p-0 relative focus-within:z-20 flex items-center justify-center text-sm",
149
+ day_button: cn(
150
+ "inline-flex size-8 items-center justify-center rounded-md bg-transparent text-sm font-normal cursor-pointer border-none hover:bg-[var(--hc-color-border-subtle)] transition-colors"
151
+ ),
152
+ range_end: "day-range-end",
153
+ selected: "[&>button]:bg-[var(--hc-primary-600)] [&>button]:text-white [&>button]:hover:bg-[var(--hc-primary-600)] [&>button]:hover:text-white [&>button]:font-medium",
154
+ today: "[&>button]:font-semibold [&>button]:text-[var(--hc-primary-600)]",
155
+ outside: "day-outside [&>button]:bg-transparent [&>button]:text-[var(--hc-grey-200)]",
156
+ disabled: "[&>button]:text-[var(--hc-grey-200)] [&>button]:opacity-50 [&>button]:cursor-default",
157
+ range_middle: "[&>button]:bg-[var(--hc-primary-100)] [&>button]:text-foreground [&>button]:rounded-none",
158
+ range_start: "[&>button]:rounded-e-none",
159
+ dropdowns: "flex items-center gap-1.5 justify-center [&>select]:flex-1",
160
+ dropdown: "h-7 w-[88px] rounded-md border border-[var(--hc-color-border-subtle)] bg-background px-2 text-xs font-medium text-foreground cursor-pointer outline-none accent-[var(--hc-primary-600)] focus:border-[var(--hc-primary-500)] focus:ring-[2px] focus:ring-[var(--hc-primary-500)]/20 [&>option:checked]:bg-[var(--hc-primary-600)] [&>option:checked]:text-white [&>option]:py-1",
161
+ hidden: "invisible",
162
+ ...classNames
163
+ },
164
+ captionLayout,
165
+ components: {
166
+ Chevron: ({ orientation }) => {
167
+ const Icon = orientation === "left" ? import_lucide_react2.ChevronLeft : import_lucide_react2.ChevronRight;
168
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { className: "size-4" });
169
+ }
170
+ },
171
+ ...props
172
+ }
173
+ );
174
+ }
175
+ Calendar.displayName = "Calendar";
176
+
177
+ // src/components/popover.tsx
178
+ var React2 = __toESM(require("react"), 1);
179
+ var PopoverPrimitive = __toESM(require("@radix-ui/react-popover"), 1);
180
+ var import_jsx_runtime3 = require("react/jsx-runtime");
181
+ var Popover = PopoverPrimitive.Root;
182
+ var PopoverTrigger = PopoverPrimitive.Trigger;
183
+ var PopoverContent = React2.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PopoverPrimitive.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
184
+ PopoverPrimitive.Content,
185
+ {
186
+ ref,
187
+ align,
188
+ sideOffset,
189
+ className: cn(
190
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
191
+ className
192
+ ),
193
+ ...props
194
+ }
195
+ ) }));
196
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
197
+
198
+ // src/components/time-picker.tsx
199
+ var React3 = __toESM(require("react"), 1);
200
+ var import_lucide_react3 = require("lucide-react");
201
+ var import_jsx_runtime4 = require("react/jsx-runtime");
202
+ var TIME_VALUE_PATTERN = /^([01]\d|2[0-3]):([0-5]\d)$/;
203
+ var MINUTES_PER_DAY = 24 * 60;
204
+ var NOON = 12 * 60;
205
+ function pad2(value) {
206
+ return value.toString().padStart(2, "0");
207
+ }
208
+ function parseTimeValue(value) {
209
+ if (!value) return void 0;
210
+ const match = TIME_VALUE_PATTERN.exec(value.trim());
211
+ if (!match) return void 0;
212
+ return Number(match[1]) * 60 + Number(match[2]);
213
+ }
214
+ function formatTimeValue(minutesOfDay2) {
215
+ const normalised = (minutesOfDay2 % MINUTES_PER_DAY + MINUTES_PER_DAY) % MINUTES_PER_DAY;
216
+ return `${pad2(Math.floor(normalised / 60))}:${pad2(normalised % 60)}`;
217
+ }
218
+ function formatTimeLabel(minutesOfDay2, hourCycle) {
219
+ const normalised = (minutesOfDay2 % MINUTES_PER_DAY + MINUTES_PER_DAY) % MINUTES_PER_DAY;
220
+ const hours = Math.floor(normalised / 60);
221
+ const minutes = normalised % 60;
222
+ if (hourCycle === 24) return `${pad2(hours)}:${pad2(minutes)}`;
223
+ const period = hours < NOON / 60 ? "AM" : "PM";
224
+ const hour12 = hours % 12 === 0 ? 12 : hours % 12;
225
+ return `${pad2(hour12)}:${pad2(minutes)} ${period}`;
226
+ }
227
+ function isTimeInRange(minutesOfDay2, min, max) {
228
+ if (min !== void 0 && minutesOfDay2 < min) return false;
229
+ if (max !== void 0 && minutesOfDay2 > max) return false;
230
+ return true;
231
+ }
232
+ function to24Hour(hour12, period) {
233
+ const base = hour12 % 12;
234
+ return period === "AM" ? base : base + 12;
235
+ }
236
+ function buildMinuteList(minuteStep, extra) {
237
+ const step = Number.isFinite(minuteStep) ? Math.min(60, Math.max(1, Math.floor(minuteStep))) : 5;
238
+ const minutes = [];
239
+ for (let minute = 0; minute < 60; minute += step) minutes.push(minute);
240
+ if (extra !== void 0 && extra >= 0 && extra < 60 && !minutes.includes(extra)) {
241
+ minutes.push(extra);
242
+ minutes.sort((a, b) => a - b);
243
+ }
244
+ return minutes;
245
+ }
246
+ function buildAllowedSlots(minutes, min, max) {
247
+ const slots = [];
248
+ for (let hour = 0; hour < 24; hour += 1) {
249
+ for (const minute of minutes) {
250
+ const slot = hour * 60 + minute;
251
+ if (isTimeInRange(slot, min, max)) slots.push(slot);
252
+ }
253
+ }
254
+ return slots;
255
+ }
256
+ function snapToAllowedSlot(desired, allowedSlots) {
257
+ if (allowedSlots.length === 0) return void 0;
258
+ let best = allowedSlots[0];
259
+ let bestScore = Number.POSITIVE_INFINITY;
260
+ for (const slot of allowedSlots) {
261
+ const score = slot >= desired ? slot - desired : desired - slot + MINUTES_PER_DAY;
262
+ if (score < bestScore) {
263
+ bestScore = score;
264
+ best = slot;
265
+ }
266
+ }
267
+ return best;
268
+ }
269
+ var columnClasses = "flex max-h-[216px] w-16 flex-col gap-1 overflow-y-auto overscroll-contain p-1";
270
+ var cellClasses = "shrink-0 rounded-md px-2 py-1.5 text-sm font-normal text-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background aria-pressed:bg-primary aria-pressed:font-medium aria-pressed:text-primary-foreground aria-pressed:hover:bg-primary aria-pressed:hover:text-primary-foreground disabled:pointer-events-none disabled:text-muted-foreground disabled:opacity-50";
271
+ function TimeCell({ label, accessibleName, pressed, disabled, onSelect }) {
272
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
273
+ "button",
274
+ {
275
+ type: "button",
276
+ "aria-label": accessibleName,
277
+ "aria-pressed": pressed,
278
+ disabled,
279
+ onClick: onSelect,
280
+ className: cn(cellClasses),
281
+ children: label
282
+ }
283
+ );
284
+ }
285
+ function TimePickerPanel({
286
+ value,
287
+ onValueChange,
288
+ minTime,
289
+ maxTime,
290
+ minuteStep = 5,
291
+ hourCycle = 12,
292
+ hourLabel = "Hour",
293
+ minuteLabel = "Minute",
294
+ periodLabel = "Period",
295
+ className
296
+ }) {
297
+ const minMinutes = parseTimeValue(minTime);
298
+ const maxMinutes = parseTimeValue(maxTime);
299
+ const selected = parseTimeValue(value);
300
+ const minuteList = React3.useMemo(
301
+ () => buildMinuteList(minuteStep, selected === void 0 ? void 0 : selected % 60),
302
+ [minuteStep, selected]
303
+ );
304
+ const allowedSlots = React3.useMemo(
305
+ () => buildAllowedSlots(minuteList, minMinutes, maxMinutes),
306
+ [minuteList, minMinutes, maxMinutes]
307
+ );
308
+ const active = selected ?? allowedSlots[0];
309
+ const activeHour = active === void 0 ? void 0 : Math.floor(active / 60);
310
+ const activeMinute = active === void 0 ? void 0 : active % 60;
311
+ const activePeriod = activeHour !== void 0 && activeHour >= 12 ? "PM" : "AM";
312
+ const hourHasSlot = (hour) => allowedSlots.some((slot) => Math.floor(slot / 60) === hour);
313
+ const firstSlotInHour = (hour) => allowedSlots.find((slot) => Math.floor(slot / 60) === hour);
314
+ const periodHasSlot = (period) => allowedSlots.some((slot) => (slot < NOON ? "AM" : "PM") === period);
315
+ const firstSlotInPeriod = (period) => allowedSlots.find((slot) => (slot < NOON ? "AM" : "PM") === period);
316
+ const commit = (minutesOfDay2) => {
317
+ if (minutesOfDay2 === void 0) return;
318
+ onValueChange?.(formatTimeValue(minutesOfDay2));
319
+ };
320
+ const selectHour = (hour) => {
321
+ const desired = hour * 60 + (activeMinute ?? 0);
322
+ commit(isTimeInRange(desired, minMinutes, maxMinutes) ? desired : firstSlotInHour(hour));
323
+ };
324
+ const selectMinute = (minute) => {
325
+ if (activeHour === void 0) return;
326
+ commit(activeHour * 60 + minute);
327
+ };
328
+ const selectPeriod = (period) => {
329
+ if (activeHour === void 0 || activeMinute === void 0) {
330
+ commit(firstSlotInPeriod(period));
331
+ return;
332
+ }
333
+ const hour12 = activeHour % 12 === 0 ? 12 : activeHour % 12;
334
+ const desired = to24Hour(hour12, period) * 60 + activeMinute;
335
+ commit(
336
+ isTimeInRange(desired, minMinutes, maxMinutes) ? desired : firstSlotInPeriod(period)
337
+ );
338
+ };
339
+ const hourCells = hourCycle === 24 ? Array.from({ length: 24 }, (_, hour) => hour) : [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
340
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cn("flex items-start divide-x divide-border", className), children: [
341
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: cn(columnClasses), role: "group", "aria-label": hourLabel, children: hourCells.map((cell) => {
342
+ const hour = hourCycle === 24 ? cell : to24Hour(cell, activePeriod);
343
+ const label = pad2(cell);
344
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
345
+ TimeCell,
346
+ {
347
+ label,
348
+ accessibleName: `${hourLabel} ${label}`,
349
+ pressed: activeHour === hour && selected !== void 0,
350
+ disabled: !hourHasSlot(hour),
351
+ onSelect: () => selectHour(hour)
352
+ },
353
+ `hour-${cell}`
354
+ );
355
+ }) }),
356
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: cn(columnClasses), role: "group", "aria-label": minuteLabel, children: minuteList.map((minute) => {
357
+ const label = pad2(minute);
358
+ const disabled = activeHour === void 0 || !isTimeInRange(activeHour * 60 + minute, minMinutes, maxMinutes);
359
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
360
+ TimeCell,
361
+ {
362
+ label,
363
+ accessibleName: `${minuteLabel} ${label}`,
364
+ pressed: activeMinute === minute && selected !== void 0,
365
+ disabled,
366
+ onSelect: () => selectMinute(minute)
367
+ },
368
+ `minute-${minute}`
369
+ );
370
+ }) }),
371
+ hourCycle === 12 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: cn(columnClasses), role: "group", "aria-label": periodLabel, children: ["AM", "PM"].map((period) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
372
+ TimeCell,
373
+ {
374
+ label: period,
375
+ accessibleName: period,
376
+ pressed: activePeriod === period && selected !== void 0,
377
+ disabled: !periodHasSlot(period),
378
+ onSelect: () => selectPeriod(period)
379
+ },
380
+ period
381
+ )) })
382
+ ] });
383
+ }
384
+ TimePickerPanel.displayName = "TimePickerPanel";
385
+ var TimePicker = React3.forwardRef(
386
+ ({
387
+ value,
388
+ onValueChange,
389
+ minTime,
390
+ maxTime,
391
+ minuteStep = 5,
392
+ hourCycle = 12,
393
+ placeholder = "Pick a time",
394
+ clearable = true,
395
+ clearLabel = "Clear",
396
+ disabled,
397
+ id,
398
+ name,
399
+ className,
400
+ contentClassName,
401
+ open,
402
+ onOpenChange,
403
+ align = "start",
404
+ "aria-label": ariaLabel,
405
+ "aria-labelledby": ariaLabelledBy
406
+ }, ref) => {
407
+ const selected = parseTimeValue(value);
408
+ const label = selected === void 0 ? placeholder : formatTimeLabel(selected, hourCycle);
409
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Popover, { open, onOpenChange, children: [
410
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
411
+ Button,
412
+ {
413
+ ref,
414
+ id,
415
+ type: "button",
416
+ variant: "outline",
417
+ disabled,
418
+ "aria-label": ariaLabelledBy ? void 0 : ariaLabel ?? placeholder,
419
+ "aria-labelledby": ariaLabelledBy,
420
+ className: cn(
421
+ "w-[180px] justify-start text-left font-normal",
422
+ selected === void 0 && "text-muted-foreground",
423
+ className
424
+ ),
425
+ children: [
426
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ClockIcon, { className: "me-2 h-4 w-4", "aria-hidden": "true" }),
427
+ label
428
+ ]
429
+ }
430
+ ) }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
432
+ PopoverContent,
433
+ {
434
+ className: cn("w-auto p-0", contentClassName),
435
+ align,
436
+ "aria-label": ariaLabel ?? placeholder,
437
+ children: [
438
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
439
+ TimePickerPanel,
440
+ {
441
+ value,
442
+ onValueChange: (next) => onValueChange?.(next),
443
+ minTime,
444
+ maxTime,
445
+ minuteStep,
446
+ hourCycle
447
+ }
448
+ ),
449
+ clearable && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex justify-end border-t border-border p-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
450
+ Button,
451
+ {
452
+ type: "button",
453
+ variant: "ghost",
454
+ size: "sm",
455
+ className: "font-normal",
456
+ disabled: selected === void 0,
457
+ onClick: () => onValueChange?.(void 0),
458
+ children: clearLabel
459
+ }
460
+ ) })
461
+ ]
462
+ }
463
+ ),
464
+ name && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "hidden", name, value: value ?? "" })
465
+ ] });
466
+ }
467
+ );
468
+ TimePicker.displayName = "TimePicker";
469
+
470
+ // src/components/date-time-picker.tsx
471
+ var import_jsx_runtime5 = require("react/jsx-runtime");
472
+ function startOfDay(date) {
473
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
474
+ }
475
+ function isSameDay(a, b) {
476
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
477
+ }
478
+ function minutesOfDay(date) {
479
+ return date.getHours() * 60 + date.getMinutes();
480
+ }
481
+ function withTime(day, minutes) {
482
+ return new Date(
483
+ day.getFullYear(),
484
+ day.getMonth(),
485
+ day.getDate(),
486
+ Math.floor(minutes / 60),
487
+ minutes % 60,
488
+ 0,
489
+ 0
490
+ );
491
+ }
492
+ var DateTimePicker = React4.forwardRef(
493
+ ({
494
+ value,
495
+ onValueChange,
496
+ minDate,
497
+ maxDate,
498
+ disabledDates,
499
+ minuteStep = 5,
500
+ hourCycle = 12,
501
+ defaultTime = "09:00",
502
+ placeholder = "Pick a date and time",
503
+ formatValue,
504
+ clearable = true,
505
+ clearLabel = "Clear",
506
+ timeLabel = "Time",
507
+ disabled,
508
+ id,
509
+ name,
510
+ className,
511
+ contentClassName,
512
+ open,
513
+ onOpenChange,
514
+ align = "start",
515
+ "aria-label": ariaLabel,
516
+ "aria-labelledby": ariaLabelledBy
517
+ }, ref) => {
518
+ const activeDay = React4.useMemo(() => {
519
+ if (value) return startOfDay(value);
520
+ const today = startOfDay(/* @__PURE__ */ new Date());
521
+ if (minDate && today < startOfDay(minDate)) return startOfDay(minDate);
522
+ if (maxDate && today > startOfDay(maxDate)) return startOfDay(maxDate);
523
+ return today;
524
+ }, [value, minDate, maxDate]);
525
+ const timeWindowFor = React4.useCallback(
526
+ (day) => ({
527
+ minTime: minDate && isSameDay(day, minDate) ? formatTimeValue(minutesOfDay(minDate)) : void 0,
528
+ maxTime: maxDate && isSameDay(day, maxDate) ? formatTimeValue(minutesOfDay(maxDate)) : void 0
529
+ }),
530
+ [minDate, maxDate]
531
+ );
532
+ const activeWindow = timeWindowFor(activeDay);
533
+ const calendarDisabled = React4.useMemo(() => {
534
+ const matchers = [];
535
+ if (minDate) matchers.push({ before: startOfDay(minDate) });
536
+ if (maxDate) matchers.push({ after: startOfDay(maxDate) });
537
+ if (Array.isArray(disabledDates)) matchers.push(...disabledDates);
538
+ else if (disabledDates !== void 0) matchers.push(disabledDates);
539
+ return matchers;
540
+ }, [minDate, maxDate, disabledDates]);
541
+ const handleDaySelect = (day) => {
542
+ if (!day) return;
543
+ const desired = value ? minutesOfDay(value) : parseTimeValue(defaultTime) ?? 0;
544
+ const { minTime, maxTime } = timeWindowFor(day);
545
+ const min = parseTimeValue(minTime);
546
+ const max = parseTimeValue(maxTime);
547
+ if (isTimeInRange(desired, min, max)) {
548
+ onValueChange?.(withTime(day, desired));
549
+ return;
550
+ }
551
+ const snapped = snapToAllowedSlot(
552
+ desired,
553
+ buildAllowedSlots(buildMinuteList(minuteStep, desired % 60), min, max)
554
+ );
555
+ if (snapped === void 0) return;
556
+ onValueChange?.(withTime(day, snapped));
557
+ };
558
+ const handleTimeChange = (next) => {
559
+ const minutes = parseTimeValue(next);
560
+ if (minutes === void 0) return;
561
+ onValueChange?.(withTime(value ?? activeDay, minutes));
562
+ };
563
+ const label = value ? formatValue?.(value) ?? `${value.toLocaleDateString()} ${formatTimeLabel(minutesOfDay(value), hourCycle)}` : placeholder;
564
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Popover, { open, onOpenChange, children: [
565
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
566
+ Button,
567
+ {
568
+ ref,
569
+ id,
570
+ type: "button",
571
+ variant: "outline",
572
+ disabled,
573
+ "aria-label": ariaLabelledBy ? void 0 : ariaLabel ?? placeholder,
574
+ "aria-labelledby": ariaLabelledBy,
575
+ className: cn(
576
+ "w-[280px] justify-start text-left font-normal",
577
+ !value && "text-muted-foreground",
578
+ className
579
+ ),
580
+ children: [
581
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react4.CalendarClockIcon, { className: "me-2 h-4 w-4", "aria-hidden": "true" }),
582
+ label
583
+ ]
584
+ }
585
+ ) }),
586
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
587
+ PopoverContent,
588
+ {
589
+ className: cn("w-auto p-0", contentClassName),
590
+ align,
591
+ "aria-label": ariaLabel ?? placeholder,
592
+ children: [
593
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col sm:flex-row sm:items-start sm:divide-x sm:divide-border", children: [
594
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
595
+ Calendar,
596
+ {
597
+ mode: "single",
598
+ selected: value,
599
+ defaultMonth: activeDay,
600
+ onSelect: handleDaySelect,
601
+ disabled: calendarDisabled,
602
+ autoFocus: true
603
+ }
604
+ ),
605
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "border-t border-border p-2 sm:border-t-0", children: [
606
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "px-1 pb-1 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: timeLabel }),
607
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
608
+ TimePickerPanel,
609
+ {
610
+ value: value ? formatTimeValue(minutesOfDay(value)) : void 0,
611
+ onValueChange: handleTimeChange,
612
+ minTime: activeWindow.minTime,
613
+ maxTime: activeWindow.maxTime,
614
+ minuteStep,
615
+ hourCycle
616
+ }
617
+ )
618
+ ] })
619
+ ] }),
620
+ clearable && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex justify-end border-t border-border p-2", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
621
+ Button,
622
+ {
623
+ type: "button",
624
+ variant: "ghost",
625
+ size: "sm",
626
+ className: "font-normal",
627
+ disabled: !value,
628
+ onClick: () => onValueChange?.(void 0),
629
+ children: clearLabel
630
+ }
631
+ ) })
632
+ ]
633
+ }
634
+ ),
635
+ name && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
636
+ "input",
637
+ {
638
+ type: "hidden",
639
+ name,
640
+ value: value ? `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(
641
+ value.getDate()
642
+ ).padStart(2, "0")}T${formatTimeValue(minutesOfDay(value))}` : ""
643
+ }
644
+ )
645
+ ] });
646
+ }
647
+ );
648
+ DateTimePicker.displayName = "DateTimePicker";
649
+ // Annotate the CommonJS export names for ESM import in node:
650
+ 0 && (module.exports = {
651
+ DateTimePicker
652
+ });
653
+ //# sourceMappingURL=date-time-picker.cjs.map