@underverse-ui/underverse 0.2.95 → 0.2.96

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
@@ -8492,6 +8492,9 @@ function CalendarTimelineHeader(props) {
8492
8492
  title,
8493
8493
  resourcesHeaderLabel,
8494
8494
  labels,
8495
+ newEventLabel,
8496
+ newEventDisabled,
8497
+ onNewEventClick,
8495
8498
  activeView,
8496
8499
  sizeConfig,
8497
8500
  navigate,
@@ -8542,24 +8545,38 @@ function CalendarTimelineHeader(props) {
8542
8545
  ] }),
8543
8546
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("h2", { className: cn("ml-3 font-semibold tracking-tight truncate text-foreground", sizeConfig.titleClass), children: title })
8544
8547
  ] }),
8545
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex items-center bg-muted/40 rounded-xl p-1 gap-0.5", children: ["month", "week", "day"].map((v) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
8546
- Button_default,
8547
- {
8548
- variant: activeView === v ? "default" : "ghost",
8549
- size: "sm",
8550
- onClick: () => setView(v),
8551
- className: cn(
8552
- sizeConfig.controlButtonTextClass,
8553
- "rounded-lg font-medium transition-all duration-200 gap-1.5",
8554
- activeView === v ? "bg-primary text-primary-foreground shadow-sm shadow-primary/25" : "hover:bg-background/80 text-muted-foreground hover:text-foreground"
8555
- ),
8556
- children: [
8557
- VIEW_ICONS[v],
8558
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "hidden sm:inline", children: labels[v] })
8559
- ]
8560
- },
8561
- v
8562
- )) })
8548
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
8549
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
8550
+ Button_default,
8551
+ {
8552
+ variant: "default",
8553
+ size: "sm",
8554
+ icon: import_lucide_react18.Plus,
8555
+ disabled: newEventDisabled || !onNewEventClick,
8556
+ onClick: onNewEventClick,
8557
+ className: cn(sizeConfig.controlButtonTextClass, "rounded-lg font-medium transition-all duration-200 gap-1.5"),
8558
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "hidden sm:inline", children: newEventLabel })
8559
+ }
8560
+ ),
8561
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex items-center bg-muted/40 rounded-xl p-1 gap-0.5", children: ["month", "week", "day"].map((v) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
8562
+ Button_default,
8563
+ {
8564
+ variant: activeView === v ? "default" : "ghost",
8565
+ size: "sm",
8566
+ onClick: () => setView(v),
8567
+ className: cn(
8568
+ sizeConfig.controlButtonTextClass,
8569
+ "rounded-lg font-medium transition-all duration-200 gap-1.5",
8570
+ activeView === v ? "bg-primary text-primary-foreground shadow-sm shadow-primary/25" : "hover:bg-background/80 text-muted-foreground hover:text-foreground"
8571
+ ),
8572
+ children: [
8573
+ VIEW_ICONS[v],
8574
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "hidden sm:inline", children: labels[v] })
8575
+ ]
8576
+ },
8577
+ v
8578
+ )) })
8579
+ ] })
8563
8580
  ] }),
