@kopexa/date-picker 1.2.1 → 1.4.0

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.
@@ -27,6 +27,11 @@ var datePickerMessages = defineMessages({
27
27
  id: "date-picker.select_date_and_time",
28
28
  defaultMessage: "Select date and time",
29
29
  description: "Placeholder for date+time input"
30
+ },
31
+ save: {
32
+ id: "date-picker.save",
33
+ defaultMessage: "Save",
34
+ description: "Button to commit the staged date selection (only shown when an onSave callback is provided)"
30
35
  }
31
36
  });
32
37
 
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  datePickerMessages
4
- } from "./chunk-HPM5Y2V6.mjs";
4
+ } from "./chunk-6IFLG64O.mjs";
5
5
 
6
6
  // src/date-picker-field.tsx
7
7
  import {
@@ -24,14 +24,24 @@ import {
24
24
  DatePickerTableHeader,
25
25
  DatePickerTableRow,
26
26
  DatePickerTrigger,
27
+ DatePickerValueText,
27
28
  DatePickerView,
28
29
  DatePickerViewControl,
29
30
  DatePickerViewTrigger
30
31
  } from "@ark-ui/react/date-picker";
31
32
  import { Portal } from "@ark-ui/react/portal";
32
- import { CalendarDateTime } from "@internationalized/date";
33
+ import {
34
+ CalendarDate,
35
+ CalendarDateTime,
36
+ getLocalTimeZone
37
+ } from "@internationalized/date";
33
38
  import { useSafeIntl } from "@kopexa/i18n";
34
- import { useCallback, useMemo, useState } from "react";
39
+ import {
40
+ useCallback,
41
+ useEffect,
42
+ useMemo,
43
+ useState
44
+ } from "react";
35
45
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
36
46
  function CalendarIcon({ className }) {
37
47
  return /* @__PURE__ */ jsxs(
@@ -109,6 +119,55 @@ function XIcon({ className }) {
109
119
  }
110
120
  );
111
121
  }
122
+ function getDatePartOrder(locale) {
123
+ try {
124
+ const fmt = new Intl.DateTimeFormat(locale, {
125
+ day: "2-digit",
126
+ month: "2-digit",
127
+ year: "numeric"
128
+ });
129
+ return fmt.formatToParts(new Date(2024, 0, 1)).filter(
130
+ (p) => p.type === "day" || p.type === "month" || p.type === "year"
131
+ ).map((p) => p.type);
132
+ } catch {
133
+ return ["day", "month", "year"];
134
+ }
135
+ }
136
+ function finalizeDateParts(parts) {
137
+ var _a, _b, _c;
138
+ const day = (_a = parts.day) != null ? _a : 1;
139
+ const month = (_b = parts.month) != null ? _b : 1;
140
+ let year = (_c = parts.year) != null ? _c : (/* @__PURE__ */ new Date()).getFullYear();
141
+ if (year < 100) year = year < 50 ? 2e3 + year : 1900 + year;
142
+ if (month < 1 || month > 12) return void 0;
143
+ if (day < 1 || day > 31) return void 0;
144
+ return { year, month, day };
145
+ }
146
+ function parseLocalizedDate(value, locale) {
147
+ const trimmed = value.trim();
148
+ if (!trimmed) return void 0;
149
+ const order = getDatePartOrder(locale);
150
+ const segments = trimmed.split(/[./\-\s]+/).filter(Boolean);
151
+ if (segments.length === 3 && segments.every((s) => /^\d+$/.test(s))) {
152
+ const map2 = {};
153
+ order.forEach((field, i) => {
154
+ map2[field] = Number.parseInt(segments[i], 10);
155
+ });
156
+ return finalizeDateParts(map2);
157
+ }
158
+ const digits = trimmed.replace(/\D/g, "");
159
+ const widths = digits.length === 8 ? { day: 2, month: 2, year: 4 } : digits.length === 6 ? { day: 2, month: 2, year: 2 } : digits.length === 4 ? { day: 2, month: 2, year: 0 } : null;
160
+ if (!widths) return void 0;
161
+ let pos = 0;
162
+ const map = {};
163
+ for (const field of order) {
164
+ const w = widths[field];
165
+ if (w === 0) continue;
166
+ map[field] = Number.parseInt(digits.slice(pos, pos + w), 10);
167
+ pos += w;
168
+ }
169
+ return finalizeDateParts(map);
170
+ }
112
171
  var styles = {
113
172
  control: "relative flex items-center",
114
173
  input: "w-full h-9 rounded-md border bg-transparent pl-3 pr-9 text-sm outline-none focus:ring-2 focus:ring-ring",
@@ -132,9 +191,14 @@ var styles = {
132
191
  yearCellTrigger: "inline-flex items-center justify-center w-full py-2 text-sm rounded-md transition-colors hover:bg-muted data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[disabled]:text-muted-foreground/30 data-[disabled]:pointer-events-none",
133
192
  footer: "flex items-center gap-1 pt-2 mt-2 border-t",
134
193
  footerButton: "text-sm px-2 py-1 rounded-md hover:bg-muted transition-colors",
194
+ footerSaveButton: "ml-auto text-sm px-3 py-1 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors",
135
195
  timeInput: "h-9 rounded-md border bg-transparent px-3 text-sm outline-none focus:ring-2 focus:ring-ring",
136
196
  label: "text-sm font-medium",
137
- timeTrigger: "flex-1 h-9 rounded-md border bg-transparent px-3 text-sm text-left hover:bg-muted transition-colors flex items-center justify-between"
197
+ timeTrigger: "flex-1 h-9 rounded-md border bg-transparent px-3 text-sm text-left hover:bg-muted transition-colors flex items-center justify-between",
198
+ defaultGhostTrigger: "inline-flex items-center gap-2 h-9 rounded-md px-2.5 text-sm font-normal text-foreground transition-colors hover:bg-muted focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[placeholder]:text-muted-foreground",
199
+ defaultGhostPlaceholder: "text-muted-foreground",
200
+ timeRow: "mt-2 pt-2 border-t flex items-center gap-2",
201
+ timeRowLabel: "text-xs text-muted-foreground"
138
202
  };
139
203
  function DayView() {
140
204
  return /* @__PURE__ */ jsx(DatePickerView, { view: "day", children: /* @__PURE__ */ jsx(DatePickerContext, { children: (api) => /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -239,9 +303,19 @@ function YearView() {
239
303
  )) }) })
240
304
  ] }) }) });
