diff --git a/apps/web/design-system/chip.tsx b/apps/web/design-system/chip.tsx index 7e8675c693..1a5f376389 100644 --- a/apps/web/design-system/chip.tsx +++ b/apps/web/design-system/chip.tsx @@ -86,6 +86,8 @@ type LinkableRelationChipProps = { className?: string; disableLink?: boolean; sortableDragHandleListeners?: DraggableSyntheticListeners; + /** Callback ref marking the dots button as the drag handle (next-gen @dnd-kit/react). */ + sortableDragHandleRef?: (element: HTMLButtonElement | null) => void; truncateLabel?: boolean; children: React.ReactNode; @@ -198,6 +200,7 @@ export function LinkableRelationChip({ className = '', disableLink = false, sortableDragHandleListeners, + sortableDragHandleRef, truncateLabel = false, children, }: LinkableRelationChipProps) { @@ -216,6 +219,16 @@ export function LinkableRelationChip({ const triggerRef = React.useRef(null); const contentRef = React.useRef(null); + // Stable merged ref: an inline callback would re-run on every render (null then node), + // detaching/re-attaching the drag handle during hover/popover state updates. + const dotsButtonRef = React.useCallback( + (node: HTMLButtonElement | null) => { + triggerRef.current = node; + sortableDragHandleRef?.(node); + }, + [sortableDragHandleRef] + ); + const shouldClamp = !truncateLabel && typeof children === 'string' && children.length >= 42; const labelInner = truncateLabel ? {children} : children; @@ -305,7 +318,7 @@ export function LinkableRelationChip({