@underverse-ui/underverse 1.0.4 → 1.0.5

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.cjs CHANGED
@@ -733,6 +733,8 @@ var defaultTranslations = {
733
733
  month: "Month",
734
734
  week: "Week",
735
735
  day: "Day",
736
+ sprint: "Sprint",
737
+ sprints: "{n} sprints",
736
738
  resourcesHeader: "Resources",
737
739
  expandGroup: "Expand group",
738
740
  collapseGroup: "Collapse group",
@@ -809,6 +811,8 @@ var defaultTranslations = {
809
811
  month: "Th\xE1ng",
810
812
  week: "Tu\u1EA7n",
811
813
  day: "Ng\xE0y",
814
+ sprint: "Sprint",
815
+ sprints: "{n} sprint",
812
816
  resourcesHeader: "T\xE0i nguy\xEAn",
813
817
  expandGroup: "M\u1EDF nh\xF3m",
814
818
  collapseGroup: "Thu g\u1ECDn nh\xF3m",
@@ -885,6 +889,8 @@ var defaultTranslations = {
885
889
  month: "\uC6D4",
886
890
  week: "\uC8FC",
887
891
  day: "\uC77C",
892
+ sprint: "\uC2A4\uD504\uB9B0\uD2B8",
893
+ sprints: "{n} \uC2A4\uD504\uB9B0\uD2B8",
888
894
  resourcesHeader: "\uB9AC\uC18C\uC2A4",
889
895
  expandGroup: "\uADF8\uB8F9 \uD3BC\uCE58\uAE30",
890
896
  collapseGroup: "\uADF8\uB8F9 \uC811\uAE30",
@@ -961,6 +967,8 @@ var defaultTranslations = {
961
967
  month: "\u6708",
962
968
  week: "\u9031",
963
969
  day: "\u65E5",
970
+ sprint: "\u30B9\u30D7\u30EA\u30F3\u30C8",
971
+ sprints: "{n} \u30B9\u30D7\u30EA\u30F3\u30C8",
964
972
  resourcesHeader: "\u30EA\u30BD\u30FC\u30B9",
965
973
  expandGroup: "\u30B0\u30EB\u30FC\u30D7\u3092\u5C55\u958B",
966
974
  collapseGroup: "\u30B0\u30EB\u30FC\u30D7\u3092\u6298\u308A\u305F\u305F\u3080",
@@ -8571,6 +8579,10 @@ function startOfZonedMonth(date, timeZone) {
8571
8579
  const p = getZonedParts(date, timeZone);
8572
8580
  return new Date(zonedTimeToUtcMs({ year: p.year, month: p.month, day: 1, hour: 0, minute: 0, second: 0 }, timeZone));
8573
8581
  }
8582
+ function startOfZonedYear(date, timeZone) {
8583
+ const p = getZonedParts(date, timeZone);
8584
+ return new Date(zonedTimeToUtcMs({ year: p.year, month: 1, day: 1, hour: 0, minute: 0, second: 0 }, timeZone));
8585
+ }
8574
8586
  function addZonedDays(date, days, timeZone) {
8575
8587
  const p = getZonedParts(date, timeZone);
8576
8588
  return new Date(zonedTimeToUtcMs({ ...p, day: p.day + days }, timeZone));
@@ -8583,6 +8595,9 @@ function addZonedMonths(date, months, timeZone) {
8583
8595
  const clampedDay = Math.min(p.day, daysInTargetMonth);
8584
8596
  return new Date(zonedTimeToUtcMs({ year: next.year, month: next.month, day: clampedDay, hour: p.hour, minute: p.minute, second: p.second }, timeZone));
8585
8597
  }
8598
+ function addZonedYears(date, years, timeZone) {
8599
+ return addZonedMonths(date, years * 12, timeZone);
8600
+ }
8586
8601
  function startOfZonedWeek(date, weekStartsOn, timeZone) {
8587
8602
  const p = getZonedParts(date, timeZone);
8588
8603
  const weekday = new Date(Date.UTC(p.year, p.month - 1, p.day)).getUTCDay();
@@ -8821,6 +8836,13 @@ function defaultSlotHeader(slotStart, view, locale, timeZone) {
8821
8836
  if (view === "day") {
8822
8837
  return getDtf(locale, timeZone, { hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(slotStart);
8823
8838
  }
8839
+ if (view === "sprint") {
8840
+ const { week } = getIsoWeekInfo(slotStart, timeZone);
8841
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "inline-flex flex-col items-center leading-tight", children: [
8842
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70", children: "S" }),
8843
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "text-sm font-semibold text-foreground", children: String(week).padStart(2, "0") })
8844
+ ] });
8845
+ }
8824
8846
  const weekday = getDtf(locale, timeZone, { weekday: "short" }).format(slotStart);
8825
8847
  const day = getDtf(locale, timeZone, { day: "numeric" }).format(slotStart);
8826
8848
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "inline-flex flex-col items-center leading-tight", children: [
@@ -8881,7 +8903,10 @@ function getGroupResourceCounts(resources) {
8881
8903
  function computeSlotStarts(args) {
8882
8904
  const { view, date, timeZone, weekStartsOn, dayTimeStepMinutes, dayRangeMode, workHours } = args;
8883
8905
  const baseDayStart = startOfZonedDay(date, timeZone);
8884
- const start = view === "month" ? startOfZonedMonth(date, timeZone) : view === "week" ? startOfZonedWeek(date, weekStartsOn, timeZone) : baseDayStart;
8906
+ const start = view === "month" ? startOfZonedMonth(date, timeZone) : view === "week" ? startOfZonedWeek(date, weekStartsOn, timeZone) : view === "sprint" ? (() => {
8907
+ const yearStart = startOfZonedYear(date, timeZone);
8908
+ return startOfZonedWeek(yearStart, weekStartsOn, timeZone);
8909
+ })() : baseDayStart;
8885
8910
  if (view === "day") {
8886
8911
  const step = Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes)));
8887
8912
  const stepMs = step * 6e4;
@@ -8898,6 +8923,21 @@ function computeSlotStarts(args) {
8898
8923
  }
8899
8924
  return { start: start2, end: end3, slotStarts: slotStarts2 };
8900
8925
  }
8926
+ if (view === "sprint") {
8927
+ const yearStart = startOfZonedYear(date, timeZone);
8928
+ const nextYearStart = startOfZonedYear(addZonedMonths(yearStart, 12, timeZone), timeZone);
8929
+ const lastDayOfYear = addZonedDays(nextYearStart, -1, timeZone);
8930
+ const lastSlotStart = startOfZonedWeek(lastDayOfYear, weekStartsOn, timeZone);
8931
+ const end2 = addZonedDays(lastSlotStart, 7, timeZone);
8932
+ const slotStarts2 = [];
8933
+ let cur2 = start;
8934
+ let guard2 = 0;
8935
+ while (cur2.getTime() < end2.getTime() && guard2++ < 60) {
8936
+ slotStarts2.push(cur2);
8937
+ cur2 = addZonedDays(cur2, 7, timeZone);
8938
+ }
8939
+ return { start, end: end2, slotStarts: slotStarts2 };
8940
+ }
8901
8941
  const end = view === "month" ? startOfZonedMonth(addZonedMonths(start, 1, timeZone), timeZone) : addZonedDays(start, 7, timeZone);
8902
8942
  const slotStarts = [];
8903
8943
  let cur = start;
@@ -8946,7 +8986,8 @@ var import_jsx_runtime34 = require("react/jsx-runtime");
8946
8986
  var VIEW_ICONS = {
8947
8987
  month: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.CalendarRange, { className: "h-4 w-4" }),
8948
8988
  week: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.CalendarDays, { className: "h-4 w-4" }),
8949
- day: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.Calendar, { className: "h-4 w-4" })
8989
+ day: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.Calendar, { className: "h-4 w-4" }),
8990
+ sprint: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.CalendarDays, { className: "h-4 w-4" })
8950
8991
  };
