@pos-360/horizon 0.30.1 → 0.30.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,11 +1,11 @@
1
- import { cn, Label, Tooltip, Text, mergeRefs } from './chunk-EZDGMHS7.mjs';
1
+ import { cn, Label, Tooltip, Text, TextButton, mergeRefs } from './chunk-EZL7UCWW.mjs';
2
2
  import * as React10 from 'react';
3
3
  import { useState, useEffect, useCallback } from 'react';
4
4
  import { Slot } from '@radix-ui/react-slot';
5
5
  import { cva } from 'class-variance-authority';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
8
- import { Check, X, ChevronRight, Circle, ChevronDown, ChevronUp, Search, Minus, PenLine, SlidersHorizontal, CalendarIcon, ChevronLeft, Clock } from 'lucide-react';
8
+ import { Check, X, ChevronRight, Circle, ChevronDown, ChevronUp, Search, Minus, PenLine, SlidersHorizontal, CalendarIcon, Clock, ChevronLeft } from 'lucide-react';
9
9
  import * as DialogPrimitive from '@radix-ui/react-dialog';
10
10
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
11
11
  import * as PopoverPrimitive from '@radix-ui/react-popover';
@@ -2108,6 +2108,7 @@ function TimeInput({
2108
2108
  max,
2109
2109
  onChange,
2110
2110
  onComplete,
2111
+ onDirty,
2111
2112
  inputRef,
2112
2113
  disabled = false,
2113
2114
  "aria-label": ariaLabel
@@ -2115,32 +2116,51 @@ function TimeInput({
2115
2116
  const [editValue, setEditValue] = React10.useState(null);
2116
2117
  const internalRef = React10.useRef(null);
2117
2118
  const committedRef = React10.useRef(false);
2119
+ const pendingDigitRef = React10.useRef(null);
2118
2120
  const setRefs = React10.useCallback(mergeRefs(internalRef, inputRef), [inputRef]);
2119
- const handleChange = (e) => {
2120
- const raw = e.target.value.replace(/\D/g, "").slice(0, 2);
2121
- if (raw === "") {
2122
- setEditValue(raw);
2123
- return;
2124
- }
2125
- if (raw.length === 1) {
2126
- setEditValue(raw);
2127
- return;
2128
- }
2121
+ const commitTwoDigits = (raw) => {
2129
2122
  const parsed = parseInt(raw, 10);
2130
2123
  const clamped = clamp(parsed, min2, max);
2124
+ pendingDigitRef.current = null;
2131
2125
  committedRef.current = true;
2132
2126
  onChange(clamped);
2133
2127
  setEditValue(null);
2134
2128
  onComplete?.();
2135
2129
  };
2130
+ const handleChange = (e) => {
2131
+ const allDigits = e.target.value.replace(/\D/g, "");
2132
+ if (allDigits === "") {
2133
+ setEditValue("");
2134
+ pendingDigitRef.current = null;
2135
+ return;
2136
+ }
2137
+ if (pendingDigitRef.current !== null) {
2138
+ if (allDigits.length > 2) {
2139
+ commitTwoDigits(pendingDigitRef.current + allDigits[allDigits.length - 1]);
2140
+ return;
2141
+ }
2142
+ pendingDigitRef.current = null;
2143
+ setEditValue("");
2144
+ return;
2145
+ }
2146
+ if (allDigits.length === 1) {
2147
+ pendingDigitRef.current = allDigits;
2148
+ setEditValue("0" + allDigits);
2149
+ onDirty?.();
2150
+ return;
2151
+ }
2152
+ commitTwoDigits(allDigits.slice(0, 2));
2153
+ };
2136
2154
  const commit = () => {
2137
2155
  if (committedRef.current) {
2138
2156
  committedRef.current = false;
2157
+ pendingDigitRef.current = null;
2139
2158
  setEditValue(null);
2140
2159
  return;
2141
2160
  }
2142
2161
  if (editValue === null) return;
2143
2162
  if (editValue === "") {
2163
+ pendingDigitRef.current = null;
2144
2164
  setEditValue(null);
2145
2165
  return;
2146
2166
  }
@@ -2148,6 +2168,7 @@ function TimeInput({
2148
2168
  if (!isNaN(parsed)) {
2149
2169
  onChange(clamp(parsed, min2, max));
2150
2170
  }
2171
+ pendingDigitRef.current = null;
2151
2172
  setEditValue(null);
2152
2173
  };
2153
2174
  const handleKeyDown = (e) => {
@@ -2155,16 +2176,19 @@ function TimeInput({
2155
2176
  commit();
2156
2177
  internalRef.current?.blur();
2157
2178
  } else if (e.key === "Escape") {
2179
+ pendingDigitRef.current = null;
2158
2180
  setEditValue(null);
2159
2181
  internalRef.current?.blur();
2160
2182
  } else if (e.key === "ArrowUp") {
2161
2183
  e.preventDefault();
2184
+ pendingDigitRef.current = null;
2162
2185
  const curr = value ?? min2;
2163
2186
  const next = curr >= max ? min2 : curr + 1;
2164
2187
  onChange(next);
2165
2188
  setEditValue(pad(next));
2166
2189
  } else if (e.key === "ArrowDown") {
2167
2190
  e.preventDefault();
2191
+ pendingDigitRef.current = null;
2168
2192
  const curr = value ?? max;
2169
2193
  const next = curr <= min2 ? max : curr - 1;
2170
2194
  onChange(next);
@@ -2182,6 +2206,7 @@ function TimeInput({
2182
2206
  value: editValue ?? (value === null ? "--" : pad(value)),
2183
2207
  onChange: handleChange,
2184
2208
  onFocus: (e) => {
2209
+ pendingDigitRef.current = null;
2185
2210
  setEditValue("");
2186
2211
  requestAnimationFrame(() => e.target.select());
2187
2212
  },
@@ -2232,6 +2257,7 @@ function TimeField({
2232
2257
  hourRef,
2233
2258
  minuteRef,
2234
2259
  onMinuteComplete,
2260
+ onDirty,
2235
2261
  disabled = false
2236
2262
  }) {
2237
2263
  const { hour12, period } = to12Hour(value.hour);
@@ -2270,6 +2296,7 @@ function TimeField({
2270
2296
  max: 12,
2271
2297
  onChange: handleHourChange,
2272
2298
  onComplete: focusMinute,
2299
+ onDirty,
2273
2300
  inputRef: hourRef,
2274
2301
  disabled,
2275
2302
  "aria-label": `${label} hour`
@@ -2293,6 +2320,7 @@ function TimeField({
2293
2320
  max: 59,
2294
2321
  onChange: handleMinuteChange,
2295
2322
  onComplete: onMinuteComplete,
2323
+ onDirty,
2296
2324
  inputRef: mergedMinuteRef,
2297
2325
  disabled,
2298
2326
  "aria-label": `${label} minute`
@@ -2309,11 +2337,14 @@ function TimeField({
2309
2337
  ] })
2310
2338
  ] });
2311
2339
  }
2312
- function TimePickerColumn({ value, onChange, disabled = false }) {
2313
- const toHourRef = React10.useRef(null);
2340
+ function useTimeFieldState(value) {
2314
2341
  const fromSet = isTimeSet(value.from);
2315
2342
  const toSet = isTimeSet(value.to);
2316
2343
  const bothSet = fromSet && toSet;
2344
+ const orphanMinutes = value.from.hour === null && value.from.minute !== null || value.to.hour === null && value.to.minute !== null;
2345
+ const fromPartial = !fromSet && (value.from.hour !== null || value.from.minute !== null);
2346
+ const toPartial = !toSet && (value.to.hour !== null || value.to.minute !== null);
2347
+ const oneSideMissing = fromSet && !toSet && !toPartial || toSet && !fromSet && !fromPartial;
2317
2348
  const fromMinutes = (value.from.hour ?? 0) * 60 + (value.from.minute ?? 0);
2318
2349
  const toMinutes = (value.to.hour ?? 0) * 60 + (value.to.minute ?? 0);
2319
2350
  const bothEqual = fromMinutes === toMinutes;
@@ -2323,140 +2354,142 @@ function TimePickerColumn({ value, onChange, disabled = false }) {
2323
2354
  const durationRemaining = durationMinutes % 60;
2324
2355
  const showDuration = bothSet && !bothEqual;
2325
2356
  const durationLabel = durationRemaining > 0 ? `${durationHours}h ${durationRemaining}m window${isOvernight ? " (overnight)" : ""}` : `${durationHours}h window${isOvernight ? " (overnight)" : ""}`;
2326
- return /* @__PURE__ */ jsxs(
2327
- "div",
2328
- {
2329
- className: cn(
2330
- "flex flex-col px-2 pt-2 pb-2 gap-1",
2331
- "border-t border-gray-100 dark:border-neutral-700"
2357
+ const incompleteHint = orphanMinutes ? "Please set hours for the time to take effect" : fromPartial || toPartial ? "Please complete both hour and minute fields" : oneSideMissing ? `Please set the ${fromSet ? "to" : "from"} time` : null;
2358
+ return { showDuration, durationLabel, isOvernight, incompleteHint };
2359
+ }
2360
+ function TimePickerColumn({ value, onChange, disabled = false, onRemove, showRemove = false, onDirty }) {
2361
+ const toHourRef = React10.useRef(null);
2362
+ const { showDuration, durationLabel, isOvernight, incompleteHint } = useTimeFieldState(value);
2363
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col px-2 pt-2 pb-2 gap-1", children: [
2364
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2365
+ /* @__PURE__ */ jsx(
2366
+ Clock,
2367
+ {
2368
+ className: cn(
2369
+ "w-4 h-4 shrink-0",
2370
+ disabled ? "text-gray-300 dark:text-gray-600" : "text-gray-400 dark:text-gray-500"
2371
+ )
2372
+ }
2332
2373
  ),
2333
- children: [
2334
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2335
- /* @__PURE__ */ jsx(
2336
- Clock,
2337
- {
2338
- className: cn(
2339
- "w-4 h-4 shrink-0",
2340
- disabled ? "text-gray-300 dark:text-gray-600" : "text-gray-400 dark:text-gray-500"
2341
- )
2342
- }
2343
- ),
2344
- /* @__PURE__ */ jsx(
2345
- "span",
2346
- {
2347
- className: cn(
2348
- "text-xs font-semibold uppercase tracking-wider",
2349
- disabled ? "text-gray-300 dark:text-gray-600" : "text-gray-400 dark:text-gray-500"
2350
- ),
2351
- children: "Time"
2352
- }
2353
- ),
2354
- /* @__PURE__ */ jsx("span", { className: "text-[9px] font-medium text-gray-400 dark:text-gray-600 tracking-wide", children: "(Optional)" })
2355
- ] }),
2356
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 pl-6", children: [
2357
- /* @__PURE__ */ jsx(
2358
- TimeField,
2359
- {
2360
- label: "From",
2361
- value: value.from,
2362
- onChange: (from) => onChange({ ...value, from }),
2363
- onMinuteComplete: () => toHourRef.current?.focus(),
2364
- disabled
2365
- }
2366
- ),
2367
- /* @__PURE__ */ jsx(
2368
- TimeField,
2369
- {
2370
- label: "To",
2371
- value: value.to,
2372
- onChange: (to) => onChange({ ...value, to }),
2373
- hourRef: toHourRef,
2374
- disabled
2375
- }
2374
+ /* @__PURE__ */ jsx(
2375
+ "span",
2376
+ {
2377
+ className: cn(
2378
+ "text-xs font-semibold uppercase tracking-wider",
2379
+ disabled ? "text-gray-300 dark:text-gray-600" : "text-gray-400 dark:text-gray-500"
2376
2380
  ),
2377
- showDuration && /* @__PURE__ */ jsx("span", { className: cn(
2378
- "text-[10px] font-medium",
2379
- isOvernight ? "text-amber-500 dark:text-amber-400" : "text-gray-400 dark:text-gray-500"
2380
- ), children: durationLabel })
2381
- ] })
2382
- ]
2383
- }
2384
- );
2381
+ children: "Time"
2382
+ }
2383
+ ),
2384
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] font-medium text-gray-400 dark:text-gray-600 tracking-wide", children: "(Optional)" }),
2385
+ showRemove && onRemove && /* @__PURE__ */ jsx("div", { className: "ml-auto", children: /* @__PURE__ */ jsx(
2386
+ TextButton,
2387
+ {
2388
+ variant: "danger",
2389
+ onClick: onRemove,
2390
+ size: "sm",
2391
+ className: "!text-[0.625rem]",
2392
+ children: "Remove"
2393
+ }
2394
+ ) })
2395
+ ] }),
2396
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 pl-6", children: [
2397
+ /* @__PURE__ */ jsx(
2398
+ TimeField,
2399
+ {
2400
+ label: "From",
2401
+ value: value.from,
2402
+ onChange: (from) => onChange({ ...value, from }),
2403
+ onMinuteComplete: () => toHourRef.current?.focus(),
2404
+ onDirty,
2405
+ disabled
2406
+ }
2407
+ ),
2408
+ /* @__PURE__ */ jsx(
2409
+ TimeField,
2410
+ {
2411
+ label: "To",
2412
+ value: value.to,
2413
+ onChange: (to) => onChange({ ...value, to }),
2414
+ hourRef: toHourRef,
2415
+ onDirty,
2416
+ disabled
2417
+ }
2418
+ ),
2419
+ showDuration && /* @__PURE__ */ jsx("span", { className: cn(
2420
+ "text-[10px] font-medium",
2421
+ isOvernight ? "text-amber-500 dark:text-amber-400" : "text-gray-400 dark:text-gray-500"
2422
+ ), children: durationLabel }),
2423
+ incompleteHint && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-amber-500 dark:text-amber-400", children: incompleteHint })
2424
+ ] })
2425
+ ] });
2385
2426
  }
