"use client"; import React, { useState, useCallback, useRef, useEffect } from "react"; import { createPortal } from "react-dom"; import { IconPlus, IconTrash, IconGripHorizontal, IconPlayerPlay } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { Textarea } from "@kandev/ui/textarea"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@kandev/ui/tooltip"; import { cn } from "@/lib/utils"; type SelectionPosition = { x: number; y: number; }; type PlanSelectionPopoverProps = { selectedText: string; position: SelectionPosition; onAdd: (comment: string, selectedText: string) => void; onAddAndRun?: (comment: string, selectedText: string) => void; onClose: () => void; editingComment?: string; onDelete?: () => void; }; const POPOVER_WIDTH = 340; const POPOVER_HEIGHT = 180; const MARGIN = 8; function computePopoverPosition(position: SelectionPosition): { left: number; top: number } { // Place directly below cursor, aligned to left of click let left = position.x; let top = position.y + 4; // Clamp horizontal — keep popover on screen if (left + POPOVER_WIDTH > window.innerWidth - MARGIN) { left = window.innerWidth - POPOVER_WIDTH - MARGIN; } if (left < MARGIN) left = MARGIN; // If overflows bottom, flip above cursor if (top + POPOVER_HEIGHT > window.innerHeight - MARGIN) { top = position.y - POPOVER_HEIGHT - 4; } if (top < MARGIN) top = MARGIN; return { left, top }; } /** Drag support for the popover. */ function useDrag() { const [offset, setOffset] = useState({ dx: 0, dy: 0 }); const dragging = useRef(false); const startPos = useRef({ x: 0, y: 0 }); const startOffset = useRef({ dx: 0, dy: 0 }); const onMouseDown = useCallback( (e: React.MouseEvent) => { e.preventDefault(); dragging.current = true; startPos.current = { x: e.clientX, y: e.clientY }; startOffset.current = { ...offset }; const onMouseMove = (ev: MouseEvent) => { if (!dragging.current) return; setOffset({ dx: startOffset.current.dx + ev.clientX - startPos.current.x, dy: startOffset.current.dy + ev.clientY - startPos.current.y, }); }; const onMouseUp = () => { dragging.current = false; document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); }; document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); }, [offset], ); return { offset, onMouseDown }; } function usePopoverDismiss( onClose: () => void, popoverRef: React.RefObject, ) { useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) onClose(); }; const timer = setTimeout(() => { document.addEventListener("mousedown", handleClickOutside); }, 100); return () => { clearTimeout(timer); document.removeEventListener("mousedown", handleClickOutside); }; }, [onClose, popoverRef]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [onClose]); } function usePopoverComposer( comment: string, selectedText: string, onAdd: (comment: string, selectedText: string) => void, onClose: () => void, onAddAndRun?: (comment: string, selectedText: string) => void, ) { const handleSubmit = useCallback(() => { if (!comment.trim()) return; onAdd(comment.trim(), selectedText); onClose(); }, [comment, onAdd, selectedText, onClose]); const handleSubmitAndRun = useCallback(() => { if (!comment.trim() || !onAddAndRun) return; onAddAndRun(comment.trim(), selectedText); onClose(); }, [comment, onAddAndRun, selectedText, onClose]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); if (e.shiftKey && onAddAndRun) { handleSubmitAndRun(); } else { handleSubmit(); } } }, [handleSubmit, handleSubmitAndRun, onAddAndRun], ); return { handleSubmit, handleSubmitAndRun, handleKeyDown, isDisabled: !comment.trim(), previewText: selectedText.length > 80 ? selectedText.slice(0, 80).trim() + "\u2026" : selectedText, }; } function PopoverActions({ isEditing, isDisabled, onSubmit, onSubmitAndRun, onDelete, }: { isEditing: boolean; isDisabled: boolean; onSubmit: () => void; onSubmitAndRun?: () => void; onDelete?: () => void; }) { return (
⌘+Enter to {isEditing ? "update" : "add"} {onSubmitAndRun && !isEditing ? ", ⌘+Shift+Enter to run" : ""} {isEditing && onDelete && ( )}

{isEditing ? "Update comment" : "Save comment for review"}

{onSubmitAndRun && !isEditing && (

Save and send to agent

)}
); } export function PlanSelectionPopover({ selectedText, position, onAdd, onAddAndRun, onClose, editingComment, onDelete, }: PlanSelectionPopoverProps) { const [comment, setComment] = useState(editingComment || ""); const textareaRef = useRef(null); const popoverRef = useRef(null); const { offset, onMouseDown } = useDrag(); useEffect(() => { textareaRef.current?.focus(); }, []); usePopoverDismiss(onClose, popoverRef); const effectiveOnAddAndRun = editingComment ? undefined : onAddAndRun; const { handleSubmit, handleSubmitAndRun, handleKeyDown, isDisabled, previewText } = usePopoverComposer(comment, selectedText, onAdd, onClose, effectiveOnAddAndRun); const { left, top } = computePopoverPosition(position); const handleDelete = onDelete ? () => { onDelete(); onClose(); } : undefined; return createPortal(

“{previewText}”