@kopexa/date-picker 1.2.0 → 1.3.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.
package/dist/index.js CHANGED
@@ -181,6 +181,55 @@ function XIcon({ className }) {
181
181
  }
182
182
  );
183
183
  }
184
+ function getDatePartOrder(locale) {
185
+ try {
186
+ const fmt = new Intl.DateTimeFormat(locale, {
187
+ day: "2-digit",
188
+ month: "2-digit",
189
+ year: "numeric"
190
+ });
191
+ return fmt.formatToParts(new Date(2024, 0, 1)).filter(
192
+ (p) => p.type === "day" || p.type === "month" || p.type === "year"
193
+ ).map((p) => p.type);
194
+ } catch {
195
+ return ["day", "month", "year"];
196
+ }
197
+ }
198
+ function finalizeDateParts(parts) {
199
+ var _a, _b, _c;
200
+ const day = (_a = parts.day) != null ? _a : 1;
201
+ const month = (_b = parts.month) != null ? _b : 1;
202
+ let year = (_c = parts.year) != null ? _c : (/* @__PURE__ */ new Date()).getFullYear();
203
+ if (year < 100) year = year < 50 ? 2e3 + year : 1900 + year;
204
+ if (month < 1 || month > 12) return void 0;
205
+ if (day < 1 || day > 31) return void 0;
206
+ return { year, month, day };
207
+ }
208
+ function parseLocalizedDate(value, locale) {
209
+ const trimmed = value.trim();
210
+ if (!trimmed) return void 0;
211
+ const order = getDatePartOrder(locale);
212
+ const segments = trimmed.split(/[./\-\s]+/).filter(Boolean);
213
+ if (segments.length === 3 && segments.every((s) => /^\d+$/.test(s))) {
214
+ const map2 = {};
215
+ order.forEach((field, i) => {
216
+ map2[field] = Number.parseInt(segments[i], 10);
217
+ });
218
+ return finalizeDateParts(map2);
219
+ }
220
+ const digits = trimmed.replace(/\D/g, "");
221
+ 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;
222
+ if (!widths) return void 0;
223
+ let pos = 0;
224
+ const map = {};
225
+ for (const field of order) {
226
+ const w = widths[field];
227
+ if (w === 0) continue;
228
+ map[field] = Number.parseInt(digits.slice(pos, pos + w), 10);
229
+ pos += w;
230
+ }
231
+ return finalizeDateParts(map);
232
+ }
184
233
  var styles = {
185
234
  control: "relative flex items-center",
186
235
  input: "w-full h-9 rounded-md border bg-transparent pl-3 pr-9 text-sm outline-none focus:ring-2 focus:ring-ring",
@@ -206,7 +255,11 @@ var styles = {
206
255
  footerButton: "text-sm px-2 py-1 rounded-md hover:bg-muted transition-colors",
207
256
  timeInput: "h-9 rounded-md border bg-transparent px-3 text-sm outline-none focus:ring-2 focus:ring-ring",
208
257
  label: "text-sm font-medium",
209
- 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"
258
+ 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",
259
+ 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",
260
+ defaultGhostPlaceholder: "text-muted-foreground",
261
+ timeRow: "mt-2 pt-2 border-t flex items-center gap-2",
262
+ timeRowLabel: "text-xs text-muted-foreground"
210
263
  };
211
264
  function DayView() {
212
265
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerView, { view: "day", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerContext, { children: (api) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -311,9 +364,17 @@ function YearView() {
311
364
  )) }) })
312
365
  ] }) }) });
313
366
  }
367
+ function CalendarPanel() {
368
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DayView, {}),
370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MonthView, {}),
371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YearView, {})
372
+ ] });
373
+ }
314
374
  function CalendarFooter({
315
375
  todayLabel,
316
- clearLabel
376
+ clearLabel,
377
+ clearable = true
317
378
  }) {
318
379
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerContext, { children: (api) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.footer, children: [
319
380
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -325,7 +386,7 @@ function CalendarFooter({
325
386
  children: todayLabel
326
387
  }
327
388
  ),
328
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
389
+ clearable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
329
390
  "button",
330
391
  {
331
392
  type: "button",
@@ -336,6 +397,34 @@ function CalendarFooter({
336
397
  )
337
398
  ] }) });
338
399
  }
