@sqlrooms/ui 0.29.0-rc.1 → 0.29.0-rc.11

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.
Files changed (118) hide show
  1. package/LICENSE.md +2 -1
  2. package/README.md +98 -4
  3. package/dist/components/accordion.d.ts +2 -2
  4. package/dist/components/accordion.d.ts.map +1 -1
  5. package/dist/components/accordion.js +3 -3
  6. package/dist/components/accordion.js.map +1 -1
  7. package/dist/components/alert.js +1 -1
  8. package/dist/components/alert.js.map +1 -1
  9. package/dist/components/card.js +1 -1
  10. package/dist/components/card.js.map +1 -1
  11. package/dist/components/combobox.d.ts +67 -1
  12. package/dist/components/combobox.d.ts.map +1 -1
  13. package/dist/components/combobox.js +58 -32
  14. package/dist/components/combobox.js.map +1 -1
  15. package/dist/components/command.js +1 -1
  16. package/dist/components/command.js.map +1 -1
  17. package/dist/components/editable-text.d.ts +2 -1
  18. package/dist/components/editable-text.d.ts.map +1 -1
  19. package/dist/components/editable-text.js +38 -11
  20. package/dist/components/editable-text.js.map +1 -1
  21. package/dist/components/error-pane.d.ts.map +1 -1
  22. package/dist/components/error-pane.js +2 -2
  23. package/dist/components/error-pane.js.map +1 -1
  24. package/dist/components/hover-card.d.ts +7 -0
  25. package/dist/components/hover-card.d.ts.map +1 -0
  26. package/dist/components/hover-card.js +15 -0
  27. package/dist/components/hover-card.js.map +1 -0
  28. package/dist/components/modifier-scroll-overlay.d.ts +16 -0
  29. package/dist/components/modifier-scroll-overlay.d.ts.map +1 -0
  30. package/dist/components/modifier-scroll-overlay.js +31 -0
  31. package/dist/components/modifier-scroll-overlay.js.map +1 -0
  32. package/dist/components/progress-modal.d.ts +2 -0
  33. package/dist/components/progress-modal.d.ts.map +1 -1
  34. package/dist/components/progress-modal.js +2 -2
  35. package/dist/components/progress-modal.js.map +1 -1
  36. package/dist/components/progress.d.ts.map +1 -1
  37. package/dist/components/progress.js.map +1 -1
  38. package/dist/components/resizable.d.ts +302 -20
  39. package/dist/components/resizable.d.ts.map +1 -1
  40. package/dist/components/resizable.js +12 -5
  41. package/dist/components/resizable.js.map +1 -1
  42. package/dist/components/run-button.d.ts +19 -0
  43. package/dist/components/run-button.d.ts.map +1 -0
  44. package/dist/components/run-button.js +17 -0
  45. package/dist/components/run-button.js.map +1 -0
  46. package/dist/components/settings-panel-header.d.ts +16 -0
  47. package/dist/components/settings-panel-header.d.ts.map +1 -0
  48. package/dist/components/settings-panel-header.js +9 -0
  49. package/dist/components/settings-panel-header.js.map +1 -0
  50. package/dist/components/sidebar.d.ts +3 -2
  51. package/dist/components/sidebar.d.ts.map +1 -1
  52. package/dist/components/sidebar.js +16 -12
  53. package/dist/components/sidebar.js.map +1 -1
  54. package/dist/components/sonner.d.ts +4 -0
  55. package/dist/components/sonner.d.ts.map +1 -0
  56. package/dist/components/sonner.js +37 -0
  57. package/dist/components/sonner.js.map +1 -0
  58. package/dist/components/switch.d.ts +5 -1
  59. package/dist/components/switch.d.ts.map +1 -1
  60. package/dist/components/switch.js +7 -2
  61. package/dist/components/switch.js.map +1 -1
  62. package/dist/components/tab-strip.d.ts +27 -7
  63. package/dist/components/tab-strip.d.ts.map +1 -1
  64. package/dist/components/tab-strip.js +165 -26
  65. package/dist/components/tab-strip.js.map +1 -1
  66. package/dist/components/textarea.d.ts +3 -0
  67. package/dist/components/textarea.d.ts.map +1 -1
  68. package/dist/components/textarea.js +87 -11
  69. package/dist/components/textarea.js.map +1 -1
  70. package/dist/components/theme-switch.d.ts.map +1 -1
  71. package/dist/components/theme-switch.js +3 -3
  72. package/dist/components/theme-switch.js.map +1 -1
  73. package/dist/components/tree.d.ts.map +1 -1
  74. package/dist/components/tree.js +4 -3
  75. package/dist/components/tree.js.map +1 -1
  76. package/dist/hooks/use-mobile.d.ts.map +1 -1
  77. package/dist/hooks/use-mobile.js +1 -0
  78. package/dist/hooks/use-mobile.js.map +1 -1
  79. package/dist/hooks/useAspectRatioDimensions.js +3 -3
  80. package/dist/hooks/useAspectRatioDimensions.js.map +1 -1
  81. package/dist/hooks/useCombobox.d.ts +33 -0
  82. package/dist/hooks/useCombobox.d.ts.map +1 -0
  83. package/dist/hooks/useCombobox.js +30 -0
  84. package/dist/hooks/useCombobox.js.map +1 -0
  85. package/dist/hooks/useDebounce.d.ts +33 -0
  86. package/dist/hooks/useDebounce.d.ts.map +1 -0
  87. package/dist/hooks/useDebounce.js +45 -0
  88. package/dist/hooks/useDebounce.js.map +1 -0
  89. package/dist/hooks/useDebouncedCallback.d.ts +45 -0
  90. package/dist/hooks/useDebouncedCallback.d.ts.map +1 -0
  91. package/dist/hooks/useDebouncedCallback.js +70 -0
  92. package/dist/hooks/useDebouncedCallback.js.map +1 -0
  93. package/dist/hooks/useDebouncedValue.d.ts +23 -0
  94. package/dist/hooks/useDebouncedValue.d.ts.map +1 -0
  95. package/dist/hooks/useDebouncedValue.js +41 -0
  96. package/dist/hooks/useDebouncedValue.js.map +1 -0
  97. package/dist/index.d.ts +36 -29
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +34 -28
  100. package/dist/index.js.map +1 -1
  101. package/dist/theme/theme-provider.d.ts +22 -1
  102. package/dist/theme/theme-provider.d.ts.map +1 -1
  103. package/dist/theme/theme-provider.js +59 -20
  104. package/dist/theme/theme-provider.js.map +1 -1
  105. package/package.json +21 -16
  106. package/tailwind-preset.css +78 -0
  107. package/dist/components/toast.d.ts +0 -16
  108. package/dist/components/toast.d.ts.map +0 -1
  109. package/dist/components/toast.js +0 -35
  110. package/dist/components/toast.js.map +0 -1
  111. package/dist/components/toaster.d.ts +0 -3
  112. package/dist/components/toaster.d.ts.map +0 -1
  113. package/dist/components/toaster.js +0 -24
  114. package/dist/components/toaster.js.map +0 -1
  115. package/dist/hooks/use-toast.d.ts +0 -45
  116. package/dist/hooks/use-toast.d.ts.map +0 -1
  117. package/dist/hooks/use-toast.js +0 -129
  118. package/dist/hooks/use-toast.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { DndContext, PointerSensor, closestCenter, useSensor, useSensors, } from '@dnd-kit/core';