8951
8992
  function CalendarTimelineHeader(props) {
8952
8993
  const {
@@ -8971,7 +9012,7 @@ function CalendarTimelineHeader(props) {
8971
9012
  slotHeaderNodes
8972
9013
  } = props;
8973
9014
  const resolvedAvailableViews = React28.useMemo(
8974
- () => availableViews?.length ? availableViews : ["month", "week", "day"],
9015
+ () => availableViews?.length ? availableViews : ["month", "week", "day", "sprint"],
8975
9016
  [availableViews]
8976
9017
  );
8977
9018
  const showViewSwitcher = resolvedAvailableViews.length > 1;
@@ -9034,7 +9075,7 @@ function CalendarTimelineHeader(props) {
9034
9075
  onApplyDateTime(tempDate);
9035
9076
  setTodayOpen(false);
9036
9077
  }, [onApplyDateTime, tempDate]);
9037
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "sticky top-0 z-30 bg-linear-to-b from-background via-background to-background/95 border-b border-border/40 backdrop-blur-xl", children: [
9078
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "sticky top-0 z-30 bg-linear-to-b from-card via-card to-card/95 border-b border-border/40 backdrop-blur-xl", children: [
9038
9079
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: cn("flex items-center justify-between gap-4", sizeConfig.headerPaddingClass), children: [
9039
9080
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0", children: [
9040
9081
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center bg-muted/40 rounded-full p-1 gap-0.5", children: [
@@ -9045,7 +9086,7 @@ function CalendarTimelineHeader(props) {
9045
9086
  size: "icon",
9046
9087
  onClick: () => navigate(-1),
9047
9088
  "aria-label": labels.prev,
9048
- className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-background/80 transition-all duration-200"),
9089
+ className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-card/80 transition-all duration-200"),
9049
9090
  children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.ChevronLeft, { className: "h-4 w-4" })
9050
9091
  }
9051
9092
  ),
@@ -9060,7 +9101,7 @@ function CalendarTimelineHeader(props) {
9060
9101
  {
9061
9102
  variant: "ghost",
9062
9103
  size: "sm",
9063
- className: cn(sizeConfig.controlButtonTextClass, "rounded-full hover:bg-background/80 font-medium transition-all duration-200"),
9104
+ className: cn(sizeConfig.controlButtonTextClass, "rounded-full hover:bg-card/80 font-medium transition-all duration-200"),
9064
9105
  children: labels.today
9065
9106
  }
9066
9107
  ),
@@ -9140,7 +9181,7 @@ function CalendarTimelineHeader(props) {
9140
9181
  size: "icon",
9141
9182
  onClick: () => navigate(1),
9142
9183
  "aria-label": labels.next,
9143
- className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-background/80 transition-all duration-200"),
9184
+ className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-card/80 transition-all duration-200"),
9144
9185
  children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.ChevronRight, { className: "h-4 w-4" })
9145
9186
  }
9146
9187
  )
