@sqlrooms/ui 0.29.0-rc.1 → 0.29.0-rc.10
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/LICENSE.md +2 -1
- package/README.md +98 -4
- package/dist/components/accordion.d.ts +2 -2
- package/dist/components/accordion.d.ts.map +1 -1
- package/dist/components/accordion.js +3 -3
- package/dist/components/accordion.js.map +1 -1
- package/dist/components/alert.js +1 -1
- package/dist/components/alert.js.map +1 -1
- package/dist/components/card.js +1 -1
- package/dist/components/card.js.map +1 -1
- package/dist/components/combobox.d.ts +67 -1
- package/dist/components/combobox.d.ts.map +1 -1
- package/dist/components/combobox.js +58 -32
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/editable-text.d.ts +2 -1
- package/dist/components/editable-text.d.ts.map +1 -1
- package/dist/components/editable-text.js +38 -11
- package/dist/components/editable-text.js.map +1 -1
- package/dist/components/error-pane.d.ts.map +1 -1
- package/dist/components/error-pane.js +2 -2
- package/dist/components/error-pane.js.map +1 -1
- package/dist/components/hover-card.d.ts +7 -0
- package/dist/components/hover-card.d.ts.map +1 -0
- package/dist/components/hover-card.js +15 -0
- package/dist/components/hover-card.js.map +1 -0
- package/dist/components/modifier-scroll-overlay.d.ts +16 -0
- package/dist/components/modifier-scroll-overlay.d.ts.map +1 -0
- package/dist/components/modifier-scroll-overlay.js +31 -0
- package/dist/components/modifier-scroll-overlay.js.map +1 -0
- package/dist/components/progress-modal.d.ts +2 -0
- package/dist/components/progress-modal.d.ts.map +1 -1
- package/dist/components/progress-modal.js +2 -2
- package/dist/components/progress-modal.js.map +1 -1
- package/dist/components/progress.d.ts.map +1 -1
- package/dist/components/progress.js.map +1 -1
- package/dist/components/resizable.d.ts +302 -20
- package/dist/components/resizable.d.ts.map +1 -1
- package/dist/components/resizable.js +12 -5
- package/dist/components/resizable.js.map +1 -1
- package/dist/components/run-button.d.ts +19 -0
- package/dist/components/run-button.d.ts.map +1 -0
- package/dist/components/run-button.js +17 -0
- package/dist/components/run-button.js.map +1 -0
- package/dist/components/settings-panel-header.d.ts +16 -0
- package/dist/components/settings-panel-header.d.ts.map +1 -0
- package/dist/components/settings-panel-header.js +9 -0
- package/dist/components/settings-panel-header.js.map +1 -0
- package/dist/components/sidebar.d.ts +3 -2
- package/dist/components/sidebar.d.ts.map +1 -1
- package/dist/components/sidebar.js +16 -12
- package/dist/components/sidebar.js.map +1 -1
- package/dist/components/sonner.d.ts +4 -0
- package/dist/components/sonner.d.ts.map +1 -0
- package/dist/components/sonner.js +37 -0
- package/dist/components/sonner.js.map +1 -0
- package/dist/components/switch.d.ts +5 -1
- package/dist/components/switch.d.ts.map +1 -1
- package/dist/components/switch.js +7 -2
- package/dist/components/switch.js.map +1 -1
- package/dist/components/tab-strip.d.ts +27 -7
- package/dist/components/tab-strip.d.ts.map +1 -1
- package/dist/components/tab-strip.js +165 -26
- package/dist/components/tab-strip.js.map +1 -1
- package/dist/components/textarea.d.ts +3 -0
- package/dist/components/textarea.d.ts.map +1 -1
- package/dist/components/textarea.js +87 -11
- package/dist/components/textarea.js.map +1 -1
- package/dist/components/theme-switch.d.ts.map +1 -1
- package/dist/components/theme-switch.js +3 -3
- package/dist/components/theme-switch.js.map +1 -1
- package/dist/components/tree.d.ts.map +1 -1
- package/dist/components/tree.js +4 -3
- package/dist/components/tree.js.map +1 -1
- package/dist/hooks/use-mobile.d.ts.map +1 -1
- package/dist/hooks/use-mobile.js +1 -0
- package/dist/hooks/use-mobile.js.map +1 -1
- package/dist/hooks/useCombobox.d.ts +33 -0
- package/dist/hooks/useCombobox.d.ts.map +1 -0
- package/dist/hooks/useCombobox.js +30 -0
- package/dist/hooks/useCombobox.js.map +1 -0
- package/dist/hooks/useDebounce.d.ts +33 -0
- package/dist/hooks/useDebounce.d.ts.map +1 -0
- package/dist/hooks/useDebounce.js +45 -0
- package/dist/hooks/useDebounce.js.map +1 -0
- package/dist/hooks/useDebouncedCallback.d.ts +45 -0
- package/dist/hooks/useDebouncedCallback.d.ts.map +1 -0
- package/dist/hooks/useDebouncedCallback.js +70 -0
- package/dist/hooks/useDebouncedCallback.js.map +1 -0
- package/dist/hooks/useDebouncedValue.d.ts +23 -0
- package/dist/hooks/useDebouncedValue.d.ts.map +1 -0
- package/dist/hooks/useDebouncedValue.js +41 -0
- package/dist/hooks/useDebouncedValue.js.map +1 -0
- package/dist/index.d.ts +36 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -28
- package/dist/index.js.map +1 -1
- package/dist/theme/theme-provider.d.ts +22 -1
- package/dist/theme/theme-provider.d.ts.map +1 -1
- package/dist/theme/theme-provider.js +59 -20
- package/dist/theme/theme-provider.js.map +1 -1
- package/package.json +16 -16
- package/tailwind-preset.css +78 -0
- package/dist/components/toast.d.ts +0 -16
- package/dist/components/toast.d.ts.map +0 -1
- package/dist/components/toast.js +0 -35
- package/dist/components/toast.js.map +0 -1
- package/dist/components/toaster.d.ts +0 -3
- package/dist/components/toaster.d.ts.map +0 -1
- package/dist/components/toaster.js +0 -24
- package/dist/components/toaster.js.map +0 -1
- package/dist/hooks/use-toast.d.ts +0 -45
- package/dist/hooks/use-toast.d.ts.map +0 -1
- package/dist/hooks/use-toast.js +0 -129
- 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,
|
|
28
|
-
const
|
|
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
|
-
|
|
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-
|
|
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 })] })),
|
|
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,
|
|
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
|
|
95
|
-
const
|
|
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
|
|
102
|
-
|
|
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(
|
|
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("
|
|
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({
|
|
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(
|
|
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,
|
|
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-
|
|
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,
|