fix(app-shell-tab-bar): preserve drag handler and transform style under ContextMenuTrigger asChild

`ContextMenuTrigger asChild` clones the tab button element with merged
props (onContextMenu, ref, etc). Both PinnedTabButton and NormalTabButton
were spreading `{...rest}` AFTER the drag handler and transform style,
so any prop injected by the trigger would overwrite drag.onPointerDown
and the translateX/transition/zIndex/opacity that useTabDrag relies on,
breaking tab drag/reorder.

Move `{...rest}` to the start of the JSX props so the drag handler,
transform style, and drag-related classes always win on a key collision.
For PinnedTabButton (which doesn't omit `style`/`className` from its
incoming props) also merge any incoming style/className through so
trigger-side styling is preserved alongside the drag values.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: suyao <sy20010504@gmail.com>
This commit is contained in:
suyao
2026-05-10 13:25:46 +08:00
parent 2c3592b680
commit f2d8db5f76

View File

@@ -114,7 +114,10 @@ type PinnedTabButtonProps = {
const PinnedTabButton = ({ tab, isActive, onSelect, drag, tabRef, tone, ref, ...rest }: PinnedTabButtonProps) => {
return (
<Tooltip placement="bottom" content={tab.title} delay={600}>
{/* Spread `rest` (which carries injected ContextMenuTrigger props) first so the */}
{/* drag handler / transform style / drag classes always win on a key collision. */}
<button
{...rest}
ref={(el) => {
tabRef(el)
if (typeof ref === 'function') ref(el)
@@ -126,6 +129,7 @@ const PinnedTabButton = ({ tab, isActive, onSelect, drag, tabRef, tone, ref, ...
onClick={onSelect}
title={tab.title}
style={{
...rest.style,
transform: `translateX(${drag.translateX}px)`,
transition: drag.isDragging || drag.noTransition ? 'none' : 'transform 200ms ease',
zIndex: drag.isDragging ? 50 : 'auto',
@@ -134,9 +138,9 @@ const PinnedTabButton = ({ tab, isActive, onSelect, drag, tabRef, tone, ref, ...
className={cn(
'flex h-7 w-7 items-center justify-center rounded-full transition-colors duration-150',
drag.isDragging ? 'cursor-grabbing' : 'cursor-default',
isActive ? tone.activeClass : tone.hoverClass
)}
{...rest}>
isActive ? tone.activeClass : tone.hoverClass,
rest.className
)}>
<TabIcon tab={tab} size={14} />
</button>
</Tooltip>
@@ -198,7 +202,10 @@ const NormalTabButton = ({
const showIconOverlayClose = isCloseable && showClose && isNarrow
return (
// Spread `rest` (which carries injected ContextMenuTrigger props) first so the
// drag handler / transform style / drag classes always win on a key collision.
<button
{...rest}
ref={setRefs}
data-tab-id={tab.id}
type="button"
@@ -215,8 +222,7 @@ const NormalTabButton = ({
showRightClose ? 'pr-1 pl-2' : 'px-2',
drag.isDragging ? 'cursor-grabbing' : 'cursor-default',
isActive ? tone.activeClass : tone.hoverClass
)}
{...rest}>
)}>
{/* Icon — on narrow tabs, X overlay replaces icon on hover (Chrome-style) */}
<div className="relative flex h-[13px] w-[13px] shrink-0 items-center justify-center">
<TabIcon tab={tab} size={13} className={cn(showIconOverlayClose && 'group-hover:hidden')} />