@@ -9169,7 +9210,7 @@ function CalendarTimelineHeader(props) {
9169
9210
  className: cn(
9170
9211
  sizeConfig.controlButtonTextClass,
9171
9212
  "rounded-full font-medium transition-all duration-200 gap-1.5",
9172
- activeView === v ? "bg-primary text-primary-foreground shadow-sm shadow-primary/25" : "hover:bg-background/80 text-muted-foreground hover:text-foreground"
9213
+ activeView === v ? "bg-primary text-primary-foreground shadow-sm shadow-primary/25" : "hover:bg-card/80 text-muted-foreground hover:text-foreground"
9173
9214
  ),
9174
9215
  children: [
9175
9216
  VIEW_ICONS[v],
@@ -9184,7 +9225,7 @@ function CalendarTimelineHeader(props) {
9184
9225
  showLeftColumn ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
9185
9226
  "div",
9186
9227
  {
9187
- className: "shrink-0 border-r border-border/30 bg-muted/20 flex items-center justify-center relative group/uv-ct-top-left",
9228
+ className: "shrink-0 border-r border-border/30 bg-card/60 flex items-center justify-center relative group/uv-ct-top-left",
9188
9229
  style: { width: effectiveResourceColumnWidth, minWidth: effectiveResourceColumnWidth },
9189
9230
  children: [
9190
9231
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "text-xs font-medium text-muted-foreground/70 uppercase tracking-wider", children: resourcesHeaderLabel }),
@@ -9239,7 +9280,7 @@ function DefaultGroupRow(props) {
9239
9280
  "span",
9240
9281
  {
9241
9282
  className: cn(
9242
- "inline-flex items-center justify-center w-5 h-5 rounded-md bg-background/60 transition-transform duration-200",
9283
+ "inline-flex items-center justify-center w-5 h-5 rounded-md bg-card/60 transition-transform duration-200",
9243
9284
  collapsed ? "" : "rotate-180"
9244
9285
  ),
9245
9286
  children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react19.ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
@@ -9260,7 +9301,7 @@ function ResourceRowCell(props) {
9260
9301
  "div",
9261
9302
  {
9262
9303
  className: cn(
9263
- "h-full w-full flex items-center border-b border-border/30 bg-linear-to-r from-background to-background/95 relative",
9304
+ "h-full w-full flex items-center border-b border-border/30 bg-linear-to-r from-card to-card/95 relative",
9264
9305
  sizeConfig.resourceRowClass,
9265
9306
  "hover:from-muted/30 hover:to-muted/10 transition-all duration-200 group/uv-ct-row-header"
9266
9307
  ),
@@ -9320,7 +9361,7 @@ var CalendarTimelineGridOverlay = React29.memo(function CalendarTimelineGridOver
9320
9361
  if (showToday && idx === todaySlotIdx) return null;
9321
9362
  const left = slotLefts[idx] ?? 0;
9322
9363
  const width = slotWidths[idx] ?? 0;
9323
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "absolute top-0 h-full bg-destructive/5", style: { left, width }, "aria-hidden": true }, `we_${idx}`);
9364
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "absolute top-0 h-full bg-muted/20", style: { left, width }, "aria-hidden": true }, `we_${idx}`);
9324
9365
  }) : null,
9325
9366
  showToday ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9326
9367
  "div",
@@ -9383,7 +9424,8 @@ function useTimelineSlots(args) {
9383
9424
  dayRangeMode,
9384
9425
  workHours,
9385
9426
  resolvedNow,
9386
- formatters
9427
+ formatters,
9428
+ dueDateSprint
9387
9429
  } = args;
9388
9430
  const { slots, range } = React31.useMemo(() => {
9389
9431
  const { start, end, slotStarts: slotStarts2 } = computeSlotStarts({
@@ -9396,17 +9438,89 @@ function useTimelineSlots(args) {
9396
9438
  workHours
9397
9439
  });
9398
9440
  const todayStart = startOfZonedDay(resolvedNow, resolvedTimeZone).getTime();
9399
- const slotItems = slotStarts2.map((s) => ({
9441
+ const nowMs = resolvedNow.getTime();
9442
+ const sprintDefs = (() => {
9443
+ if (activeView !== "sprint") return [];
9444
+ if (!dueDateSprint) return [];
9445
+ const out = [];
9446
+ for (const v of Object.values(dueDateSprint)) {
9447
+ const startInput = v.range_date?.start;
9448
+ const endInput = v.range_date?.end;
9449
+ if (startInput == null || endInput == null) continue;
9450
+ const startRaw = toDate2(startInput);
9451
+ const endRaw = toDate2(endInput);
9452
+ if (Number.isNaN(startRaw.getTime()) || Number.isNaN(endRaw.getTime())) continue;
9453
+ const startMs = startOfZonedDay(startRaw, resolvedTimeZone).getTime();
9454
+ const endMs = startOfZonedDay(endRaw, resolvedTimeZone).getTime();
9455
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) continue;
9456
+ if (endMs <= startMs) continue;
9457
+ out.push({ startMs, endMs, title: v.title });
9458
+ }
9459
+ out.sort((a, b) => a.startMs - b.startMs);
9460
+ return out;
9461
+ })();
9462
+ const sprintRangeText = (() => {
9463
+ if (activeView !== "sprint") return null;
9464
+ const df = getDtf(resolvedLocale, resolvedTimeZone, { month: "short", day: "numeric" });
9465
+ return (startMs, endMs) => {
9466
+ const a = df.format(new Date(startMs));
9467
+ const b = df.format(new Date(endMs - 1));
9468
+ return a === b ? a : `${a}\u2013${b}`;
9469
+ };
9470
+ })();
9471
+ const matchSprintDef = (slotStart, idx) => {
9472
+ if (activeView !== "sprint") return null;
9473
+ if (sprintDefs.length === 0) return null;
9474
+ const sMs = startOfZonedDay(slotStart, resolvedTimeZone).getTime();
9475
+ const byRange = sprintDefs.find((d) => sMs >= d.startMs && sMs < d.endMs) ?? null;
9476
+ return byRange ?? sprintDefs[idx] ?? null;
9477
+ };
9478
+ const slotItems = slotStarts2.map((s, idx) => ({
9400
9479
  start: s,
9401
- label: formatters?.slotHeader?.(s, { view: activeView, locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? defaultSlotHeader(s, activeView, resolvedLocale, resolvedTimeZone),
9402
- isToday: startOfZonedDay(s, resolvedTimeZone).getTime() === todayStart,
9403
- isWeekend: activeView === "day" ? false : (() => {
9480
+ label: formatters?.slotHeader?.(s, { view: activeView, locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? (() => {
9481
+ if (activeView !== "sprint") return defaultSlotHeader(s, activeView, resolvedLocale, resolvedTimeZone);
9482
+ const match = matchSprintDef(s, idx);
9483
+ if (match && sprintRangeText) {
9484
+ const rangeText = sprintRangeText(match.startMs, match.endMs);
9485
+ return React31.createElement(
9486
+ "span",
9487
+ { className: "inline-flex flex-col items-center leading-tight" },
9488
+ React31.createElement("span", { className: "text-[11px] font-semibold text-foreground truncate max-w-[8rem]" }, match.title),
9489
+ React31.createElement("span", { className: "text-[10px] font-medium text-muted-foreground/70" }, rangeText)
9490
+ );
9491
+ }
9492
+ return React31.createElement(
9493
+ "span",
9494
+ { className: "inline-flex flex-col items-center leading-tight" },
9495
+ React31.createElement("span", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70" }, "S"),
9496
+ React31.createElement("span", { className: "text-sm font-semibold text-foreground" }, String(idx + 1).padStart(2, "0"))
9497
+ );
9498
+ })(),
9499
+ isToday: (() => {
9500
+ if (activeView !== "sprint") return startOfZonedDay(s, resolvedTimeZone).getTime() === todayStart;
9501
+ const match = matchSprintDef(s, idx);
9502
+ const sprintEndMs = match ? match.endMs : addZonedDays(s, 7, resolvedTimeZone).getTime();
9503
+ return nowMs >= s.getTime() && nowMs < sprintEndMs;
9504
+ })(),
9505
+ isWeekend: activeView === "day" || activeView === "sprint" ? false : (() => {
9404
9506
  const wd = getZonedWeekday(s, resolvedTimeZone);
9405
9507
  return wd === 0 || wd === 6;
9406
9508
  })()
9407
9509
  }));
9408
9510
  return { slots: slotItems, range: { start, end } };
9409
- }, [activeView, activeDate, dayRangeMode, dayTimeStepMinutes, formatters, resolvedLocale, resolvedNow, resolvedTimeZone, weekStartsOn, workHours]);
9511
+ }, [
9512
+ activeView,
9513
+ activeDate,
9514
+ dayRangeMode,
9515
+ dayTimeStepMinutes,
9516
+ dueDateSprint,
9517
+ formatters,
9518
+ resolvedLocale,
9519
+ resolvedNow,
9520
+ resolvedTimeZone,
9521
+ weekStartsOn,
9522
+ workHours
9523
+ ]);
9410
9524
  const slotStarts = React31.useMemo(() => slots.map((s) => s.start), [slots]);
9411
9525
  const todaySlotIdx = React31.useMemo(() => slots.findIndex((s) => s.isToday), [slots]);
9412
9526
  const weekendSlotIdxs = React31.useMemo(() => {
@@ -9697,6 +9811,7 @@ function CalendarTimeline({
9697
9811
  view,
9698
9812
  defaultView = "month",
9699
9813
  onViewChange,
9814
+ dueDateSprint,
9700
9815
  date,
9701
9816
  defaultDate,
9702
9817
  onDateChange,
@@ -9729,6 +9844,8 @@ function CalendarTimeline({
9729
9844
  adaptiveSlotWidths,
9730
9845
  dayEventStyle = "span",
9731
9846
  dayEventMaxWidth,
9847
+ monthEventStyle = "span",
9848
+ monthEventMaxWidth,
9732
9849
  dayTimeStepMinutes = 60,
9733
9850
  enableEventTooltips = true,
9734
9851
  dayHeaderMode = "full",
@@ -9823,18 +9940,29 @@ function CalendarTimeline({
9823
9940
  setInternalRowHeight(defaultRowHeight);
9824
9941
  }, [defaultRowHeight, isControlledRowHeight]);
9825
9942
  const effectiveRowHeight = isControlledRowHeight ? rowHeight : internalRowHeight;
9826
- const effectiveSlotMinWidth = slotMinWidth ?? sizeConfig.slotMinWidth;
9943
+ const baseSlotMinWidth = slotMinWidth ?? sizeConfig.slotMinWidth;
9827
9944
  const colMin = minResourceColumnWidth ?? 160;
9828
9945
  const colMax = maxResourceColumnWidth ?? 520;
9829
9946
  const rowMin = minRowHeight ?? 36;
9830
9947
  const rowMax = maxRowHeight ?? 120;
9831
9948
  const availableViews = React32.useMemo(
9832
- () => onlyView ? [onlyView] : ["month", "week", "day"],
9949
+ () => onlyView ? [onlyView] : ["month", "week", "day", "sprint"],
9833
9950
  [onlyView]
9834
9951
  );
9835
9952
  const isControlledView = view !== void 0;
9836
9953
  const [internalView, setInternalView] = React32.useState(() => onlyView ?? defaultView);
9837
9954
  const activeView = onlyView ? onlyView : isControlledView ? view : internalView;
9955
+ const effectiveSlotMinWidth = React32.useMemo(() => {
9956
+ if (slotMinWidth == null) {
9957
+ if (activeView === "month" && monthEventStyle === "compact") {
9958
+ return clamp4(Math.round(sizeConfig.slotMinWidth * 0.55), 32, sizeConfig.slotMinWidth);
9959
+ }
9960
+ if (activeView === "sprint") {
9961
+ return clamp4(Math.round(sizeConfig.slotMinWidth * 0.4), 18, 48);
9962
+ }
9963
+ }
9964
+ return baseSlotMinWidth;
9965
+ }, [activeView, baseSlotMinWidth, monthEventStyle, sizeConfig.slotMinWidth, slotMinWidth]);
9838
9966
  const isControlledDate = date !== void 0;
9839
9967
  const [internalDate, setInternalDate] = React32.useState(() => defaultDate ?? /* @__PURE__ */ new Date());
9840
9968
  const activeDate = isControlledDate ? date : internalDate;
@@ -9847,6 +9975,7 @@ function CalendarTimeline({
9847
9975
  month: labels?.month ?? t("month"),
9848
9976
  week: labels?.week ?? t("week"),
9849
9977
  day: labels?.day ?? t("day"),
9978
+ sprint: labels?.sprint ?? t("sprint"),
9850
9979
  newEvent: labels?.newEvent ?? t("newEvent"),
9851
9980
  createEventTitle: labels?.createEventTitle ?? t("createEventTitle"),
9852
9981
  create: labels?.create ?? t("create"),
@@ -9887,6 +10016,10 @@ function CalendarTimeline({
9887
10016
  setDate(addZonedDays(base, dir * 7, resolvedTimeZone));
9888
10017
  return;
9889
10018
  }
10019
+ if (activeView === "sprint") {
10020
+ setDate(addZonedYears(base, dir, resolvedTimeZone));
10021
+ return;
10022
+ }
9890
10023
  setDate(addZonedDays(base, dir, resolvedTimeZone));
9891
10024
  },
9892
10025
  [activeDate, activeView, resolvedTimeZone, setDate]
@@ -9912,7 +10045,8 @@ function CalendarTimeline({
9912
10045
  dayRangeMode,
9913
10046
  workHours,
9914
10047
  resolvedNow,
9915
- formatters
10048
+ formatters,
10049
+ dueDateSprint
9916
10050
  });
9917
10051
  React32.useEffect(() => {
9918
10052
  onRangeChange?.(range);
@@ -10158,9 +10292,15 @@ function CalendarTimeline({
10158
10292
  const rangeText = ya === yb ? `${a} \u2013 ${b}, ${ya}` : `${a}, ${ya} \u2013 ${b}, ${yb}`;
10159
10293
  return `${l.week} ${week} \u2022 ${rangeText}`;
10160
10294
  }
10295
+ if (activeView === "sprint") {
10296
+ const fmtYear = getDtf(resolvedLocale, resolvedTimeZone, { year: "numeric" });
10297
+ const yearText = fmtYear.format(activeDate);
10298
+ const count = Math.max(0, slots.length);
10299
+ return `${l.sprint} \u2022 ${yearText} \u2022 ${t("sprints", { n: count })}`;
10300
+ }
10161
10301
  const fmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
10162
10302
  return fmt.format(range.start);
10163
- }, [activeDate, activeView, formatters, l.week, range.end, range.start, resolvedLocale, resolvedTimeZone]);
10303
+ }, [activeDate, activeView, formatters, l.sprint, l.week, range.end, range.start, resolvedLocale, resolvedTimeZone, slots.length, t]);
10164
10304
  const createMode = interactions?.createMode ?? "drag";
10165
10305
  const canCreate = !isViewOnly && (interactions?.creatable ?? false) && !!onCreateEvent;
10166
10306
  const [createOpen, setCreateOpen] = React32.useState(false);
@@ -10175,11 +10315,54 @@ function CalendarTimeline({
10175
10315
  disabled: r.disabled ?? false
10176
10316
  }));
10177
10317
  }, [resources]);
10178
- const slotPickerLabel = React32.useMemo(() => {
10318
+ const formatCreateBoundaryLabel = React32.useMemo(() => {
10179
10319
  const timeFmt = getDtf(resolvedLocale, resolvedTimeZone, { hour: "2-digit", minute: "2-digit", hourCycle: "h23" });
10180
10320
  const dayFmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "short", month: "short", day: "numeric" });
10181
- return (d) => activeView === "day" ? timeFmt.format(d) : dayFmt.format(d);
10182
- }, [activeView, resolvedLocale, resolvedTimeZone]);
10321
+ const yearFmt = getDtf(resolvedLocale, resolvedTimeZone, { year: "numeric" });
10322
+ const sprintDefs = (() => {
10323
+ if (!dueDateSprint) return [];
10324
+ const out = [];
10325
+ for (const v of Object.values(dueDateSprint)) {
10326
+ const startInput = v.range_date?.start;
10327
+ const endInput = v.range_date?.end;
10328
+ if (startInput == null || endInput == null) continue;
10329
+ const startRaw = toDate2(startInput);
10330
+ const endRaw = toDate2(endInput);
10331
+ if (Number.isNaN(startRaw.getTime()) || Number.isNaN(endRaw.getTime())) continue;
10332
+ const startMs = startOfZonedDay(startRaw, resolvedTimeZone).getTime();
10333
+ const endMs = startOfZonedDay(endRaw, resolvedTimeZone).getTime();
10334
+ if (endMs <= startMs) continue;
10335
+ out.push({ startMs, endMs, title: v.title });
10336
+ }
10337
+ out.sort((a, b) => a.startMs - b.startMs);
10338
+ return out;
10339
+ })();
10340
+ const sprintTitleForStart = (slotStart, idx) => {
10341
+ if (activeView !== "sprint") return null;
10342
+ if (sprintDefs.length === 0) return null;
10343
+ const sMs = startOfZonedDay(slotStart, resolvedTimeZone).getTime();
10344
+ const match = sprintDefs.find((d) => sMs >= d.startMs && sMs < d.endMs) ?? sprintDefs[idx] ?? null;
10345
+ if (!match) return null;
10346
+ return typeof match.title === "string" || typeof match.title === "number" ? String(match.title) : null;
10347
+ };
10348
+ return (d, opts) => {
10349
+ if (activeView === "day") return timeFmt.format(d);
10350
+ if (activeView === "sprint") {
10351
+ const y = yearFmt.format(d);
10352
+ const idx = opts?.boundaryIdx;
10353
+ if (typeof idx === "number") {
10354
+ const slotStart = slotStarts[clamp4(idx, 0, Math.max(0, slotStarts.length - 1))] ?? d;
10355
+ const dynamicTitle = sprintTitleForStart(slotStart, idx);
10356
+ if (dynamicTitle) return `${dynamicTitle} \u2022 ${dayFmt.format(d)}, ${y}`;
10357
+ const sprintNumber = opts?.kind === "start" ? idx + 1 : Math.min(idx + 1, Math.max(1, slotStarts.length));
10358
+ const sprintText = `${l.sprint} ${String(sprintNumber).padStart(2, "0")}`;
10359
+ const dateText = dayFmt.format(d);
10360
+ return `${sprintText} \u2022 ${dateText}, ${y}`;
10361
+ }
10362
+ }
10363
+ return dayFmt.format(d);
10364
+ };
10365
+ }, [activeView, dueDateSprint, l.sprint, resolvedLocale, resolvedTimeZone, slotStarts, slotStarts.length]);
10183
10366
  const openCreate = React32.useCallback(() => {
10184
10367
  if (!canCreate) return;
10185
10368
  if (activeEventSheetOpen) setEventSheetOpen(false);
@@ -10216,16 +10399,16 @@ function CalendarTimeline({
10216
10399
  setCreateEndIdx((prev) => Math.min(slots.length, Math.max(prev, createStartIdx + 1)));
10217
10400
  }, [createStartIdx, slots.length]);
10218
10401
  const createStartOptions = React32.useMemo(() => {
10219
- return slotStarts.map((d, idx) => ({ label: slotPickerLabel(d), value: idx }));
10220
- }, [slotStarts, slotPickerLabel]);
10402
+ return slotStarts.map((d, idx) => ({ label: formatCreateBoundaryLabel(d, { kind: "start", boundaryIdx: idx }), value: idx }));
10403
+ }, [formatCreateBoundaryLabel, slotStarts]);
10221
10404
  const createEndOptions = React32.useMemo(() => {
10222
10405
  const out = [];
10223
10406
  for (let idx = createStartIdx + 1; idx <= slotStarts.length; idx++) {
10224
10407
  const boundary = idx >= slotStarts.length ? range.end : slotStarts[idx];
10225
- out.push({ label: slotPickerLabel(boundary), value: idx });
10408
+ out.push({ label: formatCreateBoundaryLabel(boundary, { kind: "end", boundaryIdx: idx }), value: idx });
10226
10409
  }
10227
10410
  return out;
10228
- }, [createStartIdx, range.end, slotPickerLabel, slotStarts]);
10411
+ }, [createStartIdx, formatCreateBoundaryLabel, range.end, slotStarts]);
10229
10412
  const commitCreate = React32.useCallback(() => {
10230
10413
  if (!onCreateEvent) return;
10231
10414
  if (!createResourceId) return;
@@ -10279,6 +10462,9 @@ function CalendarTimeline({
10279
10462
  const stepMs = Math.trunc(Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes))) * 6e4);
10280
10463
  return { start, end: new Date(start.getTime() + stepMs) };
10281
10464
  }
10465
+ if (activeView === "sprint") {
10466
+ return { start, end: addZonedDays(start, 7, resolvedTimeZone) };
10467
+ }
10282
10468
  return { start, end: addZonedDays(start, 1, resolvedTimeZone) };
10283
10469
  },
10284
10470
  [activeView, dayTimeStepMinutes, resolvedTimeZone, slotStarts]
@@ -10561,7 +10747,7 @@ function CalendarTimeline({
10561
10747
  className: cn(
10562
10748
  sizeConfig.slotHeaderClass,
10563
10749
  activeView !== "day" && s.isToday && "bg-primary/8 border-l-primary/40",
10564
- activeView !== "day" && !s.isToday && s.isWeekend && "bg-destructive/8 text-destructive-foreground"
10750
+ activeView !== "day" && !s.isToday && s.isWeekend && "bg-muted/25"
10565
10751
  ),
10566
10752
  dayHeaderMarks
10567
10753
  },
@@ -10592,7 +10778,7 @@ function CalendarTimeline({
10592
10778
  {
10593
10779
  title,
10594
10780
  resourcesHeaderLabel: t("resourcesHeader"),
10595
- labels: { today: l.today, prev: l.prev, next: l.next, month: l.month, week: l.week, day: l.day },
10781
+ labels: { today: l.today, prev: l.prev, next: l.next, month: l.month, week: l.week, day: l.day, sprint: l.sprint },
10596
10782
  newEventLabel: l.newEvent,
10597
10783
  newEventDisabled: isViewOnly || !canCreate || resources.length === 0,
10598
10784
  onNewEventClick: isViewOnly ? void 0 : openCreate,
@@ -10627,8 +10813,9 @@ function CalendarTimeline({
10627
10813
  "div",
10628
10814
  {
10629
10815
  className: cn(
10630
- "border border-border/40 rounded-2xl md:rounded-3xl overflow-hidden bg-background/95 backdrop-blur-sm",
10631
- "shadow-sm hover:shadow-md transition-shadow duration-300",
10816
+ "rounded-2xl md:rounded-3xl overflow-hidden bg-card text-card-foreground backdrop-blur-sm",
10817
+ "border border-border shadow-sm md:hover:shadow-md",
10818
+ "transition-[transform,box-shadow,border-color,background-color] duration-300 ease-soft",
10632
10819
  densityClass,
10633
10820
  className
10634
10821
  ),
@@ -10761,12 +10948,19 @@ function CalendarTimeline({
10761
10948
  })();
10762
10949
  const resource = resourceById.get(ev.resourceId);
10763
10950
  const tooltipTitle = ev.title || ev.id;
10764
- const shouldCompact = activeView === "day" && dayEventStyle === "compact";
10951
+ const shouldCompact = activeView === "day" && dayEventStyle === "compact" || activeView === "month" && monthEventStyle === "compact";
10765
10952
  const eventInsetX = 2;
10766
10953
  const leftInset = left + eventInsetX;
10767
10954
  const widthInset = Math.max(1, width - eventInsetX * 2);
10768
- const defaultMaxVisual = clamp4(Math.round(fixedSlotWidth * 1.2), 160, 360);
10769
- const maxVisual = clamp4(Math.round(dayEventMaxWidth ?? defaultMaxVisual), 80, 1200);
10955
+ const defaultMaxVisual = (() => {
10956
+ if (activeView === "month") return clamp4(Math.round(fixedSlotWidth * 2.5), 72, 360);
10957
+ return clamp4(Math.round(fixedSlotWidth * 1.2), 160, 360);
10958
+ })();
10959
+ const maxVisual = clamp4(
10960
+ Math.round(activeView === "month" ? monthEventMaxWidth ?? defaultMaxVisual : dayEventMaxWidth ?? defaultMaxVisual),
10961
+ 48,
10962
+ 1200
10963
+ );
10770
10964
  const visualWidth = shouldCompact ? Math.min(widthInset, maxVisual) : widthInset;
10771
10965
  const isClipped = shouldCompact && widthInset > visualWidth + 1;
10772
10966
  const block = /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
@@ -10811,7 +11005,7 @@ function CalendarTimeline({
10811
11005
  "transition-all duration-150 ease-out",
10812
11006
  "backdrop-blur-sm",
10813
11007
  ev.className,
10814
- isPreview && "ring-2 ring-primary/50 ring-offset-1 ring-offset-background scale-[1.02]"
11008
+ isPreview && "ring-2 ring-primary/50 ring-offset-1 ring-offset-card scale-[1.02]"
10815
11009
  ),
10816
11010
  style: {
10817
11011
  width: visualWidth,
@@ -10823,7 +11017,7 @@ function CalendarTimeline({
10823
11017
  },
10824
11018
  children: [
10825
11019
  node,
10826
- isClipped ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "pointer-events-none absolute inset-y-0 right-0 w-10 bg-linear-to-l from-background/50 to-transparent flex items-center justify-end pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "text-xs text-muted-foreground/80", children: "\u2026" }) }) : null
11020
+ isClipped ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "pointer-events-none absolute inset-y-0 right-0 w-10 bg-linear-to-l from-card/50 to-transparent flex items-center justify-end pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "text-xs text-muted-foreground/80", children: "\u2026" }) }) : null
10827
11021
  ]
10828
11022
  }
10829
11023
  ),
@@ -10879,7 +11073,7 @@ function CalendarTimeline({
10879
11073
  className: cn(
10880
11074
  "pointer-events-none absolute z-20",
10881
11075
  "h-5 w-5 rounded-full",
10882
- "bg-background/80 backdrop-blur-sm",
11076
+ "bg-card/80 backdrop-blur-sm",
10883
11077
  "border border-border/60 shadow-xs",
10884
11078
  "flex items-center justify-center"
10885
11079
  ),