Browse Source

feat: show header controls on node body hover, not just header hover

Add hoveredNodeId to store, set from BaseNode mouse events, read in
FloatingNodeHeader. Controls now appear when hovering any part of
the node, not just the floating header strip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
5cf4c92f52
  1. 3
      src/components/nodes/BaseNode.tsx
  2. 9
      src/components/nodes/FloatingNodeHeader.tsx
  3. 7
      src/store/workflowStore.ts

3
src/components/nodes/BaseNode.tsx

@ -32,6 +32,7 @@ export function BaseNode({
}: BaseNodeProps) { }: BaseNodeProps) {
const currentNodeIds = useWorkflowStore((state) => state.currentNodeIds); const currentNodeIds = useWorkflowStore((state) => state.currentNodeIds);
const nodes = useWorkflowStore((state) => state.nodes); const nodes = useWorkflowStore((state) => state.nodes);
const setHoveredNodeId = useWorkflowStore((state) => state.setHoveredNodeId);
const isCurrentlyExecuting = currentNodeIds.includes(id); const isCurrentlyExecuting = currentNodeIds.includes(id);
const { getNodes, setNodes } = useReactFlow(); const { getNodes, setNodes } = useReactFlow();
@ -82,6 +83,8 @@ export function BaseNode({
${!fullBleed && selected ? "border-blue-500 ring-2 ring-blue-500/40 shadow-lg shadow-blue-500/25" : ""} ${!fullBleed && selected ? "border-blue-500 ring-2 ring-blue-500/40 shadow-lg shadow-blue-500/25" : ""}
${className} ${className}
`} `}
onMouseEnter={() => setHoveredNodeId(id)}
onMouseLeave={() => setHoveredNodeId(null)}
> >
<div className={contentClassName ?? (fullBleed ? "flex-1 min-h-0 relative" : "px-3 pb-4 flex-1 min-h-0 overflow-hidden flex flex-col")}>{children}</div> <div className={contentClassName ?? (fullBleed ? "flex-1 min-h-0 relative" : "px-3 pb-4 flex-1 min-h-0 overflow-hidden flex flex-col")}>{children}</div>
</div> </div>

9
src/components/nodes/FloatingNodeHeader.tsx

@ -4,6 +4,7 @@ import { ReactNode, useState, useEffect, useRef, useCallback } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { useReactFlow } from "@xyflow/react"; import { useReactFlow } from "@xyflow/react";
import { NodeType } from "@/types"; import { NodeType } from "@/types";
import { useWorkflowStore } from "@/store/workflowStore";
export interface CommentNavigationProps { export interface CommentNavigationProps {
currentIndex: number; currentIndex: number;
@ -51,7 +52,9 @@ export function FloatingNodeHeader({
onCommentChange, onCommentChange,
commentNavigation, commentNavigation,
}: FloatingNodeHeaderProps) { }: FloatingNodeHeaderProps) {
const [isHovered, setIsHovered] = useState(false); const [isHeaderHovered, setIsHeaderHovered] = useState(false);
const isBodyHovered = useWorkflowStore((state) => state.hoveredNodeId === id);
const isHovered = isHeaderHovered || isBodyHovered;
const [isEditingTitle, setIsEditingTitle] = useState(false); const [isEditingTitle, setIsEditingTitle] = useState(false);
const [editTitleValue, setEditTitleValue] = useState(customTitle || ""); const [editTitleValue, setEditTitleValue] = useState(customTitle || "");
const [isEditingComment, setIsEditingComment] = useState(false); const [isEditingComment, setIsEditingComment] = useState(false);
@ -278,8 +281,8 @@ export function FloatingNodeHeader({
width: `${width}px`, width: `${width}px`,
zIndex: selected ? 10000 : 9000, zIndex: selected ? 10000 : 9000,
}} }}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHeaderHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHeaderHovered(false)}
onMouseDown={handleHeaderMouseDown} onMouseDown={handleHeaderMouseDown}
> >
<div className="px-1 py-1 flex items-center justify-between w-full"> <div className="px-1 py-1 flex items-center justify-between w-full">

7
src/store/workflowStore.ts

@ -243,9 +243,11 @@ interface WorkflowStore {
openModalCount: number; openModalCount: number;
isModalOpen: boolean; isModalOpen: boolean;
showQuickstart: boolean; showQuickstart: boolean;
hoveredNodeId: string | null;
incrementModalCount: () => void; incrementModalCount: () => void;
decrementModalCount: () => void; decrementModalCount: () => void;
setShowQuickstart: (show: boolean) => void; setShowQuickstart: (show: boolean) => void;
setHoveredNodeId: (id: string | null) => void;
// Execution // Execution
isRunning: boolean; isRunning: boolean;
@ -450,6 +452,7 @@ const workflowStoreImpl: StateCreator<WorkflowStore> = (set, get) => ({
openModalCount: 0, openModalCount: 0,
isModalOpen: false, isModalOpen: false,
showQuickstart: true, showQuickstart: true,
hoveredNodeId: null,
isRunning: false, isRunning: false,
currentNodeIds: [], // Changed from currentNodeId for parallel execution currentNodeIds: [], // Changed from currentNodeId for parallel execution
pausedAtNodeId: null, pausedAtNodeId: null,
@ -522,6 +525,10 @@ const workflowStoreImpl: StateCreator<WorkflowStore> = (set, get) => ({
set({ showQuickstart: show }); set({ showQuickstart: show });
}, },
setHoveredNodeId: (id: string | null) => {
set({ hoveredNodeId: id });
},
addNode: (type: NodeType, position: XYPosition, initialData?: Partial<WorkflowNodeData>) => { addNode: (type: NodeType, position: XYPosition, initialData?: Partial<WorkflowNodeData>) => {
const id = `${type}-${++nodeIdCounter}`; const id = `${type}-${++nodeIdCounter}`;

Loading…
Cancel
Save