400
+ function DateTimeFooter({
401
+ todayLabel,
402
+ clearLabel,
403
+ clearable = true,
404
+ onSelectNow,
405
+ onClear
406
+ }) {
407
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.footer, children: [
408
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
409
+ "button",
410
+ {
411
+ type: "button",
412
+ onClick: onSelectNow,
413
+ className: `${styles.footerButton} text-foreground`,
414
+ children: todayLabel
415
+ }
416
+ ),
417
+ clearable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
418
+ "button",
419
+ {
420
+ type: "button",
421
+ onClick: onClear,
422
+ className: `${styles.footerButton} text-destructive`,
423
+ children: clearLabel
424
+ }
425
+ )
426
+ ] });
427
+ }
339
428
  function DatePickerField({
340
429
  label,
341
430
  value,
@@ -352,7 +441,10 @@ function DatePickerField({
352
441
  todayLabel: todayLabelProp,
353
442
  clearLabel: clearLabelProp,
354
443
  className,
355
- rootProps
444
+ rootProps,
445
+ variant = "input",
446
+ trigger,
447
+ formatValue
356
448
  }) {
357
449
  var _a;
358
450
  const intl = (0, import_i18n2.useSafeIntl)();
@@ -362,6 +454,31 @@ function DatePickerField({
362
454
  const placeholder = placeholderProp != null ? placeholderProp : intl.formatMessage(
363
455
  showTime ? datePickerMessages.select_date_and_time : datePickerMessages.select_date
364
456
  );
457
+ if (variant === "trigger") {
458
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
459
+ DateTriggerPickerField,
460
+ {
461
+ label,
462
+ value,
463
+ defaultValue,
464
+ onValueChange,
465
+ showTime,
466
+ clearable,
467
+ locale,
468
+ min,
469
+ max,
470
+ disabled,
471
+ readOnly,
472
+ placeholder,
473
+ todayLabel,
474
+ clearLabel,
475
+ className,
476
+ rootProps,
477
+ trigger,
478
+ formatValue
479
+ }
480
+ );
481
+ }
365
482
  if (showTime) {
366
483
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
367
484
  DateTimePickerField,
@@ -398,6 +515,10 @@ function DatePickerField({
398
515
  selectionMode: "single",
399
516
  outsideDaySelectable: true,
400
517
  closeOnSelect: true,
518
+ parse: (input, details) => {
519
+ const parts = parseLocalizedDate(input, details.locale);
520
+ return parts ? new import_date.CalendarDate(parts.year, parts.month, parts.day) : void 0;
521
+ },
401
522
  className,
402
523
  ...rootProps,
403
524
  children: [
@@ -416,10 +537,15 @@ function DatePickerField({
416
537
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerTrigger, { className: styles.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarIcon, { className: "size-4" }) })
417
538
  ] }),
418
539
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_portal.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerPositioner, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_date_picker2.DatePickerContent, { className: styles.content, children: [
419
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DayView, {}),
420
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MonthView, {}),
421
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YearView, {}),
422
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarFooter, { todayLabel, clearLabel })
540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarPanel, {}),
541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
542
+ CalendarFooter,
543
+ {
544
+ todayLabel,
545
+ clearLabel,
546
+ clearable
547
+ }
548
+ )
423
549
  ] }) }) })
424
550
  ]
425
551
  }
@@ -517,6 +643,22 @@ function DateTimePickerField({
517
643
  view: "day"
518
644
  });
519
645
  }, [onValueChange]);
646
+ const handleSelectNow = (0, import_react.useCallback)(() => {
647
+ const now = /* @__PURE__ */ new Date();
648
+ const nowValue = new import_date.CalendarDateTime(
649
+ now.getFullYear(),
650
+ now.getMonth() + 1,
651
+ now.getDate(),
652
+ now.getHours(),
653
+ now.getMinutes()
654
+ );
655
+ setInternalValue([nowValue]);
656
+ onValueChange == null ? void 0 : onValueChange({
657
+ value: [nowValue],
658
+ valueAsString: [nowValue.toString()],
659
+ view: "day"
660
+ });
661
+ }, [onValueChange]);
520
662
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
521
663
  import_date_picker2.DatePickerRoot,