2
+ import { DndContext, DragOverlay, PointerSensor, closestCenter, useDndMonitor, useSensor, useSensors, } from '@dnd-kit/core';
3
3
  import { restrictToHorizontalAxis, restrictToParentElement, } from '@dnd-kit/modifiers';
4
4
  import { SortableContext, arrayMove, horizontalListSortingStrategy, useSortable, } from '@dnd-kit/sortable';
5
5
  import { EllipsisVerticalIcon, ListCollapseIcon, PlusIcon, SearchIcon, XIcon, } from 'lucide-react';
6
- import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
6
+ import React, { createContext, useContext, useEffect, useLayoutEffect, useId, useMemo, useRef, useState, } from 'react';
7
7
  const DRAG_MODIFIERS = [restrictToHorizontalAxis, restrictToParentElement];
8
8
  import { cn } from '../lib/utils';
9
9
  import { Button } from './button';
@@ -21,11 +21,43 @@ function useTabStripContext() {
21
21
  }
22
22
  return context;
23
23
  }
24
+ function useOptionalTabStripContext() {
25
+ return useContext(TabStripContext);
26
+ }
27
+ function getFontSizeStyle(fontSize) {
28
+ return fontSize === undefined
29
+ ? undefined
30
+ : { fontSize };
31
+ }
32
+ function mergeFontSizeStyle(fontSize, style) {
33
+ if (fontSize === undefined)
34
+ return style;
35
+ if (style === undefined)
36
+ return { fontSize };
37
+ return { ...style, fontSize: style?.fontSize ?? fontSize };
38
+ }
39
+ const DEFAULT_TAB_DRAG_KIND = 'sqlrooms.tab';
40
+ const TAB_STRIP_BUTTON_CLASSNAMES = [
41
+ 'flex h-full min-w-0 min-h-7 items-center',
42
+ 'hover:bg-primary/10 overflow-hidden px-6 py-1 font-normal',
43
+ 'focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-inset',
44
+ ];
24
45
  /**
25
46
  * A single sortable tab item.
26
47
  */
