@farmzone/fz-react-ui 1.1.2 → 1.1.4

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