522
664
  {
@@ -530,13 +672,33 @@ function DateTimePickerField({
530
672
  selectionMode: "single",
531
673
  outsideDaySelectable: true,
532
674
  closeOnSelect: false,
675
+ parse: (input, details) => {
676
+ const parts = parseLocalizedDate(input, details.locale);
677
+ if (!parts) return void 0;
678
+ const prev = currentValue[0];
679
+ const hour = prev && "hour" in prev ? prev.hour : 0;
680
+ const minute = prev && "minute" in prev ? prev.minute : 0;
681
+ return new import_date.CalendarDateTime(
682
+ parts.year,
683
+ parts.month,
684
+ parts.day,
685
+ hour,
686
+ minute
687
+ );
688
+ },
533
689
  className,
534
690
  ...rootProps,
535
691
  children: [
536
692
  label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerLabel, { className: styles.label, children: label }),
537
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_date_picker2.DatePickerControl, { className: "flex items-center gap-1", children: [
693
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_date_picker2.DatePickerControl, { className: "flex items-center", children: [
538
694
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex-1 flex items-center", children: [
539
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerInput, { className: styles.input, placeholder }),
695
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
696
+ import_date_picker2.DatePickerInput,
697
+ {
698
+ 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",
699
+ placeholder
700
+ }
701
+ ),
540
702
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerTrigger, { className: styles.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarIcon, { className: "size-4" }) })
541
703
  ] }),
542
704
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -547,23 +709,174 @@ function DateTimePickerField({
547
709
  onChange: handleTimeChange,
548
710
  disabled,
549
711
  readOnly,
550
- className: styles.timeInput
712
+ 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"}`
551
713
  }
552
714
  ),
553
715
  clearable && !disabled && !readOnly && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
554
- import_date_picker2.DatePickerClearTrigger,
716
+ "button",
555
717
  {
556
- className: "inline-flex items-center justify-center size-9 rounded-md border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
718
+ type: "button",
719
+ className: "inline-flex items-center justify-center size-9 rounded-r-md border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
557
720
  onClick: handleClear,
558
721
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XIcon, { className: "size-4" })
559
722
  }
560
723
  )
561
724
  ] }),