2386
- function TimePickerRow({ value, onChange, disabled = false }) {
2427
+ function TimePickerRow({ value, onChange, disabled = false, onRemove, showRemove = false, onDirty }) {
2387
2428
  const toHourRef = React10.useRef(null);
2388
- const fromSet = isTimeSet(value.from);
2389
- const toSet = isTimeSet(value.to);
2390
- const bothSet = fromSet && toSet;
2391
- const fromMinutes = (value.from.hour ?? 0) * 60 + (value.from.minute ?? 0);
2392
- const toMinutes = (value.to.hour ?? 0) * 60 + (value.to.minute ?? 0);
2393
- const bothEqual = fromMinutes === toMinutes;
2394
- const isOvernight = bothSet && toMinutes < fromMinutes;
2395
- const durationMinutes = bothSet && !bothEqual ? isOvernight ? 24 * 60 - fromMinutes + toMinutes : toMinutes - fromMinutes : 0;
2396
- const durationHours = Math.floor(durationMinutes / 60);
2397
- const durationRemaining = durationMinutes % 60;
2398
- const showDuration = bothSet && !bothEqual;
2399
- const durationLabel = durationRemaining > 0 ? `${durationHours}h ${durationRemaining}m window${isOvernight ? " (overnight)" : ""}` : `${durationHours}h window${isOvernight ? " (overnight)" : ""}`;
2400
- return /* @__PURE__ */ jsx(
2401
- "div",
2402
- {
2403
- className: cn(
2404
- "flex flex-col px-4 pt-2 pb-3 gap-1",
2405
- "border-t border-gray-100 dark:border-neutral-700"
2429
+ const { showDuration, durationLabel, isOvernight, incompleteHint } = useTimeFieldState(value);
2430
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col px-4 pt-2 pb-3 gap-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
2431
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
2432
+ /* @__PURE__ */ jsx(
2433
+ Clock,
2434
+ {
2435
+ className: cn(
2436
+ "w-4 h-4 shrink-0",
2437
+ disabled ? "text-gray-300 dark:text-gray-600" : "text-gray-400 dark:text-gray-500"
2438
+ )
2439
+ }
2406
2440
  ),
2407
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
2408
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
2409
- /* @__PURE__ */ jsx(
2410
- Clock,
2411
- {
2412
- className: cn(
2413
- "w-4 h-4 shrink-0",
2414
- disabled ? "text-gray-300 dark:text-gray-600" : "text-gray-400 dark:text-gray-500"
2415
- )
2416
- }
2417
- ),
2418
- /* @__PURE__ */ jsx("span", { className: "text-[9px] font-medium text-gray-300 dark:text-gray-600 tracking-wide", children: "OPT" })
2419
- ] }),
2420
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 flex-1", children: [
2421
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-5", children: [
2422
- /* @__PURE__ */ jsx(
2423
- TimeField,
2424
- {
2425
- label: "From",
2426
- value: value.from,
2427
- onChange: (from) => onChange({ ...value, from }),
2428
- onMinuteComplete: () => toHourRef.current?.focus(),
2429
- disabled
2430
- }
2431
- ),
2432
- /* @__PURE__ */ jsx(
2433
- "div",
2434
- {
2435
- className: cn(
2436
- "w-4 h-px",
2437
- disabled ? "bg-gray-200 dark:bg-neutral-700" : "bg-gray-300 dark:bg-neutral-600"
2438
- )
2439
- }
2440
- ),
2441
- /* @__PURE__ */ jsx(
2442
- TimeField,
2443
- {
2444
- label: "To",
2445
- value: value.to,
2446
- onChange: (to) => onChange({ ...value, to }),
2447
- hourRef: toHourRef,
2448
- disabled
2449
- }
2441
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] font-medium text-gray-300 dark:text-gray-600 tracking-wide", children: "OPT" })
2442
+ ] }),
2443
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 flex-1", children: [
2444
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2445
+ /* @__PURE__ */ jsx(
2446
+ TimeField,
2447
+ {
2448
+ label: "From",
2449
+ value: value.from,
2450
+ onChange: (from) => onChange({ ...value, from }),
2451
+ onMinuteComplete: () => toHourRef.current?.focus(),
2452
+ onDirty,
2453
+ disabled
2454
+ }
2455
+ ),
2456
+ /* @__PURE__ */ jsx(
2457
+ "div",
2458
+ {
2459
+ className: cn(
2460
+ "w-4 h-px",
2461
+ disabled ? "bg-gray-200 dark:bg-neutral-700" : "bg-gray-300 dark:bg-neutral-600"
2450
2462
  )
2451
- ] }),
2452
- showDuration && /* @__PURE__ */ jsx("span", { className: cn(
2453
- "text-[10px] font-medium pl-0.5",
2454
- isOvernight ? "text-amber-500 dark:text-amber-400" : "text-gray-400 dark:text-gray-500"
2455
- ), children: durationLabel })
2456
- ] })
2457
- ] })
2458
- }
2459
- );
2463
+ }
2464
+ ),
2465
+ /* @__PURE__ */ jsx(
2466
+ TimeField,
2467
+ {
2468
+ label: "To",
2469
+ value: value.to,
2470
+ onChange: (to) => onChange({ ...value, to }),
2471
+ hourRef: toHourRef,
2472
+ onDirty,
2473
+ disabled
2474
+ }
2475
+ ),
2476
+ showRemove && onRemove && /* @__PURE__ */ jsx(
2477
+ Button,
2478
+ {
2479
+ variant: "outline",
2480
+ onClick: onRemove,
2481
+ className: "text-[0.625rem] border border-red-100 dark:border-red-700 rounded-md px-2 py-1 h-8 text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-300 transition-colors",
2482
+ children: "Remove"
2483
+ }
2484
+ )
2485
+ ] }),
2486
+ showDuration && /* @__PURE__ */ jsx("span", { className: cn(
2487
+ "text-[10px] font-medium pl-0.5",
2488
+ isOvernight ? "text-amber-500 dark:text-amber-400" : "text-gray-400 dark:text-gray-500"
2489
+ ), children: durationLabel }),
2490
+ incompleteHint && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium pl-0.5 text-amber-500 dark:text-amber-400", children: incompleteHint })
2491
+ ] })
2492
+ ] }) });
2460
2493
  }
