@upstash/react-redis-browser 0.2.5 → 0.2.6

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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/databrowser/index.tsx
2
- import { useEffect as useEffect14, useMemo as useMemo8 } from "react";
2
+ import { useEffect as useEffect14, useMemo as useMemo9, useRef as useRef6 } from "react";
3
3
 
4
4
  // src/redis-context.tsx
5
5
  import { createContext, useContext, useMemo } from "react";
@@ -219,7 +219,8 @@ import { jsx as jsx2 } from "react/jsx-runtime";
219
219
  var DatabrowserContext = createContext2(void 0);
220
220
  var DatabrowserProvider = ({
221
221
  children,
222
- storage
222
+ storage,
223
+ rootRef
223
224
  }) => {
224
225
  const store = useMemo2(() => {
225
226
  if (!storage) return create(storeCreator);
@@ -241,18 +242,29 @@ var DatabrowserProvider = ({
241
242
  removeItem: () => {
242
243
  }
243
244
  },
244
- version: 1,
245
+ version: 2,
245
246
  // @ts-expect-error Reset the store for < v1
246
- migrate: (state, version) => {
247
+ migrate: (originalState, version) => {
248
+ const state = originalState;
247
249
  if (version === 0) {
248
250
  return;
249
251
  }
252
+ if (version === 1) {
253
+ return {
254
+ ...state,
255
+ tabs: state.tabs.map(([id, data]) => [id, { ...data, id }])
256
+ };
257
+ }
250
258
  return state;
251
259
  }
252
260
  })
253
261
  );
254
262
  }, []);
255
- return /* @__PURE__ */ jsx2(DatabrowserContext.Provider, { value: { store }, children });
263
+ return /* @__PURE__ */ jsx2(DatabrowserContext.Provider, { value: { store, rootRef }, children });
264
+ };
265
+ var useDatabrowserRootRef = () => {
266
+ const { rootRef } = useDatabrowser();
267
+ return rootRef;
256
268
  };
