From 6e1d9df7f061f568cc72e63decf07186df6e41a5 Mon Sep 17 00:00:00 2001 From: Hiren Kangad Date: Thu, 12 Mar 2026 19:34:08 +0530 Subject: [PATCH] feat(review): add InlineCommentComposer presentational component --- .../feedback/InlineCommentComposer.tsx | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 frontend/src/components/feedback/InlineCommentComposer.tsx diff --git a/frontend/src/components/feedback/InlineCommentComposer.tsx b/frontend/src/components/feedback/InlineCommentComposer.tsx new file mode 100644 index 0000000..32c0a07 --- /dev/null +++ b/frontend/src/components/feedback/InlineCommentComposer.tsx @@ -0,0 +1,181 @@ +import React, { useRef, useEffect } from 'react'; +import { + PANEL_BG, BORDER, TEXT_PRIMARY, TEXT_MUTED, ACCENT, + INPUT_BG, INPUT_BORDER, INPUT_BORDER_FOCUS, +} from './feedbackStyles'; + +interface InlineCommentComposerProps { + visible: boolean; + x: number; + y: number; + placement: 'right' | 'left'; + objectLabel?: string; + value: string; + onChange: (value: string) => void; + onSubmit: () => void; + onCancel: () => void; + submitting?: boolean; + autoFocus?: boolean; +} + +const COMPOSER_WIDTH = 260; +const OFFSET_X = 16; + +export default function InlineCommentComposer({ + visible, + x, + y, + placement, + objectLabel, + value, + onChange, + onSubmit, + onCancel, + submitting = false, + autoFocus = true, +}: InlineCommentComposerProps) { + const textareaRef = useRef(null); + + useEffect(() => { + if (visible && autoFocus && textareaRef.current) { + requestAnimationFrame(() => textareaRef.current?.focus()); + } + }, [visible, autoFocus]); + + if (!visible) return null; + + const hasText = value.trim().length > 0; + + const left = placement === 'right' ? x + OFFSET_X : x - OFFSET_X - COMPOSER_WIDTH; + const top = y - 30; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + if (hasText && !submitting) onSubmit(); + } + if (e.key === 'Escape') { + e.preventDefault(); + e.stopPropagation(); + onCancel(); + } + }; + + return ( +
e.stopPropagation()} + onPointerDown={(e) => e.stopPropagation()} + > + {objectLabel && ( +
+ {objectLabel} +
+ )} + +