Skip to content

Commit bbac1af

Browse files
Copilotsawka
andcommitted
fix: overlay vtab drop indicator to remove accent gap
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
1 parent 6704e53 commit bbac1af

1 file changed

Lines changed: 49 additions & 42 deletions

File tree

frontend/app/tab/vtabbar.tsx

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export function VTabBar({ tabs, activeTabId, width, className, onSelectTab, onCl
3434
const [orderedTabs, setOrderedTabs] = useState<VTabItem[]>(tabs);
3535
const [dragTabId, setDragTabId] = useState<string | null>(null);
3636
const [dropIndex, setDropIndex] = useState<number | null>(null);
37+
const [dropLineTop, setDropLineTop] = useState<number | null>(null);
3738
const dragSourceRef = useRef<string | null>(null);
3839

3940
useEffect(() => {
@@ -46,6 +47,7 @@ export function VTabBar({ tabs, activeTabId, width, className, onSelectTab, onCl
4647
dragSourceRef.current = null;
4748
setDragTabId(null);
4849
setDropIndex(null);
50+
setDropLineTop(null);
4951
};
5052

5153
const reorder = (targetIndex: number) => {
@@ -69,24 +71,18 @@ export function VTabBar({ tabs, activeTabId, width, className, onSelectTab, onCl
6971
onReorderTabs?.(nextTabs.map((tab) => tab.id));
7072
};
7173

72-
const getDropLineClass = (index: number) =>
73-
cn(
74-
"h-0 border-t-2",
75-
dragTabId != null && dropIndex === index ? "border-accent/80" : "border-transparent",
76-
index > 0 && index < orderedTabs.length && "my-px"
77-
);
78-
7974
return (
8075
<div
8176
className={cn("flex h-full min-w-[100px] max-w-[400px] flex-col overflow-hidden border-r border-border bg-panel", className)}
8277
style={{ width: barWidth }}
8378
>
8479
<div
85-
className="flex min-h-0 flex-1 flex-col overflow-y-auto"
80+
className="relative flex min-h-0 flex-1 flex-col overflow-y-auto"
8681
onDragOver={(event) => {
8782
event.preventDefault();
8883
if (event.target === event.currentTarget) {
8984
setDropIndex(orderedTabs.length);
85+
setDropLineTop(event.currentTarget.scrollHeight);
9086
}
9187
}}
9288
onDrop={(event) => {
@@ -97,42 +93,53 @@ export function VTabBar({ tabs, activeTabId, width, className, onSelectTab, onCl
9793
clearDragState();
9894
}}
9995
>
100-
<div className={getDropLineClass(0)} />
10196
{orderedTabs.map((tab, index) => (
102-
<div key={tab.id} className="flex flex-col">
103-
<VTab
104-
tab={tab}
105-
active={tab.id === activeTabId}
106-
isDragging={dragTabId === tab.id}
107-
isReordering={dragTabId != null}
108-
onSelect={() => onSelectTab?.(tab.id)}
109-
onClose={onCloseTab ? () => onCloseTab(tab.id) : undefined}
110-
onRename={onRenameTab ? (newName) => onRenameTab(tab.id, newName) : undefined}
111-
onDragStart={(event) => {
112-
dragSourceRef.current = tab.id;
113-
event.dataTransfer.effectAllowed = "move";
114-
event.dataTransfer.setData("text/plain", tab.id);
115-
setDragTabId(tab.id);
116-
setDropIndex(index);
117-
}}
118-
onDragOver={(event) => {
119-
event.preventDefault();
120-
const rect = event.currentTarget.getBoundingClientRect();
121-
const insertBefore = event.clientY < rect.top + rect.height / 2;
122-
setDropIndex(insertBefore ? index : index + 1);
123-
}}
124-
onDrop={(event) => {
125-
event.preventDefault();
126-
if (dropIndex != null) {
127-
reorder(dropIndex);
128-
}
129-
clearDragState();
130-
}}
131-
onDragEnd={clearDragState}
132-
/>
133-
<div className={getDropLineClass(index + 1)} />
134-
</div>
97+
<VTab
98+
key={tab.id}
99+
tab={tab}
100+
active={tab.id === activeTabId}
101+
isDragging={dragTabId === tab.id}
102+
isReordering={dragTabId != null}
103+
onSelect={() => onSelectTab?.(tab.id)}
104+
onClose={onCloseTab ? () => onCloseTab(tab.id) : undefined}
105+
onRename={onRenameTab ? (newName) => onRenameTab(tab.id, newName) : undefined}
106+
onDragStart={(event) => {
107+
dragSourceRef.current = tab.id;
108+
event.dataTransfer.effectAllowed = "move";
109+
event.dataTransfer.setData("text/plain", tab.id);
110+
setDragTabId(tab.id);
111+
setDropIndex(index);
112+
setDropLineTop(event.currentTarget.offsetTop);
113+
}}
114+
onDragOver={(event) => {
115+
event.preventDefault();
116+
const rect = event.currentTarget.getBoundingClientRect();
117+
const relativeY = event.clientY - rect.top;
118+
const midpoint = event.currentTarget.offsetHeight / 2;
119+
const insertBefore = relativeY < midpoint;
120+
setDropIndex(insertBefore ? index : index + 1);
121+
setDropLineTop(
122+
insertBefore
123+
? event.currentTarget.offsetTop
124+
: event.currentTarget.offsetTop + event.currentTarget.offsetHeight
125+
);
126+
}}
127+
onDrop={(event) => {
128+
event.preventDefault();
129+
if (dropIndex != null) {
130+
reorder(dropIndex);
131+
}
132+
clearDragState();
133+
}}
134+
onDragEnd={clearDragState}
135+
/>
135136
))}
137+
{dragTabId != null && dropIndex != null && dropLineTop != null && (
138+
<div
139+
className="pointer-events-none absolute left-0 right-0 border-t-2 border-accent/80"
140+
style={{ top: dropLineTop, transform: "translateY(-1px)" }}
141+
/>
142+
)}
136143
</div>
137144
</div>
138145
);

0 commit comments

Comments
 (0)