@kalyx/react 0.4.0 → 1.0.0-rc.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/CHANGELOG.md CHANGED
@@ -1,5 +1,133 @@
1
1
  # @kalyx/react
2
2
 
3
+ ## 1.0.0-rc.0
4
+
5
+ ### Major Changes
6
+
7
+ - ca7180e: chore: v1.0 milestone — API freeze.
8
+
9
+ Kalyx v1.0 declares the public API stable. This is a milestone release bundling the v0.5 surface additions (MonthPicker, YearPicker, WeekPicker, DatePicker.Presets, `onOpenChange`/`onCalendarNavigate` event callbacks) with an explicit commitment to semantic versioning going forward.
10
+
11
+ ### What v1.0 commits to
12
+
13
+ - **Public API surface** — exports from `@kalyx/react` and `@kalyx/core` listed in their `index.ts` files. Any breaking change requires a major bump.
14
+ - **Compositional structure** — Root + subcomponent names (`DatePicker.Input`, `DatePicker.Calendar`, …) are stable. Removal or renaming requires a major bump.
15
+ - **Value semantics** — ISO 8601 UTC strings for single dates, `DateRange` `{start, end}` for ranges. `displayTimezone` behavior (civil-midnight-in-tz for date selection) is stable.
16
+ - **Accessibility contracts** — role/aria-\* attributes emitted by each component are stable.
17
+
18
+ ### What v1.0 does NOT freeze
19
+
20
+ - Internal implementation details (non-exported functions, component file layout).
21
+ - CSS class name strings on elements — no classes are applied by default; only when a consumer passes them via `classNames` props.
22
+ - Error message text.
23
+ - Peer dependency version ranges (may expand to cover new React majors).
24
+
25
+ ### Breaking changes vs 0.4.x
26
+
27
+ None. v1.0 is API-compatible with 0.4.x — existing code continues to work. The major bump communicates stability commitment, not breakage.
28
+
29
+ ### Minor Changes
30
+
31
+ - 3db8444: feat: add `DatePicker.Presets` and `DatePicker.Preset` for single-date quick selection.
32
+
33
+ Mirrors the existing `RangePicker.Presets` API. Pass a predefined `value` key (`today`, `tomorrow`, `yesterday`, `startOfMonth`, `endOfMonth`, `startOfYear`) or a direct ISO via `date`.
34
+
35
+ ```tsx
36
+ <DatePicker value={date} onChange={setDate}>
37
+ <DatePicker.Input />
38
+ <DatePicker.Popover>
39
+ <DatePicker.Presets>
40
+ <DatePicker.Preset value="today">Today</DatePicker.Preset>
41
+ <DatePicker.Preset value="tomorrow">Tomorrow</DatePicker.Preset>
42
+ <DatePicker.Preset date="2026-12-25T00:00:00.000Z">
43
+ Christmas
44
+ </DatePicker.Preset>
45
+ </DatePicker.Presets>
46
+ <DatePicker.Calendar />
47
+ </DatePicker.Popover>
48
+ </DatePicker>
49
+ ```
50
+
51
+ - Active preset is marked `aria-selected="true"` when its resolved date matches the current value (timezone-aware).
52
+ - Clicking a preset commits and closes the popover.
53
+ - `displayTimezone` is honored when resolving "today"-relative presets.
54
+
55
+ - 56e1ce9: feat: add `onOpenChange` and `onCalendarNavigate` callbacks on `DatePicker`, `RangePicker`, and `DateTimePicker` Root components.
56
+
57
+ - `onOpenChange(isOpen: boolean)` fires whenever the popover opens or closes (regardless of trigger — click, keyboard, outside click, selection).
58
+ - `onCalendarNavigate(viewMonth: ISODateString)` fires when the calendar view moves to a different month. The emitted value is the first day of the newly-visible month in UTC.
59
+
60
+ Neither callback fires on initial mount. `TimePicker` does not expose these callbacks since it has no popover or calendar.
61
+
62
+ - 6fc7c59: feat: add `MonthPicker` — a headless month selector.
63
+
64
+ `MonthPicker` stores the selected month as the first day of that month in UTC-ISO form (e.g., `"2026-04-01T00:00:00.000Z"`). It reuses `DatePicker` infrastructure (Input, Trigger, Popover), so the only new primitive is `MonthPicker.Grid`, a 12-month commit grid with year navigation.
65
+
66
+ ```tsx
67
+ <MonthPicker value={month} onChange={setMonth}>
68
+ <MonthPicker.Input placeholder="Pick a month" />
69
+ <MonthPicker.Popover>
70
+ <MonthPicker.Grid />
71
+ </MonthPicker.Popover>
72
+ </MonthPicker>
73
+ ```
74
+
75
+ - Default `displayFormat` is `"yyyy-MM"`.
76
+ - `displayTimezone` is supported (commits map to civil midnight of month-start in the target zone).
77
+ - Month selection highlighting is timezone-aware — the grid reflects the month of the current value even when stored in zone-adjusted UTC form.
78
+ - Primary UX is click-to-select; full `yyyy-MM-dd` typed input still works via the inherited Input behavior.
79
+
80
+ - 6fdf8fe: feat: add `WeekPicker` — a headless week selector.
81
+
82
+ `WeekPicker` stores the selected week as a `DateRange` covering all seven days (based on `weekStartsOn`). Unlike `RangePicker`, a single click on any day selects the entire week containing that day.
83
+
84
+ ```tsx
85
+ <WeekPicker value={week} onChange={setWeek} weekStartsOn={1}>
86
+ <WeekPicker.Input part="start" />
87
+ <WeekPicker.Input part="end" />
88
+ <WeekPicker.Popover>
89
+ <WeekPicker.Calendar />
90
+ </WeekPicker.Popover>
91
+ </WeekPicker>
92
+ ```
93
+
94
+ - Reuses `RangePicker` Root / Input / Popover; only `WeekPicker.Calendar` is new.
95
+ - `weekStartsOn` (0=Sunday, 1=Monday) controls which seven days constitute a week.
96
+ - Enter / Space on the focused day commits the full week containing it.
97
+ - `displayTimezone`, `disabled` rules, and all other RangePicker props are supported.
98
+
99
+ - 6fc7c59: feat: add `YearPicker` — a headless year selector.
100
+
101
+ `YearPicker` stores the selected year as Jan 1 of that year in UTC-ISO form (e.g., `"2026-01-01T00:00:00.000Z"`). It reuses `DatePicker` infrastructure (Input, Trigger, Popover) and exposes `YearPicker.Grid`, a 12-year decade commit grid with decade navigation.
102
+
103
+ ```tsx
104
+ <YearPicker value={year} onChange={setYear}>
105
+ <YearPicker.Input placeholder="Pick a year" />
106
+ <YearPicker.Popover>
107
+ <YearPicker.Grid />
108
+ </YearPicker.Popover>
109
+ </YearPicker>
110
+ ```
111
+
112
+ - Default `displayFormat` is `"yyyy"`.
113
+ - `displayTimezone` is supported with timezone-aware year highlighting.
114
+ - Primary UX is click-to-select; full `yyyy-MM-dd` typed input still works via the inherited Input behavior.
115
+
116
+ ### Patch Changes
117
+
118
+ - 1ca818c: fix(react): prevent WeekPicker from mutating RangePicker.Calendar
119
+
120
+ `WeekPicker` previously called `Object.assign(RangePickerRoot, { ..., Calendar: WeekPickerCalendar })`, which mutates the shared `RangePickerRoot` function object. Because `RangePicker.Calendar` is attached to the same object (via the earlier `Object.assign` in `RangePicker/index.ts`), importing `WeekPicker` would overwrite `RangePicker.Calendar` with `WeekPickerCalendar`.
121
+
122
+ Users of `RangePicker` would then see week-selection behavior (single click commits a full week and closes the popover) instead of the documented two-click range flow — even without importing `WeekPicker` directly, because both pickers share the module graph.
123
+
124
+ Added an internal `WeekPickerRoot` wrapper that the `Object.assign` target now uses, preserving `RangePickerRoot.Calendar` intact.
125
+
126
+ Caught by the `RangePicker › select range in start-date -> end-date order` Playwright test; all existing behavior is restored.
127
+
128
+ - Updated dependencies [ca7180e]
129
+ - @kalyx/core@1.0.0-rc.0
130
+
3
131
  ## 0.4.0