241
305
  }
306
+ function CalendarPanel() {
307
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
308
+ /* @__PURE__ */ jsx(DayView, {}),
309
+ /* @__PURE__ */ jsx(MonthView, {}),
310
+ /* @__PURE__ */ jsx(YearView, {})
311
+ ] });
312
+ }
242
313
  function CalendarFooter({
243
314
  todayLabel,
244
- clearLabel
315
+ clearLabel,
316
+ saveLabel,
317
+ clearable = true,
318
+ onSave
245
319
  }) {
246
320
  return /* @__PURE__ */ jsx(DatePickerContext, { children: (api) => /* @__PURE__ */ jsxs("div", { className: styles.footer, children: [
247
321
  /* @__PURE__ */ jsx(
@@ -253,7 +327,7 @@ function CalendarFooter({
253
327
  children: todayLabel
254
328
  }
255
329
  ),
256
- /* @__PURE__ */ jsx(
330
+ clearable && /* @__PURE__ */ jsx(
257
331
  "button",
258
332
  {
259
333
  type: "button",
@@ -261,6 +335,64 @@ function CalendarFooter({
261
335
  className: `${styles.footerButton} text-destructive`,
262
336
  children: clearLabel
263
337
  }
338
+ ),
339
+ onSave && /* @__PURE__ */ jsx(
340
+ "button",
341
+ {
342
+ type: "button",
343
+ onClick: () => {
344
+ onSave({
345
+ value: api.value,
346
+ valueAsString: api.valueAsString,
347
+ view: api.view
348
+ });
349
+ api.setOpen(false);
350
+ },
351
+ className: styles.footerSaveButton,
352
+ children: saveLabel
353
+ }
354
+ )
355
+ ] }) });
356
+ }
357
+ function DateTimeFooter({
358
+ todayLabel,
359
+ clearLabel,
360
+ saveLabel,
361
+ clearable = true,
362
+ onSelectNow,
363
+ onClear,
364
+ onSave
365
+ }) {
366
+ return /* @__PURE__ */ jsx(DatePickerContext, { children: (api) => /* @__PURE__ */ jsxs("div", { className: styles.footer, children: [
367
+ /* @__PURE__ */ jsx(
368
+ "button",
369
+ {
370
+ type: "button",
371
+ onClick: onSelectNow,
372
+ className: `${styles.footerButton} text-foreground`,
373
+ children: todayLabel
374
+ }
375
+ ),
376
+ clearable && /* @__PURE__ */ jsx(
377
+ "button",
378
+ {
379
+ type: "button",
380
+ onClick: onClear,
381
+ className: `${styles.footerButton} text-destructive`,
382
+ children: clearLabel
383
+ }
384
+ ),
385
+ onSave && /* @__PURE__ */ jsx(
386
+ "button",
387
+ {
388
+ type: "button",
389
+ onClick: () => {
390
+ onSave();
391
+ api.setOpen(false);
392
+ },
393
+ className: styles.footerSaveButton,
394
+ children: saveLabel
395
+ }
264
396
  )
265
397
  ] }) });
266
398
  }
@@ -280,16 +412,49 @@ function DatePickerField({
280
412
  todayLabel: todayLabelProp,
281
413
  clearLabel: clearLabelProp,
282
414
  className,
283
- rootProps
415
+ rootProps,
416
+ variant = "input",
417
+ trigger,
418
+ formatValue,
419
+ onSave,
420
+ saveLabel: saveLabelProp
284
421
  }) {
285
422
  var _a;
286
423
  const intl = useSafeIntl();
287
424
  const locale = (_a = localeProp != null ? localeProp : intl.locale) != null ? _a : "en-US";
288
425
  const todayLabel = todayLabelProp != null ? todayLabelProp : intl.formatMessage(datePickerMessages.today);
289
426
  const clearLabel = clearLabelProp != null ? clearLabelProp : intl.formatMessage(datePickerMessages.clear);
427
+ const saveLabel = saveLabelProp != null ? saveLabelProp : intl.formatMessage(datePickerMessages.save);
290
428
  const placeholder = placeholderProp != null ? placeholderProp : intl.formatMessage(
291
429
  showTime ? datePickerMessages.select_date_and_time : datePickerMessages.select_date
292
430
  );
431
+ if (variant === "trigger") {
432
+ return /* @__PURE__ */ jsx(
433
+ DateTriggerPickerField,
434
+ {
435
+ label,
436
+ value,
437
+ defaultValue,
438
+ onValueChange,
439
+ showTime,
440
+ clearable,
441
+ locale,
442
+ min,
443
+ max,
444
+ disabled,
445
+ readOnly,
446
+ placeholder,
447
+ todayLabel,
448
+ clearLabel,
449
+ saveLabel,
450
+ className,
451
+ rootProps,
452
+ trigger,
453
+ formatValue,
454
+ onSave
455
+ }
456
+ );
457
+ }
293
458
  if (showTime) {
294
459
  return /* @__PURE__ */ jsx(
295
460
  DateTimePickerField,
@@ -307,8 +472,10 @@ function DatePickerField({
307
472
  placeholder,
308
473
  todayLabel,
309
474
  clearLabel,
475
+ saveLabel,
310
476
  className,
311
- rootProps
477
+ rootProps,
478
+ onSave
312
479
  }
313
480
  );
314
481
  }
@@ -326,6 +493,10 @@ function DatePickerField({
326
493
  selectionMode: "single",
327
494
  outsideDaySelectable: true,
328
495
  closeOnSelect: true,
496
+ parse: (input, details) => {
497
+ const parts = parseLocalizedDate(input, details.locale);
498
+ return parts ? new CalendarDate(parts.year, parts.month, parts.day) : void 0;
499
+ },
329
500
  className,
330
501
  ...rootProps,
331
502
  children: [
@@ -344,10 +515,17 @@ function DatePickerField({
344
515
  ] }) : /* @__PURE__ */ jsx(DatePickerTrigger, { className: styles.trigger, children: /* @__PURE__ */ jsx(CalendarIcon, { className: "size-4" }) })
345
516
  ] }),
346
517
  /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(DatePickerPositioner, { children: /* @__PURE__ */ jsxs(DatePickerContent, { className: styles.content, children: [
347
- /* @__PURE__ */ jsx(DayView, {}),
348
- /* @__PURE__ */ jsx(MonthView, {}),
349
- /* @__PURE__ */ jsx(YearView, {}),
350
- /* @__PURE__ */ jsx(CalendarFooter, { todayLabel, clearLabel })
518
+ /* @__PURE__ */ jsx(CalendarPanel, {}),
519
+ /* @__PURE__ */ jsx(
520
+ CalendarFooter,
521
+ {
522
+ todayLabel,
523
+ clearLabel,
524
+ saveLabel,
525
+ clearable,
526
+ onSave
527
+ }
528
+ )
351
529
  ] }) }) })
352
530
  ]
353
531
  }
@@ -367,14 +545,17 @@ function DateTimePickerField({
367
545
  placeholder: placeholderProp,
368
546
  todayLabel: todayLabelProp,
369
547
  clearLabel: clearLabelProp,
548
+ saveLabel: saveLabelProp,
370
549
  className,
371
- rootProps
550
+ rootProps,
551
+ onSave
372
552
  }) {
373
553
  var _a;
374
554
  const intl = useSafeIntl();
375
555
  const locale = (_a = localeProp != null ? localeProp : intl.locale) != null ? _a : "en-US";
376
556
  const todayLabel = todayLabelProp != null ? todayLabelProp : intl.formatMessage(datePickerMessages.today);
377
557
  const clearLabel = clearLabelProp != null ? clearLabelProp : intl.formatMessage(datePickerMessages.clear);
558
+ const saveLabel = saveLabelProp != null ? saveLabelProp : intl.formatMessage(datePickerMessages.save);
378
559
  const placeholder = placeholderProp != null ? placeholderProp : intl.formatMessage(datePickerMessages.select_date_and_time);
379
560
  const [internalValue, setInternalValue] = useState(() => {
380
561
  const initial = valueProp != null ? valueProp : defaultValue;
@@ -445,6 +626,22 @@ function DateTimePickerField({
445
626
  view: "day"
446
627
  });
447
628
  }, [onValueChange]);
629
+ const handleSelectNow = useCallback(() => {
630
+ const now = /* @__PURE__ */ new Date();
631
+ const nowValue = new CalendarDateTime(
632
+ now.getFullYear(),
633
+ now.getMonth() + 1,
634
+ now.getDate(),
635
+ now.getHours(),
636
+ now.getMinutes()
637
+ );
638
+ setInternalValue([nowValue]);
639
+ onValueChange == null ? void 0 : onValueChange({
640
+ value: [nowValue],
641
+ valueAsString: [nowValue.toString()],
642
+ view: "day"
643
+ });
644
+ }, [onValueChange]);
448
645
  return /* @__PURE__ */ jsxs(
449
646
  DatePickerRoot,
450
647
  {
@@ -458,13 +655,33 @@ function DateTimePickerField({
458
655
  selectionMode: "single",
459
656
  outsideDaySelectable: true,
460
657
  closeOnSelect: false,
658
+ parse: (input, details) => {
659
+ const parts = parseLocalizedDate(input, details.locale);
660
+ if (!parts) return void 0;
661
+ const prev = currentValue[0];
662
+ const hour = prev && "hour" in prev ? prev.hour : 0;
663
+ const minute = prev && "minute" in prev ? prev.minute : 0;
664
+ return new CalendarDateTime(
665
+ parts.year,
666
+ parts.month,
667
+ parts.day,
668
+ hour,
669
+ minute
670
+ );
671
+ },
461
672
  className,
462
673
  ...rootProps,
463
674
  children: [
464
675
  label && /* @__PURE__ */ jsx(DatePickerLabel, { className: styles.label, children: label }),
465
- /* @__PURE__ */ jsxs(DatePickerControl, { className: "flex items-center gap-1", children: [
676
+ /* @__PURE__ */ jsxs(DatePickerControl, { className: "flex items-center", children: [
466
677
  /* @__PURE__ */ jsxs("div", { className: "relative flex-1 flex items-center", children: [
467
- /* @__PURE__ */ jsx(DatePickerInput, { className: styles.input, placeholder }),
678
+ /* @__PURE__ */ jsx(
679
+ DatePickerInput,
680
+ {
681
+ className: "w-full h-9 rounded-l-md border border-r-0 bg-transparent pl-3 pr-9 text-sm outline-none focus:ring-2 focus:ring-ring",
682
+ placeholder
683
+ }
684
+ ),
468
685
  /* @__PURE__ */ jsx(DatePickerTrigger, { className: styles.trigger, children: /* @__PURE__ */ jsx(CalendarIcon, { className: "size-4" }) })
469
686
  ] }),
470
687
  /* @__PURE__ */ jsx(
@@ -475,23 +692,215 @@ function DateTimePickerField({
475
692
  onChange: handleTimeChange,
476
693
  disabled,
477
694
  readOnly,
478
- className: styles.timeInput
695
+ className: `h-9 border bg-transparent px-3 text-sm outline-none focus:ring-2 focus:ring-ring ${clearable && !disabled && !readOnly ? "border-r-0" : "rounded-r-md"}`
479
696
  }
480
697
  ),
481
698
  clearable && !disabled && !readOnly && /* @__PURE__ */ jsx(
482
- DatePickerClearTrigger,
699
+ "button",
483
700
  {
484
- className: "inline-flex items-center justify-center size-9 rounded-md border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
701
+ type: "button",
702
+ className: "inline-flex items-center justify-center size-9 rounded-r-md border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
485
703
  onClick: handleClear,
486
704
  children: /* @__PURE__ */ jsx(XIcon, { className: "size-4" })
487
705
  }
488
706
  )
489
707
  ] }),
490
708
  /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(DatePickerPositioner, { children: /* @__PURE__ */ jsxs(DatePickerContent, { className: styles.content, children: [
491
- /* @__PURE__ */ jsx(DayView, {}),
492
- /* @__PURE__ */ jsx(MonthView, {}),
493
- /* @__PURE__ */ jsx(YearView, {}),
494
- /* @__PURE__ */ jsx(CalendarFooter, { todayLabel, clearLabel })
709
+ /* @__PURE__ */ jsx(CalendarPanel, {}),
710
+ /* @__PURE__ */ jsx(
711
+ DateTimeFooter,
712
+ {
713
+ todayLabel,
714
+ clearLabel,
715
+ saveLabel,
716
+ clearable,
717
+ onSelectNow: handleSelectNow,
718
+ onClear: handleClear,
719
+ onSave: onSave ? () => onSave({
720
+ value: currentValue,
721
+ valueAsString: currentValue.map((v) => v.toString()),
722
+ view: "day"
723
+ }) : void 0
724
+ }
725
+ )
726
+ ] }) }) })
727
+ ]
728
+ }
729
+ );
730
+ }
731
+ function DateTriggerPickerField({
732
+ label,
733
+ value: valueProp,
734
+ defaultValue,
735
+ onValueChange,
736
+ showTime = false,
737
+ clearable = true,
738
+ locale: localeProp,
739
+ min,
740
+ max,
741
+ disabled,
742
+ readOnly,
743
+ placeholder: placeholderProp,
744
+ todayLabel: todayLabelProp,
745
+ clearLabel: clearLabelProp,
746
+ saveLabel: saveLabelProp,
747
+ className,
748
+ rootProps,
749
+ trigger,
750
+ formatValue,
751
+ onSave
752
+ }) {
753
+ var _a;
754
+ const intl = useSafeIntl();
755
+ const locale = (_a = localeProp != null ? localeProp : intl.locale) != null ? _a : "en-US";
756
+ const todayLabel = todayLabelProp != null ? todayLabelProp : intl.formatMessage(datePickerMessages.today);
757
+ const clearLabel = clearLabelProp != null ? clearLabelProp : intl.formatMessage(datePickerMessages.clear);
758
+ const saveLabel = saveLabelProp != null ? saveLabelProp : intl.formatMessage(datePickerMessages.save);
759
+ const placeholder = placeholderProp != null ? placeholderProp : intl.formatMessage(
760
+ showTime ? datePickerMessages.select_date_and_time : datePickerMessages.select_date
761
+ );
762
+ const [draft, setDraft] = useState(
763
+ () => {
764
+ var _a2;
765
+ return (_a2 = valueProp != null ? valueProp : defaultValue) != null ? _a2 : [];
766
+ }
767
+ );
768
+ const [committed, setCommitted] = useState(
769
+ () => {
770
+ var _a2;
771
+ return (_a2 = valueProp != null ? valueProp : defaultValue) != null ? _a2 : [];
772
+ }
773
+ );
774
+ useEffect(() => {
775
+ if (valueProp !== void 0) {
776
+ setCommitted(valueProp);
777
+ if (!onSave) setDraft(valueProp);
778
+ }
779
+ }, [valueProp, onSave]);
780
+ const currentValue = onSave ? draft : valueProp != null ? valueProp : draft;
781
+ const handleDateChange = useCallback(
782
+ (details) => {
783
+ const next = details.value[0];
784
+ if (!showTime || !next) {
785
+ setDraft(details.value);
786
+ onValueChange == null ? void 0 : onValueChange(details);
787
+ return;
788
+ }
789
+ const prev = currentValue[0];
790
+ const prevHour = prev && "hour" in prev ? prev.hour : 0;
791
+ const prevMinute = prev && "minute" in prev ? prev.minute : 0;
792
+ const merged = new CalendarDateTime(
793
+ next.year,
794
+ next.month,
795
+ next.day,
796
+ prevHour,
797
+ prevMinute
798
+ );
799
+ setDraft([merged]);
800
+ onValueChange == null ? void 0 : onValueChange({ ...details, value: [merged] });
801
+ },
802
+ [currentValue, onValueChange, showTime]
803
+ );
804
+ const handleTimeChange = useCallback(
805
+ (e) => {
806
+ const [hours, minutes] = e.currentTarget.value.split(":").map(Number);
807
+ const prev = currentValue[0];
808
+ const base = prev && "hour" in prev ? prev : prev ? new CalendarDateTime(prev.year, prev.month, prev.day, 0, 0) : (() => {
809
+ const now = /* @__PURE__ */ new Date();
810
+ return new CalendarDateTime(
811
+ now.getFullYear(),
812
+ now.getMonth() + 1,
813
+ now.getDate(),
814
+ 0,
815
+ 0
816
+ );
817
+ })();
818
+ const updated = base.set({ hour: hours, minute: minutes });
819
+ setDraft([updated]);
820
+ onValueChange == null ? void 0 : onValueChange({
821
+ value: [updated],
822
+ valueAsString: [updated.toString()],
823
+ view: "day"
824
+ });
825
+ },
826
+ [currentValue, onValueChange]
827
+ );
828
+ const userOnOpenChange = rootProps == null ? void 0 : rootProps.onOpenChange;
829
+ const handleOpenChange = useCallback(
830
+ (details) => {
831
+ if (onSave && details.open) {
832
+ setDraft(committed);
833
+ }
834
+ userOnOpenChange == null ? void 0 : userOnOpenChange(details);
835
+ },
836
+ [committed, onSave, userOnOpenChange]
837
+ );
838
+ const handleSaveCommit = useCallback(
839
+ (details) => {
840
+ setCommitted(details.value);
841
+ onSave == null ? void 0 : onSave(details);
842
+ },
843
+ [onSave]
844
+ );
845
+ const formatter = useMemo(() => {
846
+ if (formatValue) return formatValue;
847
+ const fmt = new Intl.DateTimeFormat(
848
+ locale,
849
+ showTime ? { dateStyle: "medium", timeStyle: "short" } : { dateStyle: "medium" }
850
+ );
851
+ return (v) => fmt.format(v.toDate(getLocalTimeZone()));
852
+ }, [formatValue, locale, showTime]);
853
+ const timeValue = currentValue[0] && "hour" in currentValue[0] ? `${String(currentValue[0].hour).padStart(2, "0")}:${String(currentValue[0].minute).padStart(2, "0")}` : "";
854
+ const triggerElement = trigger != null ? trigger : onSave ? /* @__PURE__ */ jsxs("button", { type: "button", className: styles.defaultGhostTrigger, children: [
855
+ /* @__PURE__ */ jsx(CalendarIcon, { className: "size-4 shrink-0 opacity-70" }),
856
+ /* @__PURE__ */ jsx("span", { children: committed[0] ? formatter(committed[0]) : placeholder })
857
+ ] }) : /* @__PURE__ */ jsxs("button", { type: "button", className: styles.defaultGhostTrigger, children: [
858
+ /* @__PURE__ */ jsx(CalendarIcon, { className: "size-4 shrink-0 opacity-70" }),
859
+ /* @__PURE__ */ jsx(DatePickerValueText, { placeholder, children: ({ value }) => formatter(value) })
860
+ ] });
861
+ return /* @__PURE__ */ jsxs(
862
+ DatePickerRoot,
863
+ {
864
+ value: currentValue,
865
+ defaultValue,
866
+ onValueChange: handleDateChange,
867
+ locale,
868
+ min,
869
+ max,
870
+ disabled,
871
+ readOnly,
872
+ selectionMode: "single",
873
+ outsideDaySelectable: true,
874
+ closeOnSelect: !showTime && !onSave,
875
+ className,
876
+ ...rootProps,
877
+ onOpenChange: handleOpenChange,
878
+ children: [
879
+ label && /* @__PURE__ */ jsx(DatePickerLabel, { className: styles.label, children: label }),
880
+ /* @__PURE__ */ jsx(DatePickerControl, { children: /* @__PURE__ */ jsx(DatePickerTrigger, { asChild: true, children: triggerElement }) }),
881
+ /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(DatePickerPositioner, { children: /* @__PURE__ */ jsxs(DatePickerContent, { className: styles.content, children: [
882
+ /* @__PURE__ */ jsx(CalendarPanel, {}),
883
+ showTime && /* @__PURE__ */ jsx("div", { className: styles.timeRow, children: /* @__PURE__ */ jsx(
884
+ "input",
885
+ {
886
+ type: "time",
887
+ value: timeValue,
888
+ onChange: handleTimeChange,
889
+ disabled,
890
+ readOnly,
891
+ className: styles.timeInput
892
+ }
893
+ ) }),
894
+ /* @__PURE__ */ jsx(
895
+ CalendarFooter,
896
+ {
897
+ todayLabel,
898
+ clearLabel,
899
+ saveLabel,
900
+ clearable,
901
+ onSave: onSave ? handleSaveCommit : void 0
902
+ }
903
+ )
495
904
  ] }) }) })
496
905
  ]
497
906
  }
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { DatePickerValueChangeDetails, DatePickerRootProps } from '@ark-ui/react/date-picker';
3
3
  import { DateValue } from '@internationalized/date';
4
+ import { ReactElement } from 'react';
4
5
 
5
6
  type DatePickerFieldProps = {
6
7
  /** Label text */
@@ -35,7 +36,43 @@ type DatePickerFieldProps = {
35
36
  className?: string;
36
37
  /** Additional Ark UI Root props */
37
38
  rootProps?: Partial<DatePickerRootProps>;
39
+ /**
40
+ * Visual variant.
41
+ * - `"input"` (default): bordered input + calendar button + optional inline time/clear
42
+ * - `"trigger"`: a single ghost-style button that opens the popover
43
+ * with the calendar (and time picker, if `showTime`). Use `trigger`
44
+ * to provide a custom trigger element.
45
+ */
46
+ variant?: "input" | "trigger";
47
+ /**
48
+ * Custom trigger element rendered in `variant="trigger"` mode. Wrapped
49
+ * with `DatePickerTrigger asChild`, so any clickable element works
50
+ * (sight Button, Chip, etc.). The element should display the value —
51
+ * pass `<DatePickerValueText placeholder={…} />` as its children, or
52
+ * use the `formatValue` prop for default formatting.
53
+ */
54
+ trigger?: ReactElement;
55
+ /**
56
+ * Custom formatter for the trigger label (only used by the default
57
+ * `variant="trigger"` button). Receives the selected `DateValue` and
58
+ * returns a display string. Defaults to `Intl.DateTimeFormat` with the
59
+ * picker's locale, including time when `showTime` is true.
60
+ */
61
+ formatValue?: (value: DateValue) => string;
62
+ /**
63
+ * When provided, a "Save" button is rendered in the popover footer.
64
+ * `onValueChange` still fires per interaction so the picker stays
65
+ * controllable, but `onSave` is the explicit commit signal — callers
66
+ * that need draft-then-commit semantics (e.g. avoiding a save on every
67
+ * minute increment of the time picker) should listen here instead of
68
+ * `onValueChange`. Clicking Save also closes the popover.
69
+ */
70
+ onSave?: (details: DatePickerValueChangeDetails) => void;
71
+ /**
72
+ * Override the Save button label. Defaults to the i18n message.
73
+ */
74
+ saveLabel?: string;
38
75
  };
39
- declare function DatePickerField({ label, value, defaultValue, onValueChange, showTime, clearable, locale: localeProp, min, max, disabled, readOnly, placeholder: placeholderProp, todayLabel: todayLabelProp, clearLabel: clearLabelProp, className, rootProps, }: DatePickerFieldProps): react_jsx_runtime.JSX.Element;
76
+ declare function DatePickerField({ label, value, defaultValue, onValueChange, showTime, clearable, locale: localeProp, min, max, disabled, readOnly, placeholder: placeholderProp, todayLabel: todayLabelProp, clearLabel: clearLabelProp, className, rootProps, variant, trigger, formatValue, onSave, saveLabel: saveLabelProp, }: DatePickerFieldProps): react_jsx_runtime.JSX.Element;
40
77
 
41
78
  export { DatePickerField, type DatePickerFieldProps };