import React, { useRef, useEffect } from 'react'; import { useOptionalThemeSystem } from '@/contexts/useThemeSystem'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; import { useDeviceInfo } from '@/lib/device'; export interface InlineCommentInputProps { initialText?: string; onSave: (text: string, range?: { start: number; end: number; side?: 'additions' | 'deletions' }) => void; onCancel: () => void; fileLabel?: string; lineRange?: { start: number; end: number; side?: 'additions' | 'deletions' }; isEditing?: boolean; className?: string; maxWidth?: number; } export function InlineCommentInput({ initialText = '', onSave, onCancel, fileLabel, lineRange, isEditing = false, className, maxWidth, }: InlineCommentInputProps) { const themeContext = useOptionalThemeSystem(); const currentTheme = themeContext?.currentTheme; const { isMobile } = useDeviceInfo(); const [text, setText] = React.useState(initialText); const textareaRef = useRef(null); // Stable range snapshot to prevent race with selection clearing const stableRangeRef = useRef(lineRange); useEffect(() => { if (lineRange) { stableRangeRef.current = lineRange; } }, [lineRange]); const normalizeRange = (range?: { start: number; end: number; side?: 'additions' | 'deletions' }) => { if (!range) return undefined; const start = Math.min(range.start, range.end); const end = Math.max(range.start, range.end); return { ...range, start, end }; }; const displayRange = normalizeRange(lineRange); // Focus on mount (desktop only) or when becoming visible useEffect(() => { const textarea = textareaRef.current; if (!textarea) return; const scrollContainer = textarea.closest('.overlay-scrollbar-container') as HTMLElement | null; const prevScrollTop = scrollContainer?.scrollTop ?? window.scrollY; const prevScrollLeft = scrollContainer?.scrollLeft ?? window.scrollX; if (isMobile) { textarea.scrollIntoView({ behavior: 'auto', block: 'nearest' }); try { textarea.focus({ preventScroll: true }); } catch { textarea.focus(); } return; } try { textarea.focus({ preventScroll: true }); } catch { textarea.focus(); } const len = textarea.value.length; try { textarea.setSelectionRange(len, len); } catch (err) { void err; } requestAnimationFrame(() => { if (scrollContainer) { scrollContainer.scrollTop = prevScrollTop; scrollContainer.scrollLeft = prevScrollLeft; } else { window.scrollTo({ top: prevScrollTop, left: prevScrollLeft }); } }); }, [isMobile]); const handleKeyDown = (e: React.KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { e.preventDefault(); if (text.trim()) { onSave(text, normalizeRange(stableRangeRef.current)); } } else if (e.key === 'Escape') { e.preventDefault(); onCancel(); } }; const handleSaveClick = (e: React.MouseEvent | React.TouchEvent | React.PointerEvent) => { // Stop propagation to prevent parent selection clearing before save e.stopPropagation(); if (text.trim()) { onSave(text, normalizeRange(stableRangeRef.current)); } }; return (
e.stopPropagation()} onTouchStart={(e) => e.stopPropagation()} >
{(fileLabel || lineRange) && (
{fileLabel && {fileLabel}} {fileLabel && lineRange && } {displayRange && Lines {displayRange.start}-{displayRange.end}}
)}