27
- function SortableTab({ tab, tabClassName, editingTabId, hideCloseButton, onClose, onStartEditing, onStopEditing, onInlineRename, renderTabTitle, renderTabMenu, renderTabLabel, }) {
28
- const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: tab.id });
48
+ function SortableTab({ tab, sortableId, tabClassName, editingTabId, closeable = true, onClose, onActivate, onStartEditing, onStopEditing, onInlineRename, renderTabMenu, renderTabLabel, dndScopeId, getTabDragData, fontSize, }) {
49
+ const tabDragData = getTabDragData?.(tab);
50
+ const isEditing = editingTabId === tab.id;
51
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
52
+ id: sortableId,
53
+ disabled: isEditing,
54
+ data: {
55
+ kind: DEFAULT_TAB_DRAG_KIND,
56
+ ...tabDragData,
57
+ tabId: tab.id,
58
+ tabStripId: dndScopeId,
59
+ },
60
+ });
29
61
  const style = {
30
62
  // Use Translate instead of Transform to avoid scale changes that squeeze the tab
31
63
  transform: transform
@@ -35,16 +67,17 @@ function SortableTab({ tab, tabClassName, editingTabId, hideCloseButton, onClose
35
67
  opacity: isDragging ? 0.5 : 1,
36
68
  };
37
69
  const menuContent = renderTabMenu?.(tab);
38
- return (_jsx("div", { ref: setNodeRef, className: "h-full shrink-0", style: style, "data-tab-id": tab.id, ...attributes, ...listeners, tabIndex: -1, children: _jsx("div", { "data-state": editingTabId === tab.id ? 'editing' : undefined, className: cn('data-[state=inactive]:hover:bg-primary/5', 'group flex h-full max-w-[200px] min-w-[100px] shrink-0 cursor-grab', 'items-center justify-between gap-1 overflow-hidden rounded-b-none', 'py-0 pr-1 pl-4 font-normal data-[state=active]:shadow-none', tabClassName, editingTabId === tab.id && 'focus-visible:ring-0'), children: _jsxs("div", { className: "relative flex h-full min-w-0 flex-1 items-center", children: [_jsx(TabsTrigger, { value: tab.id, tabIndex: editingTabId === tab.id ? -1 : undefined, "data-editing": editingTabId === tab.id ? '' : undefined, className: cn('flex h-full min-w-0 flex-1 items-center justify-start gap-1', 'hover:bg-primary/10 overflow-hidden px-6 py-1 font-normal', 'min-h-7', 'data-[state=active]:bg-primary/10 data-[state=active]:text-foreground data-[state=active]:shadow-none', 'focus-visible:ring-primary focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-inset', editingTabId === tab.id && 'focus-visible:ring-0'), onDoubleClick: () => onStartEditing(tab.id), children: editingTabId !== tab.id ? (_jsx("div", { className: "truncate text-sm", children: renderTabLabel ? renderTabLabel(tab) : tab.name })) : (_jsx(EditableText, { value: tab.name, onChange: (newName) => onInlineRename(tab.id, newName), className: "h-6 min-w-0 flex-1 truncate text-sm shadow-none", isEditing: true, autoFocus: true, selectOnFocus: true, allowTabFocusWhenNotEditing: false, onEditingChange: (isEditing) => {
70
+ const fontSizeStyle = getFontSizeStyle(fontSize);
71
+ return (_jsx("div", { ref: setNodeRef, className: "h-full shrink-0", style: style, "data-tab-id": tab.id, ...(isEditing ? {} : attributes), ...(isEditing ? {} : listeners), tabIndex: -1, children: _jsx("div", { "data-state": isEditing ? 'editing' : undefined, className: cn('data-[state=inactive]:hover:bg-primary/5', 'group/tab flex h-full max-w-[200px] min-w-[100px] shrink-0 cursor-grab', 'items-center justify-between gap-1 overflow-hidden rounded-b-none', 'py-0 pr-1 font-normal data-[state=active]:shadow-none', tabClassName, isEditing && 'focus-visible:ring-0'), children: _jsxs("div", { className: "relative flex h-full min-w-0 flex-1 items-center", children: [_jsx(TabsTrigger, { value: tab.id, tabIndex: isEditing ? -1 : undefined, "data-editing": isEditing ? '' : undefined, className: cn(...TAB_STRIP_BUTTON_CLASSNAMES, 'flex-1 gap-1', isEditing ? 'justify-start' : 'justify-center text-center', 'data-[state=active]:bg-primary/10 data-[state=active]:text-foreground data-[state=active]:shadow-none', isEditing && 'focus-visible:ring-0'), style: fontSizeStyle, onClick: () => onActivate(tab.id), onDoubleClick: () => onStartEditing(tab.id), children: !isEditing ? (_jsx("div", { className: "min-w-0 flex-1 truncate text-center", children: renderTabLabel ? renderTabLabel(tab) : tab.name })) : (_jsx(EditableText, { value: tab.name, onChange: (newName) => onInlineRename(tab.id, newName), className: "h-6 min-w-0 flex-1 truncate shadow-none", style: fontSizeStyle, isEditing: true, autoFocus: true, selectOnFocus: true, allowTabFocusWhenNotEditing: false, onEditingChange: (isEditing) => {
39
72
  if (!isEditing) {
40
73
  onStopEditing();
41
74
  }
42
- } })) }), _jsxs("div", { className: "flex shrink-0 items-center", children: [menuContent && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": "Tab options", className: "hover:bg-primary/10 focus-visible:bg-primary/10 focus-visible:ring-primary absolute top-1/2 left-1 flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded p-1 opacity-0 outline-hidden group-hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-0 data-[state=open]:opacity-100", onMouseDown: (event) => {
75
+ } })) }), _jsxs("div", { className: "flex shrink-0 items-center", children: [menuContent && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": "Tab options", className: "hover:bg-primary/10 focus-visible:bg-primary/10 focus-visible:ring-ring absolute top-1/2 left-1 flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded p-1 opacity-0 outline-hidden group-hover/tab:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-0 data-[state=open]:opacity-100", onMouseDown: (event) => {
43
76
  event.stopPropagation();
44
77
  event.preventDefault();
45
78
  }, onClick: (event) => {
46
79
  event.stopPropagation();
47
- }, children: _jsx(EllipsisVerticalIcon, { className: "h-3 w-3" }) }) }), _jsx(DropdownMenuContent, { align: "start", children: menuContent })] })), !hideCloseButton && (_jsx("button", { type: "button", "aria-label": "Close tab", className: "hover:bg-primary/10 focus-visible:bg-primary/10 focus-visible:ring-primary absolute top-1/2 right-1 flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded p-1 opacity-0 outline-hidden group-hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-0", onMouseDown: (event) => {
80
+ }, children: _jsx(EllipsisVerticalIcon, { className: "h-3 w-3" }) }) }), _jsx(DropdownMenuContent, { align: "start", style: fontSizeStyle, children: menuContent })] })), closeable && (_jsx("button", { type: "button", "aria-label": "Close tab", className: "hover:bg-primary/10 focus-visible:bg-primary/10 focus-visible:ring-ring absolute top-1/2 right-1 flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded p-1 opacity-0 outline-hidden group-hover/tab:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-0", onMouseDown: (event) => {
48
81
  event.stopPropagation();
49
82
  event.preventDefault();
50
83
  }, onClick: (event) => {
@@ -57,10 +90,11 @@ function SortableTab({ tab, tabClassName, editingTabId, hideCloseButton, onClose
57
90
  * A menu item for the tab's dropdown menu.
58
91
  */
59
92
  function TabStripMenuItem({ children, onClick, variant = 'default', className, disabled, }) {
93
+ const { fontSize } = useTabStripContext();
60
94
  return (_jsx(DropdownMenuItem, { disabled: disabled, onClick: (event) => {
61
95
  event.stopPropagation();
62
96
  onClick?.();
63
- }, className: cn(variant === 'destructive' && 'text-destructive focus:text-destructive', className), children: children }));
97
+ }, className: cn(variant === 'destructive' && 'text-destructive focus:text-destructive', className), style: getFontSizeStyle(fontSize), children: children }));
64
98
  }
65
99
  /**
66
100
  * A separator for the tab's dropdown menu.
@@ -81,32 +115,115 @@ function TabStripSearchItemAction({ icon, onClick, 'aria-label': ariaLabel, clas
81
115
  * Renders the scrollable row of open tabs with drag-to-reorder support.
82
116
  */
83
117
  function TabStripTabs({ className, tabClassName }) {
84
- const { openTabItems, editingTabId, scrollContainerRef, onOpenTabsChange, renderTabTitle, renderTabMenu, renderTabLabel, preventCloseLastTab, handleStartEditing, handleStopEditing, handleInlineRename, handleClose, } = useTabStripContext();
118
+ const { openTabItems, editingTabId, scrollContainerRef, onOpenTabsChange, dndMode, dndScopeId, getTabDragData, renderTabMenu, renderTabLabel, preventCloseLastTab, closeable, fontSize, handleStartEditing, handleStopEditing, handleInlineRename, handleClose, onActivate, } = useTabStripContext();
85
119
  const sensors = useSensors(useSensor(PointerSensor, {
86
120
  activationConstraint: {
87
121
  distance: 5, // 5px movement before drag starts (allows clicks)
88
122
  },
89
123
  }));
124
+ const [activeDraggedTabId, setActiveDraggedTabId] = useState(null);
125
+ const [hideSharedDragOverlay, setHideSharedDragOverlay] = useState(false);
126
+ const isDragInsideTabStrip = (event) => {
127
+ const rect = event.active.rect.current.translated ?? event.active.rect.current.initial;
128
+ const tabStripRect = scrollContainerRef.current?.getBoundingClientRect();
129
+ if (!rect || !tabStripRect)
130
+ return false;
131
+ const centerX = rect.left + rect.width / 2;
132
+ const centerY = rect.top + rect.height / 2;
133
+ return (centerX >= tabStripRect.left &&
134
+ centerX <= tabStripRect.right &&
135
+ centerY >= tabStripRect.top &&
136
+ centerY <= tabStripRect.bottom);
137
+ };
138
+ const shouldHideSharedDragOverlay = (event) => {
139
+ if (isDragInsideTabStrip(event)) {
140
+ return true;
141
+ }
142
+ if ('over' in event) {
143
+ return event.over?.data.current?.tabStripId === dndScopeId;
144
+ }
145
+ return false;
146
+ };
90
147
  const handleDragEnd = (event) => {
91
148
  const { active, over } = event;
92
149
  if (!over || active.id === over.id || !onOpenTabsChange)
93
150
  return;
94
- const oldIndex = openTabItems.findIndex((tab) => tab.id === active.id);
95
- const newIndex = openTabItems.findIndex((tab) => tab.id === over.id);
151
+ const activeData = active.data.current;
152
+ const overData = over.data.current;
153
+ const activeTabId = typeof activeData?.tabId === 'string'
154
+ ? activeData.tabId
155
+ : String(active.id);
156
+ const overTabId = typeof overData?.tabId === 'string' ? overData.tabId : String(over.id);
157
+ if (dndMode === 'shared' &&
158
+ (activeData?.tabStripId !== dndScopeId ||
159
+ overData?.tabStripId !== dndScopeId)) {
160
+ return;
161
+ }
162
+ const oldIndex = openTabItems.findIndex((tab) => tab.id === activeTabId);
163
+ const newIndex = openTabItems.findIndex((tab) => tab.id === overTabId);
96
164
  if (oldIndex !== -1 && newIndex !== -1) {
97
165
  const newOrder = arrayMove(openTabItems.map((t) => t.id), oldIndex, newIndex);
98
166
  onOpenTabsChange(newOrder);
99
167
  }
100
168
  };
101
- const tabIds = useMemo(() => openTabItems.map((t) => t.id), [openTabItems]);
102
- return (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, modifiers: DRAG_MODIFIERS, autoScroll: true, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: tabIds, strategy: horizontalListSortingStrategy, children: _jsx(ScrollableRow, { className: "h-full min-w-0 flex-1", scrollRef: scrollContainerRef, scrollClassName: cn('flex h-full min-w-0 items-center gap-1 overflow-x-auto overflow-y-visible', 'py-1 pl-1 pr-1 scroll-pl-7 scroll-pr-7 [&::-webkit-scrollbar]:hidden', className), arrowVisibility: "always", arrowClassName: "w-7", arrowIconClassName: "h-4 w-4 opacity-60", children: openTabItems.map((tab) => (_jsx(SortableTab, { tab: tab, tabClassName: tabClassName, editingTabId: editingTabId, hideCloseButton: preventCloseLastTab && openTabItems.length === 1, onClose: handleClose, onStartEditing: handleStartEditing, onStopEditing: handleStopEditing, onInlineRename: handleInlineRename, renderTabTitle: renderTabTitle, renderTabMenu: renderTabMenu, renderTabLabel: renderTabLabel }, tab.id))) }) }) }));
169
+ const getSortableId = (tabId) => dndMode === 'shared' ? `${dndScopeId}:${tabId}` : tabId;
170
+ const tabIds = useMemo(() => openTabItems.map((t) => getSortableId(t.id)),
171
+ // eslint-disable-next-line react-hooks/exhaustive-deps
172
+ [openTabItems, dndMode, dndScopeId]);
173
+ const content = (_jsx(SortableContext, { items: tabIds, strategy: horizontalListSortingStrategy, children: _jsx(ScrollableRow, { className: "h-full min-w-0 shrink overflow-hidden", scrollRef: scrollContainerRef, scrollClassName: cn('flex h-full min-w-0 items-center gap-1 overflow-x-auto overflow-y-visible', 'pl-1 pr-1 scroll-pl-7 scroll-pr-7 [&::-webkit-scrollbar]:hidden', className), arrowVisibility: "always", arrowClassName: "w-7", arrowIconClassName: "h-4 w-4 opacity-60", children: openTabItems.map((tab) => (_jsx(SortableTab, { tab: tab, sortableId: getSortableId(tab.id), tabClassName: tabClassName, editingTabId: editingTabId, closeable: closeable && !(preventCloseLastTab && openTabItems.length === 1), onClose: handleClose, onActivate: onActivate ?? (() => undefined), onStartEditing: handleStartEditing, onStopEditing: handleStopEditing, onInlineRename: handleInlineRename, renderTabMenu: renderTabMenu, renderTabLabel: renderTabLabel, dndScopeId: dndScopeId, getTabDragData: getTabDragData, fontSize: fontSize }, tab.id))) }) }));
174
+ if (dndMode === 'shared') {
175
+ const activeDraggedTab = activeDraggedTabId
176
+ ? openTabItems.find((tab) => tab.id === activeDraggedTabId)
177
+ : undefined;
178
+ return (_jsxs(_Fragment, { children: [_jsx(SharedTabStripDndMonitor, { dndScopeId: dndScopeId, handleDragEnd: handleDragEnd, setActiveDraggedTabId: setActiveDraggedTabId, setHideSharedDragOverlay: setHideSharedDragOverlay, shouldHideSharedDragOverlay: shouldHideSharedDragOverlay }), content, _jsx(DragOverlay, { dropAnimation: null, children: activeDraggedTab && !hideSharedDragOverlay ? (_jsx(TabDragOverlay, { tab: activeDraggedTab, renderTabLabel: renderTabLabel, fontSize: fontSize })) : null })] }));
179
+ }
180
+ return (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, modifiers: DRAG_MODIFIERS, autoScroll: true, onDragEnd: handleDragEnd, children: content }));
181
+ }
182
+ function SharedTabStripDndMonitor({ dndScopeId, handleDragEnd, setActiveDraggedTabId, setHideSharedDragOverlay, shouldHideSharedDragOverlay, }) {
183
+ const isOwnTabDrag = (event) => event.active.data.current?.tabStripId === dndScopeId;
184
+ useDndMonitor({
185
+ onDragStart: (event) => {
186
+ const activeData = event.active.data.current;
187
+ if (!isOwnTabDrag(event)) {
188
+ return;
189
+ }
190
+ setActiveDraggedTabId(typeof activeData?.tabId === 'string' ? activeData.tabId : null);
191
+ setHideSharedDragOverlay(true);
192
+ },
193
+ onDragMove: (event) => {
194
+ if (!isOwnTabDrag(event))
195
+ return;
196
+ setHideSharedDragOverlay(shouldHideSharedDragOverlay(event));
197
+ },
198
+ onDragOver: (event) => {
199
+ if (!isOwnTabDrag(event))
200
+ return;
201
+ setHideSharedDragOverlay(shouldHideSharedDragOverlay(event));
202
+ },
203
+ onDragEnd: (event) => {
204
+ if (!isOwnTabDrag(event))
205
+ return;
206
+ handleDragEnd(event);
207
+ setActiveDraggedTabId(null);
208
+ setHideSharedDragOverlay(false);
209
+ },
210
+ onDragCancel: () => {
211
+ setActiveDraggedTabId(null);
212
+ setHideSharedDragOverlay(false);
213
+ },
214
+ });
215
+ return null;
216
+ }
217
+ function TabDragOverlay({ tab, renderTabLabel, fontSize, }) {
218
+ return (_jsx("div", { className: cn('bg-primary/15 text-foreground border-primary/50 flex h-7 max-w-[200px] min-w-[100px]', 'cursor-grabbing items-center justify-center gap-1 overflow-hidden rounded-md border px-6 py-1', 'text-sm shadow-lg'), style: getFontSizeStyle(fontSize), children: _jsx("div", { className: "truncate text-center", children: renderTabLabel ? renderTabLabel(tab) : tab.name }) }));
103
219
  }
104
220
  /**
105
221
  * Renders the dropdown with search for browsing tabs.
106
222
  * Shows open tabs first and closed tabs second (dimmed). When searching, shows all matching tabs.
107
223
  */
108
224
  function TabStripSearchDropdown({ className, triggerClassName, autoFocus = true, tooltip, triggerIcon, emptyMessage = 'No tabs', searchEmptyMessage = 'No matching tabs', closedTabsLabel = 'Recently closed', sortSearchItems = 'recent', getTabLastOpenedAt, }) {
109
- const { openTabItems, search, setSearch, closedTabs, filteredTabs, closedTabIds, openTabs, preventCloseLastTab, onOpenTabsChange, onSelect, renderSearchItemActions, getLastOpenedAt, handleClose, } = useTabStripContext();
225
+ const { openTabItems, search, setSearch, closedTabs, filteredTabs, closedTabIds, openTabs, preventCloseLastTab, onOpenTabsChange, onSelect, renderSearchItemActions, renderSearchItemLabel, getLastOpenedAt, handleClose, fontSize, } = useTabStripContext();
226
+ const fontSizeStyle = getFontSizeStyle(fontSize);
110
227
  const [isOpen, setIsOpen] = useState(false);
111
228
  const isSearching = search.trim().length > 0;
112
229
  const handleOpenChange = (open) => {
@@ -157,8 +274,8 @@ function TabStripSearchDropdown({ className, triggerClassName, autoFocus = true,
157
274
  const disableClose = preventCloseLastTab && (openTabsList.length ?? 0) <= 1;
158
275
  return (_jsxs(_Fragment, { children: [renderSearchItemActions?.(tab), _jsx(TabStripSearchItemAction, { icon: _jsx(XIcon, { className: "h-3 w-3" }), "aria-label": `Close ${tab.name}`, onClick: disableClose ? undefined : () => handleClose(tab.id), className: cn(disableClose && 'pointer-events-none opacity-40') })] }));
159
276
  };
160
- const triggerButton = (_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", "aria-label": "Browse tabs", className: cn('hover:bg-primary/10 h-full shrink-0', triggerClassName), children: triggerIcon ?? _jsx(ListCollapseIcon, { className: "h-4 w-4" }) }) }));
161
- return (_jsxs(DropdownMenu, { open: isOpen, onOpenChange: handleOpenChange, children: [tooltip ? (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: triggerButton }), _jsx(TooltipContent, { children: tooltip })] }) })) : (triggerButton), _jsxs(DropdownMenuContent, { align: "start", onCloseAutoFocus: (event) => event.preventDefault(), className: cn('flex max-h-[400px] max-w-[240px] flex-col overflow-x-hidden', className), children: [_jsxs("div", { className: "flex shrink-0 items-center gap-1 px-2", children: [_jsx(SearchIcon, { className: "text-muted-foreground", size: 14 }), _jsx(Input, { value: search, onChange: (event) => setSearch(event.target.value), onKeyDown: (event) => {
277
+ const triggerButton = (_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", "aria-label": "Browse tabs", size: "icon", className: cn(...TAB_STRIP_BUTTON_CLASSNAMES, triggerClassName), style: fontSizeStyle, children: triggerIcon ?? _jsx(ListCollapseIcon, { className: "h-4 w-4" }) }) }));
278
+ return (_jsxs(DropdownMenu, { open: isOpen, onOpenChange: handleOpenChange, children: [tooltip ? (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: triggerButton }), _jsx(TooltipContent, { children: tooltip })] }) })) : (triggerButton), _jsxs(DropdownMenuContent, { align: "start", onCloseAutoFocus: (event) => event.preventDefault(), className: cn('flex max-h-[400px] max-w-[240px] flex-col overflow-x-hidden', className), style: fontSizeStyle, children: [_jsxs("div", { className: "flex shrink-0 items-center gap-1 px-2", children: [_jsx(SearchIcon, { className: "text-muted-foreground", size: 14 }), _jsx(Input, { value: search, onChange: (event) => setSearch(event.target.value), onKeyDown: (event) => {
162
279
  if (event.key === 'ArrowDown' || event.key === 'Tab') {
163
280
  event.preventDefault();
164
281
  const firstItem = event.currentTarget
@@ -169,25 +286,38 @@ function TabStripSearchDropdown({ className, triggerClassName, autoFocus = true,
169
286
  else {
170
287
  event.stopPropagation();
171
288
  }
172
- }, onKeyUp: (event) => event.stopPropagation(), className: "border-none text-xs shadow-none focus-visible:ring-0", placeholder: "Search...", "aria-label": "Search", autoFocus: autoFocus })] }), _jsx(DropdownMenuSeparator, { className: "shrink-0" }), _jsx("div", { className: "overflow-x-hidden overflow-y-auto", children: isSearching ? (filteredTabs.length === 0 ? (_jsx(DropdownTabItems, { tabs: filteredTabs, emptyMessage: searchEmptyMessage, onTabClick: handleTabClick, renderActions: renderSearchItemActions })) : (_jsxs(_Fragment, { children: [_jsx(DropdownTabItems, { tabs: filteredOpenTabs, onTabClick: handleTabClick, renderActions: renderOpenTabActions }), filteredClosedTabs.length > 0 && (_jsxs(_Fragment, { children: [filteredOpenTabs.length > 0 && _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { className: "text-muted-foreground py-1 text-xs font-medium", children: closedTabsLabel }), _jsx(DropdownTabItems, { tabs: filteredClosedTabs, onTabClick: handleTabClick, renderActions: renderSearchItemActions, getItemClassName: () => 'text-muted-foreground' })] }))] }))) : (_jsx(_Fragment, { children: hasAnyTabs ? (_jsxs(_Fragment, { children: [_jsx(DropdownTabItems, { tabs: openTabsList, onTabClick: handleTabClick, renderActions: renderOpenTabActions }), closedTabsList.length > 0 && (_jsxs(_Fragment, { children: [openTabsList.length > 0 && _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { className: "text-muted-foreground py-1 text-xs font-medium", children: closedTabsLabel }), _jsx(DropdownTabItems, { tabs: closedTabsList, onTabClick: handleTabClick, renderActions: renderSearchItemActions, getItemClassName: () => 'text-muted-foreground' })] }))] })) : (_jsx(DropdownTabItems, { tabs: [], emptyMessage: emptyMessage, onTabClick: handleTabClick, renderActions: renderSearchItemActions })) })) })] })] }));
289
+ }, onKeyUp: (event) => event.stopPropagation(), className: "border-none text-xs shadow-none focus-visible:ring-0", style: fontSizeStyle, placeholder: "Search...", "aria-label": "Search", autoFocus: autoFocus })] }), _jsx(DropdownMenuSeparator, { className: "shrink-0" }), _jsx("div", { className: "overflow-x-hidden overflow-y-auto", children: isSearching ? (filteredTabs.length === 0 ? (_jsx(DropdownTabItems, { tabs: filteredTabs, emptyMessage: searchEmptyMessage, onTabClick: handleTabClick, renderLabel: renderSearchItemLabel, renderActions: renderSearchItemActions })) : (_jsxs(_Fragment, { children: [_jsx(DropdownTabItems, { tabs: filteredOpenTabs, onTabClick: handleTabClick, renderLabel: renderSearchItemLabel, renderActions: renderOpenTabActions }), filteredClosedTabs.length > 0 && (_jsxs(_Fragment, { children: [filteredOpenTabs.length > 0 && _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { className: "text-muted-foreground py-1 text-xs font-medium", style: fontSizeStyle, children: closedTabsLabel }), _jsx(DropdownTabItems, { tabs: filteredClosedTabs, onTabClick: handleTabClick, renderLabel: renderSearchItemLabel, renderActions: renderSearchItemActions, getItemClassName: () => 'text-muted-foreground' })] }))] }))) : (_jsx(_Fragment, { children: hasAnyTabs ? (_jsxs(_Fragment, { children: [_jsx(DropdownTabItems, { tabs: openTabsList, onTabClick: handleTabClick, renderLabel: renderSearchItemLabel, renderActions: renderOpenTabActions }), closedTabsList.length > 0 && (_jsxs(_Fragment, { children: [openTabsList.length > 0 && _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { className: "text-muted-foreground py-1 text-xs font-medium", style: fontSizeStyle, children: closedTabsLabel }), _jsx(DropdownTabItems, { tabs: closedTabsList, onTabClick: handleTabClick, renderLabel: renderSearchItemLabel, renderActions: renderSearchItemActions, getItemClassName: () => 'text-muted-foreground' })] }))] })) : (_jsx(DropdownTabItems, { tabs: [], emptyMessage: emptyMessage, onTabClick: handleTabClick, renderLabel: renderSearchItemLabel, renderActions: renderSearchItemActions })) })) })] })] }));
173
290
  }
