@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.js CHANGED
@@ -557,6 +557,8 @@ var defaultTranslations = {
557
557
  month: "Month",
558
558
  week: "Week",
559
559
  day: "Day",
560
+ sprint: "Sprint",
561
+ sprints: "{n} sprints",
560
562
  resourcesHeader: "Resources",
561
563
  expandGroup: "Expand group",
562
564
  collapseGroup: "Collapse group",
@@ -633,6 +635,8 @@ var defaultTranslations = {
633
635
  month: "Th\xE1ng",
634
636
  week: "Tu\u1EA7n",
635
637
  day: "Ng\xE0y",
638
+ sprint: "Sprint",
639
+ sprints: "{n} sprint",
636
640
  resourcesHeader: "T\xE0i nguy\xEAn",
637
641
  expandGroup: "M\u1EDF nh\xF3m",
638
642
  collapseGroup: "Thu g\u1ECDn nh\xF3m",
@@ -709,6 +713,8 @@ var defaultTranslations = {
709
713
  month: "\uC6D4",
710
714
  week: "\uC8FC",
711
715
  day: "\uC77C",
716
+ sprint: "\uC2A4\uD504\uB9B0\uD2B8",
717
+ sprints: "{n} \uC2A4\uD504\uB9B0\uD2B8",
712
718
  resourcesHeader: "\uB9AC\uC18C\uC2A4",
713
719
  expandGroup: "\uADF8\uB8F9 \uD3BC\uCE58\uAE30",
714
720
  collapseGroup: "\uADF8\uB8F9 \uC811\uAE30",
@@ -785,6 +791,8 @@ var defaultTranslations = {
785
791
  month: "\u6708",
786
792
  week: "\u9031",
787
793
  day: "\u65E5",
794
+ sprint: "\u30B9\u30D7\u30EA\u30F3\u30C8",
795
+ sprints: "{n} \u30B9\u30D7\u30EA\u30F3\u30C8",
788
796
  resourcesHeader: "\u30EA\u30BD\u30FC\u30B9",
789
797
  expandGroup: "\u30B0\u30EB\u30FC\u30D7\u3092\u5C55\u958B",
790
798
  collapseGroup: "\u30B0\u30EB\u30FC\u30D7\u3092\u6298\u308A\u305F\u305F\u3080",
@@ -8395,6 +8403,10 @@ function startOfZonedMonth(date, timeZone) {
8395
8403
  const p = getZonedParts(date, timeZone);
8396
8404
  return new Date(zonedTimeToUtcMs({ year: p.year, month: p.month, day: 1, hour: 0, minute: 0, second: 0 }, timeZone));
8397
8405
  }
8406
+ function startOfZonedYear(date, timeZone) {
8407
+ const p = getZonedParts(date, timeZone);
8408
+ return new Date(zonedTimeToUtcMs({ year: p.year, month: 1, day: 1, hour: 0, minute: 0, second: 0 }, timeZone));
8409
+ }
8398
8410
  function addZonedDays(date, days, timeZone) {
8399
8411
  const p = getZonedParts(date, timeZone);
8400
8412
  return new Date(zonedTimeToUtcMs({ ...p, day: p.day + days }, timeZone));
@@ -8407,6 +8419,9 @@ function addZonedMonths(date, months, timeZone) {
8407
8419
  const clampedDay = Math.min(p.day, daysInTargetMonth);
8408
8420
  return new Date(zonedTimeToUtcMs({ year: next.year, month: next.month, day: clampedDay, hour: p.hour, minute: p.minute, second: p.second }, timeZone));
8409
8421
  }
8422
+ function addZonedYears(date, years, timeZone) {
8423
+ return addZonedMonths(date, years * 12, timeZone);
8424
+ }
8410
8425
  function startOfZonedWeek(date, weekStartsOn, timeZone) {
8411
8426
  const p = getZonedParts(date, timeZone);
8412
8427
  const weekday = new Date(Date.UTC(p.year, p.month - 1, p.day)).getUTCDay();
@@ -8645,6 +8660,13 @@ function defaultSlotHeader(slotStart, view, locale, timeZone) {
8645
8660
  if (view === "day") {
8646
8661
  return getDtf(locale, timeZone, { hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(slotStart);
8647
8662
  }
8663
+ if (view === "sprint") {
8664
+ const { week } = getIsoWeekInfo(slotStart, timeZone);
8665
+ return /* @__PURE__ */ jsxs28("span", { className: "inline-flex flex-col items-center leading-tight", children: [
8666
+ /* @__PURE__ */ jsx33("span", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70", children: "S" }),
8667
+ /* @__PURE__ */ jsx33("span", { className: "text-sm font-semibold text-foreground", children: String(week).padStart(2, "0") })
8668
+ ] });
8669
+ }
8648
8670
  const weekday = getDtf(locale, timeZone, { weekday: "short" }).format(slotStart);
8649
8671
  const day = getDtf(locale, timeZone, { day: "numeric" }).format(slotStart);
8650
8672
  return /* @__PURE__ */ jsxs28("span", { className: "inline-flex flex-col items-center leading-tight", children: [
@@ -8705,7 +8727,10 @@ function getGroupResourceCounts(resources) {
8705
8727
  function computeSlotStarts(args) {
8706
8728
  const { view, date, timeZone, weekStartsOn, dayTimeStepMinutes, dayRangeMode, workHours } = args;
8707
8729
  const baseDayStart = startOfZonedDay(date, timeZone);
8708
- const start = view === "month" ? startOfZonedMonth(date, timeZone) : view === "week" ? startOfZonedWeek(date, weekStartsOn, timeZone) : baseDayStart;
8730
+ const start = view === "month" ? startOfZonedMonth(date, timeZone) : view === "week" ? startOfZonedWeek(date, weekStartsOn, timeZone) : view === "sprint" ? (() => {
8731
+ const yearStart = startOfZonedYear(date, timeZone);
8732
+ return startOfZonedWeek(yearStart, weekStartsOn, timeZone);
8733
+ })() : baseDayStart;
8709
8734
  if (view === "day") {
8710
8735
  const step = Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes)));
8711
8736
  const stepMs = step * 6e4;
@@ -8722,6 +8747,21 @@ function computeSlotStarts(args) {
8722
8747
  }
8723
8748
  return { start: start2, end: end3, slotStarts: slotStarts2 };
8724
8749
  }
8750
+ if (view === "sprint") {
8751
+ const yearStart = startOfZonedYear(date, timeZone);
8752
+ const nextYearStart = startOfZonedYear(addZonedMonths(yearStart, 12, timeZone), timeZone);
8753
+ const lastDayOfYear = addZonedDays(nextYearStart, -1, timeZone);
8754
+ const lastSlotStart = startOfZonedWeek(lastDayOfYear, weekStartsOn, timeZone);
8755
+ const end2 = addZonedDays(lastSlotStart, 7, timeZone);
8756
+ const slotStarts2 = [];
8757
+ let cur2 = start;
8758
+ let guard2 = 0;
8759
+ while (cur2.getTime() < end2.getTime() && guard2++ < 60) {
8760
+ slotStarts2.push(cur2);
8761
+ cur2 = addZonedDays(cur2, 7, timeZone);
8762
+ }
8763
+ return { start, end: end2, slotStarts: slotStarts2 };
8764
+ }
8725
8765
  const end = view === "month" ? startOfZonedMonth(addZonedMonths(start, 1, timeZone), timeZone) : addZonedDays(start, 7, timeZone);
8726
8766
  const slotStarts = [];
8727
8767
  let cur = start;
@@ -8770,7 +8810,8 @@ import { jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
8770
8810
  var VIEW_ICONS = {
8771
8811
  month: /* @__PURE__ */ jsx34(CalendarRange, { className: "h-4 w-4" }),
8772
8812
  week: /* @__PURE__ */ jsx34(CalendarDays, { className: "h-4 w-4" }),
8773
- day: /* @__PURE__ */ jsx34(Calendar3, { className: "h-4 w-4" })
8813
+ day: /* @__PURE__ */ jsx34(Calendar3, { className: "h-4 w-4" }),
8814
+ sprint: /* @__PURE__ */ jsx34(CalendarDays, { className: "h-4 w-4" })
8774
8815
  };
8775
8816
  function CalendarTimelineHeader(props) {
8776
8817
  const {
@@ -8795,7 +8836,7 @@ function CalendarTimelineHeader(props) {
8795
8836
  slotHeaderNodes
8796
8837
  } = props;
8797
8838
  const resolvedAvailableViews = React28.useMemo(
8798
- () => availableViews?.length ? availableViews : ["month", "week", "day"],
8839
+ () => availableViews?.length ? availableViews : ["month", "week", "day", "sprint"],
8799
8840
  [availableViews]
8800
8841
  );
8801
8842
  const showViewSwitcher = resolvedAvailableViews.length > 1;
@@ -8858,7 +8899,7 @@ function CalendarTimelineHeader(props) {
8858
8899
  onApplyDateTime(tempDate);
8859
8900
  setTodayOpen(false);
8860
8901
  }, [onApplyDateTime, tempDate]);
8861
- return /* @__PURE__ */ jsxs29("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: [
8902
+ return /* @__PURE__ */ jsxs29("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: [
8862
8903
  /* @__PURE__ */ jsxs29("div", { className: cn("flex items-center justify-between gap-4", sizeConfig.headerPaddingClass), children: [
8863
8904
  /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-1.5 min-w-0", children: [
8864
8905
  /* @__PURE__ */ jsxs29("div", { className: "flex items-center bg-muted/40 rounded-full p-1 gap-0.5", children: [
@@ -8869,7 +8910,7 @@ function CalendarTimelineHeader(props) {
8869
8910
  size: "icon",
8870
8911
  onClick: () => navigate(-1),
8871
8912
  "aria-label": labels.prev,
8872
- className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-background/80 transition-all duration-200"),
8913
+ className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-card/80 transition-all duration-200"),
8873
8914
  children: /* @__PURE__ */ jsx34(ChevronLeft4, { className: "h-4 w-4" })
8874
8915
  }
8875
8916
  ),
@@ -8884,7 +8925,7 @@ function CalendarTimelineHeader(props) {
8884
8925
  {
8885
8926
  variant: "ghost",
8886
8927
  size: "sm",
8887
- className: cn(sizeConfig.controlButtonTextClass, "rounded-full hover:bg-background/80 font-medium transition-all duration-200"),
8928
+ className: cn(sizeConfig.controlButtonTextClass, "rounded-full hover:bg-card/80 font-medium transition-all duration-200"),
8888
8929
  children: labels.today
8889
8930
  }
8890
8931
  ),
@@ -8964,7 +9005,7 @@ function CalendarTimelineHeader(props) {
8964
9005
  size: "icon",
8965
9006
  onClick: () => navigate(1),
8966
9007
  "aria-label": labels.next,
8967
- className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-background/80 transition-all duration-200"),
9008
+ className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-card/80 transition-all duration-200"),
8968
9009
  children: /* @__PURE__ */ jsx34(ChevronRight5, { className: "h-4 w-4" })
8969
9010
  }
8970
9011
  )
@@ -8993,7 +9034,7 @@ function CalendarTimelineHeader(props) {
8993
9034
  className: cn(
8994
9035
  sizeConfig.controlButtonTextClass,
8995
9036
  "rounded-full font-medium transition-all duration-200 gap-1.5",
8996
- activeView === v ? "bg-primary text-primary-foreground shadow-sm shadow-primary/25" : "hover:bg-background/80 text-muted-foreground hover:text-foreground"
9037
+ activeView === v ? "bg-primary text-primary-foreground shadow-sm shadow-primary/25" : "hover:bg-card/80 text-muted-foreground hover:text-foreground"
8997
9038
  ),
8998
9039
  children: [
8999
9040
  VIEW_ICONS[v],
@@ -9008,7 +9049,7 @@ function CalendarTimelineHeader(props) {
9008
9049
  showLeftColumn ? /* @__PURE__ */ jsxs29(
9009
9050
  "div",
9010
9051
  {
9011
- className: "shrink-0 border-r border-border/30 bg-muted/20 flex items-center justify-center relative group/uv-ct-top-left",
9052
+ className: "shrink-0 border-r border-border/30 bg-card/60 flex items-center justify-center relative group/uv-ct-top-left",
9012
9053
  style: { width: effectiveResourceColumnWidth, minWidth: effectiveResourceColumnWidth },
9013
9054
  children: [
9014
9055
  /* @__PURE__ */ jsx34("span", { className: "text-xs font-medium text-muted-foreground/70 uppercase tracking-wider", children: resourcesHeaderLabel }),
@@ -9063,7 +9104,7 @@ function DefaultGroupRow(props) {
9063
9104
  "span",
9064
9105
  {
9065
9106
  className: cn(
9066
- "inline-flex items-center justify-center w-5 h-5 rounded-md bg-background/60 transition-transform duration-200",
9107
+ "inline-flex items-center justify-center w-5 h-5 rounded-md bg-card/60 transition-transform duration-200",
9067
9108
  collapsed ? "" : "rotate-180"
9068
9109
  ),
9069
9110
  children: /* @__PURE__ */ jsx35(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
@@ -9084,7 +9125,7 @@ function ResourceRowCell(props) {
9084
9125
  "div",
9085
9126
  {
9086
9127
  className: cn(
9087
- "h-full w-full flex items-center border-b border-border/30 bg-linear-to-r from-background to-background/95 relative",
9128
+ "h-full w-full flex items-center border-b border-border/30 bg-linear-to-r from-card to-card/95 relative",
9088
9129
  sizeConfig.resourceRowClass,
9089
9130
  "hover:from-muted/30 hover:to-muted/10 transition-all duration-200 group/uv-ct-row-header"
9090
9131
  ),
@@ -9144,7 +9185,7 @@ var CalendarTimelineGridOverlay = React29.memo(function CalendarTimelineGridOver
9144
9185
  if (showToday && idx === todaySlotIdx) return null;
9145
9186
  const left = slotLefts[idx] ?? 0;
9146
9187
  const width = slotWidths[idx] ?? 0;
9147
- return /* @__PURE__ */ jsx36("div", { className: "absolute top-0 h-full bg-destructive/5", style: { left, width }, "aria-hidden": true }, `we_${idx}`);
9188
+ return /* @__PURE__ */ jsx36("div", { className: "absolute top-0 h-full bg-muted/20", style: { left, width }, "aria-hidden": true }, `we_${idx}`);
9148
9189
  }) : null,
9149
9190
  showToday ? /* @__PURE__ */ jsx36(
9150
9191
  "div",
@@ -9207,7 +9248,8 @@ function useTimelineSlots(args) {
9207
9248
  dayRangeMode,
9208
9249
  workHours,
9209
9250
  resolvedNow,
9210
- formatters
9251
+ formatters,
9252
+ dueDateSprint
9211
9253
  } = args;
9212
9254
  const { slots, range } = React31.useMemo(() => {
9213
9255
  const { start, end, slotStarts: slotStarts2 } = computeSlotStarts({
@@ -9220,17 +9262,89 @@ function useTimelineSlots(args) {
9220
9262
  workHours
9221
9263
  });
9222
9264
  const todayStart = startOfZonedDay(resolvedNow, resolvedTimeZone).getTime();
9223
- const slotItems = slotStarts2.map((s) => ({
9265
+ const nowMs = resolvedNow.getTime();
9266
+ const sprintDefs = (() => {
9267
+ if (activeView !== "sprint") return [];
9268
+ if (!dueDateSprint) return [];
9269
+ const out = [];
9270
+ for (const v of Object.values(dueDateSprint)) {
9271
+ const startInput = v.range_date?.start;
9272
+ const endInput = v.range_date?.end;
9273
+ if (startInput == null || endInput == null) continue;
9274
+ const startRaw = toDate2(startInput);
9275
+ const endRaw = toDate2(endInput);
9276
+ if (Number.isNaN(startRaw.getTime()) || Number.isNaN(endRaw.getTime())) continue;
9277
+ const startMs = startOfZonedDay(startRaw, resolvedTimeZone).getTime();
9278
+ const endMs = startOfZonedDay(endRaw, resolvedTimeZone).getTime();
9279
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) continue;
9280
+ if (endMs <= startMs) continue;
9281
+ out.push({ startMs, endMs, title: v.title });
9282
+ }
9283
+ out.sort((a, b) => a.startMs - b.startMs);
9284
+ return out;
9285
+ })();
9286
+ const sprintRangeText = (() => {
9287
+ if (activeView !== "sprint") return null;
9288
+ const df = getDtf(resolvedLocale, resolvedTimeZone, { month: "short", day: "numeric" });
9289
+ return (startMs, endMs) => {
9290
+ const a = df.format(new Date(startMs));
9291
+ const b = df.format(new Date(endMs - 1));
9292
+ return a === b ? a : `${a}\u2013${b}`;
9293
+ };
9294
+ })();
9295
+ const matchSprintDef = (slotStart, idx) => {
9296
+ if (activeView !== "sprint") return null;
9297
+ if (sprintDefs.length === 0) return null;
9298
+ const sMs = startOfZonedDay(slotStart, resolvedTimeZone).getTime();
9299
+ const byRange = sprintDefs.find((d) => sMs >= d.startMs && sMs < d.endMs) ?? null;
9300
+ return byRange ?? sprintDefs[idx] ?? null;
9301
+ };
9302
+ const slotItems = slotStarts2.map((s, idx) => ({
9224
9303
  start: s,
9225
- label: formatters?.slotHeader?.(s, { view: activeView, locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? defaultSlotHeader(s, activeView, resolvedLocale, resolvedTimeZone),
9226
- isToday: startOfZonedDay(s, resolvedTimeZone).getTime() === todayStart,
9227
- isWeekend: activeView === "day" ? false : (() => {
9304
+ label: formatters?.slotHeader?.(s, { view: activeView, locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? (() => {
9305
+ if (activeView !== "sprint") return defaultSlotHeader(s, activeView, resolvedLocale, resolvedTimeZone);
9306
+ const match = matchSprintDef(s, idx);
9307
+ if (match && sprintRangeText) {
9308
+ const rangeText = sprintRangeText(match.startMs, match.endMs);
9309
+ return React31.createElement(
9310
+ "span",
9311
+ { className: "inline-flex flex-col items-center leading-tight" },
9312
+ React31.createElement("span", { className: "text-[11px] font-semibold text-foreground truncate max-w-[8rem]" }, match.title),
9313
+ React31.createElement("span", { className: "text-[10px] font-medium text-muted-foreground/70" }, rangeText)
9314
+ );
9315
+ }
9316
+ return React31.createElement(
9317
+ "span",
9318
+ { className: "inline-flex flex-col items-center leading-tight" },
9319
+ React31.createElement("span", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70" }, "S"),
9320
+ React31.createElement("span", { className: "text-sm font-semibold text-foreground" }, String(idx + 1).padStart(2, "0"))
9321
+ );
9322
+ })(),
9323
+ isToday: (() => {
9324
+ if (activeView !== "sprint") return startOfZonedDay(s, resolvedTimeZone).getTime() === todayStart;
9325
+ const match = matchSprintDef(s, idx);
9326
+ const sprintEndMs = match ? match.endMs : addZonedDays(s, 7, resolvedTimeZone).getTime();
9327
+ return nowMs >= s.getTime() && nowMs < sprintEndMs;
9328
+ })(),
9329
+ isWeekend: activeView === "day" || activeView === "sprint" ? false : (() => {
9228
9330
  const wd = getZonedWeekday(s, resolvedTimeZone);
9229
9331
  return wd === 0 || wd === 6;
9230
9332
  })()
9231
9333
  }));
9232
9334
  return { slots: slotItems, range: { start, end } };
9233
- }, [activeView, activeDate, dayRangeMode, dayTimeStepMinutes, formatters, resolvedLocale, resolvedNow, resolvedTimeZone, weekStartsOn, workHours]);
9335
+ }, [
9336
+ activeView,
9337
+ activeDate,
9338
+ dayRangeMode,
9339
+ dayTimeStepMinutes,
9340
+ dueDateSprint,
9341
+ formatters,
9342
+ resolvedLocale,
9343
+ resolvedNow,
9344
+ resolvedTimeZone,
9345
+ weekStartsOn,
9346
+ workHours
9347
+ ]);
9234
9348
  const slotStarts = React31.useMemo(() => slots.map((s) => s.start), [slots]);
9235
9349
  const todaySlotIdx = React31.useMemo(() => slots.findIndex((s) => s.isToday), [slots]);
9236
9350
  const weekendSlotIdxs = React31.useMemo(() => {
@@ -9521,6 +9635,7 @@ function CalendarTimeline({
9521
9635
  view,
9522
9636
  defaultView = "month",
9523
9637
  onViewChange,
9638
+ dueDateSprint,
9524
9639
  date,
9525
9640
  defaultDate,
9526
9641
  onDateChange,
@@ -9553,6 +9668,8 @@ function CalendarTimeline({
9553
9668
  adaptiveSlotWidths,
9554
9669
  dayEventStyle = "span",
9555
9670
  dayEventMaxWidth,
9671
+ monthEventStyle = "span",
9672
+ monthEventMaxWidth,
9556
9673
  dayTimeStepMinutes = 60,
9557
9674
  enableEventTooltips = true,
9558
9675
  dayHeaderMode = "full",
@@ -9647,18 +9764,29 @@ function CalendarTimeline({
9647
9764
  setInternalRowHeight(defaultRowHeight);
9648
9765
  }, [defaultRowHeight, isControlledRowHeight]);
9649
9766
  const effectiveRowHeight = isControlledRowHeight ? rowHeight : internalRowHeight;
9650
- const effectiveSlotMinWidth = slotMinWidth ?? sizeConfig.slotMinWidth;
9767
+ const baseSlotMinWidth = slotMinWidth ?? sizeConfig.slotMinWidth;
9651
9768
  const colMin = minResourceColumnWidth ?? 160;
9652
9769
  const colMax = maxResourceColumnWidth ?? 520;
9653
9770
  const rowMin = minRowHeight ?? 36;
9654
9771
  const rowMax = maxRowHeight ?? 120;
9655
9772
  const availableViews = React32.useMemo(
9656
- () => onlyView ? [onlyView] : ["month", "week", "day"],
9773
+ () => onlyView ? [onlyView] : ["month", "week", "day", "sprint"],
9657
9774
  [onlyView]
9658
9775
  );
9659
9776
  const isControlledView = view !== void 0;
9660
9777
  const [internalView, setInternalView] = React32.useState(() => onlyView ?? defaultView);
9661
9778
  const activeView = onlyView ? onlyView : isControlledView ? view : internalView;
9779
+ const effectiveSlotMinWidth = React32.useMemo(() => {
9780
+ if (slotMinWidth == null) {
9781
+ if (activeView === "month" && monthEventStyle === "compact") {
9782
+ return clamp4(Math.round(sizeConfig.slotMinWidth * 0.55), 32, sizeConfig.slotMinWidth);
9783
+ }
9784
+ if (activeView === "sprint") {
9785
+ return clamp4(Math.round(sizeConfig.slotMinWidth * 0.4), 18, 48);
9786
+ }
9787
+ }
9788
+ return baseSlotMinWidth;
9789
+ }, [activeView, baseSlotMinWidth, monthEventStyle, sizeConfig.slotMinWidth, slotMinWidth]);
9662
9790
  const isControlledDate = date !== void 0;
9663
9791
  const [internalDate, setInternalDate] = React32.useState(() => defaultDate ?? /* @__PURE__ */ new Date());
9664
9792
  const activeDate = isControlledDate ? date : internalDate;
@@ -9671,6 +9799,7 @@ function CalendarTimeline({
9671
9799
  month: labels?.month ?? t("month"),
9672
9800
  week: labels?.week ?? t("week"),
9673
9801
  day: labels?.day ?? t("day"),
9802
+ sprint: labels?.sprint ?? t("sprint"),
9674
9803
  newEvent: labels?.newEvent ?? t("newEvent"),
9675
9804
  createEventTitle: labels?.createEventTitle ?? t("createEventTitle"),
9676
9805
  create: labels?.create ?? t("create"),
@@ -9711,6 +9840,10 @@ function CalendarTimeline({
9711
9840
  setDate(addZonedDays(base, dir * 7, resolvedTimeZone));
9712
9841
  return;
9713
9842
  }
9843
+ if (activeView === "sprint") {
9844
+ setDate(addZonedYears(base, dir, resolvedTimeZone));
9845
+ return;
9846
+ }
9714
9847
  setDate(addZonedDays(base, dir, resolvedTimeZone));
9715
9848
  },
9716
9849
  [activeDate, activeView, resolvedTimeZone, setDate]
@@ -9736,7 +9869,8 @@ function CalendarTimeline({
9736
9869
  dayRangeMode,
9737
9870
  workHours,
9738
9871
  resolvedNow,
9739
- formatters
9872
+ formatters,
9873
+ dueDateSprint
9740
9874
  });
9741
9875
  React32.useEffect(() => {
9742
9876
  onRangeChange?.(range);
@@ -9982,9 +10116,15 @@ function CalendarTimeline({
9982
10116
  const rangeText = ya === yb ? `${a} \u2013 ${b}, ${ya}` : `${a}, ${ya} \u2013 ${b}, ${yb}`;
9983
10117
  return `${l.week} ${week} \u2022 ${rangeText}`;
9984
10118
  }
10119
+ if (activeView === "sprint") {
10120
+ const fmtYear = getDtf(resolvedLocale, resolvedTimeZone, { year: "numeric" });
10121
+ const yearText = fmtYear.format(activeDate);
10122
+ const count = Math.max(0, slots.length);
10123
+ return `${l.sprint} \u2022 ${yearText} \u2022 ${t("sprints", { n: count })}`;
10124
+ }
9985
10125
  const fmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
9986
10126
  return fmt.format(range.start);
9987
- }, [activeDate, activeView, formatters, l.week, range.end, range.start, resolvedLocale, resolvedTimeZone]);
10127
+ }, [activeDate, activeView, formatters, l.sprint, l.week, range.end, range.start, resolvedLocale, resolvedTimeZone, slots.length, t]);
9988
10128
  const createMode = interactions?.createMode ?? "drag";
9989
10129
  const canCreate = !isViewOnly && (interactions?.creatable ?? false) && !!onCreateEvent;
9990
10130
  const [createOpen, setCreateOpen] = React32.useState(false);
@@ -9999,11 +10139,54 @@ function CalendarTimeline({
9999
10139
  disabled: r.disabled ?? false
10000
10140
  }));
10001
10141
  }, [resources]);
10002
- const slotPickerLabel = React32.useMemo(() => {
10142
+ const formatCreateBoundaryLabel = React32.useMemo(() => {
10003
10143
  const timeFmt = getDtf(resolvedLocale, resolvedTimeZone, { hour: "2-digit", minute: "2-digit", hourCycle: "h23" });
10004
10144
  const dayFmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "short", month: "short", day: "numeric" });
10005
- return (d) => activeView === "day" ? timeFmt.format(d) : dayFmt.format(d);
10006
- }, [activeView, resolvedLocale, resolvedTimeZone]);
10145
+ const yearFmt = getDtf(resolvedLocale, resolvedTimeZone, { year: "numeric" });
10146
+ const sprintDefs = (() => {
10147
+ if (!dueDateSprint) return [];
10148
+ const out = [];
10149
+ for (const v of Object.values(dueDateSprint)) {
10150
+ const startInput = v.range_date?.start;
10151
+ const endInput = v.range_date?.end;
10152
+ if (startInput == null || endInput == null) continue;
10153
+ const startRaw = toDate2(startInput);
10154
+ const endRaw = toDate2(endInput);
10155
+ if (Number.isNaN(startRaw.getTime()) || Number.isNaN(endRaw.getTime())) continue;
10156
+ const startMs = startOfZonedDay(startRaw, resolvedTimeZone).getTime();
10157
+ const endMs = startOfZonedDay(endRaw, resolvedTimeZone).getTime();
10158
+ if (endMs <= startMs) continue;
10159
+ out.push({ startMs, endMs, title: v.title });
10160
+ }
10161
+ out.sort((a, b) => a.startMs - b.startMs);
10162
+ return out;
10163
+ })();
10164
+ const sprintTitleForStart = (slotStart, idx) => {
10165
+ if (activeView !== "sprint") return null;
10166
+ if (sprintDefs.length === 0) return null;
10167
+ const sMs = startOfZonedDay(slotStart, resolvedTimeZone).getTime();
10168
+ const match = sprintDefs.find((d) => sMs >= d.startMs && sMs < d.endMs) ?? sprintDefs[idx] ?? null;
10169
+ if (!match) return null;
10170
+ return typeof match.title === "string" || typeof match.title === "number" ? String(match.title) : null;
10171
+ };
10172
+ return (d, opts) => {
10173
+ if (activeView === "day") return timeFmt.format(d);
10174
+ if (activeView === "sprint") {
10175
+ const y = yearFmt.format(d);
10176
+ const idx = opts?.boundaryIdx;
10177
+ if (typeof idx === "number") {
10178
+ const slotStart = slotStarts[clamp4(idx, 0, Math.max(0, slotStarts.length - 1))] ?? d;
10179
+ const dynamicTitle = sprintTitleForStart(slotStart, idx);
10180
+ if (dynamicTitle) return `${dynamicTitle} \u2022 ${dayFmt.format(d)}, ${y}`;
10181
+ const sprintNumber = opts?.kind === "start" ? idx + 1 : Math.min(idx + 1, Math.max(1, slotStarts.length));
10182
+ const sprintText = `${l.sprint} ${String(sprintNumber).padStart(2, "0")}`;
10183
+ const dateText = dayFmt.format(d);
10184
+ return `${sprintText} \u2022 ${dateText}, ${y}`;
10185
+ }
10186
+ }
10187
+ return dayFmt.format(d);
10188
+ };
10189
+ }, [activeView, dueDateSprint, l.sprint, resolvedLocale, resolvedTimeZone, slotStarts, slotStarts.length]);
10007
10190
  const openCreate = React32.useCallback(() => {
10008
10191
  if (!canCreate) return;
10009
10192
  if (activeEventSheetOpen) setEventSheetOpen(false);
@@ -10040,16 +10223,16 @@ function CalendarTimeline({
10040
10223
  setCreateEndIdx((prev) => Math.min(slots.length, Math.max(prev, createStartIdx + 1)));
10041
10224
  }, [createStartIdx, slots.length]);
10042
10225
  const createStartOptions = React32.useMemo(() => {
10043
- return slotStarts.map((d, idx) => ({ label: slotPickerLabel(d), value: idx }));
10044
- }, [slotStarts, slotPickerLabel]);
10226
+ return slotStarts.map((d, idx) => ({ label: formatCreateBoundaryLabel(d, { kind: "start", boundaryIdx: idx }), value: idx }));
10227
+ }, [formatCreateBoundaryLabel, slotStarts]);
10045
10228
  const createEndOptions = React32.useMemo(() => {
10046
10229
  const out = [];
10047
10230
  for (let idx = createStartIdx + 1; idx <= slotStarts.length; idx++) {
10048
10231
  const boundary = idx >= slotStarts.length ? range.end : slotStarts[idx];
10049
- out.push({ label: slotPickerLabel(boundary), value: idx });
10232
+ out.push({ label: formatCreateBoundaryLabel(boundary, { kind: "end", boundaryIdx: idx }), value: idx });
10050
10233
  }
10051
10234
  return out;
10052
- }, [createStartIdx, range.end, slotPickerLabel, slotStarts]);
10235
+ }, [createStartIdx, formatCreateBoundaryLabel, range.end, slotStarts]);
10053
10236
  const commitCreate = React32.useCallback(() => {
10054
10237
  if (!onCreateEvent) return;
10055
10238
  if (!createResourceId) return;
@@ -10103,6 +10286,9 @@ function CalendarTimeline({
10103
10286
  const stepMs = Math.trunc(Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes))) * 6e4);
10104
10287
  return { start, end: new Date(start.getTime() + stepMs) };
10105
10288
  }
10289
+ if (activeView === "sprint") {
10290
+ return { start, end: addZonedDays(start, 7, resolvedTimeZone) };
10291
+ }
10106
10292
  return { start, end: addZonedDays(start, 1, resolvedTimeZone) };
10107
10293
  },
10108
10294
  [activeView, dayTimeStepMinutes, resolvedTimeZone, slotStarts]
@@ -10385,7 +10571,7 @@ function CalendarTimeline({
10385
10571
  className: cn(
10386
10572
  sizeConfig.slotHeaderClass,
10387
10573
  activeView !== "day" && s.isToday && "bg-primary/8 border-l-primary/40",
10388
- activeView !== "day" && !s.isToday && s.isWeekend && "bg-destructive/8 text-destructive-foreground"
10574
+ activeView !== "day" && !s.isToday && s.isWeekend && "bg-muted/25"
10389
10575
  ),
10390
10576
  dayHeaderMarks
10391
10577
  },
@@ -10416,7 +10602,7 @@ function CalendarTimeline({
10416
10602
  {
10417
10603
  title,
10418
10604
  resourcesHeaderLabel: t("resourcesHeader"),
10419
- labels: { today: l.today, prev: l.prev, next: l.next, month: l.month, week: l.week, day: l.day },
10605
+ labels: { today: l.today, prev: l.prev, next: l.next, month: l.month, week: l.week, day: l.day, sprint: l.sprint },
10420
10606
  newEventLabel: l.newEvent,
10421
10607
  newEventDisabled: isViewOnly || !canCreate || resources.length === 0,
10422
10608
  onNewEventClick: isViewOnly ? void 0 : openCreate,
@@ -10451,8 +10637,9 @@ function CalendarTimeline({
10451
10637
  "div",
10452
10638
  {
10453
10639
  className: cn(
10454
- "border border-border/40 rounded-2xl md:rounded-3xl overflow-hidden bg-background/95 backdrop-blur-sm",
10455
- "shadow-sm hover:shadow-md transition-shadow duration-300",
10640
+ "rounded-2xl md:rounded-3xl overflow-hidden bg-card text-card-foreground backdrop-blur-sm",
10641
+ "border border-border shadow-sm md:hover:shadow-md",
10642
+ "transition-[transform,box-shadow,border-color,background-color] duration-300 ease-soft",
10456
10643
  densityClass,
10457
10644
  className
10458
10645
  ),
@@ -10585,12 +10772,19 @@ function CalendarTimeline({
10585
10772
  })();
10586
10773
  const resource = resourceById.get(ev.resourceId);
10587
10774
  const tooltipTitle = ev.title || ev.id;
10588
- const shouldCompact = activeView === "day" && dayEventStyle === "compact";
10775
+ const shouldCompact = activeView === "day" && dayEventStyle === "compact" || activeView === "month" && monthEventStyle === "compact";
10589
10776
  const eventInsetX = 2;
10590
10777
  const leftInset = left + eventInsetX;
10591
10778
  const widthInset = Math.max(1, width - eventInsetX * 2);
10592
- const defaultMaxVisual = clamp4(Math.round(fixedSlotWidth * 1.2), 160, 360);
10593
- const maxVisual = clamp4(Math.round(dayEventMaxWidth ?? defaultMaxVisual), 80, 1200);
10779
+ const defaultMaxVisual = (() => {
10780
+ if (activeView === "month") return clamp4(Math.round(fixedSlotWidth * 2.5), 72, 360);
10781
+ return clamp4(Math.round(fixedSlotWidth * 1.2), 160, 360);
10782
+ })();
10783
+ const maxVisual = clamp4(
10784
+ Math.round(activeView === "month" ? monthEventMaxWidth ?? defaultMaxVisual : dayEventMaxWidth ?? defaultMaxVisual),
10785
+ 48,
10786
+ 1200
10787
+ );
10594
10788
  const visualWidth = shouldCompact ? Math.min(widthInset, maxVisual) : widthInset;
10595
10789
  const isClipped = shouldCompact && widthInset > visualWidth + 1;
10596
10790
  const block = /* @__PURE__ */ jsxs33(
@@ -10635,7 +10829,7 @@ function CalendarTimeline({
10635
10829
  "transition-all duration-150 ease-out",
10636
10830
  "backdrop-blur-sm",
10637
10831
  ev.className,
10638
- isPreview && "ring-2 ring-primary/50 ring-offset-1 ring-offset-background scale-[1.02]"
10832
+ isPreview && "ring-2 ring-primary/50 ring-offset-1 ring-offset-card scale-[1.02]"
10639
10833
  ),
10640
10834
  style: {
10641
10835
  width: visualWidth,
@@ -10647,7 +10841,7 @@ function CalendarTimeline({
10647
10841
  },
10648
10842
  children: [
10649
10843
  node,
10650
- isClipped ? /* @__PURE__ */ jsx38("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__ */ jsx38("span", { className: "text-xs text-muted-foreground/80", children: "\u2026" }) }) : null
10844
+ isClipped ? /* @__PURE__ */ jsx38("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__ */ jsx38("span", { className: "text-xs text-muted-foreground/80", children: "\u2026" }) }) : null
10651
10845
  ]
10652
10846
  }
10653
10847
  ),
@@ -10703,7 +10897,7 @@ function CalendarTimeline({
10703
10897
  className: cn(
10704
10898
  "pointer-events-none absolute z-20",
10705
10899
  "h-5 w-5 rounded-full",
10706
- "bg-background/80 backdrop-blur-sm",
10900
+ "bg-card/80 backdrop-blur-sm",
10707
10901
  "border border-border/60 shadow-xs",
10708
10902
  "flex items-center justify-center"
10709
10903
  ),