2461
2494
  var WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
2462
2495
  function getCalendarDays(month) {
@@ -2584,6 +2617,167 @@ function CalendarMonth({
2584
2617
  )
2585
2618
  ] });
2586
2619
  }
2620
+ function useDateRangePicker({
2621
+ value,
2622
+ onChange,
2623
+ showTimePicker = false,
2624
+ timeValue,
2625
+ onTimeChange
2626
+ }) {
2627
+ const [open, setOpen] = React10.useState(false);
2628
+ const [internalRange, setInternalRange] = React10.useState({
2629
+ from: void 0,
2630
+ to: void 0
2631
+ });
2632
+ const [draft, setDraft] = React10.useState({
2633
+ from: void 0,
2634
+ to: void 0
2635
+ });
2636
+ const [hoverDate, setHoverDate] = React10.useState();
2637
+ const [viewMonth, setViewMonth] = React10.useState(
2638
+ () => startOfMonth(value?.from ?? /* @__PURE__ */ new Date())
2639
+ );
2640
+ const [activePreset, setActivePreset] = React10.useState();
2641
+ const [committedInternalTime, setCommittedInternalTime] = React10.useState(DEFAULT_TIME_RANGE);
2642
+ const [draftTime, setDraftTime] = React10.useState(DEFAULT_TIME_RANGE);
2643
+ const [timeExpanded, setTimeExpanded] = React10.useState(false);
2644
+ const [timeDirty, setTimeDirty] = React10.useState(false);
2645
+ const committedRange = value ?? internalRange;
2646
+ const committedTime = timeValue ?? committedInternalTime;
2647
+ const timeVisible = !!(draft.from && draft.to);
2648
+ const hasAnyTimeInput = timeDirty || draftTime.from.hour !== null || draftTime.from.minute !== null || draftTime.to.hour !== null || draftTime.to.minute !== null;
2649
+ const isTimeComplete2 = draftTime.from.hour !== null && draftTime.from.minute !== null && draftTime.to.hour !== null && draftTime.to.minute !== null;
2650
+ const hasPartialTime = showTimePicker && timeExpanded && !isTimeComplete2;
2651
+ const canClear = !!(draft.from || committedRange.from);
2652
+ const canApply = !(draft.from && !draft.to) && !!(draft.from || committedRange.from) && !hasPartialTime;
2653
+ const resetTime = React10.useCallback(() => {
2654
+ setDraftTime(DEFAULT_TIME_RANGE);
2655
+ setTimeExpanded(false);
2656
+ setTimeDirty(false);
2657
+ }, []);
2658
+ const handleTimeChange = React10.useCallback((newTime) => {
2659
+ setDraftTime(newTime);
2660
+ }, []);
2661
+ const handleOpenChange = React10.useCallback(
2662
+ (newOpen) => {
2663
+ if (newOpen) {
2664
+ setDraft(committedRange);
2665
+ if (committedRange.from)
2666
+ setViewMonth(startOfMonth(committedRange.from));
2667
+ setDraftTime(committedTime);
2668
+ const hasExistingTime = committedTime.from.hour !== null || committedTime.from.minute !== null || committedTime.to.hour !== null || committedTime.to.minute !== null;
2669
+ setTimeExpanded(hasExistingTime);
2670
+ setTimeDirty(false);
2671
+ }
2672
+ setOpen(newOpen);
2673
+ },
2674
+ [committedRange, committedTime]
2675
+ );
2676
+ const handleDayClick = React10.useCallback(
2677
+ (date) => {
2678
+ const { from, to } = draft;
2679
+ if (!from || from && to) {
2680
+ setDraft({ from: date, to: void 0 });
2681
+ setActivePreset(void 0);
2682
+ if (showTimePicker && timeExpanded) {
2683
+ resetTime();
2684
+ }
2685
+ return;
2686
+ }
2687
+ const [start, end] = isBefore(from, date) ? [from, date] : [date, from];
2688
+ setDraft({ from: start, to: end });
2689
+ setHoverDate(void 0);
2690
+ },
2691
+ [draft, showTimePicker, timeExpanded, resetTime]
2692
+ );
2693
+ const handlePreset = React10.useCallback(
2694
+ (preset) => {
2695
+ const newRange = preset.getRange();
2696
+ setDraft(newRange);
2697
+ setActivePreset(preset.label);
2698
+ if (newRange.from) setViewMonth(startOfMonth(newRange.from));
2699
+ if (showTimePicker) resetTime();
2700
+ },
2701
+ [showTimePicker, resetTime]
2702
+ );
2703
+ const handleApply = React10.useCallback(() => {
2704
+ if (draft.from && !draft.to) return;
2705
+ const newRange = draft.from && draft.to ? draft : void 0;
2706
+ const rangeChanged = newRange?.from?.getTime() !== committedRange.from?.getTime() || newRange?.to?.getTime() !== committedRange.to?.getTime();
2707
+ const timeChanged = draftTime.from.hour !== committedTime.from.hour || draftTime.from.minute !== committedTime.from.minute || draftTime.to.hour !== committedTime.to.hour || draftTime.to.minute !== committedTime.to.minute;
2708
+ if (rangeChanged) {
2709
+ if (onChange) onChange(newRange);
2710
+ else setInternalRange(newRange ?? { from: void 0, to: void 0 });
2711
+ }
2712
+ if (timeChanged) {
2713
+ if (onTimeChange) onTimeChange(draftTime);
2714
+ else setCommittedInternalTime(draftTime);
2715
+ }
2716
+ setOpen(false);
2717
+ }, [draft, onChange, committedRange, draftTime, committedTime, onTimeChange]);
2718
+ const handleClear = React10.useCallback(() => {
2719
+ setDraft({ from: void 0, to: void 0 });
2720
+ setActivePreset(void 0);
2721
+ if (showTimePicker) resetTime();
2722
+ }, [showTimePicker, resetTime]);
2723
+ const expandTime = React10.useCallback(() => {
2724
+ setTimeExpanded(true);
2725
+ }, []);
2726
+ const markTimeDirty = React10.useCallback(() => {
2727
+ setTimeDirty(true);
2728
+ }, []);
2729
+ return {
2730
+ open,
2731
+ draft,
2732
+ hoverDate,
2733
+ viewMonth,
2734
+ activePreset,
2735
+ committedRange,
2736
+ committedTime,
2737
+ draftTime,
2738
+ timeExpanded,
2739
+ timeVisible,
2740
+ hasAnyTimeInput,
2741
+ canClear,
2742
+ canApply,
2743
+ showTimePicker,
2744
+ setHoverDate,
2745
+ setViewMonth,
2746
+ handleTimeChange,
2747
+ handleOpenChange,
2748
+ handleDayClick,
2749
+ handlePreset,
2750
+ handleApply,
2751
+ handleClear,
2752
+ resetTime,
2753
+ expandTime,
2754
+ markTimeDirty
2755
+ };
2756
+ }
2757
+ function formatTime(hour, minute) {
2758
+ const period = hour >= 12 ? "PM" : "AM";
2759
+ const h = hour % 12 || 12;
2760
+ const m = minute.toString().padStart(2, "0");
2761
+ return `${h}:${m} ${period}`;
2762
+ }
2763
+ function isTimeComplete(time) {
2764
+ return time.from.hour !== null && time.from.minute !== null && time.to.hour !== null && time.to.minute !== null;
2765
+ }
2766
+ function formatDateRange(range, placeholder, time) {
2767
+ if (!range?.from) return placeholder;
2768
+ const hasTime = time && isTimeComplete(time);
2769
+ if (!range.to || isSameDay(range.from, range.to)) {
2770
+ const dateStr = format(range.from, "MMM d, yyyy");
2771
+ return hasTime ? `${dateStr} ${formatTime(time.from.hour, time.from.minute)} \u2013 ${formatTime(time.to.hour, time.to.minute)}` : dateStr;
2772
+ }
2773
+ const sameYear = range.from.getFullYear() === range.to.getFullYear();
2774
+ const fromDate = format(range.from, sameYear ? "MMM d" : "MMM d, yyyy");
2775
+ const toDate = format(range.to, "MMM d, yyyy");
2776
+ if (hasTime) {
2777
+ return `${fromDate} ${formatTime(time.from.hour, time.from.minute)} \u2013 ${toDate} ${formatTime(time.to.hour, time.to.minute)}`;
2778
+ }
2779
+ return `${fromDate} \u2013 ${toDate}`;
2780
+ }
2587
2781
  var DEFAULT_PRESETS = [
2588
2782
  {
2589
2783
  label: "Today",
@@ -2631,26 +2825,6 @@ var DEFAULT_PRESETS = [
2631
2825
  }
2632
2826
  }
2633
2827
  ];
