@underverse-ui/underverse 0.2.97 → 0.2.99

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
@@ -8121,6 +8121,22 @@ function startOfZonedDay(date, timeZone) {
8121
8121
  const p = getZonedParts(date, timeZone);
8122
8122
  return new Date(zonedTimeToUtcMs({ ...p, hour: 0, minute: 0, second: 0 }, timeZone));
8123
8123
  }
8124
+ function zonedDateAtTime(date, timeZone, time) {
8125
+ const p = getZonedParts(date, timeZone);
8126
+ return new Date(
8127
+ zonedTimeToUtcMs(
8128
+ {
8129
+ year: p.year,
8130
+ month: p.month,
8131
+ day: p.day,
8132
+ hour: time.hour,
8133
+ minute: time.minute ?? 0,
8134
+ second: time.second ?? 0
8135
+ },
8136
+ timeZone
8137
+ )
8138
+ );
8139
+ }
8124
8140
  function startOfZonedMonth(date, timeZone) {
8125
8141
  const p = getZonedParts(date, timeZone);
8126
8142
  return new Date(zonedTimeToUtcMs({ year: p.year, month: p.month, day: 1, hour: 0, minute: 0, second: 0 }, timeZone));
@@ -8308,9 +8324,10 @@ function useClientWidth(ref) {
8308
8324
  var SIZE_CONFIG_BY_SIZE = {
8309
8325
  sm: {
8310
8326
  resourceColumnWidth: 200,
8311
- rowHeight: 66,
8327
+ rowHeight: 84,
8328
+ groupRowHeight: 36,
8312
8329
  slotMinWidth: 52,
8313
- eventHeight: 40,
8330
+ eventHeight: 46,
8314
8331
  laneGap: 3,
8315
8332
  lanePaddingY: 5,
8316
8333
  densityClass: "text-xs",
@@ -8324,9 +8341,10 @@ var SIZE_CONFIG_BY_SIZE = {
8324
8341
  },
8325
8342
  md: {
8326
8343
  resourceColumnWidth: 240,
8327
- rowHeight: 78,
8344
+ rowHeight: 96,
8345
+ groupRowHeight: 40,
8328
8346
  slotMinWidth: 64,
8329
- eventHeight: 48,
8347
+ eventHeight: 54,
8330
8348
  laneGap: 4,
8331
8349
  lanePaddingY: 6,
8332
8350
  densityClass: "text-sm",
@@ -8340,9 +8358,10 @@ var SIZE_CONFIG_BY_SIZE = {
8340
8358
  },
8341
8359
  xl: {
8342
8360
  resourceColumnWidth: 280,
8343
- rowHeight: 90,
8361
+ rowHeight: 112,
8362
+ groupRowHeight: 44,
8344
8363
  slotMinWidth: 76,
8345
- eventHeight: 56,
8364
+ eventHeight: 62,
8346
8365
  laneGap: 5,
8347
8366
  lanePaddingY: 8,
8348
8367
  densityClass: "text-base",
@@ -8426,17 +8445,24 @@ function getGroupResourceCounts(resources) {
8426
8445
  return counts;
8427
8446
  }
8428
8447
  function computeSlotStarts(args) {
8429
- const { view, date, timeZone, weekStartsOn, dayTimeStepMinutes } = args;
8430
- const start = view === "month" ? startOfZonedMonth(date, timeZone) : view === "week" ? startOfZonedWeek(date, weekStartsOn, timeZone) : startOfZonedDay(date, timeZone);
8448
+ const { view, date, timeZone, weekStartsOn, dayTimeStepMinutes, dayRangeMode, workHours } = args;
8449
+ const baseDayStart = startOfZonedDay(date, timeZone);
8450
+ const start = view === "month" ? startOfZonedMonth(date, timeZone) : view === "week" ? startOfZonedWeek(date, weekStartsOn, timeZone) : baseDayStart;
8431
8451
  if (view === "day") {
8432
8452
  const step = Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes)));
8433
8453
  const stepMs = step * 6e4;
8434
- const end2 = addZonedDays(start, 1, timeZone);
8454
+ const hours = workHours ?? { startHour: 8, endHour: 17 };
8455
+ const boundedStartHour = clamp3(Math.trunc(hours.startHour), 0, 23);
8456
+ const boundedEndHour = clamp3(Math.trunc(hours.endHour), 1, 24);
8457
+ const isWork = dayRangeMode === "work";
8458
+ const start2 = isWork ? zonedDateAtTime(baseDayStart, timeZone, { hour: boundedStartHour }) : start;
8459
+ const end2 = isWork ? boundedEndHour === 24 ? addZonedDays(baseDayStart, 1, timeZone) : zonedDateAtTime(baseDayStart, timeZone, { hour: clamp3(boundedEndHour, 0, 23) }) : addZonedDays(start, 1, timeZone);
8460
+ const end3 = end2.getTime() > start2.getTime() ? end2 : addZonedDays(start2, 1, timeZone);
8435
8461
  const slotStarts2 = [];
8436
- for (let cur2 = start.getTime(), guard2 = 0; cur2 < end2.getTime() && guard2++ < 2e3; cur2 += stepMs) {
8462
+ for (let cur2 = start2.getTime(), guard2 = 0; cur2 < end3.getTime() && guard2++ < 2e3; cur2 += stepMs) {
8437
8463
  slotStarts2.push(new Date(cur2));
8438
8464
  }
8439
- return { start, end: end2, slotStarts: slotStarts2 };
8465
+ return { start: start2, end: end3, slotStarts: slotStarts2 };
8440
8466
  }
8441
8467
  const end = view === "month" ? startOfZonedMonth(addZonedMonths(start, 1, timeZone), timeZone) : addZonedDays(start, 7, timeZone);
8442
8468
  const slotStarts = [];
@@ -8736,6 +8762,8 @@ function CalendarTimeline({
8736
8762
  enableLayoutResize,
8737
8763
  slotMinWidth,
8738
8764
  dayTimeStepMinutes = 60,
8765
+ dayRangeMode,
8766
+ workHours,
8739
8767
  maxLanesPerRow = 3,
8740
8768
  now,
8741
8769
  renderResource,
@@ -8897,7 +8925,9 @@ function CalendarTimeline({
8897
8925
  date: activeDate,
8898
8926
  timeZone: resolvedTimeZone,
8899
8927
  weekStartsOn,
8900
- dayTimeStepMinutes
8928
+ dayTimeStepMinutes,
8929
+ dayRangeMode,
8930
+ workHours
8901
8931
  });
8902
8932
  const todayStart = startOfZonedDay(resolvedNow, resolvedTimeZone).getTime();
8903
8933
  const slotItems = slotStarts2.map((s) => ({
@@ -8906,7 +8936,7 @@ function CalendarTimeline({
8906
8936
  isToday: startOfZonedDay(s, resolvedTimeZone).getTime() === todayStart
8907
8937
  }));
8908
8938
  return { slots: slotItems, range: { start, end } };
8909
- }, [activeView, activeDate, resolvedTimeZone, resolvedLocale, weekStartsOn, dayTimeStepMinutes, resolvedNow, formatters]);
8939
+ }, [activeView, activeDate, resolvedTimeZone, resolvedLocale, weekStartsOn, dayTimeStepMinutes, dayRangeMode, workHours, resolvedNow, formatters]);
8910
8940
  React28.useEffect(() => {
8911
8941
  onRangeChange?.(range);
8912
8942
  }, [range.start, range.end, onRangeChange]);
@@ -8916,10 +8946,11 @@ function CalendarTimeline({
8916
8946
  const bodyClientWidth = useClientWidth(bodyRef);
8917
8947
  const slotStarts = React28.useMemo(() => slots.map((s) => s.start), [slots]);
8918
8948
  const slotWidth = React28.useMemo(() => {
8919
- if (activeView !== "week") return effectiveSlotMinWidth;
8920
- if (bodyClientWidth <= 0) return effectiveSlotMinWidth;
8921
- if (slots.length <= 0) return effectiveSlotMinWidth;
8922
- return Math.max(effectiveSlotMinWidth, bodyClientWidth / slots.length);
8949
+ const baseSlotWidth = activeView === "month" ? effectiveSlotMinWidth * 3 : activeView === "day" ? effectiveSlotMinWidth * 3 : effectiveSlotMinWidth;
8950
+ if (activeView !== "week") return baseSlotWidth;
8951
+ if (bodyClientWidth <= 0) return baseSlotWidth;
8952
+ if (slots.length <= 0) return baseSlotWidth;
8953
+ return Math.max(baseSlotWidth, bodyClientWidth / slots.length);
8923
8954
  }, [activeView, bodyClientWidth, effectiveSlotMinWidth, slots.length]);
8924
8955
  const gridWidth = slots.length * slotWidth;
8925
8956
  const normalizedEvents = React28.useMemo(() => {
@@ -8995,9 +9026,9 @@ function CalendarTimeline({
8995
9026
  const rowHeightsArray = React28.useMemo(() => {
8996
9027
  return rows.map((r) => {
8997
9028
  if (r.kind === "resource") return getResourceRowHeight(r.resource.id);
8998
- return effectiveRowHeight;
9029
+ return sizeConfig.groupRowHeight;
8999
9030
  });
9000
- }, [effectiveRowHeight, getResourceRowHeight, rows]);
9031
+ }, [getResourceRowHeight, rows, sizeConfig.groupRowHeight]);
9001
9032
  const virtualResult = useVirtualVariableRows({
9002
9033
  enabled: virt,
9003
9034
  overscan,
@@ -9176,18 +9207,33 @@ function CalendarTimeline({
9176
9207
  const dragRef = React28.useRef(null);
9177
9208
  const [preview, setPreview] = React28.useState(null);
9178
9209
  const suppressNextEventClickRef = React28.useRef(false);
9210
+ const autoScrollStateRef = React28.useRef({
9211
+ dir: 0,
9212
+ speed: 0,
9213
+ lastClientX: 0,
9214
+ lastClientY: 0
9215
+ });
9216
+ const autoScrollRafRef = React28.useRef(null);
9217
+ const stopAutoScroll = React28.useCallback(() => {
9218
+ if (autoScrollRafRef.current != null) cancelAnimationFrame(autoScrollRafRef.current);
9219
+ autoScrollRafRef.current = null;
9220
+ autoScrollStateRef.current.dir = 0;
9221
+ autoScrollStateRef.current.speed = 0;
9222
+ }, []);
9179
9223
  const getPointerContext = React28.useCallback(
9180
9224
  (clientX, clientY, opts) => {
9181
9225
  const body = bodyRef.current;
9182
9226
  if (!body) return null;
9183
- const el = document.elementFromPoint(clientX, clientY);
9184
- if (!el || !body.contains(el)) return null;
9185
9227
  const bodyRect = body.getBoundingClientRect();
9186
- const x = clientX - bodyRect.left + body.scrollLeft;
9228
+ const probeX = clamp3(clientX, bodyRect.left + 1, bodyRect.right - 1);
9229
+ const probeY = clamp3(clientY, bodyRect.top + 1, bodyRect.bottom - 1);
9230
+ const el = document.elementFromPoint(probeX, probeY);
9231
+ const x = probeX - bodyRect.left + body.scrollLeft;
9187
9232
  const epsilon = opts?.biasLeft ? 0.01 : 0;
9188
9233
  const slotIdx = clamp3(Math.floor((x - epsilon) / slotWidth), 0, Math.max(0, slots.length - 1));
9189
- const rowEl = el?.closest?.("[data-uv-ct-row]");
9190
- const rid = rowEl?.dataset?.uvCtRow ?? null;
9234
+ const rowEl = el && body.contains(el) ? el.closest?.("[data-uv-ct-row]") ?? null : null;
9235
+ const rid = rowEl?.dataset?.uvCtRow ?? opts?.fallbackResourceId ?? null;
9236
+ if (!rid) return null;
9191
9237
  return { slotIdx, resourceId: rid, x };
9192
9238
  },
9193
9239
  [slotWidth, slots.length]
@@ -9203,6 +9249,97 @@ function CalendarTimeline({
9203
9249
  },
9204
9250
  [activeView, dayTimeStepMinutes, resolvedTimeZone, slotStarts]
9205
9251
  );
9252
+ const updateDragPreview = React28.useCallback(
9253
+ (clientX, clientY) => {
9254
+ const drag = dragRef.current;
9255
+ if (!drag) return;
9256
+ const ctx = getPointerContext(clientX, clientY, drag.mode === "create" ? { biasLeft: true, fallbackResourceId: drag.resourceId } : { fallbackResourceId: drag.resourceId });
9257
+ if (!ctx) return;
9258
+ const { slotIdx } = ctx;
9259
+ const movedEnough = Math.abs(clientX - drag.startClientX) > 3 || Math.abs(clientY - drag.startClientY) > 3 || slotIdx !== drag.startSlotIdx || ctx.resourceId !== drag.startRowResourceId;
9260
+ if (movedEnough) suppressNextEventClickRef.current = true;
9261
+ if (drag.mode === "create") {
9262
+ const a = Math.min(drag.startSlotIdx, slotIdx);
9263
+ const b = Math.max(drag.startSlotIdx, slotIdx) + 1;
9264
+ const s = slotToDate(a).start;
9265
+ const e2 = b >= slots.length ? range.end : slotToDate(b).start;
9266
+ setPreview({ resourceId: drag.resourceId, start: s, end: e2 });
9267
+ return;
9268
+ }
9269
+ const targetSlotStart = slotToDate(slotIdx).start;
9270
+ const originSlotStart = slotToDate(drag.startSlotIdx).start;
9271
+ const deltaMs = targetSlotStart.getTime() - originSlotStart.getTime();
9272
+ if (drag.mode === "move") {
9273
+ const nextStart = new Date(drag.originStart.getTime() + deltaMs);
9274
+ const nextEnd = new Date(drag.originEnd.getTime() + deltaMs);
9275
+ setPreview({ eventId: drag.eventId, resourceId: ctx.resourceId, start: nextStart, end: nextEnd });
9276
+ drag.resourceId = ctx.resourceId;
9277
+ return;
9278
+ }
9279
+ if (drag.mode === "resize-start") {
9280
+ const nextStart = new Date(clamp3(targetSlotStart.getTime(), range.start.getTime(), drag.originEnd.getTime() - 6e4));
9281
+ setPreview({ eventId: drag.eventId, resourceId: drag.resourceId, start: nextStart, end: drag.originEnd });
9282
+ return;
9283
+ }
9284
+ if (drag.mode === "resize-end") {
9285
+ const nextEnd = new Date(clamp3(targetSlotStart.getTime(), drag.originStart.getTime() + 6e4, range.end.getTime()));
9286
+ setPreview({ eventId: drag.eventId, resourceId: drag.resourceId, start: drag.originStart, end: nextEnd });
9287
+ return;
9288
+ }
9289
+ },
9290
+ [getPointerContext, range.end, range.start, slotToDate, slots.length]
9291
+ );
9292
+ const autoScrollTick = React28.useCallback(() => {
9293
+ const drag = dragRef.current;
9294
+ const body = bodyRef.current;
9295
+ const st = autoScrollStateRef.current;
9296
+ if (!drag || !body || st.dir === 0) {
9297
+ stopAutoScroll();
9298
+ return;
9299
+ }
9300
+ const maxScrollLeft = Math.max(0, body.scrollWidth - body.clientWidth);
9301
+ const prevLeft = body.scrollLeft;
9302
+ const nextLeft = clamp3(prevLeft + st.dir * st.speed, 0, maxScrollLeft);
9303
+ if (nextLeft === prevLeft) {
9304
+ stopAutoScroll();
9305
+ return;
9306
+ }
9307
+ body.scrollLeft = nextLeft;
9308
+ updateDragPreview(st.lastClientX, st.lastClientY);
9309
+ autoScrollRafRef.current = requestAnimationFrame(autoScrollTick);
9310
+ }, [stopAutoScroll, updateDragPreview]);
9311
+ const updateAutoScrollFromPointer = React28.useCallback(
9312
+ (clientX, clientY) => {
9313
+ const body = bodyRef.current;
9314
+ if (!body) return;
9315
+ const rect = body.getBoundingClientRect();
9316
+ const edge = 56;
9317
+ let dir = 0;
9318
+ let speed = 0;
9319
+ if (clientX < rect.left + edge) {
9320
+ dir = -1;
9321
+ const dist = clientX - rect.left;
9322
+ const t2 = clamp3(1 - dist / edge, 0, 1);
9323
+ speed = 8 + t2 * 28;
9324
+ } else if (clientX > rect.right - edge) {
9325
+ dir = 1;
9326
+ const dist = rect.right - clientX;
9327
+ const t2 = clamp3(1 - dist / edge, 0, 1);
9328
+ speed = 8 + t2 * 28;
9329
+ }
9330
+ autoScrollStateRef.current.lastClientX = clientX;
9331
+ autoScrollStateRef.current.lastClientY = clientY;
9332
+ autoScrollStateRef.current.dir = dir;
9333
+ autoScrollStateRef.current.speed = speed;
9334
+ if (dir === 0) {
9335
+ stopAutoScroll();
9336
+ return;
9337
+ }
9338
+ if (autoScrollRafRef.current == null) autoScrollRafRef.current = requestAnimationFrame(autoScrollTick);
9339
+ },
9340
+ [autoScrollTick, stopAutoScroll]
9341
+ );
9342
+ React28.useEffect(() => stopAutoScroll, [stopAutoScroll]);
9206
9343
  const onPointerDownEvent = (e, ev, mode) => {
9207
9344
  if (e.button !== 0 || e.ctrlKey) return;
9208
9345
  if (isViewOnly) return;
@@ -9214,6 +9351,8 @@ function CalendarTimeline({
9214
9351
  suppressNextEventClickRef.current = false;
9215
9352
  const startIdx = binarySearchLastLE(slotStarts, ev._start);
9216
9353
  const endIdx = binarySearchFirstGE(slotStarts, ev._end);
9354
+ const pointerCtx = getPointerContext(e.clientX, e.clientY, { fallbackResourceId: ev.resourceId });
9355
+ const grabSlotIdx = pointerCtx?.slotIdx ?? startIdx;
9217
9356
  dragRef.current = {
9218
9357
  mode,
9219
9358
  eventId: ev.id,
@@ -9222,7 +9361,7 @@ function CalendarTimeline({
9222
9361
  originEnd: ev._end,
9223
9362
  durationMs: ev._end.getTime() - ev._start.getTime(),
9224
9363
  pointerId: e.pointerId,
9225
- startSlotIdx: startIdx,
9364
+ startSlotIdx: grabSlotIdx,
9226
9365
  startRowResourceId: ev.resourceId,
9227
9366
  startClientX: e.clientX,
9228
9367
  startClientY: e.clientY
@@ -9278,44 +9417,14 @@ function CalendarTimeline({
9278
9417
  const onPointerMove = (e) => {
9279
9418
  const drag = dragRef.current;
9280
9419
  if (!drag || drag.pointerId !== e.pointerId) return;
9281
- const ctx = getPointerContext(e.clientX, e.clientY, drag.mode === "create" ? { biasLeft: true } : void 0);
9282
- if (!ctx || !ctx.resourceId) return;
9283
- const { slotIdx } = ctx;
9284
- const movedEnough = Math.abs(e.clientX - drag.startClientX) > 3 || Math.abs(e.clientY - drag.startClientY) > 3 || slotIdx !== drag.startSlotIdx || ctx.resourceId !== drag.startRowResourceId;
9285
- if (movedEnough) suppressNextEventClickRef.current = true;
9286
- if (drag.mode === "create") {
9287
- const a = Math.min(drag.startSlotIdx, slotIdx);
9288
- const b = Math.max(drag.startSlotIdx, slotIdx) + 1;
9289
- const s = slotToDate(a).start;
9290
- const e2 = b >= slots.length ? range.end : slotToDate(b).start;
9291
- setPreview({ resourceId: drag.resourceId, start: s, end: e2 });
9292
- return;
9293
- }
9294
- const targetSlotStart = slotToDate(slotIdx).start;
9295
- const originSlotStart = slotToDate(drag.startSlotIdx).start;
9296
- const deltaMs = targetSlotStart.getTime() - originSlotStart.getTime();
9297
- if (drag.mode === "move") {
9298
- const nextStart = new Date(drag.originStart.getTime() + deltaMs);
9299
- const nextEnd = new Date(drag.originEnd.getTime() + deltaMs);
9300
- setPreview({ eventId: drag.eventId, resourceId: ctx.resourceId, start: nextStart, end: nextEnd });
9301
- drag.resourceId = ctx.resourceId;
9302
- return;
9303
- }
9304
- if (drag.mode === "resize-start") {
9305
- const nextStart = new Date(clamp3(targetSlotStart.getTime(), range.start.getTime(), drag.originEnd.getTime() - 6e4));
9306
- setPreview({ eventId: drag.eventId, resourceId: drag.resourceId, start: nextStart, end: drag.originEnd });
9307
- return;
9308
- }
9309
- if (drag.mode === "resize-end") {
9310
- const nextEnd = new Date(clamp3(targetSlotStart.getTime(), drag.originStart.getTime() + 6e4, range.end.getTime()));
9311
- setPreview({ eventId: drag.eventId, resourceId: drag.resourceId, start: drag.originStart, end: nextEnd });
9312
- return;
9313
- }
9420
+ updateAutoScrollFromPointer(e.clientX, e.clientY);
9421
+ updateDragPreview(e.clientX, e.clientY);
9314
9422
  };
9315
9423
  const onPointerUp = (e) => {
9316
9424
  const drag = dragRef.current;
9317
9425
  if (!drag || drag.pointerId !== e.pointerId) return;
9318
9426
  dragRef.current = null;
9427
+ stopAutoScroll();
9319
9428
  if (!preview) {
9320
9429
  setPreview(null);
9321
9430
  return;
@@ -9532,10 +9641,26 @@ function CalendarTimeline({
9532
9641
  timeZone: resolvedTimeZone,
9533
9642
  view: activeView
9534
9643
  }) ?? defaultEventTime({ start: ev._start, end: ev._end, locale: resolvedLocale, timeZone: resolvedTimeZone, view: activeView });
9535
- const node = renderEvent?.(ev, { left, width, lane }) ?? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "h-full px-2.5 truncate flex items-center gap-1.5", children: [
9536
- ev.title ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "font-semibold text-xs truncate leading-none", children: ev.title }) : null,
9537
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-[11px] opacity-70 truncate ml-auto leading-none", children: timeText })
9538
- ] });
9644
+ const node = renderEvent?.(ev, { left, width, lane, height: layout.eventHeight, timeText }) ?? (() => {
9645
+ const showTime = layout.eventHeight >= 24;
9646
+ const titleMaxLines = showTime ? layout.eventHeight >= 34 ? 2 : 1 : 1;
9647
+ const isPlainTitle = typeof ev.title === "string" || typeof ev.title === "number";
9648
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "h-full px-2.5 flex items-center min-w-0 overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "w-full grid grid-cols-[1fr_auto] gap-x-2 items-start min-w-0 overflow-hidden", children: [
9649
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9650
+ "div",
9651
+ {
9652
+ className: cn("text-xs font-semibold leading-snug min-w-0 overflow-hidden", isPlainTitle ? "break-words" : ""),
9653
+ style: isPlainTitle ? {
9654
+ display: "-webkit-box",
9655
+ WebkitBoxOrient: "vertical",
9656
+ WebkitLineClamp: titleMaxLines
9657
+ } : void 0,
9658
+ children: ev.title ?? null
9659
+ }
9660
+ ),
9661
+ showTime ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "text-[11px] opacity-70 leading-snug whitespace-nowrap", children: timeText }) : null
9662
+ ] }) });
9663
+ })();
9539
9664
  const resource = resourceById.get(ev.resourceId);
9540
9665
  const tooltipTitle = ev.title || ev.id;
9541
9666
  const tooltipContent = /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-col gap-0.5", children: [
@@ -9551,6 +9676,7 @@ function CalendarTimeline({
9551
9676
  "shadow-sm hover:shadow-md hover:scale-[1.02] hover:z-10",
9552
9677
  "transition-all duration-150 ease-out",
9553
9678
  "backdrop-blur-sm",
9679
+ "overflow-hidden",
9554
9680
  ev.className,
9555
9681
  isPreview && "ring-2 ring-primary/50 ring-offset-1 ring-offset-background scale-[1.02] z-10"
9556
9682
  ),