174
- function DropdownTabItems({ tabs, emptyMessage, onTabClick, renderActions, getItemClassName, }) {
291
+ function DropdownTabItems({ tabs, emptyMessage, onTabClick, renderLabel, renderActions, getItemClassName, }) {
292
+ const { fontSize } = useTabStripContext();
293
+ const fontSizeStyle = getFontSizeStyle(fontSize);
175
294
  if (tabs.length === 0) {
176
295
  if (!emptyMessage)
177
296
  return null;
178
- return (_jsx(DropdownMenuLabel, { className: "items-center justify-center text-xs", children: emptyMessage }));
297
+ return (_jsx(DropdownMenuLabel, { className: "items-center justify-center text-xs", style: fontSizeStyle, children: emptyMessage }));
179
298
  }
180
- return (_jsx(_Fragment, { children: tabs.map((tab) => (_jsxs(DropdownMenuItem, { onClick: () => onTabClick?.(tab.id), className: cn('flex h-7 cursor-pointer items-center justify-between truncate', getItemClassName?.(tab)), children: [_jsx("span", { className: "xs truncate pl-1", children: tab.name }), renderActions && (_jsx("div", { className: "flex items-center gap-2", children: renderActions(tab) }))] }, tab.id))) }));
299
+ return (_jsx(_Fragment, { children: tabs.map((tab) => (_jsxs(DropdownMenuItem, { onClick: () => onTabClick?.(tab.id), className: cn('flex h-7 cursor-pointer items-center justify-between truncate', getItemClassName?.(tab)), style: fontSizeStyle, children: [_jsx("div", { className: "min-w-0 truncate pl-1 text-xs", style: fontSizeStyle, children: renderLabel ? renderLabel(tab) : tab.name }), renderActions && (_jsx("div", { className: "flex items-center gap-2", children: renderActions(tab) }))] }, tab.id))) }));
181
300
  }
301
+ /**
302
+ * A general-purpose button for the tab strip.
303
+ */
304
+ const TabStripButton = React.forwardRef(function TabStripButtonComponent({ className, style, ...props }, ref) {
305
+ const context = useOptionalTabStripContext();
306
+ return (_jsx(Button, { ref: ref, size: "icon", variant: "ghost", style: mergeFontSizeStyle(context?.fontSize, style), className: cn(...TAB_STRIP_BUTTON_CLASSNAMES, 'h-full shrink-0', className), ...props }));
307
+ });
308
+ TabStripButton.displayName = 'TabStripButton';
182
309
  /**
183
310
  * Renders a button to create a new tab.
184
311
  */
185
- function TabStripNewButton({ className, tooltip }) {
186
- const { onCreate } = useTabStripContext();
312
+ function TabStripNewButton({ tooltip, ...props }) {
313
+ const { onCreate, fontSize } = useTabStripContext();
187
314
  if (!onCreate) {
188
315
  return null;
189
316
  }
190
- const button = (_jsx(Button, { size: "icon", variant: "ghost", "aria-label": "Create new tab", onClick: () => onCreate(), className: cn('hover:bg-primary/10 h-full shrink-0', className), children: _jsx(PlusIcon, { className: "h-4 w-4" }) }));
317
+ const button = (_jsx(TabStripButton, { ...props, style: mergeFontSizeStyle(fontSize, props.style), "aria-label": props['aria-label'] || 'Create new tab', onClick: (e) => {
318
+ onCreate();
319
+ props.onClick?.(e);
320
+ }, children: _jsx(PlusIcon, { className: "h-4 w-4" }) }));
191
321
  if (tooltip) {
192
322
  return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: button }), _jsx(TooltipContent, { children: tooltip })] }) }));