8564
8581
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex border-t border-border/20", children: [
8565
8582
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
@@ -8728,6 +8745,7 @@ function CalendarTimeline({
8728
8745
  onRangeChange,
8729
8746
  onEventClick,
8730
8747
  onEventDoubleClick,
8748
+ onCreateEventClick,
8731
8749
  onCreateEvent,
8732
8750
  onEventMove,
8733
8751
  onEventResize,
@@ -8815,6 +8833,13 @@ function CalendarTimeline({
8815
8833
  month: labels?.month ?? t("month"),
8816
8834
  week: labels?.week ?? t("week"),
8817
8835
  day: labels?.day ?? t("day"),
8836
+ newEvent: labels?.newEvent ?? t("newEvent"),
8837
+ createEventTitle: labels?.createEventTitle ?? t("createEventTitle"),
8838
+ create: labels?.create ?? t("create"),
8839
+ cancel: labels?.cancel ?? t("cancel"),
8840
+ resource: labels?.resource ?? t("resource"),
8841
+ start: labels?.start ?? t("start"),
8842
+ end: labels?.end ?? t("end"),
8818
8843
  expandGroup: labels?.expandGroup ?? t("expandGroup"),
8819
8844
  collapseGroup: labels?.collapseGroup ?? t("collapseGroup"),
8820
8845
  more: labels?.more ?? ((n) => t("more", { n })),
@@ -9082,6 +9107,69 @@ function CalendarTimeline({
9082
9107
  const eventHeight = sizeConfig.eventHeight;
9083
9108
  const laneGap = sizeConfig.laneGap;
9084
9109
  const lanePaddingY = sizeConfig.lanePaddingY;
9110
+ const createMode = interactions?.createMode ?? "drag";
9111
+ const canCreate = (interactions?.creatable ?? false) && !!onCreateEvent;
9112
+ const [createOpen, setCreateOpen] = React28.useState(false);
9113
+ const [createResourceId, setCreateResourceId] = React28.useState(null);
9114
+ const [createStartIdx, setCreateStartIdx] = React28.useState(0);
9115
+ const [createEndIdx, setCreateEndIdx] = React28.useState(1);
9116
+ const resourceOptions = React28.useMemo(() => {
9117
+ return resources.map((r) => ({
9118
+ label: typeof r.label === "string" ? r.label : r.id,
9119
+ value: r.id,
9120
+ description: r.groupId ? String(r.groupId) : void 0,
9121
+ disabled: r.disabled ?? false
9122
+ }));
9123
+ }, [resources]);
9124
+ const slotPickerLabel = React28.useMemo(() => {
9125
+ const timeFmt = getDtf(resolvedLocale, resolvedTimeZone, { hour: "2-digit", minute: "2-digit", hourCycle: "h23" });
9126
+ const dayFmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "short", month: "short", day: "numeric" });
9127
+ return (d) => activeView === "day" ? timeFmt.format(d) : dayFmt.format(d);
9128
+ }, [activeView, resolvedLocale, resolvedTimeZone]);
9129
+ const openCreate = React28.useCallback(() => {
9130
+ if (!canCreate) return;
9131
+ if (activeEventSheetOpen) setEventSheetOpen(false);
9132
+ const firstResource = resources.find((r) => !r.disabled)?.id ?? resources[0]?.id ?? null;
9133
+ setCreateResourceId(firstResource ?? null);
9134
+ let startIdx = 0;
9135
+ if (slots.length > 0) {
9136
+ if (activeView === "day") {
9137
+ const inRange = resolvedNow.getTime() >= range.start.getTime() && resolvedNow.getTime() < range.end.getTime();
9138
+ startIdx = clamp3(inRange ? binarySearchFirstGE(slotStarts, resolvedNow) : 0, 0, slots.length - 1);
9139
+ } else {
9140
+ const dayStart = startOfZonedDay(activeDate, resolvedTimeZone);
9141
+ startIdx = clamp3(binarySearchLastLE(slotStarts, dayStart), 0, slots.length - 1);
9142
+ }
9143
+ }
9144
+ setCreateStartIdx(startIdx);
9145
+ setCreateEndIdx(Math.min(slots.length, startIdx + 1));
9146
+ setCreateOpen(true);
9147
+ }, [activeDate, activeEventSheetOpen, activeView, canCreate, range.end, range.start, resolvedNow, resolvedTimeZone, resources, setEventSheetOpen, slotStarts, slots.length]);
9148
+ React28.useEffect(() => {
9149
+ setCreateEndIdx((prev) => Math.min(slots.length, Math.max(prev, createStartIdx + 1)));
9150
+ }, [createStartIdx, slots.length]);
9151
+ const createStartOptions = React28.useMemo(() => {
9152
+ return slotStarts.map((d, idx) => ({ label: slotPickerLabel(d), value: idx }));
9153
+ }, [slotStarts, slotPickerLabel]);
9154
+ const createEndOptions = React28.useMemo(() => {
9155
+ const out = [];
9156
+ for (let idx = createStartIdx + 1; idx <= slotStarts.length; idx++) {
9157
+ const boundary = idx >= slotStarts.length ? range.end : slotStarts[idx];
9158
+ out.push({ label: slotPickerLabel(boundary), value: idx });
9159
+ }
9160
+ return out;
9161
+ }, [createStartIdx, range.end, slotPickerLabel, slotStarts]);
9162
+ const commitCreate = React28.useCallback(() => {
9163
+ if (!onCreateEvent) return;
9164
+ if (!createResourceId) return;
9165
+ const start = slotStarts[clamp3(createStartIdx, 0, Math.max(0, slotStarts.length - 1))];
9166
+ if (!start) return;
9167
+ const endBoundary = createEndIdx >= slotStarts.length ? range.end : slotStarts[createEndIdx];
9168
+ if (!endBoundary) return;
9169
+ if (endBoundary.getTime() <= start.getTime()) return;
9170
+ onCreateEvent({ resourceId: createResourceId, start, end: endBoundary });
9171
+ setCreateOpen(false);
9172
+ }, [createEndIdx, createResourceId, createStartIdx, onCreateEvent, range.end, slotStarts]);
9085
9173
  const dragRef = React28.useRef(null);
9086
9174
  const [preview, setPreview] = React28.useState(null);
9087
9175
  const suppressNextEventClickRef = React28.useRef(false);
@@ -9142,6 +9230,7 @@ function CalendarTimeline({
9142
9230
  const onPointerDownCell = (e) => {
9143
9231
  if (e.button !== 0 || e.ctrlKey) return;
9144
9232
  if (!(interactions?.creatable ?? false) || !onCreateEvent) return;
9233
+ if (createMode === "click") return;
9145
9234
  const ctx = getPointerContext(e.clientX, e.clientY, { biasLeft: true });
9146
9235
  if (!ctx?.resourceId) return;
9147
9236
  const { start } = slotToDate(ctx.slotIdx);
@@ -9162,6 +9251,24 @@ function CalendarTimeline({
9162
9251
  e.currentTarget.setPointerCapture(e.pointerId);
9163
9252
  e.preventDefault();
9164
9253
  };
9254
+ const onClickCell = (e) => {
9255
+ if (e.button !== 0 || e.ctrlKey) return;
9256
+ if (!(interactions?.creatable ?? false)) return;
9257
+ if (createMode !== "click") return;
9258
+ if (!onCreateEventClick) return;
9259
+ const ctx = getPointerContext(e.clientX, e.clientY, { biasLeft: true });
9260
+ if (!ctx?.resourceId) return;
9261
+ const { start, end } = slotToDate(ctx.slotIdx);
9262
+ onCreateEventClick({
9263
+ resourceId: ctx.resourceId,
9264
+ start,
9265
+ end: ctx.slotIdx + 1 >= slots.length ? range.end : end,
9266
+ slotIdx: ctx.slotIdx,
9267
+ view: activeView,
9268
+ locale: resolvedLocale,
9269
+ timeZone: resolvedTimeZone
9270
+ });
9271
+ };
9165
9272
  const onPointerMove = (e) => {
9166
9273
  const drag = dragRef.current;
9167
9274
  if (!drag || drag.pointerId !== e.pointerId) return;
@@ -9276,6 +9383,9 @@ function CalendarTimeline({
9276
9383
  title,
9277
9384
  resourcesHeaderLabel: t("resourcesHeader"),
9278
9385
  labels: { today: l.today, prev: l.prev, next: l.next, month: l.month, week: l.week, day: l.day },
9386
+ newEventLabel: l.newEvent,
9387
+ newEventDisabled: !canCreate || resources.length === 0,
9388
+ onNewEventClick: openCreate,
9279
9389
  activeView,
9280
9390
  sizeConfig,
9281
9391
  navigate,
@@ -9391,7 +9501,7 @@ function CalendarTimeline({
9391
9501
  "data-uv-ct-row": r.id,
9392
9502
  children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "relative shrink-0", style: { width: gridWidth, minWidth: gridWidth, height: "100%" }, children: [
9393
9503
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-px bg-border/25" }),
9394
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "absolute inset-0", onPointerDown: onPointerDownCell, "data-uv-ct-timeline": true, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "absolute inset-0 flex", children: slots.map((s, i2) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9504
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "absolute inset-0", onPointerDown: onPointerDownCell, onClick: onClickCell, "data-uv-ct-timeline": true, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "absolute inset-0 flex", children: slots.map((s, i2) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9395
9505
  "div",
9396
9506
  {
9397
9507
  className: cn(
@@ -9563,6 +9673,70 @@ function CalendarTimeline({
9563
9673
  ] })
9564
9674
  ] })
9565
9675
  }
9676
+ ) : null,
9677
+ canCreate ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9678
+ Sheet,
9679
+ {
9680
+ open: createOpen,
9681
+ onOpenChange: setCreateOpen,
9682
+ side: "right",
9683
+ size: "md",
9684
+ title: l.createEventTitle,
9685
+ description: activeView === "day" ? l.day : activeView === "week" ? l.week : l.month,
9686
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "space-y-4", children: [
9687
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "space-y-2", children: [
9688
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "text-xs text-muted-foreground", children: l.resource }),
9689
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9690
+ Combobox,
9691
+ {
9692
+ options: resourceOptions,
9693
+ value: createResourceId,
9694
+ onChange: (v) => setCreateResourceId(v),
9695
+ placeholder: l.resource
9696
+ }
9697
+ )
9698
+ ] }),
9699
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "grid grid-cols-2 gap-3", children: [
9700
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "space-y-2", children: [
9701
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "text-xs text-muted-foreground", children: l.start }),
9702
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9703
+ Combobox,
9704
+ {
9705
+ options: createStartOptions,
9706
+ value: createStartIdx,
9707
+ onChange: (v) => setCreateStartIdx(Number(v)),
9708
+ placeholder: l.start
9709
+ }
9710
+ )
9711
+ ] }),
9712
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "space-y-2", children: [
9713
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "text-xs text-muted-foreground", children: l.end }),
9714
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9715
+ Combobox,
9716
+ {
9717
+ options: createEndOptions,
9718
+ value: createEndIdx,
9719
+ onChange: (v) => setCreateEndIdx(Number(v)),
9720
+ placeholder: l.end
9721
+ }
9722
+ )
9723
+ ] })
9724
+ ] }),
9725
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center justify-end gap-2 pt-2", children: [
9726
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Button_default, { variant: "ghost", size: "sm", onClick: () => setCreateOpen(false), children: l.cancel }),
9727
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9728
+ Button_default,
9729
+ {
9730
+ variant: "default",
9731
+ size: "sm",
9732
+ onClick: commitCreate,
9733
+ disabled: !createResourceId || createEndIdx <= createStartIdx || createStartOptions.length === 0,
9734
+ children: l.create
9735
+ }
9736
+ )
9737
+ ] })
9738
+ ] })
9739
+ }
9566
9740
  ) : null
9567
9741
  ]
9568
9742
  }