257
269
  var useDatabrowser = () => {
258
270
  const context = useContext2(DatabrowserContext);
@@ -271,8 +283,10 @@ var storeCreator = (set, get) => ({
271
283
  addTab: () => {
272
284
  const id = crypto.randomUUID();
273
285
  const newTabData = {
286
+ id,
274
287
  selectedKey: void 0,
275
- search: { key: "", type: void 0 }
288
+ search: { key: "", type: void 0 },
289
+ pinned: false
276
290
  };
277
291
  set((old) => ({
278
292
  tabs: [...old.tabs, [id, newTabData]],
@@ -282,6 +296,9 @@ var storeCreator = (set, get) => ({
282
296
  },
283
297
  reorderTabs: (oldIndex, newIndex) => {
284
298
  set((old) => {
299
+ const [, oldTabData] = old.tabs[oldIndex];
300
+ const [, newTabData] = old.tabs[newIndex];
301
+ if (oldTabData.pinned || newTabData.pinned) return old;
285
302
  const newTabs = [...old.tabs];
286
303
  const [movedTab] = newTabs.splice(oldIndex, 1);
287
304
  newTabs.splice(newIndex, 0, movedTab);
@@ -292,6 +309,8 @@ var storeCreator = (set, get) => ({
292
309
  set((old) => {
293
310
  const tabIndex = old.tabs.findIndex(([tabId]) => tabId === id);
294
311
  if (tabIndex === -1) return old;
312
+ const [, tabData] = old.tabs[tabIndex];
313
+ if (tabData.pinned) return old;
295
314
  const newTabs = [...old.tabs];
296
315
  newTabs.splice(tabIndex, 1);
297
316
  let selectedTab = old.selectedTab;
@@ -302,6 +321,59 @@ var storeCreator = (set, get) => ({
302
321
  return { tabs: newTabs, selectedTab };
303
322
  });
304
323
  },
324
+ forceRemoveTab: (id) => {
325
+ set((old) => {
326
+ const tabIndex = old.tabs.findIndex(([tabId]) => tabId === id);
327
+ if (tabIndex === -1) return old;
328
+ const newTabs = [...old.tabs];
329
+ newTabs.splice(tabIndex, 1);
330
+ let selectedTab = old.selectedTab;
331
+ if (selectedTab === id) {
332
+ const [newId] = newTabs[tabIndex - 1] ?? newTabs[tabIndex];
333
+ selectedTab = newTabs.length > 0 ? newId : void 0;
334
+ }
335
+ return { tabs: newTabs, selectedTab };
336
+ });
337
+ },
338
+ togglePinTab: (id) => {
339
+ set((old) => {
340
+ const tabIndex = old.tabs.findIndex(([tabId2]) => tabId2 === id);
341
+ if (tabIndex === -1) return old;
342
+ const newTabs = [...old.tabs];
343
+ const [tabId, tabData] = newTabs[tabIndex];
344
+ newTabs[tabIndex] = [tabId, { ...tabData, pinned: !tabData.pinned }];
345
+ return { ...old, tabs: newTabs };
346
+ });
347
+ },
348
+ duplicateTab: (id) => {
349
+ let newId;
350
+ set((old) => {
351
+ const tabIndex = old.tabs.findIndex(([tabId]) => tabId === id);
352
+ if (tabIndex === -1) return old;
353
+ const newTabs = [...old.tabs];
354
+ const [, tabData] = newTabs[tabIndex];
355
+ newId = crypto.randomUUID();
356
+ const duplicated = [newId, { ...tabData, id: newId }];
357
+ newTabs.splice(tabIndex + 1, 0, duplicated);
358
+ return { ...old, tabs: newTabs, selectedTab: newId };
359
+ });
360
+ return newId;
361
+ },
362
+ closeOtherTabs: (id) => {
363
+ set((old) => {
364
+ const exists = old.tabs.some(([tabId]) => tabId === id);
365
+ if (!exists) return old;
366
+ const newTabs = old.tabs.filter(([tabId]) => tabId === id);
367
+ return { ...old, tabs: newTabs, selectedTab: id };
368
+ });
369
+ },
370
+ closeAllButPinned: () => {
371
+ set((old) => {
372
+ const newTabs = old.tabs.filter(([, data]) => data.pinned);
373
+ const newSelected = newTabs.length > 0 ? newTabs[0][0] : void 0;
374
+ return { ...old, tabs: newTabs, selectedTab: newSelected };
375
+ });
376
+ },
305
377
  selectTab: (id) => {
306
378
  set({ selectedTab: id });
307
379
  },
@@ -410,6 +482,7 @@ var useTab = () => {
410
482
  selectedKey: tabData.selectedKey,
411
483
  selectedListItem: tabData.selectedListItem,
412
484
  search: tabData.search,
485
+ pinned: tabData.pinned,
413
486
  setSelectedKey: (key) => setSelectedKey(tabId, key),
414
487
  setSelectedListItem: (item) => setSelectedListItem(tabId, item),
415
488
  setSearch: (search) => setSearch(tabId, search),
@@ -3234,7 +3307,7 @@ var useKeyType = (key) => {
3234
3307
 
3235
3308
  // src/components/databrowser/components/display/display-list.tsx
3236
3309
  import { useMemo as useMemo7 } from "react";
3237
- import { IconTrash } from "@tabler/icons-react";
3310
+ import { IconTrash as IconTrash2 } from "@tabler/icons-react";
3238
3311
 
3239
3312
  // src/components/ui/button.tsx
3240
3313
  import * as React3 from "react";
@@ -3334,8 +3407,7 @@ var useAddKey = () => {
3334
3407
  }
3335
3408
  case "hash": {
3336
3409
  await redis.hset(key, {
3337
- field: "field",
3338
- value: "value"
3410
+ field: "value"
3339
3411
  });
3340
3412
  break;
3341
3413
  }
@@ -4150,6 +4222,7 @@ var useSetTTL = () => {
4150
4222
 
4151
4223
  // src/components/databrowser/components/item-context-menu.tsx
4152
4224
  import { useState as useState5 } from "react";
4225
+ import { IconCopy, IconExternalLink, IconTrash } from "@tabler/icons-react";
4153
4226
  import { ContextMenuSeparator as ContextMenuSeparator2 } from "@radix-ui/react-context-menu";
4154
4227
 
4155
4228
  // src/components/ui/context-menu.tsx
@@ -4448,7 +4521,7 @@ var ItemContextMenu = ({
4448
4521
  }
4449
4522
  ),
4450
4523
  /* @__PURE__ */ jsxs11(ContextMenuContent, { children: [
4451
- /* @__PURE__ */ jsx20(
4524
+ /* @__PURE__ */ jsxs11(
4452
4525
  ContextMenuItem,
4453
4526
  {
4454
4527
  onClick: () => {
@@ -4458,10 +4531,14 @@ var ItemContextMenu = ({
4458
4531
  description: "Key copied to clipboard"
4459
4532
  });
4460
4533
  },
4461
- children: "Copy key"
4534
+ className: "gap-2",
4535
+ children: [
4536
+ /* @__PURE__ */ jsx20(IconCopy, { size: 16 }),
4537
+ "Copy key"
4538
+ ]
4462
4539
  }
4463
4540
  ),
4464
- data?.value && /* @__PURE__ */ jsx20(
4541
+ data?.value && /* @__PURE__ */ jsxs11(
4465
4542
  ContextMenuItem,
4466
4543
  {
4467
4544
  onClick: () => {
@@ -4470,10 +4547,14 @@ var ItemContextMenu = ({
4470
4547
  description: "Value copied to clipboard"
4471
4548
  });
4472
4549
  },
4473
- children: "Copy value"
4550
+ className: "gap-2",
4551
+ children: [
4552
+ /* @__PURE__ */ jsx20(IconCopy, { size: 16 }),
4553
+ "Copy value"
4554
+ ]
4474
4555
  }
4475
4556
  ),
4476
- /* @__PURE__ */ jsx20(
4557
+ /* @__PURE__ */ jsxs11(
4477
4558
  ContextMenuItem,
4478
4559
  {
4479
4560
  onClick: () => {
@@ -4485,11 +4566,26 @@ var ItemContextMenu = ({
4485
4566
  key: data.key
4486
4567
  });
4487
4568
  },
4488
- children: "Open in new tab"
4569
+ className: "gap-2",
4570
+ children: [
4571
+ /* @__PURE__ */ jsx20(IconExternalLink, { size: 16 }),
4572
+ "Open in new tab"
4573
+ ]
4489
4574
  }
4490
4575
  ),
4491
4576
  /* @__PURE__ */ jsx20(ContextMenuSeparator2, {}),
4492
- /* @__PURE__ */ jsx20(ContextMenuItem, { disabled: type === "stream", onClick: () => setAlertOpen(true), children: "Delete item" })
4577
+ /* @__PURE__ */ jsxs11(
4578
+ ContextMenuItem,
4579
+ {
4580
+ disabled: type === "stream",
4581
+ onClick: () => setAlertOpen(true),
4582
+ className: "gap-2",
4583
+ children: [
4584
+ /* @__PURE__ */ jsx20(IconTrash, { size: 16 }),
4585
+ "Delete item"
4586
+ ]
4587
+ }
4588
+ )
4493
4589
  ] })
4494
4590
  ] })
4495
4591
  ] });
@@ -4982,7 +5078,7 @@ import { Editor, useMonaco } from "@monaco-editor/react";
4982
5078
 
4983
5079
  // src/components/databrowser/copy-button.tsx
4984
5080
  import { useState as useState6 } from "react";
4985
- import { IconCheck, IconCopy } from "@tabler/icons-react";
5081
+ import { IconCheck, IconCopy as IconCopy2 } from "@tabler/icons-react";
4986
5082
  import { jsx as jsx30 } from "react/jsx-runtime";
4987
5083
  function CopyButton({ value, ...props }) {
4988
5084
  const [copied, setCopied] = useState6(false);
@@ -5003,7 +5099,7 @@ function CopyButton({ value, ...props }) {
5003
5099
  variant: "secondary",
5004
5100
  size: "icon-sm",
5005
5101
  ...props,
5006
- children: copied ? /* @__PURE__ */ jsx30(IconCheck, { className: "size-4 text-green-500" }) : /* @__PURE__ */ jsx30(IconCopy, { className: "size-4 text-zinc-500" })
5102
+ children: copied ? /* @__PURE__ */ jsx30(IconCheck, { className: "size-4 text-green-500" }) : /* @__PURE__ */ jsx30(IconCopy2, { className: "size-4 text-zinc-500" })
5007
5103
  }
5008
5104
  );
5009
5105
  }
@@ -5418,7 +5514,7 @@ var ListItems = ({
5418
5514
  size: "icon-sm",
5419
5515
  variant: "secondary",
5420
5516
  onClick: (e) => e.stopPropagation(),
5421
- children: /* @__PURE__ */ jsx35(IconTrash, { className: "size-4 text-zinc-500" })
5517
+ children: /* @__PURE__ */ jsx35(IconTrash2, { className: "size-4 text-zinc-500" })
5422
5518
  }
5423
5519
  )
5424
5520
  }
@@ -5696,6 +5792,7 @@ var Empty = () => {
5696
5792
 
5697
5793
  // src/components/databrowser/components/sidebar-context-menu.tsx
5698
5794
  import { useState as useState10 } from "react";
5795
+ import { IconCopy as IconCopy3, IconExternalLink as IconExternalLink2, IconTrash as IconTrash3 } from "@tabler/icons-react";
5699
5796
  import { ContextMenuSeparator as ContextMenuSeparator3 } from "@radix-ui/react-context-menu";
5700
5797
  import { Fragment as Fragment8, jsx as jsx41, jsxs as jsxs26 } from "react/jsx-runtime";
5701
5798
  var SidebarContextMenu = ({ children }) => {
@@ -5735,7 +5832,7 @@ var SidebarContextMenu = ({ children }) => {
5735
5832
  }
5736
5833
  ),
5737
5834
  /* @__PURE__ */ jsxs26(ContextMenuContent, { children: [
5738
- /* @__PURE__ */ jsx41(
5835
+ /* @__PURE__ */ jsxs26(
5739
5836
  ContextMenuItem,
5740
5837
  {
5741
5838
  onClick: () => {
@@ -5744,10 +5841,14 @@ var SidebarContextMenu = ({ children }) => {
5744
5841
  description: "Key copied to clipboard"
5745
5842
  });
5746
5843
  },
5747
- children: "Copy key"
5844
+ className: "gap-2",
5845
+ children: [
5846
+ /* @__PURE__ */ jsx41(IconCopy3, { size: 16 }),
5847
+ "Copy key"
5848
+ ]
5748
5849
  }
5749
5850
  ),
5750
- /* @__PURE__ */ jsx41(
5851
+ /* @__PURE__ */ jsxs26(
5751
5852
  ContextMenuItem,
5752
5853
  {
5753
5854
  onClick: () => {
@@ -5756,11 +5857,18 @@ var SidebarContextMenu = ({ children }) => {
5756
5857
  setSearch(newTabId, currentSearch);
5757
5858
  selectTab(newTabId);
5758
5859
  },
5759
- children: "Open in new tab"
5860
+ className: "gap-2",
5861
+ children: [
5862
+ /* @__PURE__ */ jsx41(IconExternalLink2, { size: 16 }),
5863
+ "Open in new tab"
5864
+ ]
5760
5865
  }
5761
5866
  ),
5762
5867
  /* @__PURE__ */ jsx41(ContextMenuSeparator3, {}),
5763
- /* @__PURE__ */ jsx41(ContextMenuItem, { onClick: () => setAlertOpen(true), children: "Delete key" })
5868
+ /* @__PURE__ */ jsxs26(ContextMenuItem, { onClick: () => setAlertOpen(true), className: "gap-2", children: [
5869
+ /* @__PURE__ */ jsx41(IconTrash3, { size: 16 }),
5870
+ "Delete key"
5871
+ ] })
5764
5872
  ] })
5765
5873
  ] })
5766
5874
  ] });
@@ -6037,7 +6145,7 @@ var DatabrowserInstance = ({ hidden }) => {
6037
6145
  };
6038
6146
 
6039
6147
  // src/components/databrowser/components/databrowser-tabs.tsx
6040
- import { useCallback as useCallback3, useEffect as useEffect13, useRef as useRef5, useState as useState13 } from "react";
6148
+ import { useCallback as useCallback3, useEffect as useEffect13, useMemo as useMemo8, useRef as useRef5, useState as useState13 } from "react";
6041
6149
  import {
6042
6150
  closestCenter,
6043
6151
  DndContext,
@@ -6049,7 +6157,7 @@ import {
6049
6157
  import { restrictToHorizontalAxis } from "@dnd-kit/modifiers";
6050
6158
  import { horizontalListSortingStrategy, SortableContext, useSortable } from "@dnd-kit/sortable";
6051
6159
  import { CSS } from "@dnd-kit/utilities";
6052
- import { IconPlus as IconPlus2, IconSearch as IconSearch2 } from "@tabler/icons-react";
6160
+ import { IconChevronDown as IconChevronDown2, IconPlus as IconPlus2 } from "@tabler/icons-react";
6053
6161
 
6054
6162
  // src/components/ui/command.tsx
6055
6163
  import * as React13 from "react";
@@ -6152,7 +6260,14 @@ var CommandShortcut = ({
6152
6260
  CommandShortcut.displayName = "CommandShortcut";
6153
6261
 
6154
6262
  // src/components/databrowser/components/tab.tsx
6155
- import { IconSearch, IconX as IconX2 } from "@tabler/icons-react";
6263
+ import {
6264
+ IconArrowsMinimize,
6265
+ IconCopyPlus,
6266
+ IconPin,
6267
+ IconSearch,
6268
+ IconSquareX,
6269
+ IconX as IconX2
6270
+ } from "@tabler/icons-react";
6156
6271
 
6157
6272
  // src/components/databrowser/components/tab-type-icon.tsx
6158
6273
  import { jsx as jsx49 } from "react/jsx-runtime";
@@ -6191,24 +6306,45 @@ var useOverflow = () => {
6191
6306
 
6192
6307
  // src/components/databrowser/components/tab.tsx
6193
6308
  import { jsx as jsx50, jsxs as jsxs34 } from "react/jsx-runtime";
6194
- var Tab = ({ id }) => {
6195
- const { active, search, selectedKey } = useTab();
6196
- const { selectTab, removeTab, tabs } = useDatabrowserStore();
6309
+ var Tab = ({ id, isList }) => {
6310
+ const { active, search, selectedKey, pinned } = useTab();
6311
+ const {
6312
+ selectTab,
6313
+ removeTab,
6314
+ forceRemoveTab,
6315
+ tabs,
6316
+ togglePinTab,
6317
+ duplicateTab,
6318
+ closeOtherTabs,
6319
+ closeAllButPinned
6320
+ } = useDatabrowserStore();
6321
+ const hasPinnedTabs = tabs.some(([, data]) => data.pinned);
6197
6322
  const { ref, isOverflow } = useOverflow();
6198
6323
  const label = search.key || selectedKey;
6199
6324
  const iconNode = search.key ? /* @__PURE__ */ jsx50(IconSearch, { size: 15 }) : selectedKey ? /* @__PURE__ */ jsx50(TabTypeIcon, { selectedKey }) : void 0;
6200
6325
  const tabNode = /* @__PURE__ */ jsxs34(
6201
6326
  "div",
6202
6327
  {
6328
+ id: isList ? `list-tab-${id}` : `tab-${id}`,
6203
6329
  onClick: () => selectTab(id),
6204
6330
  className: cn(
6205
- "flex h-9 cursor-pointer items-center gap-2 rounded-t-lg border border-zinc-200 px-3 text-[13px] transition-colors",
6206
- active ? "border-b-white bg-white text-zinc-900" : "bg-zinc-100 hover:bg-zinc-50"
6331
+ "flex h-9 w-full cursor-pointer items-center gap-2 px-3 text-[13px] transition-colors",
6332
+ isList && "max-w-[370px]",
6333
+ !isList && "rounded-t-lg border border-zinc-200",
6334
+ !isList && (active ? "border-b-white bg-white text-zinc-900" : "bg-zinc-100 hover:bg-zinc-50")
6207
6335
  ),
6208
6336
  children: [
6209
6337
  iconNode,
6210
- /* @__PURE__ */ jsx50("span", { ref, className: "max-w-32 truncate whitespace-nowrap", children: label || "New Tab" }),
6211
- tabs.length > 1 && /* @__PURE__ */ jsx50(
6338
+ /* @__PURE__ */ jsx50(
6339
+ "span",
6340
+ {
6341
+ ref,
6342
+ className: cn("min-w-0 grow truncate whitespace-nowrap", !isList && "max-w-32"),
6343
+ children: label || "New Tab"
6344
+ }
6345
+ ),
6346
+ pinned && /* @__PURE__ */ jsx50(IconPin, { size: 14, className: "text-zinc-500" }),
6347
+ tabs.length > 1 && !pinned && /* @__PURE__ */ jsx50(
6212
6348
  "button",
6213
6349
  {
6214
6350
  onClick: (e) => {
@@ -6222,7 +6358,48 @@ var Tab = ({ id }) => {
6222
6358
  ]
6223
6359
  }
6224
6360
  );
6225
- return /* @__PURE__ */ jsx50(SimpleTooltip, { content: isOverflow ? label : void 0, children: tabNode });
6361
+ return /* @__PURE__ */ jsxs34(ContextMenu, { children: [
6362
+ /* @__PURE__ */ jsx50(SimpleTooltip, { content: isOverflow ? label : void 0, children: /* @__PURE__ */ jsx50(ContextMenuTrigger, { asChild: true, children: tabNode }) }),
6363
+ /* @__PURE__ */ jsxs34(
6364
+ ContextMenuContent,
6365
+ {
6366
+ onClick: (e) => {
6367
+ e.stopPropagation();
6368
+ },
6369
+ children: [
6370
+ /* @__PURE__ */ jsxs34(ContextMenuItem, { onSelect: () => togglePinTab(id), className: "gap-2", children: [
6371
+ /* @__PURE__ */ jsx50(IconPin, { size: 16 }),
6372
+ pinned ? "Unpin Tab" : "Pin Tab"
6373
+ ] }),
6374
+ /* @__PURE__ */ jsxs34(ContextMenuItem, { onSelect: () => duplicateTab(id), className: "gap-2", children: [
6375
+ /* @__PURE__ */ jsx50(IconCopyPlus, { size: 16 }),
6376
+ "Duplicate Tab"
6377
+ ] }),
6378
+ /* @__PURE__ */ jsx50(ContextMenuSeparator, {}),
6379
+ /* @__PURE__ */ jsxs34(ContextMenuItem, { onSelect: () => forceRemoveTab(id), className: "gap-2", children: [
6380
+ /* @__PURE__ */ jsx50(IconX2, { size: 16 }),
6381
+ "Close Tab"
6382
+ ] }),
6383
+ /* @__PURE__ */ jsxs34(ContextMenuItem, { onSelect: () => closeOtherTabs(id), className: "gap-2", children: [
6384
+ /* @__PURE__ */ jsx50(IconSquareX, { size: 16 }),
6385
+ "Close Other Tabs"
6386
+ ] }),
6387
+ /* @__PURE__ */ jsxs34(
6388
+ ContextMenuItem,
6389
+ {
6390
+ onSelect: () => closeAllButPinned(),
6391
+ className: "gap-2",
6392
+ disabled: !hasPinnedTabs,
6393
+ children: [
6394
+ /* @__PURE__ */ jsx50(IconArrowsMinimize, { size: 16 }),
6395
+ "Close All But Pinned"
6396
+ ]
6397
+ }
6398
+ )
6399
+ ]
6400
+ }
6401
+ )
6402
+ ] });
6226
6403
  };
6227
6404
 
6228
6405
  // src/components/databrowser/components/databrowser-tabs.tsx
@@ -6230,8 +6407,12 @@ import { jsx as jsx51, jsxs as jsxs35 } from "react/jsx-runtime";
6230
6407
  var SortableTab = ({ id }) => {
6231
6408
  const [originalWidth, setOriginalWidth] = useState13(null);
6232
6409
  const textRef = useRef5(null);
6410
+ const { tabs } = useDatabrowserStore();
6411
+ const tabData = tabs.find(([tabId]) => tabId === id)?.[1];
6412
+ const isPinned = tabData?.pinned;
6233
6413
  const { attributes, listeners: listeners2, setNodeRef, transform, transition, isDragging } = useSortable({
6234
6414
  id,
6415
+ disabled: isPinned,
6235
6416
  resizeObserverConfig: {
6236
6417
  disabled: true
6237
6418
  }
@@ -6295,15 +6476,22 @@ var SortableTab = ({ id }) => {
6295
6476
  {
6296
6477
  ref: measureRef,
6297
6478
  style,
6298
- className: isDragging ? "cursor-grabbing" : "cursor-grab",
6479
+ className: isDragging ? "cursor-grabbing" : isPinned ? "cursor-default" : "cursor-grab",
6299
6480
  ...attributes,
6300
- ...listeners2,
6481
+ ...isPinned ? {} : listeners2,
6301
6482
  children: /* @__PURE__ */ jsx51(TabIdProvider, { value: id, children: /* @__PURE__ */ jsx51(Tab, { id }) })
6302
6483
  }
6303
6484
  );
6304
6485
  };
6305
6486
  var DatabrowserTabs = () => {
6306
- const { tabs, addTab, reorderTabs, selectedTab, selectTab } = useDatabrowserStore();
6487
+ const { tabs, reorderTabs, selectedTab, selectTab } = useDatabrowserStore();
6488
+ const sortedTabs = useMemo8(() => {
6489
+ return [...tabs].sort(([, a], [, b]) => {
6490
+ if (a.pinned && !b.pinned) return -1;
6491
+ if (!a.pinned && b.pinned) return 1;
6492
+ return 0;
6493
+ });
6494
+ }, [tabs]);
6307
6495
  const scrollRef = useRef5(null);
6308
6496
  const [hasLeftShadow, setHasLeftShadow] = useState13(false);
6309
6497
  const [hasRightShadow, setHasRightShadow] = useState13(false);
@@ -6404,115 +6592,103 @@ var DatabrowserTabs = () => {
6404
6592
  children: /* @__PURE__ */ jsx51(
6405
6593
  SortableContext,
6406
6594
  {
6407
- items: tabs.map(([id]) => id),
6595
+ items: sortedTabs.map(([id]) => id),
6408
6596
  strategy: horizontalListSortingStrategy,
6409
- children: selectedTab && tabs.map(([id]) => /* @__PURE__ */ jsx51(SortableTab, { id }, id))
6597
+ children: selectedTab && sortedTabs.map(([id]) => /* @__PURE__ */ jsx51(SortableTab, { id }, id))
6410
6598
  }
6411
6599
  )
6412
6600
  }
6413
6601
  ),
6414
- !isOverflow && /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-1 pl-1 pr-1", children: [
6415
- tabs.length > 4 && /* @__PURE__ */ jsx51(TabSearch, { tabs, onSelectTab: selectTab }),
6416
- /* @__PURE__ */ jsx51(
6417
- Button,
6418
- {
6419
- variant: "secondary",
6420
- size: "icon-sm",
6421
- onClick: addTab,
6422
- className: "flex-shrink-0",
6423
- title: "Add new tab",
6424
- children: /* @__PURE__ */ jsx51(IconPlus2, { className: "text-zinc-500", size: 16 })
6425
- }
6426
- )
6427
- ] })
6602
+ !isOverflow && /* @__PURE__ */ jsx51("div", { className: "flex items-center gap-1 pl-1 pr-1", children: /* @__PURE__ */ jsx51(AddTabButton, {}) })
6428
6603
  ]
6429
6604
  }
6430
6605
  )
6431
6606
  ] }),
6432
- isOverflow && /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-1 pl-1", children: [
6433
- tabs.length > 4 && /* @__PURE__ */ jsx51(TabSearch, { tabs, onSelectTab: selectTab }),
6434
- /* @__PURE__ */ jsx51(
6435
- Button,
6436
- {
6437
- variant: "secondary",
6438
- size: "icon-sm",
6439
- onClick: addTab,
6440
- className: "mr-1 flex-shrink-0",
6441
- title: "Add new tab",
6442
- children: /* @__PURE__ */ jsx51(IconPlus2, { className: "text-zinc-500", size: 16 })
6443
- }
6444
- )
6607
+ /* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-1 pl-1", children: [
6608
+ isOverflow && /* @__PURE__ */ jsx51(AddTabButton, {}),
6609
+ tabs.length > 1 && /* @__PURE__ */ jsx51(TabsListButton, { tabs, onSelectTab: selectTab })
6445
6610
  ] })
6446
6611
  ] })
6447
6612
  ] });
6448
6613
  };
6449
- function TabSearch({
6614
+ function AddTabButton() {
6615
+ const { addTab, selectTab } = useDatabrowserStore();
6616
+ const rootRef = useDatabrowserRootRef();
6617
+ const handleAddTab = () => {
6618
+ const tabsId = addTab();
6619
+ selectTab(tabsId);
6620
+ setTimeout(() => {
6621
+ const tab = rootRef?.current?.querySelector(`#tab-${tabsId}`);
6622
+ if (!tab) return;
6623
+ tab.scrollIntoView({ behavior: "smooth" });
6624
+ }, 20);
6625
+ };
6626
+ return /* @__PURE__ */ jsx51(
6627
+ Button,
6628
+ {
6629
+ "aria-label": "Add new tab",
6630
+ variant: "secondary",
6631
+ size: "icon-sm",
6632
+ onClick: handleAddTab,
6633
+ className: "flex-shrink-0",
6634
+ children: /* @__PURE__ */ jsx51(IconPlus2, { className: "text-zinc-500", size: 16 })
6635
+ }
6636
+ );
6637
+ }
6638
+ function TabsListButton({
6450
6639
  tabs,
6451
6640
  onSelectTab
6452
6641
  }) {
6453
6642
  const [open, setOpen] = useState13(false);
6454
- const [query, setQuery] = useState13("");
6455
- const items = tabs.map(([id, data]) => ({
6456
- id,
6457
- label: data.search.key || data.selectedKey || "New Tab",
6458
- searchKey: data.search.key,
6459
- selectedKey: data.selectedKey,
6460
- selectedItemKey: data.selectedListItem?.key
6461
- }));
6462
- const buildDisplayLabel = (it) => it.selectedItemKey ? `${it.label} > ${it.selectedItemKey}` : it.label;
6463
- const dedupedMap = /* @__PURE__ */ new Map();
6464
- for (const it of items) {
6465
- const display = buildDisplayLabel(it);
6466
- const key = display.toLowerCase();
6467
- if (!dedupedMap.has(key)) dedupedMap.set(key, it);
6468
- }
6469
- const deduped = [...dedupedMap.values()];
6470
- const filtered = (query ? deduped.filter((i) => buildDisplayLabel(i).toLowerCase().includes(query.toLowerCase())) : deduped).sort((a, b) => buildDisplayLabel(a).localeCompare(buildDisplayLabel(b)));
6471
- return /* @__PURE__ */ jsxs35(
6472
- Popover,
6473
- {
6474
- open,
6475
- onOpenChange: (v) => {
6476
- setOpen(v);
6477
- if (!v) setQuery("");
6478
- },
6479
- children: [
6480
- /* @__PURE__ */ jsxs35(Tooltip, { delayDuration: 400, children: [
6481
- /* @__PURE__ */ jsx51(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx51(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx51(Button, { variant: "secondary", size: "icon-sm", "aria-label": "Search in tabs", children: /* @__PURE__ */ jsx51(IconSearch2, { className: "text-zinc-500", size: 16 }) }) }) }),
6482
- /* @__PURE__ */ jsx51(TooltipContent, { side: "top", children: "Search in tabs" })
6483
- ] }),
6484
- /* @__PURE__ */ jsx51(PopoverContent, { className: "w-72 p-0", align: "end", children: /* @__PURE__ */ jsxs35(Command, { children: [
6485
- /* @__PURE__ */ jsx51(
6486
- CommandInput,
6487
- {
6488
- placeholder: "Search tabs...",
6489
- value: query,
6490
- onValueChange: (v) => setQuery(v),
6491
- className: "h-9"
6492
- }
6493
- ),
6494
- /* @__PURE__ */ jsxs35(CommandList, { children: [
6495
- /* @__PURE__ */ jsx51(CommandEmpty, { children: "No tabs" }),
6496
- /* @__PURE__ */ jsx51(CommandGroup, { children: filtered.map((item) => /* @__PURE__ */ jsxs35(
6497
- CommandItem,
6498
- {
6499
- value: buildDisplayLabel(item),
6500
- onSelect: () => {
6501
- onSelectTab(item.id);
6502
- setOpen(false);
6503
- },
6504
- children: [
6505
- item.searchKey ? /* @__PURE__ */ jsx51(IconSearch2, { size: 15 }) : /* @__PURE__ */ jsx51(TabTypeIcon, { selectedKey: item.selectedKey }),
6506
- /* @__PURE__ */ jsx51("span", { className: "truncate", children: buildDisplayLabel(item) })
6507
- ]
6508
- },
6509
- item.id
6510
- )) })
6511
- ] })
6512
- ] }) })
6513
- ]
6514
- }
6515
- );
6643
+ const sorted = useMemo8(() => {
6644
+ return [...tabs].sort(([, a], [, b]) => {
6645
+ if (a.pinned && !b.pinned) return -1;
6646
+ if (!a.pinned && b.pinned) return 1;
6647
+ return 0;
6648
+ });
6649
+ }, [tabs]);
6650
+ const rootRef = useDatabrowserRootRef();
6651
+ const handleSelectTab = (id) => {
6652
+ onSelectTab(id);
6653
+ setOpen(false);
6654
+ setTimeout(() => {
6655
+ const tab = rootRef?.current?.querySelector(`#tab-${id}`);
6656
+ if (!tab) return;
6657
+ tab.scrollIntoView({ behavior: "smooth" });
6658
+ }, 20);
6659
+ };
6660
+ return /* @__PURE__ */ jsxs35(Popover, { open, onOpenChange: setOpen, children: [
6661
+ /* @__PURE__ */ jsx51(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs35(
6662
+ Button,
6663
+ {
6664
+ variant: "secondary",
6665
+ size: "sm",
6666
+ className: "h-7 gap-1 px-2",
6667
+ "aria-label": "Search in tabs",
6668
+ children: [
6669
+ /* @__PURE__ */ jsx51("span", { className: "text-xs text-zinc-600", children: tabs.length }),
6670
+ /* @__PURE__ */ jsx51(IconChevronDown2, { className: "text-zinc-500", size: 16 })
6671
+ ]
6672
+ }
6673
+ ) }),
6674
+ /* @__PURE__ */ jsx51(PopoverContent, { className: "w-96 p-0", align: "end", children: /* @__PURE__ */ jsx51(Command, { children: /* @__PURE__ */ jsxs35(CommandList, { children: [
6675
+ /* @__PURE__ */ jsx51(CommandEmpty, { children: "No tabs" }),
6676
+ /* @__PURE__ */ jsx51(CommandGroup, { children: sorted.map(([_id, item]) => /* @__PURE__ */ jsx51(
6677
+ CommandItem,
6678
+ {
6679
+ style: {
6680
+ padding: 0
6681
+ },
6682
+ value: item.id,
6683
+ onSelect: () => {
6684
+ handleSelectTab(item.id);
6685
+ },
6686
+ children: /* @__PURE__ */ jsx51(TabIdProvider, { value: _id, children: /* @__PURE__ */ jsx51(Tab, { id: _id, isList: true }) })
6687
+ },
6688
+ item.id
6689
+ )) })
6690
+ ] }) }) })
6691
+ ] });
6516
6692
  }
6517
6693
 
6518
6694
  // src/components/databrowser/index.tsx
@@ -6523,15 +6699,17 @@ var RedisBrowser = ({
6523
6699
  hideTabs,
6524
6700
  storage
6525
6701
  }) => {
6526
- const credentials = useMemo8(() => ({ token, url }), [token, url]);
6702
+ const credentials = useMemo9(() => ({ token, url }), [token, url]);
6703
+ const rootRef = useRef6(null);
6527
6704
  useEffect14(() => {
6528
6705
  queryClient.resetQueries();
6529
6706
  }, [credentials.url]);
6530
- return /* @__PURE__ */ jsx52(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx52(RedisProvider, { redisCredentials: credentials, children: /* @__PURE__ */ jsx52(DatabrowserProvider, { storage, children: /* @__PURE__ */ jsx52(TooltipProvider, { children: /* @__PURE__ */ jsxs36(
6707
+ return /* @__PURE__ */ jsx52(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx52(RedisProvider, { redisCredentials: credentials, children: /* @__PURE__ */ jsx52(DatabrowserProvider, { storage, rootRef, children: /* @__PURE__ */ jsx52(TooltipProvider, { children: /* @__PURE__ */ jsxs36(
6531
6708
  "div",
6532
6709
  {
6533
6710
  className: "ups-db",
6534
6711
  style: { height: "100%", display: "flex", flexDirection: "column" },
6712
+ ref: rootRef,
6535
6713
  children: [
6536
6714
  !hideTabs && /* @__PURE__ */ jsx52(DatabrowserTabs, {}),
6537
6715
  /* @__PURE__ */ jsx52(DatabrowserInstances, {})