@farmzone/fz-react-ui 1.1.2 → 1.1.3

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
@@ -7887,7 +7887,8 @@ function PageFilter(props) {
7887
7887
  onChange: handleInputChange(optionKey),
7888
7888
  onKeyDown: (e) => {
7889
7889
  if (e.key === "Enter") handleSubmit();
7890
- }
7890
+ },
7891
+ leftIcon: option.leftIcon !== false ? /* @__PURE__ */ jsx(Search, { size: 16 }) : void 0
7891
7892
  }
7892
7893
  ) }, String(optionKey));
7893
7894
  case "select":
@@ -8050,6 +8051,122 @@ function easedFade(dir, opacity) {
8050
8051
  const c = (t) => `rgba(0,0,0,${(opacity * t).toFixed(3)})`;
8051
8052
  return `linear-gradient(${dir}, ${c(1)} 0%, ${c(0.72)} 20%, ${c(0.38)} 45%, ${c(0.1)} 70%, transparent 100%)`;
8052
8053
  }
8054
+ function clusterTabs(tabs, getTabGroup) {
8055
+ const clusters = [];
8056
+ const clusterByGroupKey = /* @__PURE__ */ new Map();
8057
+ for (const tab of tabs) {
8058
+ const group = getTabGroup(tab) ?? null;
8059
+ if (!group) {
8060
+ clusters.push({ group: null, tabs: [tab] });
8061
+ continue;
8062
+ }
8063
+ const existing = clusterByGroupKey.get(group.key);
8064
+ if (existing) {
8065
+ existing.tabs.push(tab);
8066
+ } else {
8067
+ const cluster = { group, tabs: [tab] };
8068
+ clusterByGroupKey.set(group.key, cluster);
8069
+ clusters.push(cluster);
8070
+ }
8071
+ }
8072
+ return clusters;
8073
+ }
8074
+ var TAB_PALETTE = [
8075
+ { base: "rgb(38, 117, 226)", bg: "rgba(38, 117, 226, 0.12)", border: "rgba(38, 117, 226, 0.4)" },
8076
+ // blue
8077
+ { base: "rgb(249, 115, 22)", bg: "rgba(249, 115, 22, 0.12)", border: "rgba(249, 115, 22, 0.4)" },
8078
+ // orange
8079
+ { base: "rgb(239, 68, 68)", bg: "rgba(239, 68, 68, 0.12)", border: "rgba(239, 68, 68, 0.4)" },
8080
+ // red
8081
+ { base: "rgb(34, 197, 94)", bg: "rgba(34, 197, 94, 0.12)", border: "rgba(34, 197, 94, 0.4)" },
8082
+ // green
8083
+ { base: "rgb(168, 85, 247)", bg: "rgba(168, 85, 247, 0.12)", border: "rgba(168, 85, 247, 0.4)" },
8084
+ // purple
8085
+ { base: "rgb(245, 158, 11)", bg: "rgba(245, 158, 11, 0.12)", border: "rgba(245, 158, 11, 0.4)" },
8086
+ // amber
8087
+ { base: "rgb(236, 72, 153)", bg: "rgba(236, 72, 153, 0.12)", border: "rgba(236, 72, 153, 0.4)" },
8088
+ // pink
8089
+ { base: "rgb(20, 184, 166)", bg: "rgba(20, 184, 166, 0.12)", border: "rgba(20, 184, 166, 0.4)" },
8090
+ // teal
8091
+ { base: "rgb(99, 102, 241)", bg: "rgba(99, 102, 241, 0.12)", border: "rgba(99, 102, 241, 0.4)" },
8092
+ // indigo
8093
+ { base: "rgb(6, 182, 212)", bg: "rgba(6, 182, 212, 0.12)", border: "rgba(6, 182, 212, 0.4)" }
8094
+ // cyan
8095
+ ];
8096
+ function assignGroupColors(groupKeys, existing) {
8097
+ const missing = groupKeys.filter((key) => !existing.has(key));
8098
+ if (missing.length === 0) return existing;
8099
+ const next = new Map(existing);
8100
+ for (const key of missing) {
8101
+ if (next.has(key)) continue;
8102
+ const used = new Set([...next.values()].map((c) => c.bg));
8103
+ const free = TAB_PALETTE.find((c) => !used.has(c.bg)) ?? TAB_PALETTE[next.size % TAB_PALETTE.length];
8104
+ next.set(key, free ?? TAB_PALETTE[0]);
8105
+ }
8106
+ return next;
8107
+ }
8108
+ function MultiTabPill(props) {
8109
+ const {
8110
+ tab,
8111
+ isActive,
8112
+ showClose,
8113
+ accentColor,
8114
+ style: s,
8115
+ onClick,
8116
+ onClose,
8117
+ registerRef,
8118
+ draggable = false,
8119
+ onPointerDown,
8120
+ dropIndicator = null,
8121
+ isDraggingThis = false
8122
+ } = props;
8123
+ return /* @__PURE__ */ jsxs(
8124
+ "div",
8125
+ {
8126
+ ref: (el) => registerRef(tab.id, el),
8127
+ onPointerDown: draggable ? onPointerDown : void 0,
8128
+ onClick: () => {
8129
+ if (!isActive) onClick();
8130
+ },
8131
+ className: `
8132
+ group relative flex items-center gap-1.5 ${s.tabPx} ${s.tabPy} flex-shrink-0
8133
+ ${s.rounded} border border-b-0 select-none
8134
+ transition-colors duration-100
8135
+ ${isActive ? s.activeTab : s.inactiveTab}
8136
+ ${draggable ? isDraggingThis ? "cursor-grabbing" : "cursor-grab" : "cursor-pointer"}
8137
+ ${isDraggingThis ? "opacity-40" : ""}
8138
+ ${dropIndicator === "before" ? "[box-shadow:inset_2px_0_0_0_#3b82f6]" : ""}
8139
+ ${dropIndicator === "after" ? "[box-shadow:inset_-2px_0_0_0_#3b82f6]" : ""}
8140
+ `,
8141
+ children: [
8142
+ isActive && /* @__PURE__ */ jsx(
8143
+ "div",
8144
+ {
8145
+ className: `absolute top-0 left-0 right-0 h-0.5 ${s.rounded} ${s.indicator}`,
8146
+ style: accentColor ? { backgroundColor: accentColor } : void 0
8147
+ }
8148
+ ),
8149
+ /* @__PURE__ */ jsx("span", { className: `${s.tabTextSize} max-w-35 truncate leading-none ${isActive ? s.activeText : s.inactiveText}`, children: tab.label }),
8150
+ showClose && /* @__PURE__ */ jsx(
8151
+ "button",
8152
+ {
8153
+ onClick: (e) => {
8154
+ e.stopPropagation();
8155
+ onClose();
8156
+ },
8157
+ className: `
8158
+ flex-shrink-0 ${s.closeBtnWH} flex items-center justify-center
8159
+ transition-all duration-100 cursor-pointer
8160
+ ${s.closeBtn}
8161
+ ${isActive ? "opacity-100" : "opacity-40 group-hover:opacity-60"}
8162
+ `,
8163
+ children: /* @__PURE__ */ jsx(X, { size: s.closeBtnSize, strokeWidth: 2.5 })
8164
+ }
8165
+ )
8166
+ ]
8167
+ }
8168
+ );
8169
+ }
8053
8170
  function MultiTabBar(props) {
8054
8171
  const {
8055
8172
  tabs,
@@ -8059,25 +8176,77 @@ function MultiTabBar(props) {
8059
8176
  onTabAdd,
8060
8177
  onTabsReorder,
8061
8178
  variant = "light",
8062
- primaryColor
8179
+ primaryColor,
8180
+ getTabGroup
8063
8181
  } = props;
8064
8182
  const s = VARIANT_STYLES[variant];
8065
8183
  const scrollRef = useRef(null);
8066
8184
  const tabRefs = useRef(/* @__PURE__ */ new Map());
8185
+ const registerTabRef = (id, el) => {
8186
+ if (el) tabRefs.current.set(id, el);
8187
+ else tabRefs.current.delete(id);
8188
+ };
8067
8189
  const [showLeftFade, setShowLeftFade] = useState(false);
8068
8190
  const [showRightFade, setShowRightFade] = useState(false);
8069
8191
  const [draggingId, setDraggingId] = useState(null);
8070
8192
  const [dropTargetId, setDropTargetId] = useState(null);
8071
8193
  const [dropSide, setDropSide] = useState("before");
8072
- const draggingIdRef = useRef(null);
8194
+ const [draggingGroupKey, setDraggingGroupKey] = useState(null);
8195
+ const [groupDropTargetKey, setGroupDropTargetKey] = useState(null);
8196
+ const [groupDropSide, setGroupDropSide] = useState("before");
8197
+ const [dragPos, setDragPos] = useState(null);
8073
8198
  const dropTargetIdRef = useRef(null);
8074
8199
  const dropSideRef = useRef("before");
8200
+ const groupDropTargetKeyRef = useRef(null);
8201
+ const groupDropSideRef = useRef("before");
8075
8202
  const tabsRef = useRef(tabs);
8076
- const dropHandledRef = useRef(false);
8077
- draggingIdRef.current = draggingId;
8078
- dropTargetIdRef.current = dropTargetId;
8079
- dropSideRef.current = dropSide;
8080
- tabsRef.current = tabs;
8203
+ useEffect(() => {
8204
+ dropTargetIdRef.current = dropTargetId;
8205
+ dropSideRef.current = dropSide;
8206
+ groupDropTargetKeyRef.current = groupDropTargetKey;
8207
+ groupDropSideRef.current = groupDropSide;
8208
+ tabsRef.current = tabs;
8209
+ });
8210
+ const groupRefs = useRef(/* @__PURE__ */ new Map());
8211
+ const registerGroupRef = (key, el) => {
8212
+ if (el) groupRefs.current.set(key, el);
8213
+ else groupRefs.current.delete(key);
8214
+ };
8215
+ const clusters = useMemo(
8216
+ () => getTabGroup ? clusterTabs(tabs, getTabGroup) : null,
8217
+ [tabs, getTabGroup]
8218
+ );
8219
+ const groupKeys = useMemo(
8220
+ () => clusters ? Array.from(new Set(clusters.map((c) => c.group?.key).filter((k) => !!k))) : [],
8221
+ [clusters]
8222
+ );
8223
+ const [groupColorAssignments, setGroupColorAssignments] = useState(() => /* @__PURE__ */ new Map());
8224
+ const groupColors = assignGroupColors(groupKeys, groupColorAssignments);
8225
+ if (groupColors !== groupColorAssignments) {
8226
+ setGroupColorAssignments(groupColors);
8227
+ }
8228
+ const [collapsedGroups, setCollapsedGroups] = useState(/* @__PURE__ */ new Set());
8229
+ const [prevActiveTabId, setPrevActiveTabId] = useState(activeTabId);
8230
+ if (activeTabId !== prevActiveTabId) {
8231
+ setPrevActiveTabId(activeTabId);
8232
+ if (clusters) {
8233
+ const activeCluster = clusters.find((c) => c.tabs.some((t) => t.id === activeTabId));
8234
+ const key = activeCluster?.group?.key;
8235
+ if (key && collapsedGroups.has(key)) {
8236
+ const next = new Set(collapsedGroups);
8237
+ next.delete(key);
8238
+ setCollapsedGroups(next);
8239
+ }
8240
+ }
8241
+ }
8242
+ const toggleGroupCollapsed = (key) => {
8243
+ setCollapsedGroups((prev) => {
8244
+ const next = new Set(prev);
8245
+ if (next.has(key)) next.delete(key);
8246
+ else next.add(key);
8247
+ return next;
8248
+ });
8249
+ };
8081
8250
  useEffect(() => {
8082
8251
  if (!activeTabId || !scrollRef.current) return;
8083
8252
  const el = tabRefs.current.get(activeTabId);
@@ -8115,9 +8284,12 @@ function MultiTabBar(props) {
8115
8284
  el.addEventListener("wheel", onWheel, { passive: false });
8116
8285
  return () => el.removeEventListener("wheel", onWheel);
8117
8286
  }, []);
8118
- const resolveDropPosition = (clientX) => {
8287
+ const reorderEnabled = !!onTabsReorder;
8288
+ const pointerStateRef = useRef(null);
8289
+ const suppressClickRef = useRef(false);
8290
+ const resolveDropPosition = (clientX, draggingTabId) => {
8119
8291
  for (const [tabId, el] of tabRefs.current) {
8120
- if (tabId === draggingIdRef.current) continue;
8292
+ if (tabId === draggingTabId) continue;
8121
8293
  const rect = el.getBoundingClientRect();
8122
8294
  if (clientX >= rect.left && clientX < rect.right) {
8123
8295
  setDropSide(clientX < rect.left + rect.width / 2 ? "before" : "after");
@@ -8128,7 +8300,7 @@ function MultiTabBar(props) {
8128
8300
  let rightmostTabId = null;
8129
8301
  let maxRight = -Infinity;
8130
8302
  for (const [tabId, el] of tabRefs.current) {
8131
- if (tabId === draggingIdRef.current) continue;
8303
+ if (tabId === draggingTabId) continue;
8132
8304
  const rect = el.getBoundingClientRect();
8133
8305
  if (rect.right > maxRight) {
8134
8306
  maxRight = rect.right;
@@ -8152,180 +8324,322 @@ function MultiTabBar(props) {
8152
8324
  newTabs.splice(insertIdx, 0, moved);
8153
8325
  onTabsReorder(newTabs);
8154
8326
  };
8327
+ const resolveGroupDropPosition = (clientX, draggingKey) => {
8328
+ const candidates = [];
8329
+ for (const [tabId, el] of tabRefs.current) {
8330
+ const tab = tabsRef.current.find((t) => t.id === tabId);
8331
+ if (tab && getTabGroup?.(tab)?.key === draggingKey) continue;
8332
+ candidates.push([`tab:${tabId}`, el]);
8333
+ }
8334
+ for (const [key, el] of groupRefs.current) {
8335
+ if (key === draggingKey) continue;
8336
+ candidates.push([`group:${key}`, el]);
8337
+ }
8338
+ for (const [unitKey, el] of candidates) {
8339
+ const rect = el.getBoundingClientRect();
8340
+ if (clientX >= rect.left && clientX < rect.right) {
8341
+ setGroupDropSide(clientX < rect.left + rect.width / 2 ? "before" : "after");
8342
+ setGroupDropTargetKey(unitKey);
8343
+ return;
8344
+ }
8345
+ }
8346
+ let rightmostKey = null;
8347
+ let maxRight = -Infinity;
8348
+ for (const [unitKey, el] of candidates) {
8349
+ const rect = el.getBoundingClientRect();
8350
+ if (rect.right > maxRight) {
8351
+ maxRight = rect.right;
8352
+ rightmostKey = unitKey;
8353
+ }
8354
+ }
8355
+ if (rightmostKey && clientX >= maxRight) {
8356
+ setGroupDropSide("after");
8357
+ setGroupDropTargetKey(rightmostKey);
8358
+ }
8359
+ };
8360
+ const reorderGroupTabs = (draggedKey, targetUnitKey, side, sourceTabs) => {
8361
+ if (!onTabsReorder || !getTabGroup) return;
8362
+ const draggedTabs = sourceTabs.filter((t) => getTabGroup(t)?.key === draggedKey);
8363
+ const rest = sourceTabs.filter((t) => getTabGroup(t)?.key !== draggedKey);
8364
+ if (draggedTabs.length === 0) return;
8365
+ let insertIdx;
8366
+ if (targetUnitKey.startsWith("group:")) {
8367
+ const targetKey = targetUnitKey.slice("group:".length);
8368
+ if (targetKey === draggedKey) return;
8369
+ const targetIndices = rest.map((t, i) => getTabGroup(t)?.key === targetKey ? i : -1).filter((i) => i !== -1);
8370
+ if (targetIndices.length === 0) return;
8371
+ insertIdx = side === "before" ? targetIndices[0] : targetIndices[targetIndices.length - 1] + 1;
8372
+ } else {
8373
+ const targetTabId = targetUnitKey.slice("tab:".length);
8374
+ const idx = rest.findIndex((t) => t.id === targetTabId);
8375
+ if (idx === -1) return;
8376
+ insertIdx = side === "before" ? idx : idx + 1;
8377
+ }
8378
+ const newTabs = [...rest.slice(0, insertIdx), ...draggedTabs, ...rest.slice(insertIdx)];
8379
+ onTabsReorder(newTabs);
8380
+ };
8155
8381
  useEffect(() => {
8156
- if (!onTabsReorder) return;
8157
- const onDocDragOver = (e) => {
8158
- if (!draggingIdRef.current) return;
8159
- e.preventDefault();
8160
- resolveDropPosition(e.clientX);
8382
+ if (!reorderEnabled) return;
8383
+ const DRAG_THRESHOLD = 4;
8384
+ const onPointerMove = (e) => {
8385
+ const state = pointerStateRef.current;
8386
+ if (!state) return;
8387
+ if (!state.dragging) {
8388
+ const dx = Math.abs(e.clientX - state.startX);
8389
+ const dy = Math.abs(e.clientY - state.startY);
8390
+ if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) return;
8391
+ state.dragging = true;
8392
+ suppressClickRef.current = true;
8393
+ if (state.kind === "tab") setDraggingId(state.id);
8394
+ else setDraggingGroupKey(state.id);
8395
+ }
8396
+ setDragPos({ x: e.clientX, y: e.clientY });
8397
+ if (state.kind === "tab") resolveDropPosition(e.clientX, state.id);
8398
+ else resolveGroupDropPosition(e.clientX, state.id);
8161
8399
  };
8162
- const onDocDrop = (e) => {
8163
- if (dropHandledRef.current) {
8164
- dropHandledRef.current = false;
8165
- return;
8400
+ const onPointerUp = () => {
8401
+ const state = pointerStateRef.current;
8402
+ if (state?.dragging) {
8403
+ if (state.kind === "tab") {
8404
+ const target = dropTargetIdRef.current;
8405
+ if (target) reorderTabs(target, dropSideRef.current, tabsRef.current, state.id);
8406
+ } else {
8407
+ const target = groupDropTargetKeyRef.current;
8408
+ if (target) reorderGroupTabs(state.id, target, groupDropSideRef.current, tabsRef.current);
8409
+ }
8166
8410
  }
8167
- e.preventDefault();
8168
- const id = draggingIdRef.current;
8169
- const target = dropTargetIdRef.current;
8170
- if (id && target) reorderTabs(target, dropSideRef.current, tabsRef.current, id);
8411
+ pointerStateRef.current = null;
8171
8412
  setDraggingId(null);
8172
8413
  setDropTargetId(null);
8414
+ setDraggingGroupKey(null);
8415
+ setGroupDropTargetKey(null);
8416
+ setDragPos(null);
8417
+ requestAnimationFrame(() => {
8418
+ suppressClickRef.current = false;
8419
+ });
8173
8420
  };
8174
- document.addEventListener("dragover", onDocDragOver);
8175
- document.addEventListener("drop", onDocDrop);
8421
+ window.addEventListener("pointermove", onPointerMove);
8422
+ window.addEventListener("pointerup", onPointerUp);
8176
8423
  return () => {
8177
- document.removeEventListener("dragover", onDocDragOver);
8178
- document.removeEventListener("drop", onDocDrop);
8424
+ window.removeEventListener("pointermove", onPointerMove);
8425
+ window.removeEventListener("pointerup", onPointerUp);
8179
8426
  };
8180
- }, [onTabsReorder]);
8181
- const handleDragStart = (e, tabId) => {
8182
- setDraggingId(tabId);
8183
- e.dataTransfer.effectAllowed = "move";
8184
- };
8185
- const handleDrop = (e, targetTabId) => {
8186
- e.preventDefault();
8187
- e.stopPropagation();
8188
- dropHandledRef.current = true;
8189
- const id = draggingIdRef.current;
8190
- if (id && id !== targetTabId) reorderTabs(targetTabId, dropSideRef.current, tabsRef.current, id);
8191
- setDraggingId(null);
8192
- setDropTargetId(null);
8193
- };
8194
- const handleContainerDrop = (e) => {
8195
- e.preventDefault();
8196
- dropHandledRef.current = true;
8197
- const id = draggingIdRef.current;
8198
- if (id && onTabsReorder) {
8199
- const fromIdx = tabsRef.current.findIndex((t) => t.id === id);
8200
- if (fromIdx !== -1 && fromIdx < tabsRef.current.length - 1) {
8201
- const newTabs = [...tabsRef.current];
8202
- const moved = newTabs.splice(fromIdx, 1)[0];
8203
- newTabs.push(moved);
8204
- onTabsReorder(newTabs);
8205
- }
8206
- }
8207
- setDraggingId(null);
8208
- setDropTargetId(null);
8427
+ }, [reorderEnabled]);
8428
+ const handlePointerDown = (e, tabId) => {
8429
+ pointerStateRef.current = { kind: "tab", id: tabId, startX: e.clientX, startY: e.clientY, dragging: false };
8209
8430
  };
8210
- const handleDragEnd = () => {
8211
- dropHandledRef.current = false;
8212
- setDraggingId(null);
8213
- setDropTargetId(null);
8431
+ const handleGroupPointerDown = (e, groupKey) => {
8432
+ pointerStateRef.current = { kind: "group", id: groupKey, startX: e.clientX, startY: e.clientY, dragging: false };
8214
8433
  };
8215
8434
  const showPlus = !!onTabAdd;
8435
+ const canClose = tabs.length > 1;
8436
+ const draggingTab = draggingId ? tabs.find((tab) => tab.id === draggingId) : void 0;
8437
+ const draggingCluster = clusters?.find((c) => c.tabs.some((t) => t.id === draggingId));
8438
+ const draggingColor = draggingCluster?.group ? groupColors.get(draggingCluster.group.key)?.border : primaryColor;
8439
+ const draggingGroupCluster = draggingGroupKey ? clusters?.find((c) => c.group?.key === draggingGroupKey) : void 0;
8440
+ const draggingGroupColor = draggingGroupKey ? groupColors.get(draggingGroupKey) : void 0;
8441
+ const ghost = draggingGroupKey && draggingGroupCluster?.group ? {
8442
+ label: draggingGroupCluster.group.label,
8443
+ bg: (draggingGroupColor ?? TAB_PALETTE[0]).bg,
8444
+ border: (draggingGroupColor ?? TAB_PALETTE[0]).border,
8445
+ color: (draggingGroupColor ?? TAB_PALETTE[0]).base
8446
+ } : draggingTab ? { label: draggingTab.label, bg: "white", border: draggingColor ?? "#e5e7eb", color: "#111827" } : null;
8216
8447
  if (tabs.length === 0) return null;
8217
- return /* @__PURE__ */ jsx("div", { className: `flex-shrink-0 w-full overflow-hidden ${s.container}`, children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-stretch", children: [
8218
- showLeftFade && /* @__PURE__ */ jsx(
8219
- "div",
8220
- {
8221
- className: "absolute left-0 top-0 bottom-0 w-10 pointer-events-none z-10",
8222
- style: { background: easedFade("to right", s.fadeOpacity) }
8223
- }
8224
- ),
8225
- showRightFade && /* @__PURE__ */ jsx(
8226
- "div",
8227
- {
8228
- className: "absolute top-0 bottom-0 w-10 pointer-events-none z-10",
8229
- style: {
8230
- right: showPlus ? PLUS_BTN_WIDTH : 0,
8231
- background: easedFade("to left", s.fadeOpacity)
8448
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
8449
+ /* @__PURE__ */ jsx("div", { className: `flex-shrink-0 w-full overflow-hidden ${s.container}`, children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-stretch", children: [
8450
+ showLeftFade && /* @__PURE__ */ jsx(
8451
+ "div",
8452
+ {
8453
+ className: "absolute left-0 top-0 bottom-0 w-10 pointer-events-none z-10",
8454
+ style: { background: easedFade("to right", s.fadeOpacity) }
8232
8455
  }
8233
- }
8234
- ),
8235
- /* @__PURE__ */ jsx(
8236
- "div",
8237
- {
8238
- ref: scrollRef,
8239
- className: `flex-1 min-w-0 flex items-end gap-0.5 ${s.scrollPadding} overflow-x-auto [&::-webkit-scrollbar]:hidden`,
8240
- style: { scrollbarWidth: "none" },
8241
- onScroll: updateFades,
8242
- onDragOver: (e) => {
8243
- if (draggingId) e.preventDefault();
8244
- },
8245
- onDrop: handleContainerDrop,
8246
- children: tabs.map((tab) => {
8247
- const isActive = tab.id === activeTabId;
8248
- const isDraggingThis = tab.id === draggingId;
8249
- const isDropTarget = dropTargetId === tab.id;
8250
- return /* @__PURE__ */ jsxs(
8251
- "div",
8252
- {
8253
- ref: (el) => {
8254
- if (el) tabRefs.current.set(tab.id, el);
8255
- else tabRefs.current.delete(tab.id);
8256
- },
8257
- draggable: !!onTabsReorder,
8258
- onClick: () => {
8259
- if (!isActive) onTabClick(tab.id);
8260
- },
8261
- onDragStart: (e) => handleDragStart(e, tab.id),
8262
- onDrop: (e) => handleDrop(e, tab.id),
8263
- onDragEnd: handleDragEnd,
8264
- className: `
8265
- group relative flex items-center gap-1.5 ${s.tabPx} ${s.tabPy} flex-shrink-0
8266
- ${s.rounded} border border-b-0 select-none
8267
- transition-colors duration-100
8268
- ${isActive ? s.activeTab : s.inactiveTab}
8269
- ${onTabsReorder ? "cursor-grab" : "cursor-pointer"}
8270
- ${isDraggingThis ? "opacity-40" : ""}
8271
- ${isDropTarget && dropSide === "before" ? "[box-shadow:inset_2px_0_0_0_#3b82f6]" : ""}
8272
- ${isDropTarget && dropSide === "after" ? "[box-shadow:inset_-2px_0_0_0_#3b82f6]" : ""}
8273
- `,
8274
- children: [
8275
- isActive && /* @__PURE__ */ jsx(
8276
- "div",
8277
- {
8278
- className: `absolute top-0 left-0 right-0 h-0.5 ${s.rounded} ${s.indicator}`,
8279
- style: primaryColor ? { backgroundColor: primaryColor } : void 0
8280
- }
8281
- ),
8282
- /* @__PURE__ */ jsx(
8283
- "span",
8284
- {
8285
- className: `${s.tabTextSize} max-w-35 truncate leading-none ${isActive ? s.activeText : s.inactiveText}`,
8286
- children: tab.label
8287
- }
8288
- ),
8289
- tabs.length > 1 && /* @__PURE__ */ jsx(
8290
- "button",
8456
+ ),
8457
+ showRightFade && /* @__PURE__ */ jsx(
8458
+ "div",
8459
+ {
8460
+ className: "absolute top-0 bottom-0 w-10 pointer-events-none z-10",
8461
+ style: {
8462
+ right: showPlus ? PLUS_BTN_WIDTH : 0,
8463
+ background: easedFade("to left", s.fadeOpacity)
8464
+ }
8465
+ }
8466
+ ),
8467
+ /* @__PURE__ */ jsx(
8468
+ "div",
8469
+ {
8470
+ ref: scrollRef,
8471
+ className: `flex-1 min-w-0 flex items-end gap-0.5 ${s.scrollPadding} overflow-x-auto [&::-webkit-scrollbar]:hidden`,
8472
+ style: { scrollbarWidth: "none" },
8473
+ onScroll: updateFades,
8474
+ children: clusters ? clusters.map((cluster) => {
8475
+ if (!cluster.group) {
8476
+ return cluster.tabs.map((tab) => {
8477
+ const isDropTarget = dropTargetId === tab.id;
8478
+ const isGroupDropTarget2 = groupDropTargetKey === `tab:${tab.id}`;
8479
+ return /* @__PURE__ */ jsx(
8480
+ MultiTabPill,
8291
8481
  {
8292
- onClick: (e) => {
8293
- e.stopPropagation();
8294
- onTabClose(tab.id);
8482
+ tab,
8483
+ isActive: tab.id === activeTabId,
8484
+ showClose: canClose,
8485
+ accentColor: primaryColor,
8486
+ style: s,
8487
+ onClick: () => {
8488
+ if (suppressClickRef.current) return;
8489
+ onTabClick(tab.id);
8295
8490
  },
8296
- className: `
8297
- flex-shrink-0 ${s.closeBtnWH} flex items-center justify-center
8298
- transition-all duration-100 cursor-pointer
8299
- ${s.closeBtn}
8300
- ${isActive ? "opacity-100" : "opacity-40 group-hover:opacity-60"}
8301
- `,
8302
- children: /* @__PURE__ */ jsx(X, { size: s.closeBtnSize, strokeWidth: 2.5 })
8303
- }
8304
- )
8305
- ]
8306
- },
8307
- tab.id
8308
- );
8309
- })
8310
- }
8311
- ),
8312
- showPlus && /* @__PURE__ */ jsx(
8313
- "div",
8314
- {
8315
- className: `flex-shrink-0 flex items-center justify-center ${s.plusBtnArea}`,
8316
- style: { width: PLUS_BTN_WIDTH },
8317
- children: /* @__PURE__ */ jsx(
8318
- "button",
8319
- {
8320
- onClick: onTabAdd,
8321
- title: "\uD604\uC7AC \uD398\uC774\uC9C0\uB97C \uC0C8 \uD0ED\uC73C\uB85C \uC5F4\uAE30",
8322
- className: `w-6 h-6 flex items-center justify-center rounded cursor-pointer transition-colors ${s.plusBtn}`,
8323
- children: /* @__PURE__ */ jsx(Plus, { size: 13, strokeWidth: 2 })
8324
- }
8325
- )
8326
- }
8327
- )
8328
- ] }) });
8491
+ onClose: () => onTabClose(tab.id),
8492
+ registerRef: registerTabRef,
8493
+ draggable: reorderEnabled,
8494
+ isDraggingThis: tab.id === draggingId,
8495
+ dropIndicator: isDropTarget ? dropSide : isGroupDropTarget2 ? groupDropSide : null,
8496
+ onPointerDown: (e) => handlePointerDown(e, tab.id)
8497
+ },
8498
+ tab.id
8499
+ );
8500
+ });
8501
+ }
8502
+ const { key, label } = cluster.group;
8503
+ const color = groupColors.get(key) ?? TAB_PALETTE[0];
8504
+ const collapsed = collapsedGroups.has(key);
8505
+ const groupHasActiveTab = cluster.tabs.some((t) => t.id === activeTabId);
8506
+ const isGroupDropTarget = groupDropTargetKey === `group:${key}`;
8507
+ const isDraggingThisGroup = draggingGroupKey === key;
8508
+ return /* @__PURE__ */ jsxs(
8509
+ "div",
8510
+ {
8511
+ ref: (el) => registerGroupRef(key, el),
8512
+ className: "flex shrink-0 flex-col items-stretch",
8513
+ children: [
8514
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
8515
+ /* @__PURE__ */ jsxs(
8516
+ "button",
8517
+ {
8518
+ type: "button",
8519
+ onClick: () => {
8520
+ if (suppressClickRef.current) return;
8521
+ toggleGroupCollapsed(key);
8522
+ },
8523
+ onPointerDown: reorderEnabled ? (e) => handleGroupPointerDown(e, key) : void 0,
8524
+ "aria-expanded": !collapsed,
8525
+ title: collapsed ? "\uD558\uC704 \uD0ED \uD3BC\uCE58\uAE30" : "\uD558\uC704 \uD0ED \uC811\uAE30",
8526
+ className: `flex items-center gap-1 rounded-md border px-2.5 py-1 text-xs font-bold whitespace-nowrap select-none ${reorderEnabled ? isDraggingThisGroup ? "cursor-grabbing" : "cursor-grab" : "cursor-pointer"} ${isDraggingThisGroup ? "opacity-40" : ""} ${isGroupDropTarget && groupDropSide === "before" ? "[box-shadow:inset_2px_0_0_0_#3b82f6]" : ""} ${isGroupDropTarget && groupDropSide === "after" ? "[box-shadow:inset_-2px_0_0_0_#3b82f6]" : ""}`,
8527
+ style: { backgroundColor: color.bg, borderColor: color.border, color: color.base },
8528
+ children: [
8529
+ collapsed ? /* @__PURE__ */ jsx(ChevronRight, { size: 12 }) : /* @__PURE__ */ jsx(ChevronDown, { size: 12 }),
8530
+ label,
8531
+ canClose && /* @__PURE__ */ jsx(
8532
+ X,
8533
+ {
8534
+ size: 12,
8535
+ onClick: (e) => {
8536
+ e.stopPropagation();
8537
+ cluster.tabs.forEach((t) => onTabClose(t.id));
8538
+ }
8539
+ }
8540
+ )
8541
+ ]
8542
+ }
8543
+ ),
8544
+ !collapsed && cluster.tabs.map((tab) => {
8545
+ const isDropTarget = dropTargetId === tab.id;
8546
+ const isTabGroupDropTarget = groupDropTargetKey === `tab:${tab.id}`;
8547
+ return /* @__PURE__ */ jsx(
8548
+ MultiTabPill,
8549
+ {
8550
+ tab,
8551
+ isActive: tab.id === activeTabId,
8552
+ showClose: canClose,
8553
+ accentColor: color.base,
8554
+ style: s,
8555
+ onClick: () => {
8556
+ if (suppressClickRef.current) return;
8557
+ onTabClick(tab.id);
8558
+ },
8559
+ onClose: () => onTabClose(tab.id),
8560
+ registerRef: registerTabRef,
8561
+ draggable: reorderEnabled,
8562
+ isDraggingThis: tab.id === draggingId,
8563
+ dropIndicator: isDropTarget ? dropSide : isTabGroupDropTarget ? groupDropSide : null,
8564
+ onPointerDown: (e) => handlePointerDown(e, tab.id)
8565
+ },
8566
+ tab.id
8567
+ );
8568
+ })
8569
+ ] }),
8570
+ /* @__PURE__ */ jsx(
8571
+ "div",
8572
+ {
8573
+ className: "mt-1 h-0.75 rounded-full",
8574
+ style: { backgroundColor: color.base, opacity: groupHasActiveTab ? 1 : 0.35 }
8575
+ }
8576
+ )
8577
+ ]
8578
+ },
8579
+ key
8580
+ );
8581
+ }) : tabs.map((tab) => {
8582
+ const isDropTarget = dropTargetId === tab.id;
8583
+ return /* @__PURE__ */ jsx(
8584
+ MultiTabPill,
8585
+ {
8586
+ tab,
8587
+ isActive: tab.id === activeTabId,
8588
+ showClose: canClose,
8589
+ accentColor: primaryColor,
8590
+ style: s,
8591
+ onClick: () => {
8592
+ if (suppressClickRef.current) return;
8593
+ onTabClick(tab.id);
8594
+ },
8595
+ onClose: () => onTabClose(tab.id),
8596
+ registerRef: registerTabRef,
8597
+ draggable: reorderEnabled,
8598
+ isDraggingThis: tab.id === draggingId,
8599
+ dropIndicator: isDropTarget ? dropSide : null,
8600
+ onPointerDown: (e) => handlePointerDown(e, tab.id)
8601
+ },
8602
+ tab.id
8603
+ );
8604
+ })
8605
+ }
8606
+ ),
8607
+ showPlus && /* @__PURE__ */ jsx(
8608
+ "div",
8609
+ {
8610
+ className: `flex-shrink-0 flex items-center justify-center ${s.plusBtnArea}`,
8611
+ style: { width: PLUS_BTN_WIDTH },
8612
+ children: /* @__PURE__ */ jsx(
8613
+ "button",
8614
+ {
8615
+ onClick: onTabAdd,
8616
+ title: "\uD604\uC7AC \uD398\uC774\uC9C0\uB97C \uC0C8 \uD0ED\uC73C\uB85C \uC5F4\uAE30",
8617
+ className: `w-6 h-6 flex items-center justify-center rounded cursor-pointer transition-colors ${s.plusBtn}`,
8618
+ children: /* @__PURE__ */ jsx(Plus, { size: 13, strokeWidth: 2 })
8619
+ }
8620
+ )
8621
+ }
8622
+ )
8623
+ ] }) }),
8624
+ dragPos && ghost ? createPortal(
8625
+ /* @__PURE__ */ jsx(
8626
+ "div",
8627
+ {
8628
+ className: "pointer-events-none fixed z-50 flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-bold whitespace-nowrap shadow-lg",
8629
+ style: {
8630
+ left: dragPos.x + 12,
8631
+ top: dragPos.y + 12,
8632
+ backgroundColor: ghost.bg,
8633
+ borderColor: ghost.border,
8634
+ color: ghost.color,
8635
+ transform: "rotate(-2deg)"
8636
+ },
8637
+ children: ghost.label
8638
+ }
8639
+ ),
8640
+ document.body
8641
+ ) : null
8642
+ ] });
8329
8643
  }
8330
8644
  var userScopedStorage = createJSONStorage(() => ({
8331
8645
  getItem: (name) => {