4
132
 
5
133
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -21,10 +21,23 @@ function useDatePickerContext(componentName) {
21
21
  }
22
22
  return context;
23
23
  }
24
+ function useChangeEffect(value, callback) {
25
+ const callbackRef = react.useRef(callback);
26
+ callbackRef.current = callback;
27
+ const prevRef = react.useRef(value);
28
+ react.useEffect(() => {
29
+ if (prevRef.current !== value) {
30
+ prevRef.current = value;
31
+ callbackRef.current?.(value);
32
+ }
33
+ }, [value]);
34
+ }
24
35
  function DatePickerRoot({
25
36
  value: controlledValue,
26
37
  defaultValue,
27
38
  onChange,
39
+ onOpenChange,
40
+ onCalendarNavigate,
28
41
  disabled = false,
29
42
  readOnly = false,
30
43
  weekStartsOn = 0,
@@ -49,6 +62,9 @@ function DatePickerRoot({
49
62
  const [focusedDate, setFocusedDate] = react.useState(
50
63
  currentValue ?? adapter.today(displayTimezone)
51
64
  );
65
+ useChangeEffect(isOpen, onOpenChange);
66
+ const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
67
+ useChangeEffect(viewMonthStart, onCalendarNavigate);
52
68
  const mergedLabels = react.useMemo(
53
69
  () => ({ ...core.DEFAULT_DATEPICKER_LABELS, ...labelsProp }),
54
70
  [labelsProp]
@@ -778,6 +794,95 @@ function DatePickerYearGrid({
778
794
  )
779
795
  ] });
780
796
  }
797
+ function DatePickerPresets({ classNames, children, ...props }) {
798
+ const ctx = useDatePickerContext("DatePicker.Presets");
799
+ return /* @__PURE__ */ jsxRuntime.jsx(
800
+ "div",
801
+ {
802
+ role: "group",
803
+ "aria-label": ctx.labels.popoverLabel,
804
+ className: classNames?.root,
805
+ ...props,
806
+ children
807
+ }
808
+ );
809
+ }
810
+ function resolveDatePreset(key, today, adapter) {
811
+ switch (key) {
812
+ case "today":
813
+ return today;
814
+ case "tomorrow":
815
+ return adapter.addDays(today, 1);
816
+ case "yesterday":
817
+ return adapter.addDays(today, -1);
818
+ case "startOfMonth":
819
+ return adapter.startOfMonth(today);
820
+ case "endOfMonth":
821
+ return adapter.startOfDay(adapter.endOfMonth(today));
822
+ case "startOfYear": {
823
+ const currentMonth = adapter.getMonth(today);
824
+ return adapter.startOfMonth(adapter.addMonths(today, -currentMonth));
825
+ }
826
+ }
827
+ }
828
+ function DatePickerPreset({
829
+ value: presetKey,
830
+ date: directDate,
831
+ children,
832
+ onClick,
833
+ ...props
834
+ }) {
835
+ const ctx = useDatePickerContext("DatePicker.Preset");
836
+ const handleClick = react.useCallback(
837
+ (e) => {
838
+ if (ctx.isDisabled || ctx.isReadOnly) return;
839
+ let resolved;
840
+ if (directDate) {
841
+ resolved = directDate;
842
+ } else if (presetKey) {
843
+ resolved = resolveDatePreset(
844
+ presetKey,
845
+ ctx.adapter.today(ctx.displayTimezone),
846
+ ctx.adapter
847
+ );
848
+ } else {
849
+ return;
850
+ }
851
+ ctx.selectDate(resolved);
852
+ onClick?.(e);
853
+ },
854
+ [ctx, presetKey, directDate, onClick]
855
+ );
856
+ const isActive = (() => {
857
+ if (!ctx.value) return false;
858
+ let target;
859
+ if (directDate) {
860
+ target = directDate;
861
+ } else if (presetKey) {
862
+ target = resolveDatePreset(
863
+ presetKey,
864
+ ctx.adapter.today(ctx.displayTimezone),
865
+ ctx.adapter
866
+ );
867
+ } else {
868
+ return false;
869
+ }
870
+ return ctx.adapter.isSameDay(ctx.value, target, ctx.displayTimezone);
871
+ })();
872
+ return /* @__PURE__ */ jsxRuntime.jsx(
873
+ "button",
874
+ {
875
+ type: "button",
876
+ role: "option",
877
+ "aria-selected": isActive,
878
+ "data-active": isActive || void 0,
879
+ disabled: ctx.isDisabled,
880
+ onClick: handleClick,
881
+ ...props,
882
+ children
883
+ }
884
+ );
885
+ }
781
886
 
782
887
  // src/components/DatePicker/index.ts
783
888
  var DatePicker = Object.assign(DatePickerRoot, {
@@ -786,7 +891,9 @@ var DatePicker = Object.assign(DatePickerRoot, {
786
891
  Popover: DatePickerPopover,
787
892
  Calendar: DatePickerCalendar,
788
893
  MonthGrid: DatePickerMonthGrid,
789
- YearGrid: DatePickerYearGrid
894
+ YearGrid: DatePickerYearGrid,
895
+ Presets: DatePickerPresets,
896
+ Preset: DatePickerPreset
790
897
  });
791
898
  var RangePickerContext = react.createContext(null);
792
899
  function useRangePickerContext(componentName) {
@@ -808,6 +915,8 @@ function RangePickerRoot({
808
915
  value: controlledValue,
809
916
  defaultValue,
810
917
  onChange,
918
+ onOpenChange,
919
+ onCalendarNavigate,
811
920
  disabled = false,
812
921
  readOnly = false,
813
922
  weekStartsOn = 0,
@@ -834,6 +943,9 @@ function RangePickerRoot({
834
943
  const [focusedDate, setFocusedDate] = react.useState(
835
944
  currentValue.start ?? adapter.today(displayTimezone)
836
945
  );
946
+ useChangeEffect(isOpen, onOpenChange);
947
+ const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
948
+ useChangeEffect(viewMonthStart, onCalendarNavigate);
837
949
  const mergedLabels = react.useMemo(
838
950
  () => ({ ...core.DEFAULT_RANGEPICKER_LABELS, ...labelsProp }),
839
951
  [labelsProp]
@@ -1056,7 +1168,11 @@ var srOnly2 = {
1056
1168
  whiteSpace: "nowrap",
1057
1169
  border: 0
1058
1170
  };
1059
- function RangePickerCalendar({ classNames, ...props }) {
1171
+ function RangePickerCalendar({
1172
+ classNames,
1173
+ selectionMode = "range",
1174
+ ...props
1175
+ }) {
1060
1176
  const ctx = useRangePickerContext("RangePicker.Calendar");
1061
1177
  const gridRef = react.useRef(null);
1062
1178
  const [announcement, setAnnouncement] = react.useState("");
@@ -1102,21 +1218,39 @@ function RangePickerCalendar({ classNames, ...props }) {
1102
1218
  },
1103
1219
  [adapter, viewMonth, ctx, locale]
1104
1220
  );
1221
+ const commitDay = react.useCallback(
1222
+ (iso) => {
1223
+ if (selectionMode === "week") {
1224
+ const weekStart = adapter.startOfWeek(iso, weekStartsOn);
1225
+ const weekEnd = adapter.startOfDay(adapter.endOfWeek(iso, weekStartsOn));
1226
+ const range = { start: weekStart, end: weekEnd };
1227
+ ctx.setRange(range);
1228
+ ctx.close();
1229
+ setAnnouncement(
1230
+ `${safeFormatFullDate2(weekStart, locale)} \u2013 ${safeFormatFullDate2(weekEnd, locale)}`
1231
+ );
1232
+ } else {
1233
+ ctx.selectDate(iso);
1234
+ setAnnouncement(safeFormatFullDate2(iso, locale));
1235
+ }
1236
+ },
1237
+ [selectionMode, adapter, weekStartsOn, ctx, locale]
1238
+ );
1105
1239
  const handleDayClick = react.useCallback(
1106
1240
  (day) => {
1107
1241
  if (day.isDisabled) return;
1108
- ctx.selectDate(day.isoString);
1109
- setAnnouncement(safeFormatFullDate2(day.isoString, locale));
1242
+ commitDay(day.isoString);
1110
1243
  },
1111
- [ctx, locale]
1244
+ [commitDay]
1112
1245
  );
1113
1246
  const handleDayMouseEnter = react.useCallback(
1114
1247
  (day) => {
1248
+ if (selectionMode === "week") return;
1115
1249
  if (selectingTarget === "end" && value.start && !day.isDisabled) {
1116
1250
  ctx.setHoverDate(day.isoString);
1117
1251
  }
1118
1252
  },
1119
- [selectingTarget, value.start, ctx]
1253
+ [selectionMode, selectingTarget, value.start, ctx]
1120
1254
  );
1121
1255
  const handleMouseLeave = react.useCallback(() => {
1122
1256
  ctx.setHoverDate(null);
@@ -1153,7 +1287,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1153
1287
  case " ":
1154
1288
  e.preventDefault();
1155
1289
  if (!core.isDateDisabled(focusedDate, disabled, adapter)) {
1156
- ctx.selectDate(focusedDate);
1290
+ commitDay(focusedDate);
1157
1291
  }
1158
1292
  return;
1159
1293
  case "Escape":
@@ -1168,12 +1302,12 @@ function RangePickerCalendar({ classNames, ...props }) {
1168
1302
  if (!adapter.isSameMonth(newFocused, viewMonth)) {
1169
1303
  ctx.setViewMonth(newFocused);
1170
1304
  }
1171
- if (selectingTarget === "end" && value.start) {
1305
+ if (selectionMode === "range" && selectingTarget === "end" && value.start) {
1172
1306
  ctx.setHoverDate(newFocused);
1173
1307
  }
1174
1308
  }
1175
1309
  },
1176
- [adapter, focusedDate, viewMonth, weekStartsOn, disabled, ctx, selectingTarget, value.start]
1310
+ [adapter, focusedDate, viewMonth, weekStartsOn, disabled, ctx, selectionMode, selectingTarget, value.start, commitDay]
1177
1311
  );
1178
1312
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, onMouseLeave: handleMouseLeave, children: [
1179
1313
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
@@ -1230,7 +1364,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1230
1364
  day.isDisabled && classNames?.dayDisabled,
1231
1365
  !day.isCurrentMonth && classNames?.dayOutsideMonth
1232
1366
  ].filter(Boolean).join(" ") || void 0;
1233
- const isSelected = day.isRangeStart || day.isRangeEnd;
1367
+ const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1234
1368
  return /* @__PURE__ */ jsxRuntime.jsx(
1235
1369
  "td",
1236
1370
  {
@@ -1702,6 +1836,8 @@ function DateTimePickerRoot({
1702
1836
  value: controlledValue,
1703
1837
  defaultValue,
1704
1838
  onChange,
1839
+ onOpenChange,
1840
+ onCalendarNavigate,
1705
1841
  format = "24h",
1706
1842
  step = 1,
1707
1843
  disabled = false,
@@ -1736,6 +1872,9 @@ function DateTimePickerRoot({
1736
1872
  const [focusedDate, setFocusedDate] = react.useState(
1737
1873
  currentValue ?? adapter.today(displayTimezone)
1738
1874
  );
1875
+ useChangeEffect(isOpen, onOpenChange);
1876
+ const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
1877
+ useChangeEffect(viewMonthStart, onCalendarNavigate);
1739
1878
  const isDisabled = typeof disabled === "boolean" ? disabled : false;
1740
1879
  const disabledRules = react.useMemo(
1741
1880
  () => Array.isArray(disabled) ? disabled : [],
@@ -1925,6 +2064,249 @@ var DateTimePicker = Object.assign(DateTimePickerRoot, {
1925
2064
  MinuteList: TimePickerMinuteList,
1926
2065
  AmPmToggle: TimePickerAmPmToggle
1927
2066
  });
2067
+ function MonthPickerRoot(props) {
2068
+ const displayFormat = props.displayFormat ?? "yyyy-MM";
2069
+ return /* @__PURE__ */ jsxRuntime.jsx(DatePickerRoot, { ...props, displayFormat });
2070
+ }
2071
+ function MonthPickerGrid({
2072
+ classNames,
2073
+ ...props
2074
+ }) {
2075
+ const ctx = useDatePickerContext("MonthPicker.Grid");
2076
+ const { adapter, viewMonth, locale, value, displayTimezone, labels } = ctx;
2077
+ const currentYear = adapter.getYear(viewMonth);
2078
+ const [valueYear, valueMonthZeroBased] = react.useMemo(() => {
2079
+ if (!value) return [null, null];
2080
+ try {
2081
+ const [y, m] = adapter.format(value, "yyyy-MM", displayTimezone).split("-").map(Number);
2082
+ return [y, m - 1];
2083
+ } catch {
2084
+ return [null, null];
2085
+ }
2086
+ }, [value, adapter, displayTimezone]);
2087
+ const today = adapter.today(displayTimezone);
2088
+ const todayYear = adapter.getYear(today);
2089
+ const todayMonth = adapter.getMonth(today);
2090
+ const navigateYear = react.useCallback(
2091
+ (direction) => {
2092
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction));
2093
+ },
2094
+ [adapter, viewMonth, ctx]
2095
+ );
2096
+ const handleMonthSelect = react.useCallback(
2097
+ (monthIndex) => {
2098
+ const target = new Date(
2099
+ Date.UTC(currentYear, monthIndex, 1)
2100
+ ).toISOString();
2101
+ ctx.selectDate(target);
2102
+ },
2103
+ [currentYear, ctx]
2104
+ );
2105
+ const months = Array.from({ length: 12 }, (_, i) => ({
2106
+ index: i,
2107
+ name: core.getMonthName(i, locale),
2108
+ isSelected: valueYear === currentYear && valueMonthZeroBased === i,
2109
+ isCurrent: todayYear === currentYear && todayMonth === i
2110
+ }));
2111
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
2112
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
2113
+ /* @__PURE__ */ jsxRuntime.jsx(
2114
+ "button",
2115
+ {
2116
+ type: "button",
2117
+ className: classNames?.navButton,
2118
+ onClick: () => navigateYear(-1),
2119
+ "aria-label": labels.prevYear,
2120
+ children: "<"
2121
+ }
2122
+ ),
2123
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: classNames?.title, children: currentYear }),
2124
+ /* @__PURE__ */ jsxRuntime.jsx(
2125
+ "button",
2126
+ {
2127
+ type: "button",
2128
+ className: classNames?.navButton,
2129
+ onClick: () => navigateYear(1),
2130
+ "aria-label": labels.nextYear,
2131
+ children: ">"
2132
+ }
2133
+ )
2134
+ ] }),
2135
+ /* @__PURE__ */ jsxRuntime.jsx(
2136
+ "div",
2137
+ {
2138
+ role: "grid",
2139
+ "aria-label": `${currentYear} months`,
2140
+ className: classNames?.grid,
2141
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2142
+ "div",
2143
+ {
2144
+ role: "row",
2145
+ className: classNames?.gridRow,
2146
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2147
+ children: months.slice(rowIndex * 3, rowIndex * 3 + 3).map((m) => {
2148
+ const monthClass = [
2149
+ classNames?.month,
2150
+ m.isSelected && classNames?.monthSelected,
2151
+ m.isCurrent && classNames?.monthCurrent
2152
+ ].filter(Boolean).join(" ") || void 0;
2153
+ return /* @__PURE__ */ jsxRuntime.jsx(
2154
+ "button",
2155
+ {
2156
+ type: "button",
2157
+ role: "gridcell",
2158
+ "aria-selected": m.isSelected || void 0,
2159
+ "aria-current": m.isCurrent ? "date" : void 0,
2160
+ "data-selected": m.isSelected || void 0,
2161
+ "data-current": m.isCurrent || void 0,
2162
+ className: monthClass,
2163
+ onClick: () => handleMonthSelect(m.index),
2164
+ children: m.name
2165
+ },
2166
+ m.index
2167
+ );
2168
+ })
2169
+ },
2170
+ rowIndex
2171
+ ))
2172
+ }
2173
+ )
2174
+ ] });
2175
+ }
2176
+
2177
+ // src/components/MonthPicker/index.ts
2178
+ var MonthPicker = Object.assign(MonthPickerRoot, {
2179
+ Input: DatePickerInput,
2180
+ Trigger: DatePickerTrigger,
2181
+ Popover: DatePickerPopover,
2182
+ Grid: MonthPickerGrid
2183
+ });
2184
+ function YearPickerRoot(props) {
2185
+ const displayFormat = props.displayFormat ?? "yyyy";
2186
+ return /* @__PURE__ */ jsxRuntime.jsx(DatePickerRoot, { ...props, displayFormat });
2187
+ }
2188
+ function YearPickerGrid({ classNames, ...props }) {
2189
+ const ctx = useDatePickerContext("YearPicker.Grid");
2190
+ const { adapter, viewMonth, value, displayTimezone, labels } = ctx;
2191
+ const currentYear = adapter.getYear(viewMonth);
2192
+ const decadeStart = currentYear - currentYear % 12;
2193
+ const valueYear = react.useMemo(() => {
2194
+ if (!value) return null;
2195
+ try {
2196
+ return Number(adapter.format(value, "yyyy", displayTimezone));
2197
+ } catch {
2198
+ return null;
2199
+ }
2200
+ }, [value, adapter, displayTimezone]);
2201
+ const todayYear = adapter.getYear(adapter.today(displayTimezone));
2202
+ const navigateDecade = react.useCallback(
2203
+ (direction) => {
2204
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
2205
+ },
2206
+ [adapter, viewMonth, ctx]
2207
+ );
2208
+ const handleYearSelect = react.useCallback(
2209
+ (year) => {
2210
+ const target = new Date(Date.UTC(year, 0, 1)).toISOString();
2211
+ ctx.selectDate(target);
2212
+ },
2213
+ [ctx]
2214
+ );
2215
+ const years = Array.from({ length: 12 }, (_, i) => {
2216
+ const year = decadeStart + i;
2217
+ return {
2218
+ value: year,
2219
+ isSelected: year === valueYear,
2220
+ isCurrent: year === todayYear
2221
+ };
2222
+ });
2223
+ const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
2224
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
2225
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
2226
+ /* @__PURE__ */ jsxRuntime.jsx(
2227
+ "button",
2228
+ {
2229
+ type: "button",
2230
+ className: classNames?.navButton,
2231
+ onClick: () => navigateDecade(-1),
2232
+ "aria-label": labels.prevDecade,
2233
+ children: "<"
2234
+ }
2235
+ ),
2236
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: classNames?.title, children: rangeLabel }),
2237
+ /* @__PURE__ */ jsxRuntime.jsx(
2238
+ "button",
2239
+ {
2240
+ type: "button",
2241
+ className: classNames?.navButton,
2242
+ onClick: () => navigateDecade(1),
2243
+ "aria-label": labels.nextDecade,
2244
+ children: ">"
2245
+ }
2246
+ )
2247
+ ] }),
2248
+ /* @__PURE__ */ jsxRuntime.jsx(
2249
+ "div",
2250
+ {
2251
+ role: "grid",
2252
+ "aria-label": rangeLabel,
2253
+ className: classNames?.grid,
2254
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2255
+ "div",
2256
+ {
2257
+ role: "row",
2258
+ className: classNames?.gridRow,
2259
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2260
+ children: years.slice(rowIndex * 3, rowIndex * 3 + 3).map((y) => {
2261
+ const yearClass = [
2262
+ classNames?.year,
2263
+ y.isSelected && classNames?.yearSelected,
2264
+ y.isCurrent && classNames?.yearCurrent
2265
+ ].filter(Boolean).join(" ") || void 0;
2266
+ return /* @__PURE__ */ jsxRuntime.jsx(
2267
+ "button",
2268
+ {
2269
+ type: "button",
2270
+ role: "gridcell",
2271
+ "aria-selected": y.isSelected || void 0,
2272
+ "aria-current": y.isCurrent ? "date" : void 0,
2273
+ "data-selected": y.isSelected || void 0,
2274
+ "data-current": y.isCurrent || void 0,
2275
+ className: yearClass,
2276
+ onClick: () => handleYearSelect(y.value),
2277
+ children: y.value
2278
+ },
2279
+ y.value
2280
+ );
2281
+ })
2282
+ },
2283
+ rowIndex
2284
+ ))
2285
+ }
2286
+ )
2287
+ ] });
2288
+ }
2289
+
2290
+ // src/components/YearPicker/index.ts
2291
+ var YearPicker = Object.assign(YearPickerRoot, {
2292
+ Input: DatePickerInput,
2293
+ Trigger: DatePickerTrigger,
2294
+ Popover: DatePickerPopover,
2295
+ Grid: YearPickerGrid
2296
+ });
2297
+ function WeekPickerRoot(props) {
2298
+ return /* @__PURE__ */ jsxRuntime.jsx(RangePickerRoot, { ...props });
2299
+ }
2300
+ function WeekPickerCalendar(props) {
2301
+ return /* @__PURE__ */ jsxRuntime.jsx(RangePickerCalendar, { ...props, selectionMode: "week" });
2302
+ }
2303
+
2304
+ // src/components/WeekPicker/index.ts
2305
+ var WeekPicker = Object.assign(WeekPickerRoot, {
2306
+ Input: RangePickerInput,
2307
+ Popover: RangePickerPopover,
2308
+ Calendar: WeekPickerCalendar
2309
+ });
1928
2310
  function useDatePicker(options = {}) {
1929
2311
  const {
1930
2312
  value: controlledValue,
@@ -2203,8 +2585,11 @@ Object.defineProperty(exports, "DateFnsAdapter", {
2203
2585
  });
2204
2586
  exports.DatePicker = DatePicker;
2205
2587
  exports.DateTimePicker = DateTimePicker;
2588
+ exports.MonthPicker = MonthPicker;
2206
2589
  exports.RangePicker = RangePicker;
2207
2590
  exports.TimePicker = TimePicker;
2591
+ exports.WeekPicker = WeekPicker;
2592
+ exports.YearPicker = YearPicker;
2208
2593
  exports.useDatePicker = useDatePicker;
2209
2594
  exports.useRangePicker = useRangePicker;
2210
2595
  exports.useTimePicker = useTimePicker;