2634
- function formatTime(tv) {
2635
- if (tv.hour === null || tv.minute === null) return "--:--";
2636
- const period = tv.hour >= 12 ? "PM" : "AM";
2637
- const h = tv.hour % 12 || 12;
2638
- const m = tv.minute.toString().padStart(2, "0");
2639
- return `${h}:${m} ${period}`;
2640
- }
2641
- function formatDateRange(range, placeholder, time) {
2642
- if (!range?.from) return placeholder;
2643
- const timeActive = time && time.from.hour !== null && time.to.hour !== null;
2644
- const timeSuffix = timeActive ? ` ${formatTime(time.from)} \u2013 ${formatTime(time.to)}` : "";
2645
- if (!range.to || isSameDay(range.from, range.to)) {
2646
- return format(range.from, "MMM d, yyyy") + timeSuffix;
2647
- }
2648
- const sameYear = range.from.getFullYear() === range.to.getFullYear();
2649
- if (sameYear) {
2650
- return `${format(range.from, "MMM d")} \u2013 ${format(range.to, "MMM d, yyyy")}${timeSuffix}`;
2651
- }
2652
- return `${format(range.from, "MMM d, yyyy")} \u2013 ${format(range.to, "MMM d, yyyy")}${timeSuffix}`;
2653
- }
2654
2828
  function DateRangePicker({
2655
2829
  value,
2656
2830
  onChange,
@@ -2664,69 +2838,15 @@ function DateRangePicker({
2664
2838
  timeValue,
2665
2839
  onTimeChange
2666
2840
  }) {
2667
- const [open, setOpen] = React10.useState(false);
2668
- const [internalRange, setInternalRange] = React10.useState({
2669
- from: void 0,
2670
- to: void 0
2671
- });
2672
- const [draft, setDraft] = React10.useState({
2673
- from: void 0,
2674
- to: void 0
2841
+ const picker = useDateRangePicker({
2842
+ value,
2843
+ onChange,
2844
+ showTimePicker,
2845
+ timeValue,
2846
+ onTimeChange
2675
2847
  });
2676
- const [hoverDate, setHoverDate] = React10.useState();
2677
- const [leftMonth, setLeftMonth] = React10.useState(
2678
- () => startOfMonth(value?.from ?? /* @__PURE__ */ new Date())
2679
- );
2680
- const [activePreset, setActivePreset] = React10.useState();
2681
- const [internalTime, setInternalTime] = React10.useState(DEFAULT_TIME_RANGE);
2682
- const committedRange = value ?? internalRange;
2683
- const currentTime = timeValue ?? internalTime;
2684
- const handleTimeChange = (newTime) => {
2685
- if (onTimeChange) onTimeChange(newTime);
2686
- else setInternalTime(newTime);
2687
- };
2688
- const timeVisible = !!(draft.from && draft.to);
2689
- const handleOpenChange = (newOpen) => {
2690
- if (newOpen) {
2691
- setDraft(committedRange);
2692
- if (committedRange.from) setLeftMonth(startOfMonth(committedRange.from));
2693
- if (!onTimeChange) setInternalTime(currentTime);
2694
- }
2695
- setOpen(newOpen);
2696
- };
2697
- const handleDayClick = (date) => {
2698
- const { from, to } = draft;
2699
- if (!from || from && to) {
2700
- setDraft({ from: date, to: void 0 });
2701
- setActivePreset(void 0);
2702
- return;
2703
- }
2704
- const [start, end] = isBefore(from, date) ? [from, date] : [date, from];
2705
- setDraft({ from: start, to: end });
2706
- setHoverDate(void 0);
2707
- };
2708
- const handlePreset = (preset) => {
2709
- const newRange = preset.getRange();
2710
- setDraft(newRange);
2711
- setActivePreset(preset.label);
2712
- if (newRange.from) setLeftMonth(startOfMonth(newRange.from));
2713
- };
2714
- const handleApply = () => {
2715
- if (draft.from && !draft.to) return;
2716
- const newRange = draft.from && draft.to ? draft : void 0;
2717
- if (onChange) onChange(newRange);
2718
- else setInternalRange(newRange ?? { from: void 0, to: void 0 });
2719
- setOpen(false);
2720
- };
2721
- const handleClear = () => {
2722
- setDraft({ from: void 0, to: void 0 });
2723
- setActivePreset(void 0);
2724
- if (showTimePicker) handleTimeChange(DEFAULT_TIME_RANGE);
2725
- };
2726
- const canClear = !!(draft.from || committedRange.from);
2727
- const canApply = !(draft.from && !draft.to) && !!(draft.from || committedRange.from);
2728
- const rightMonth = addMonths(leftMonth, 1);
2729
- return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
2848
+ const rightMonth = addMonths(picker.viewMonth, 1);
2849
+ return /* @__PURE__ */ jsxs(Popover, { open: picker.open, onOpenChange: picker.handleOpenChange, children: [
2730
2850
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
2731
2851
  "button",
2732
2852
  {
@@ -2736,15 +2856,15 @@ function DateRangePicker({
2736
2856
  "hover:bg-gray-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2",
2737
2857
  "dark:border-neutral-600 dark:bg-neutral-800 dark:hover:bg-neutral-700",
2738
2858
  "disabled:pointer-events-none disabled:opacity-50",
2739
- committedRange.from ? "text-gray-900 dark:text-gray-100" : "text-gray-400 dark:text-gray-500",
2859
+ picker.committedRange.from ? "text-gray-900 dark:text-gray-100" : "text-gray-400 dark:text-gray-500",
2740
2860
  className
2741
2861
  ),
2742
2862
  children: [
2743
2863
  /* @__PURE__ */ jsx(CalendarIcon, { className: "w-4 h-4 shrink-0 text-gray-400 dark:text-gray-500" }),
2744
2864
  /* @__PURE__ */ jsx("span", { children: formatDateRange(
2745
- committedRange,
2865
+ picker.committedRange,
2746
2866
  placeholder,
2747
- showTimePicker ? currentTime : void 0
2867
+ showTimePicker ? picker.committedTime : void 0
2748
2868
  ) })
2749
2869
  ]
2750
2870
  }
@@ -2756,10 +2876,10 @@ function DateRangePicker({
2756
2876
  section.options.map((preset) => /* @__PURE__ */ jsx(
2757
2877
  "button",
2758
2878
  {
2759
- onClick: () => handlePreset(preset),
2879
+ onClick: () => picker.handlePreset(preset),
2760
2880
  className: cn(
2761
2881
  "w-full text-left px-2 py-1.5 rounded-md text-sm transition-colors",
2762
- activePreset === preset.label ? "bg-blue-50 text-blue-600 font-medium dark:bg-blue-950/50 dark:text-blue-400" : "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-neutral-700"
2882
+ picker.activePreset === preset.label ? "bg-blue-50 text-blue-600 font-medium dark:bg-blue-950/50 dark:text-blue-400" : "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-neutral-700"
2763
2883
  ),
2764
2884
  children: preset.label
2765
2885
  },
@@ -2771,12 +2891,12 @@ function DateRangePicker({
2771
2891
  /* @__PURE__ */ jsx(
2772
2892
  CalendarMonth,
2773
2893
  {
2774
- month: leftMonth,
2775
- range: draft,
2776
- hoverDate,
2777
- onDayClick: handleDayClick,
2778
- onDayHover: setHoverDate,
2779
- onPrevMonth: () => setLeftMonth(subMonths(leftMonth, 1)),
2894
+ month: picker.viewMonth,
2895
+ range: picker.draft,
2896
+ hoverDate: picker.hoverDate,
2897
+ onDayClick: picker.handleDayClick,
2898
+ onDayHover: picker.setHoverDate,
2899
+ onPrevMonth: () => picker.setViewMonth(subMonths(picker.viewMonth, 1)),
2780
2900
  showPrevNav: true,
2781
2901
  showNextNav: false
2782
2902
  }
@@ -2786,46 +2906,72 @@ function DateRangePicker({
2786
2906
  CalendarMonth,
2787
2907
  {
2788
2908
  month: rightMonth,
2789
- range: draft,
2790
- hoverDate,
2791
- onDayClick: handleDayClick,
2792
- onDayHover: setHoverDate,
2793
- onNextMonth: () => setLeftMonth(addMonths(leftMonth, 1)),
2909
+ range: picker.draft,
2910
+ hoverDate: picker.hoverDate,
2911
+ onDayClick: picker.handleDayClick,
2912
+ onDayHover: picker.setHoverDate,
2913
+ onNextMonth: () => picker.setViewMonth(addMonths(picker.viewMonth, 1)),
2794
2914
  showPrevNav: false,
2795
2915
  showNextNav: true
2796
2916
  }
2797
2917
  )
2798
2918
  ] }),
2799
- showTimePicker && /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: timeVisible && /* @__PURE__ */ jsx(
2919
+ showTimePicker && /* @__PURE__ */ jsx(
2800
2920
  motion.div,
2801
2921
  {
2802
- initial: { height: 0, opacity: 0 },
2803
- animate: { height: "auto", opacity: 1 },
2804
- exit: { height: 0, opacity: 0 },
2805
- transition: {
2806
- height: { type: "spring", stiffness: 400, damping: 30, mass: 0.8 },
2807
- opacity: { duration: 0.2 }
2808
- },
2809
- className: "overflow-hidden",
2810
- children: /* @__PURE__ */ jsx(
2811
- TimePickerRow,
2922
+ layout: true,
2923
+ transition: { layout: { duration: 0.2, ease: "easeInOut" } },
2924
+ className: "border-t border-gray-100 dark:border-neutral-700",
2925
+ children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: picker.timeVisible && !picker.timeExpanded ? /* @__PURE__ */ jsx(
2926
+ motion.div,
2812
2927
  {
2813
- value: currentTime,
2814
- onChange: handleTimeChange
2815
- }
2816
- )
2817
- },
2818
- "time-picker"
2819
- ) }),
2928
+ initial: { opacity: 0 },
2929
+ animate: { opacity: 1 },
2930
+ exit: { opacity: 0 },
2931
+ transition: { duration: 0.12 },
2932
+ children: /* @__PURE__ */ jsx("div", { className: "px-4 py-2 ", children: /* @__PURE__ */ jsx(
2933
+ Button,
2934
+ {
2935
+ variant: "outline",
2936
+ onClick: picker.expandTime,
2937
+ leadingDecorator: /* @__PURE__ */ jsx(Clock, { className: "w-3.5 h-3.5" }),
2938
+ className: "w-full rounded-md h-9",
2939
+ children: "Add time range"
2940
+ }
2941
+ ) })
2942
+ },
2943
+ "add-time-btn"
2944
+ ) : picker.timeVisible && picker.timeExpanded ? /* @__PURE__ */ jsx(
2945
+ motion.div,
2946
+ {
2947
+ initial: { opacity: 0 },
2948
+ animate: { opacity: 1 },
2949
+ exit: { opacity: 0 },
2950
+ transition: { duration: 0.12 },
2951
+ children: /* @__PURE__ */ jsx(
2952
+ TimePickerRow,
2953
+ {
2954
+ value: picker.draftTime,
2955
+ onChange: picker.handleTimeChange,
2956
+ onRemove: picker.resetTime,
2957
+ showRemove: picker.timeExpanded,
2958
+ onDirty: picker.markTimeDirty
2959
+ }
2960
+ )
2961
+ },
2962
+ "time-picker"
2963
+ ) : null })
2964
+ }
2965
+ ),
2820
2966
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 px-4 py-3 border-t border-gray-100 dark:border-neutral-700", children: [
2821
2967
  /* @__PURE__ */ jsx(
2822
2968
  "button",
2823
2969
  {
2824
- onClick: handleClear,
2825
- disabled: !canClear,
2970
+ onClick: picker.handleClear,
2971
+ disabled: !picker.canClear,
2826
2972
  className: cn(
2827
2973
  "px-3 py-1.5 rounded-md text-sm transition-colors",
2828
- canClear ? "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-neutral-700" : "text-gray-300 dark:text-gray-600 cursor-not-allowed"
2974
+ picker.canClear ? "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-neutral-700" : "text-gray-300 dark:text-gray-600 cursor-not-allowed"
2829
2975
  ),
2830
2976
  children: "Clear"
2831
2977
  }
@@ -2833,11 +2979,11 @@ function DateRangePicker({
2833
2979
  /* @__PURE__ */ jsx(
2834
2980
  "button",
2835
2981
  {
2836
- onClick: handleApply,
2837
- disabled: !canApply,
2982
+ onClick: picker.handleApply,
2983
+ disabled: !picker.canApply,
2838
2984
  className: cn(
2839
2985
  "px-3 py-1.5 rounded-md text-sm font-medium transition-colors",
2840
- canApply ? "bg-blue-600 text-white hover:bg-blue-700 dark:hover:bg-blue-500" : "bg-blue-100 text-blue-300 cursor-not-allowed dark:bg-blue-950/30 dark:text-blue-800"
2986
+ picker.canApply ? "bg-blue-600 text-white hover:bg-blue-700 dark:hover:bg-blue-500" : "bg-blue-100 text-blue-300 cursor-not-allowed dark:bg-blue-950/30 dark:text-blue-800"
2841
2987
  ),
2842
2988
  children: "Apply"
2843
2989
  }
@@ -2847,30 +2993,6 @@ function DateRangePicker({
2847
2993
  ] }) })
2848
2994
  ] });
2849
2995
  }
2850
- function formatTime2(tv) {
2851
- if (tv.hour === null || tv.minute === null) return "";
2852
- const period = tv.hour >= 12 ? "PM" : "AM";
2853
- const h = tv.hour % 12 || 12;
2854
- const m = tv.minute.toString().padStart(2, "0");
2855
- return `${h}:${m} ${period}`;
2856
- }
2857
- function formatDateWithTime(date, dateFmt, time) {
2858
- const dateStr = format(date, dateFmt);
2859
- const timeStr = time ? formatTime2(time) : "";
2860
- return timeStr ? `${dateStr}, ${timeStr}` : dateStr;
2861
- }
2862
- function formatDateRange2(range, placeholder, time) {
2863
- if (!range?.from) return placeholder;
2864
- const hasTime = time && time.from.hour !== null && time.to.hour !== null;
2865
- if (!range.to || isSameDay(range.from, range.to)) {
2866
- return formatDateWithTime(range.from, "MMMM d, yyyy", hasTime ? time.from : void 0);
2867
- }
2868
- const sameYear = range.from.getFullYear() === range.to.getFullYear();
2869
- const fromFmt = sameYear ? "MMMM d" : "MMMM d, yyyy";
2870
- const fromStr = formatDateWithTime(range.from, fromFmt, hasTime ? time.from : void 0);
2871
- const toStr = formatDateWithTime(range.to, "MMMM d, yyyy", hasTime ? time.to : void 0);
2872
- return `${fromStr} \u2013 ${toStr}`;
2873
- }
2874
2996
  function DateRangePickerMobile({
2875
2997
  label,
2876
2998
  value,
@@ -2884,70 +3006,16 @@ function DateRangePickerMobile({
2884
3006
  timeValue,
2885
3007
  onTimeChange
2886
3008
  }) {
2887
- const [open, setOpen] = React10.useState(false);
2888
- const [internalRange, setInternalRange] = React10.useState({
2889
- from: void 0,
2890
- to: void 0
3009
+ const picker = useDateRangePicker({
3010
+ value,
3011
+ onChange,
3012
+ showTimePicker,
3013
+ timeValue,
3014
+ onTimeChange
2891
3015
  });
2892
- const [draft, setDraft] = React10.useState({
2893
- from: void 0,
2894
- to: void 0
2895
- });
2896
- const [hoverDate, setHoverDate] = React10.useState();
2897
- const [viewMonth, setViewMonth] = React10.useState(
2898
- () => startOfMonth(value?.from ?? /* @__PURE__ */ new Date())
2899
- );
2900
- const [activePreset, setActivePreset] = React10.useState();
2901
- const [internalTime, setInternalTime] = React10.useState(DEFAULT_TIME_RANGE);
2902
- const committedRange = value ?? internalRange;
2903
- const currentTime = timeValue ?? internalTime;
2904
- const handleTimeChange = (newTime) => {
2905
- if (onTimeChange) onTimeChange(newTime);
2906
- else setInternalTime(newTime);
2907
- };
2908
- const timeVisible = !!(draft.from && draft.to);
2909
- const handleOpenChange = (newOpen) => {
2910
- if (newOpen) {
2911
- setDraft(committedRange);
2912
- if (committedRange.from) setViewMonth(startOfMonth(committedRange.from));
2913
- if (!onTimeChange) setInternalTime(currentTime);
2914
- }
2915
- setOpen(newOpen);
2916
- };
2917
- const handleDayClick = (date) => {
2918
- const { from, to } = draft;
2919
- if (!from || from && to) {
2920
- setDraft({ from: date, to: void 0 });
2921
- setActivePreset(void 0);
2922
- return;
2923
- }
2924
- const [start, end] = isBefore(from, date) ? [from, date] : [date, from];
2925
- setDraft({ from: start, to: end });
2926
- setHoverDate(void 0);
2927
- };
2928
- const handlePreset = (preset) => {
2929
- const newRange = preset.getRange();
2930
- setDraft(newRange);
2931
- setActivePreset(preset.label);
2932
- if (newRange.from) setViewMonth(startOfMonth(newRange.from));
2933
- };
2934
- const handleApply = () => {
2935
- if (draft.from && !draft.to) return;
2936
- const newRange = draft.from && draft.to ? draft : void 0;
2937
- if (onChange) onChange(newRange);
2938
- else setInternalRange(newRange ?? { from: void 0, to: void 0 });
2939
- setOpen(false);
2940
- };
2941
- const handleClear = () => {
2942
- setDraft({ from: void 0, to: void 0 });
2943
- setActivePreset(void 0);
2944
- if (showTimePicker) handleTimeChange(DEFAULT_TIME_RANGE);
2945
- };
2946
- const canClear = !!(draft.from || committedRange.from);
2947
- const canApply = !(draft.from && !draft.to) && !!(draft.from || committedRange.from);
2948
3016
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-1.5", className), children: [
2949
3017
  label && /* @__PURE__ */ jsx(Label, { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: label }),
2950
- /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
3018
+ /* @__PURE__ */ jsxs(Popover, { open: picker.open, onOpenChange: picker.handleOpenChange, children: [
2951
3019
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
2952
3020
  "button",
2953
3021
  {
@@ -2957,14 +3025,14 @@ function DateRangePickerMobile({
2957
3025
  "hover:bg-gray-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2",
2958
3026
  "dark:border-neutral-600 dark:bg-neutral-800 dark:hover:bg-neutral-700",
2959
3027
  "disabled:pointer-events-none disabled:opacity-50",
2960
- committedRange.from ? "text-gray-900 dark:text-gray-100" : "text-gray-400 dark:text-gray-500"
3028
+ picker.committedRange.from ? "text-gray-900 dark:text-gray-100" : "text-gray-400 dark:text-gray-500"
2961
3029
  ),
2962
3030
  children: [
2963
3031
  /* @__PURE__ */ jsx(CalendarIcon, { className: "w-4 h-4 shrink-0 text-gray-400 dark:text-gray-500" }),
2964
- /* @__PURE__ */ jsx("span", { className: "truncate", children: formatDateRange2(
2965
- committedRange,
3032
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: formatDateRange(
3033
+ picker.committedRange,
2966
3034
  placeholder,
2967
- showTimePicker ? currentTime : void 0
3035
+ showTimePicker ? picker.committedTime : void 0
2968
3036
  ) })
2969
3037
  ]
2970
3038
  }
@@ -2977,10 +3045,10 @@ function DateRangePickerMobile({
2977
3045
  section.options.map((preset) => /* @__PURE__ */ jsx(
2978
3046
  "button",
2979
3047
  {
2980
- onClick: () => handlePreset(preset),
3048
+ onClick: () => picker.handlePreset(preset),
2981
3049
  className: cn(
2982
3050
  "w-full text-left px-2 py-1.5 rounded-md text-sm transition-colors",
2983
- activePreset === preset.label ? "bg-blue-50 text-blue-600 font-medium dark:bg-blue-950/50 dark:text-blue-400" : "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-neutral-700"
3051
+ picker.activePreset === preset.label ? "bg-blue-50 text-blue-600 font-medium dark:bg-blue-950/50 dark:text-blue-400" : "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-neutral-700"
2984
3052
  ),
2985
3053
  children: preset.label
2986
3054
  },
@@ -2992,52 +3060,73 @@ function DateRangePickerMobile({
2992
3060
  /* @__PURE__ */ jsx("div", { className: "p-2", children: /* @__PURE__ */ jsx(
2993
3061
  CalendarMonth,
2994
3062
  {
2995
- month: viewMonth,
2996
- range: draft,
2997
- hoverDate,
2998
- onDayClick: handleDayClick,
2999
- onDayHover: setHoverDate,
3000
- onPrevMonth: () => setViewMonth(subMonths(viewMonth, 1)),
3001
- onNextMonth: () => setViewMonth(addMonths(viewMonth, 1)),
3063
+ month: picker.viewMonth,
3064
+ range: picker.draft,
3065
+ hoverDate: picker.hoverDate,
3066
+ onDayClick: picker.handleDayClick,
3067
+ onDayHover: picker.setHoverDate,
3068
+ onPrevMonth: () => picker.setViewMonth(subMonths(picker.viewMonth, 1)),
3069
+ onNextMonth: () => picker.setViewMonth(addMonths(picker.viewMonth, 1)),
3002
3070
  showPrevNav: true,
3003
3071
  showNextNav: true
3004
3072
  }
3005
3073
  ) }),
3006
- showTimePicker && /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: timeVisible && /* @__PURE__ */ jsx(
3074
+ showTimePicker && /* @__PURE__ */ jsx(
3007
3075
  motion.div,
3008
3076
  {
3009
- initial: { height: 0, opacity: 0 },
3010
- animate: { height: "auto", opacity: 1 },
3011
- exit: { height: 0, opacity: 0 },
3012
- transition: {
3013
- height: {
3014
- type: "spring",
3015
- stiffness: 400,
3016
- damping: 30,
3017
- mass: 0.8
3077
+ layout: true,
3078
+ transition: { layout: { duration: 0.2, ease: "easeInOut" } },
3079
+ className: "border-t border-gray-100 dark:border-neutral-700",
3080
+ children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: picker.timeVisible && !picker.timeExpanded ? /* @__PURE__ */ jsx(
3081
+ motion.div,
3082
+ {
3083
+ initial: { opacity: 0 },
3084
+ animate: { opacity: 1 },
3085
+ exit: { opacity: 0 },
3086
+ transition: { duration: 0.12 },
3087
+ children: /* @__PURE__ */ jsx("div", { className: "px-2 py-2", children: /* @__PURE__ */ jsx(
3088
+ Button,
3089
+ {
3090
+ variant: "outline",
3091
+ onClick: picker.expandTime,
3092
+ leadingDecorator: /* @__PURE__ */ jsx(Clock, { className: "w-3.5 h-3.5" }),
3093
+ className: "w-full rounded-md h-9",
3094
+ children: "Add time range"
3095
+ }
3096
+ ) })
3018
3097
  },
3019
- opacity: { duration: 0.2 }
3020
- },
3021
- className: "overflow-hidden",
3022
- children: /* @__PURE__ */ jsx(
3023
- TimePickerColumn,
3098
+ "add-time-btn"
3099
+ ) : picker.timeVisible && picker.timeExpanded ? /* @__PURE__ */ jsx(
3100
+ motion.div,
3024
3101
  {
3025
- value: currentTime,
3026
- onChange: handleTimeChange
3027
- }
3028
- )
3029
- },
3030
- "time-picker"
3031
- ) }),
3102
+ initial: { opacity: 0 },
3103
+ animate: { opacity: 1 },
3104
+ exit: { opacity: 0 },
3105
+ transition: { duration: 0.12 },
3106
+ children: /* @__PURE__ */ jsx(
3107
+ TimePickerColumn,
3108
+ {
3109
+ value: picker.draftTime,
3110
+ onChange: picker.handleTimeChange,
3111
+ onRemove: picker.resetTime,
3112
+ showRemove: picker.timeExpanded,
3113
+ onDirty: picker.markTimeDirty
3114
+ }
3115
+ )
3116
+ },
3117
+ "time-picker"
3118
+ ) : null })
3119
+ }
3120
+ ),
3032
3121
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 px-2 py-3 border-t border-gray-100 dark:border-neutral-700", children: [
3033
3122
  /* @__PURE__ */ jsx(
3034
3123
  "button",
3035
3124
  {
3036
- onClick: handleClear,
3037
- disabled: !canClear,
3125
+ onClick: picker.handleClear,
3126
+ disabled: !picker.canClear,
3038
3127
  className: cn(
3039
3128
  "px-3 py-1.5 rounded-md text-sm transition-colors",
3040
- canClear ? "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-neutral-700" : "text-gray-300 dark:text-gray-600 cursor-not-allowed"
3129
+ picker.canClear ? "text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-neutral-700" : "text-gray-300 dark:text-gray-600 cursor-not-allowed"
3041
3130
  ),
3042
3131
  children: "Clear"
3043
3132
  }
@@ -3045,11 +3134,11 @@ function DateRangePickerMobile({
3045
3134
  /* @__PURE__ */ jsx(
3046
3135
  "button",
3047
3136
  {
3048
- onClick: handleApply,
3049
- disabled: !canApply,
3137
+ onClick: picker.handleApply,
3138
+ disabled: !picker.canApply,
3050
3139
  className: cn(
3051
3140
  "px-3 py-1.5 rounded-md text-sm font-medium transition-colors",
3052
- canApply ? "bg-blue-600 text-white hover:bg-blue-700 dark:hover:bg-blue-500" : "bg-blue-100 text-blue-300 cursor-not-allowed dark:bg-blue-950/30 dark:text-blue-800"
3141
+ picker.canApply ? "bg-blue-600 text-white hover:bg-blue-700 dark:hover:bg-blue-500" : "bg-blue-100 text-blue-300 cursor-not-allowed dark:bg-blue-950/30 dark:text-blue-800"
3053
3142
  ),
3054
3143
  children: "Apply"
3055
3144
  }
@@ -3309,5 +3398,5 @@ function PeriodComparisonSelector({
3309
3398
  }
3310
3399
 
3311
3400
  export { Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, ColumnSelection, DEFAULT_COMPARISON_PERIODS, DEFAULT_PRESETS, DEFAULT_TIME_RANGE, DateRangePicker, DateRangePickerMobile, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Form, FormControl, FormDescription, FormField, FormLabel, FormMessage, MobileDataCard, PeriodComparisonSelector, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator3 as Separator, Skeleton, SkeletonAvatar, SkeletonBadge, SkeletonButton, SkeletonCard, SkeletonIcon, SkeletonInput, SkeletonSubtitle, SkeletonTableRow, SkeletonTableRows, SkeletonText, SkeletonTitle, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, TableRowCheckbox, TableSelectAll, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toggle, buttonVariants, segmentedControlItemVariants, segmentedControlVariants, separatorVariants, switchLabelVariants, switchThumbVariants, switchTrackVariants, toggleGroupVariants, toggleItemVariants, useColumnVisibility, useFormContext, useFormFieldContext, useTableSelection };
3312
- //# sourceMappingURL=chunk-RA7KTV62.mjs.map
3313
- //# sourceMappingURL=chunk-RA7KTV62.mjs.map
3401
+ //# sourceMappingURL=chunk-HFDHLY24.mjs.map
3402
+ //# sourceMappingURL=chunk-HFDHLY24.mjs.map