@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/dist/index.js CHANGED
@@ -20,10 +20,23 @@ function useDatePickerContext(componentName) {
20
20
  }
21
21
  return context;
22
22
  }
23
+ function useChangeEffect(value, callback) {
24
+ const callbackRef = useRef(callback);
25
+ callbackRef.current = callback;
26
+ const prevRef = useRef(value);
27
+ useEffect(() => {
28
+ if (prevRef.current !== value) {
29
+ prevRef.current = value;
30
+ callbackRef.current?.(value);
31
+ }
32
+ }, [value]);
33
+ }
23
34
  function DatePickerRoot({
24
35
  value: controlledValue,
25
36
  defaultValue,
26
37
  onChange,
38
+ onOpenChange,
39
+ onCalendarNavigate,
27
40
  disabled = false,
28
41
  readOnly = false,
29
42
  weekStartsOn = 0,
@@ -48,6 +61,9 @@ function DatePickerRoot({
48
61
  const [focusedDate, setFocusedDate] = useState(
49
62
  currentValue ?? adapter.today(displayTimezone)
50
63
  );
64
+ useChangeEffect(isOpen, onOpenChange);
65
+ const viewMonthStart = useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
66
+ useChangeEffect(viewMonthStart, onCalendarNavigate);
51
67
  const mergedLabels = useMemo(
52
68
  () => ({ ...DEFAULT_DATEPICKER_LABELS, ...labelsProp }),
53
69
  [labelsProp]
@@ -777,6 +793,95 @@ function DatePickerYearGrid({
777
793
  )
778
794
  ] });
779
795
  }
796
+ function DatePickerPresets({ classNames, children, ...props }) {
797
+ const ctx = useDatePickerContext("DatePicker.Presets");
798
+ return /* @__PURE__ */ jsx(
799
+ "div",
800
+ {
801
+ role: "group",
802
+ "aria-label": ctx.labels.popoverLabel,
803
+ className: classNames?.root,
804
+ ...props,
805
+ children
806
+ }
807
+ );
808
+ }
809
+ function resolveDatePreset(key, today, adapter) {
810
+ switch (key) {
811
+ case "today":
812
+ return today;
813
+ case "tomorrow":
814
+ return adapter.addDays(today, 1);
815
+ case "yesterday":
816
+ return adapter.addDays(today, -1);
817
+ case "startOfMonth":
818
+ return adapter.startOfMonth(today);
819
+ case "endOfMonth":
820
+ return adapter.startOfDay(adapter.endOfMonth(today));
821
+ case "startOfYear": {
822
+ const currentMonth = adapter.getMonth(today);
823
+ return adapter.startOfMonth(adapter.addMonths(today, -currentMonth));
824
+ }
825
+ }
826
+ }
827
+ function DatePickerPreset({
828
+ value: presetKey,
829
+ date: directDate,
830
+ children,
831
+ onClick,
832
+ ...props
833
+ }) {
834
+ const ctx = useDatePickerContext("DatePicker.Preset");
835
+ const handleClick = useCallback(
836
+ (e) => {
837
+ if (ctx.isDisabled || ctx.isReadOnly) return;
838
+ let resolved;
839
+ if (directDate) {
840
+ resolved = directDate;
841
+ } else if (presetKey) {
842
+ resolved = resolveDatePreset(
843
+ presetKey,
844
+ ctx.adapter.today(ctx.displayTimezone),
845
+ ctx.adapter
846
+ );
847
+ } else {
848
+ return;
849
+ }
850
+ ctx.selectDate(resolved);
851
+ onClick?.(e);
852
+ },
853
+ [ctx, presetKey, directDate, onClick]
854
+ );
855
+ const isActive = (() => {
856
+ if (!ctx.value) return false;
857
+ let target;
858
+ if (directDate) {
859
+ target = directDate;
860
+ } else if (presetKey) {
861
+ target = resolveDatePreset(
862
+ presetKey,
863
+ ctx.adapter.today(ctx.displayTimezone),
864
+ ctx.adapter
865
+ );
866
+ } else {
867
+ return false;
868
+ }
869
+ return ctx.adapter.isSameDay(ctx.value, target, ctx.displayTimezone);
870
+ })();
871
+ return /* @__PURE__ */ jsx(
872
+ "button",
873
+ {
874
+ type: "button",
875
+ role: "option",
876
+ "aria-selected": isActive,
877
+ "data-active": isActive || void 0,
878
+ disabled: ctx.isDisabled,
879
+ onClick: handleClick,
880
+ ...props,
881
+ children
882
+ }
883
+ );
884
+ }
780
885
 
781
886
  // src/components/DatePicker/index.ts
782
887
  var DatePicker = Object.assign(DatePickerRoot, {
@@ -785,7 +890,9 @@ var DatePicker = Object.assign(DatePickerRoot, {
785
890
  Popover: DatePickerPopover,
786
891
  Calendar: DatePickerCalendar,
787
892
  MonthGrid: DatePickerMonthGrid,
788
- YearGrid: DatePickerYearGrid
893
+ YearGrid: DatePickerYearGrid,
894
+ Presets: DatePickerPresets,
895
+ Preset: DatePickerPreset
789
896
  });
790
897
  var RangePickerContext = createContext(null);
791
898
  function useRangePickerContext(componentName) {
@@ -807,6 +914,8 @@ function RangePickerRoot({
807
914
  value: controlledValue,
808
915
  defaultValue,
809
916
  onChange,
917
+ onOpenChange,
918
+ onCalendarNavigate,
810
919
  disabled = false,
811
920
  readOnly = false,
812
921
  weekStartsOn = 0,
@@ -833,6 +942,9 @@ function RangePickerRoot({
833
942
  const [focusedDate, setFocusedDate] = useState(
834
943
  currentValue.start ?? adapter.today(displayTimezone)
835
944
  );
945
+ useChangeEffect(isOpen, onOpenChange);
946
+ const viewMonthStart = useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
947
+ useChangeEffect(viewMonthStart, onCalendarNavigate);
836
948
  const mergedLabels = useMemo(
837
949
  () => ({ ...DEFAULT_RANGEPICKER_LABELS, ...labelsProp }),
838
950
  [labelsProp]
@@ -1055,7 +1167,11 @@ var srOnly2 = {
1055
1167
  whiteSpace: "nowrap",
1056
1168
  border: 0
1057
1169
  };
1058
- function RangePickerCalendar({ classNames, ...props }) {
1170
+ function RangePickerCalendar({
1171
+ classNames,
1172
+ selectionMode = "range",
1173
+ ...props
1174
+ }) {
1059
1175
  const ctx = useRangePickerContext("RangePicker.Calendar");
1060
1176
  const gridRef = useRef(null);
1061
1177
  const [announcement, setAnnouncement] = useState("");
@@ -1101,21 +1217,39 @@ function RangePickerCalendar({ classNames, ...props }) {
1101
1217
  },
1102
1218
  [adapter, viewMonth, ctx, locale]
1103
1219
  );
1220
+ const commitDay = useCallback(
1221
+ (iso) => {
1222
+ if (selectionMode === "week") {
1223
+ const weekStart = adapter.startOfWeek(iso, weekStartsOn);
1224
+ const weekEnd = adapter.startOfDay(adapter.endOfWeek(iso, weekStartsOn));
1225
+ const range = { start: weekStart, end: weekEnd };
1226
+ ctx.setRange(range);
1227
+ ctx.close();
1228
+ setAnnouncement(
1229
+ `${safeFormatFullDate2(weekStart, locale)} \u2013 ${safeFormatFullDate2(weekEnd, locale)}`
1230
+ );
1231
+ } else {
1232
+ ctx.selectDate(iso);
1233
+ setAnnouncement(safeFormatFullDate2(iso, locale));
1234
+ }
1235
+ },
1236
+ [selectionMode, adapter, weekStartsOn, ctx, locale]
1237
+ );
1104
1238
  const handleDayClick = useCallback(
1105
1239
  (day) => {
1106
1240
  if (day.isDisabled) return;
1107
- ctx.selectDate(day.isoString);
1108
- setAnnouncement(safeFormatFullDate2(day.isoString, locale));
1241
+ commitDay(day.isoString);
1109
1242
  },
1110
- [ctx, locale]
1243
+ [commitDay]
1111
1244
  );
1112
1245
  const handleDayMouseEnter = useCallback(
1113
1246
  (day) => {
1247
+ if (selectionMode === "week") return;
1114
1248
  if (selectingTarget === "end" && value.start && !day.isDisabled) {
1115
1249
  ctx.setHoverDate(day.isoString);
1116
1250
  }
1117
1251
  },
1118
- [selectingTarget, value.start, ctx]
1252
+ [selectionMode, selectingTarget, value.start, ctx]
1119
1253
  );
1120
1254
  const handleMouseLeave = useCallback(() => {
1121
1255
  ctx.setHoverDate(null);
@@ -1152,7 +1286,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1152
1286
  case " ":
1153
1287
  e.preventDefault();
1154
1288
  if (!isDateDisabled(focusedDate, disabled, adapter)) {
1155
- ctx.selectDate(focusedDate);
1289
+ commitDay(focusedDate);
1156
1290
  }
1157
1291
  return;
1158
1292
  case "Escape":
@@ -1167,12 +1301,12 @@ function RangePickerCalendar({ classNames, ...props }) {
1167
1301
  if (!adapter.isSameMonth(newFocused, viewMonth)) {
1168
1302
  ctx.setViewMonth(newFocused);
1169
1303
  }
1170
- if (selectingTarget === "end" && value.start) {
1304
+ if (selectionMode === "range" && selectingTarget === "end" && value.start) {
1171
1305
  ctx.setHoverDate(newFocused);
1172
1306
  }
1173
1307
  }
1174
1308
  },
1175
- [adapter, focusedDate, viewMonth, weekStartsOn, disabled, ctx, selectingTarget, value.start]
1309
+ [adapter, focusedDate, viewMonth, weekStartsOn, disabled, ctx, selectionMode, selectingTarget, value.start, commitDay]
1176
1310
  );
1177
1311
  return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, onMouseLeave: handleMouseLeave, children: [
1178
1312
  /* @__PURE__ */ jsxs("div", { className: classNames?.header, children: [
@@ -1229,7 +1363,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1229
1363
  day.isDisabled && classNames?.dayDisabled,
1230
1364
  !day.isCurrentMonth && classNames?.dayOutsideMonth
1231
1365
  ].filter(Boolean).join(" ") || void 0;
1232
- const isSelected = day.isRangeStart || day.isRangeEnd;
1366
+ const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1233
1367
  return /* @__PURE__ */ jsx(
1234
1368
  "td",
1235
1369
  {
@@ -1701,6 +1835,8 @@ function DateTimePickerRoot({
1701
1835
  value: controlledValue,
1702
1836
  defaultValue,
1703
1837
  onChange,
1838
+ onOpenChange,
1839
+ onCalendarNavigate,
1704
1840
  format = "24h",
1705
1841
  step = 1,
1706
1842
  disabled = false,
@@ -1735,6 +1871,9 @@ function DateTimePickerRoot({
1735
1871
  const [focusedDate, setFocusedDate] = useState(
1736
1872
  currentValue ?? adapter.today(displayTimezone)
1737
1873
  );
1874
+ useChangeEffect(isOpen, onOpenChange);
1875
+ const viewMonthStart = useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
1876
+ useChangeEffect(viewMonthStart, onCalendarNavigate);
1738
1877
  const isDisabled = typeof disabled === "boolean" ? disabled : false;
1739
1878
  const disabledRules = useMemo(
1740
1879
  () => Array.isArray(disabled) ? disabled : [],
@@ -1924,6 +2063,249 @@ var DateTimePicker = Object.assign(DateTimePickerRoot, {
1924
2063
  MinuteList: TimePickerMinuteList,
1925
2064
  AmPmToggle: TimePickerAmPmToggle
1926
2065
  });
2066
+ function MonthPickerRoot(props) {
2067
+ const displayFormat = props.displayFormat ?? "yyyy-MM";
2068
+ return /* @__PURE__ */ jsx(DatePickerRoot, { ...props, displayFormat });
2069
+ }
2070
+ function MonthPickerGrid({
2071
+ classNames,
2072
+ ...props
2073
+ }) {
2074
+ const ctx = useDatePickerContext("MonthPicker.Grid");
2075
+ const { adapter, viewMonth, locale, value, displayTimezone, labels } = ctx;
2076
+ const currentYear = adapter.getYear(viewMonth);
2077
+ const [valueYear, valueMonthZeroBased] = useMemo(() => {
2078
+ if (!value) return [null, null];
2079
+ try {
2080
+ const [y, m] = adapter.format(value, "yyyy-MM", displayTimezone).split("-").map(Number);
2081
+ return [y, m - 1];
2082
+ } catch {
2083
+ return [null, null];
2084
+ }
2085
+ }, [value, adapter, displayTimezone]);
2086
+ const today = adapter.today(displayTimezone);
2087
+ const todayYear = adapter.getYear(today);
2088
+ const todayMonth = adapter.getMonth(today);
2089
+ const navigateYear = useCallback(
2090
+ (direction) => {
2091
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction));
2092
+ },
2093
+ [adapter, viewMonth, ctx]
2094
+ );
2095
+ const handleMonthSelect = useCallback(
2096
+ (monthIndex) => {
2097
+ const target = new Date(
2098
+ Date.UTC(currentYear, monthIndex, 1)
2099
+ ).toISOString();
2100
+ ctx.selectDate(target);
2101
+ },
2102
+ [currentYear, ctx]
2103
+ );
2104
+ const months = Array.from({ length: 12 }, (_, i) => ({
2105
+ index: i,
2106
+ name: getMonthName(i, locale),
2107
+ isSelected: valueYear === currentYear && valueMonthZeroBased === i,
2108
+ isCurrent: todayYear === currentYear && todayMonth === i
2109
+ }));
2110
+ return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, children: [
2111
+ /* @__PURE__ */ jsxs("div", { className: classNames?.header, children: [
2112
+ /* @__PURE__ */ jsx(
2113
+ "button",
2114
+ {
2115
+ type: "button",
2116
+ className: classNames?.navButton,
2117
+ onClick: () => navigateYear(-1),
2118
+ "aria-label": labels.prevYear,
2119
+ children: "<"
2120
+ }
2121
+ ),
2122
+ /* @__PURE__ */ jsx("span", { className: classNames?.title, children: currentYear }),
2123
+ /* @__PURE__ */ jsx(
2124
+ "button",
2125
+ {
2126
+ type: "button",
2127
+ className: classNames?.navButton,
2128
+ onClick: () => navigateYear(1),
2129
+ "aria-label": labels.nextYear,
2130
+ children: ">"
2131
+ }
2132
+ )
2133
+ ] }),
2134
+ /* @__PURE__ */ jsx(
2135
+ "div",
2136
+ {
2137
+ role: "grid",
2138
+ "aria-label": `${currentYear} months`,
2139
+ className: classNames?.grid,
2140
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsx(
2141
+ "div",
2142
+ {
2143
+ role: "row",
2144
+ className: classNames?.gridRow,
2145
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2146
+ children: months.slice(rowIndex * 3, rowIndex * 3 + 3).map((m) => {
2147
+ const monthClass = [
2148
+ classNames?.month,
2149
+ m.isSelected && classNames?.monthSelected,
2150
+ m.isCurrent && classNames?.monthCurrent
2151
+ ].filter(Boolean).join(" ") || void 0;
2152
+ return /* @__PURE__ */ jsx(
2153
+ "button",
2154
+ {
2155
+ type: "button",
2156
+ role: "gridcell",
2157
+ "aria-selected": m.isSelected || void 0,
2158
+ "aria-current": m.isCurrent ? "date" : void 0,
2159
+ "data-selected": m.isSelected || void 0,
2160
+ "data-current": m.isCurrent || void 0,
2161
+ className: monthClass,
2162
+ onClick: () => handleMonthSelect(m.index),
2163
+ children: m.name
2164
+ },
2165
+ m.index
2166
+ );
2167
+ })
2168
+ },
2169
+ rowIndex
2170
+ ))
2171
+ }
2172
+ )
2173
+ ] });
2174
+ }
2175
+
2176
+ // src/components/MonthPicker/index.ts
2177
+ var MonthPicker = Object.assign(MonthPickerRoot, {
2178
+ Input: DatePickerInput,
2179
+ Trigger: DatePickerTrigger,
2180
+ Popover: DatePickerPopover,
2181
+ Grid: MonthPickerGrid
2182
+ });
2183
+ function YearPickerRoot(props) {
2184
+ const displayFormat = props.displayFormat ?? "yyyy";
2185
+ return /* @__PURE__ */ jsx(DatePickerRoot, { ...props, displayFormat });
2186
+ }
2187
+ function YearPickerGrid({ classNames, ...props }) {
2188
+ const ctx = useDatePickerContext("YearPicker.Grid");
2189
+ const { adapter, viewMonth, value, displayTimezone, labels } = ctx;
2190
+ const currentYear = adapter.getYear(viewMonth);
2191
+ const decadeStart = currentYear - currentYear % 12;
2192
+ const valueYear = useMemo(() => {
2193
+ if (!value) return null;
2194
+ try {
2195
+ return Number(adapter.format(value, "yyyy", displayTimezone));
2196
+ } catch {
2197
+ return null;
2198
+ }
2199
+ }, [value, adapter, displayTimezone]);
2200
+ const todayYear = adapter.getYear(adapter.today(displayTimezone));
2201
+ const navigateDecade = useCallback(
2202
+ (direction) => {
2203
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
2204
+ },
2205
+ [adapter, viewMonth, ctx]
2206
+ );
2207
+ const handleYearSelect = useCallback(
2208
+ (year) => {
2209
+ const target = new Date(Date.UTC(year, 0, 1)).toISOString();
2210
+ ctx.selectDate(target);
2211
+ },
2212
+ [ctx]
2213
+ );
2214
+ const years = Array.from({ length: 12 }, (_, i) => {
2215
+ const year = decadeStart + i;
2216
+ return {
2217
+ value: year,
2218
+ isSelected: year === valueYear,
2219
+ isCurrent: year === todayYear
2220
+ };
2221
+ });
2222
+ const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
2223
+ return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, children: [
2224
+ /* @__PURE__ */ jsxs("div", { className: classNames?.header, children: [
2225
+ /* @__PURE__ */ jsx(
2226
+ "button",
2227
+ {
2228
+ type: "button",
2229
+ className: classNames?.navButton,
2230
+ onClick: () => navigateDecade(-1),
2231
+ "aria-label": labels.prevDecade,
2232
+ children: "<"
2233
+ }
2234
+ ),
2235
+ /* @__PURE__ */ jsx("span", { className: classNames?.title, children: rangeLabel }),
2236
+ /* @__PURE__ */ jsx(
2237
+ "button",
2238
+ {
2239
+ type: "button",
2240
+ className: classNames?.navButton,
2241
+ onClick: () => navigateDecade(1),
2242
+ "aria-label": labels.nextDecade,
2243
+ children: ">"
2244
+ }
2245
+ )
2246
+ ] }),
2247
+ /* @__PURE__ */ jsx(
2248
+ "div",
2249
+ {
2250
+ role: "grid",
2251
+ "aria-label": rangeLabel,
2252
+ className: classNames?.grid,
2253
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsx(
2254
+ "div",
2255
+ {
2256
+ role: "row",
2257
+ className: classNames?.gridRow,
2258
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2259
+ children: years.slice(rowIndex * 3, rowIndex * 3 + 3).map((y) => {
2260
+ const yearClass = [
2261
+ classNames?.year,
2262
+ y.isSelected && classNames?.yearSelected,
2263
+ y.isCurrent && classNames?.yearCurrent
2264
+ ].filter(Boolean).join(" ") || void 0;
2265
+ return /* @__PURE__ */ jsx(
2266
+ "button",
2267
+ {
2268
+ type: "button",
2269
+ role: "gridcell",
2270
+ "aria-selected": y.isSelected || void 0,
2271
+ "aria-current": y.isCurrent ? "date" : void 0,
2272
+ "data-selected": y.isSelected || void 0,
2273
+ "data-current": y.isCurrent || void 0,
2274
+ className: yearClass,
2275
+ onClick: () => handleYearSelect(y.value),
2276
+ children: y.value
2277
+ },
2278
+ y.value
2279
+ );
2280
+ })
2281
+ },
2282
+ rowIndex
2283
+ ))
2284
+ }
2285
+ )
2286
+ ] });
2287
+ }
2288
+
2289
+ // src/components/YearPicker/index.ts
2290
+ var YearPicker = Object.assign(YearPickerRoot, {
2291
+ Input: DatePickerInput,
2292
+ Trigger: DatePickerTrigger,
2293
+ Popover: DatePickerPopover,
2294
+ Grid: YearPickerGrid
2295
+ });
2296
+ function WeekPickerRoot(props) {
2297
+ return /* @__PURE__ */ jsx(RangePickerRoot, { ...props });
2298
+ }
2299
+ function WeekPickerCalendar(props) {
2300
+ return /* @__PURE__ */ jsx(RangePickerCalendar, { ...props, selectionMode: "week" });
2301
+ }
2302
+
2303
+ // src/components/WeekPicker/index.ts
2304
+ var WeekPicker = Object.assign(WeekPickerRoot, {
2305
+ Input: RangePickerInput,
2306
+ Popover: RangePickerPopover,
2307
+ Calendar: WeekPickerCalendar
2308
+ });
1927
2309
  function useDatePicker(options = {}) {
1928
2310
  const {
1929
2311
  value: controlledValue,
@@ -2196,6 +2578,6 @@ function useTimePicker(options = {}) {
2196
2578
  };
2197
2579
  }
2198
2580
 
2199
- export { DatePicker, DateTimePicker, RangePicker, TimePicker, useDatePicker, useRangePicker, useTimePicker };
2581
+ export { DatePicker, DateTimePicker, MonthPicker, RangePicker, TimePicker, WeekPicker, YearPicker, useDatePicker, useRangePicker, useTimePicker };
2200
2582
  //# sourceMappingURL=index.js.map
2201
2583
  //# sourceMappingURL=index.js.map