562
725
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_portal.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerPositioner, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_date_picker2.DatePickerContent, { className: styles.content, children: [
563
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DayView, {}),
564
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MonthView, {}),
565
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YearView, {}),
566
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarFooter, { todayLabel, clearLabel })
726
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarPanel, {}),
727
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
728
+ DateTimeFooter,
729
+ {
730
+ todayLabel,
731
+ clearLabel,
732
+ clearable,
733
+ onSelectNow: handleSelectNow,
734
+ onClear: handleClear
735
+ }
736
+ )
737
+ ] }) }) })
738
+ ]
739
+ }
740
+ );
741
+ }
742
+ function DateTriggerPickerField({
743
+ label,
744
+ value: valueProp,
745
+ defaultValue,
746
+ onValueChange,
747
+ showTime = false,
748
+ clearable = true,
749
+ locale: localeProp,
750
+ min,
751
+ max,
752
+ disabled,
753
+ readOnly,
754
+ placeholder: placeholderProp,
755
+ todayLabel: todayLabelProp,
756
+ clearLabel: clearLabelProp,
757
+ className,
758
+ rootProps,
759
+ trigger,
760
+ formatValue
761
+ }) {
762
+ var _a;
763
+ const intl = (0, import_i18n2.useSafeIntl)();
764
+ const locale = (_a = localeProp != null ? localeProp : intl.locale) != null ? _a : "en-US";
765
+ const todayLabel = todayLabelProp != null ? todayLabelProp : intl.formatMessage(datePickerMessages.today);
766
+ const clearLabel = clearLabelProp != null ? clearLabelProp : intl.formatMessage(datePickerMessages.clear);
767
+ const placeholder = placeholderProp != null ? placeholderProp : intl.formatMessage(
768
+ showTime ? datePickerMessages.select_date_and_time : datePickerMessages.select_date
769
+ );
770
+ const [internalValue, setInternalValue] = (0, import_react.useState)(
771
+ () => {
772
+ var _a2;
773
+ return (_a2 = valueProp != null ? valueProp : defaultValue) != null ? _a2 : [];
774
+ }
775
+ );
776
+ const currentValue = (0, import_react.useMemo)(() => {
777
+ if (valueProp === void 0) return internalValue;
778
+ return valueProp;
779
+ }, [valueProp, internalValue]);
780
+ const handleDateChange = (0, import_react.useCallback)(
781
+ (details) => {
782
+ const next = details.value[0];
783
+ if (!showTime || !next) {
784
+ setInternalValue(details.value);
785
+ onValueChange == null ? void 0 : onValueChange(details);
786
+ return;
787
+ }
788
+ const prev = currentValue[0];
789
+ const prevHour = prev && "hour" in prev ? prev.hour : 0;
790
+ const prevMinute = prev && "minute" in prev ? prev.minute : 0;
791
+ const merged = new import_date.CalendarDateTime(
792
+ next.year,
793
+ next.month,
794
+ next.day,
795
+ prevHour,
796
+ prevMinute
797
+ );
798
+ setInternalValue([merged]);
799
+ onValueChange == null ? void 0 : onValueChange({ ...details, value: [merged] });
800
+ },
801
+ [currentValue, onValueChange, showTime]
802
+ );
803
+ const handleTimeChange = (0, import_react.useCallback)(
804
+ (e) => {
805
+ const [hours, minutes] = e.currentTarget.value.split(":").map(Number);
806
+ const prev = currentValue[0];
807
+ const base = prev && "hour" in prev ? prev : prev ? new import_date.CalendarDateTime(prev.year, prev.month, prev.day, 0, 0) : (() => {
808
+ const now = /* @__PURE__ */ new Date();
809
+ return new import_date.CalendarDateTime(
810
+ now.getFullYear(),
811
+ now.getMonth() + 1,
812
+ now.getDate(),
813
+ 0,
814
+ 0
815
+ );
816
+ })();
817
+ const updated = base.set({ hour: hours, minute: minutes });
818
+ setInternalValue([updated]);
819
+ onValueChange == null ? void 0 : onValueChange({
820
+ value: [updated],
821
+ valueAsString: [updated.toString()],
822
+ view: "day"
823
+ });
824
+ },
825
+ [currentValue, onValueChange]
826
+ );
827
+ const formatter = (0, import_react.useMemo)(() => {
828
+ if (formatValue) return formatValue;
829
+ const fmt = new Intl.DateTimeFormat(
830
+ locale,
831
+ showTime ? { dateStyle: "medium", timeStyle: "short" } : { dateStyle: "medium" }
832
+ );
833
+ return (v) => fmt.format(v.toDate((0, import_date.getLocalTimeZone)()));
834
+ }, [formatValue, locale, showTime]);
835
+ const timeValue = currentValue[0] && "hour" in currentValue[0] ? `${String(currentValue[0].hour).padStart(2, "0")}:${String(currentValue[0].minute).padStart(2, "0")}` : "";
836
+ const triggerElement = trigger != null ? trigger : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: styles.defaultGhostTrigger, children: [
837
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarIcon, { className: "size-4 shrink-0 opacity-70" }),
838
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerValueText, { placeholder, children: ({ value }) => formatter(value) })
839
+ ] });
840
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
841
+ import_date_picker2.DatePickerRoot,
842
+ {
843
+ value: currentValue,
844
+ defaultValue,
845
+ onValueChange: handleDateChange,
846
+ locale,
847
+ min,
848
+ max,
849
+ disabled,
850
+ readOnly,
851
+ selectionMode: "single",
852
+ outsideDaySelectable: true,
853
+ closeOnSelect: !showTime,
854
+ className,
855
+ ...rootProps,
856
+ children: [
857
+ label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerLabel, { className: styles.label, children: label }),
858
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerControl, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerTrigger, { asChild: true, children: triggerElement }) }),
859
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_portal.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker2.DatePickerPositioner, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_date_picker2.DatePickerContent, { className: styles.content, children: [
860
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarPanel, {}),
861
+ showTime && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.timeRow, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
862
+ "input",
863
+ {
864
+ type: "time",
865
+ value: timeValue,
866
+ onChange: handleTimeChange,
867
+ disabled,
868
+ readOnly,
869
+ className: styles.timeInput
870
+ }
871
+ ) }),
872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
873
+ CalendarFooter,
874
+ {
875
+ todayLabel,
876
+ clearLabel,
877
+ clearable
878
+ }
879
+ )
567
880
  ] }) }) })
568
881
  ]
569
882
  }
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  DatePickerField
4
- } from "./chunk-VYQ6BFFN.mjs";
4
+ } from "./chunk-RWJEVZ4B.mjs";
5
5
  import {
6
6
  datePickerMessages
7
7
  } from "./chunk-HPM5Y2V6.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kopexa/date-picker",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "DatePicker component for selecting dates and times",
5
5
  "keywords": [
6
6
  "date-picker",
@@ -30,7 +30,7 @@
30
30
  "dependencies": {
31
31
  "@ark-ui/react": "^5.35.0",
32
32
  "@internationalized/date": "^3.12.1",
33
- "@kopexa/i18n": "17.14.1"
33
+ "@kopexa/i18n": "17.14.3"
34
34
  },
35
35
  "clean-package": "../../../clean-package.config.json",
36
36
  "module": "dist/index.mjs",