193
323
  }
@@ -214,7 +344,9 @@ function TabStripNewButton({ className, tooltip }) {
214
344
  * <TabStrip.NewButton />
215
345
  * </TabStrip>
216
346
  */
217
- function TabStripRoot({ className, tabsListClassName, children, tabs, openTabs, selectedTabId, preventCloseLastTab = false, onClose, onOpenTabsChange, onSelect, onCreate, onRename, renderTabTitle, renderTabMenu, renderSearchItemActions, renderTabLabel, }) {
347
+ function TabStripRoot({ className, tabsListClassName, children, tabs, openTabs, selectedTabId, preventCloseLastTab = false, closeable = true, onClose, onOpenTabsChange, onSelect, onActivate, onCreate, onRename, renderTabMenu, renderSearchItemActions, renderSearchItemLabel, renderTabLabel, fontSize, dndMode = 'internal', dndScopeId, getTabDragData, }) {
348
+ const generatedDndScopeId = useId();
349
+ const actualDndScopeId = dndScopeId ?? generatedDndScopeId;
218
350
  const [search, setSearch] = useState('');
219
351
  const [editingTabId, setEditingTabId] = useState(null);
220
352
  const scrollContainerRef = useRef(null);
@@ -355,14 +487,20 @@ function TabStripRoot({ className, tabsListClassName, children, tabs, openTabs,
355
487
  selectedTabId,
356
488
  openTabs,
357
489
  preventCloseLastTab,
490
+ closeable,
358
491
  getLastOpenedAt: (tabId) => lastOpenedAtRef.current.get(tabId),
492
+ dndMode,
493
+ dndScopeId: actualDndScopeId,
494
+ getTabDragData,
495
+ fontSize,
359
496
  onOpenTabsChange,
360
497
  onSelect,
498
+ onActivate,
361
499
  onCreate,
362
500
  onRename,
363
- renderTabTitle,
364
501
  renderTabMenu,
365
502
  renderSearchItemActions,
503
+ renderSearchItemLabel,
366
504
  renderTabLabel,
367
505
  setSearch,
368
506
  handleStartEditing,
@@ -373,12 +511,13 @@ function TabStripRoot({ className, tabsListClassName, children, tabs, openTabs,
373
511
  const handleValueChange = (value) => {
374
512
  onSelect?.(value);
375
513
  };
376
- return (_jsx(Tabs, { value: selectedTabId ?? undefined, onValueChange: handleValueChange, activationMode: "manual", className: cn('bg-muted w-full min-w-0', className), children: _jsx(TabStripContext.Provider, { value: contextValue, children: _jsx(TabsList, { className: cn('flex h-9 w-full min-w-0 items-center justify-start gap-2 overflow-visible bg-transparent p-0', tabsListClassName), children: children ?? (_jsxs(_Fragment, { children: [_jsx(TabStripSearchDropdown, {}), _jsx(TabStripTabs, {}), _jsx(TabStripNewButton, {})] })) }) }) }));
514
+ return (_jsx(Tabs, { value: selectedTabId ?? undefined, onValueChange: handleValueChange, activationMode: "manual", className: cn('bg-muted w-full min-w-0', className), style: getFontSizeStyle(fontSize), children: _jsx(TabStripContext.Provider, { value: contextValue, children: _jsx(TabsList, { className: cn('flex h-9 w-full min-w-0 items-center justify-start gap-2 overflow-visible bg-transparent p-1', tabsListClassName), children: children ?? (_jsxs(_Fragment, { children: [_jsx(TabStripSearchDropdown, {}), _jsx(TabStripTabs, {}), _jsx(TabStripNewButton, {})] })) }) }) }));
377
515
  }
378
516
  // Attach subcomponents
379
517
  export const TabStrip = Object.assign(TabStripRoot, {
380
518
  Tabs: TabStripTabs,
381
519
  SearchDropdown: TabStripSearchDropdown,
520
+ Button: TabStripButton,
382
521
  NewButton: TabStripNewButton,
383
522
  MenuItem: TabStripMenuItem,
384
523
  MenuSeparator: TabStripMenuSeparator,