Skip to content

Commit 383b0ec

Browse files
authored
fix: auto-expand inline comment textarea to fit content (#14)
- Use CSS `field-sizing: content` for modern browsers (Chrome 123+, Safari 26.2+) - Add JS fallback auto-resize for browsers without field-sizing support - Track user manual resize to avoid fighting with drag handle - Reset resize state when comment is cleared (for new comments) - Enable `resize-vertical` with `overflow-y: auto` for resize handle - Increase default minHeight to 160px (~8 lines) for better editing UX - Cap maxHeight at 50vh to prevent unbounded growth Fixes #11
1 parent f712456 commit 383b0ec

File tree

1 file changed

+68
-4
lines changed

1 file changed

+68
-4
lines changed

src/browser/ui/markdown.tsx

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,7 @@ interface MarkdownEditorProps {
740740
onKeyDown?: (e: React.KeyboardEvent) => void;
741741
placeholder?: string;
742742
minHeight?: string;
743+
maxHeight?: string;
743744
autoFocus?: boolean;
744745
disabled?: boolean;
745746
}
@@ -769,7 +770,8 @@ export const MarkdownEditor = memo(function MarkdownEditor({
769770
onChange,
770771
onKeyDown,
771772
placeholder = "Leave a comment...",
772-
minHeight = "100px",
773+
minHeight = "160px", // ~8 lines at default text-sm size
774+
maxHeight = "50vh",
773775
autoFocus = false,
774776
disabled = false,
775777
}: MarkdownEditorProps) {
@@ -806,13 +808,64 @@ export const MarkdownEditor = memo(function MarkdownEditor({
806808
}
807809
}, [autoFocus]);
808810

811+
// Track if user has manually resized the textarea
812+
const userResizedRef = useRef(false);
813+
814+
// Auto-resize textarea to fit content (fallback for browsers without field-sizing)
815+
useEffect(() => {
816+
const textarea = textareaRef.current;
817+
if (!textarea) return;
818+
819+
// Skip auto-resize if user has manually resized via drag handle
820+
if (userResizedRef.current) return;
821+
822+
// Check if browser supports field-sizing (then CSS handles it)
823+
if (CSS.supports("field-sizing", "content")) return;
824+
825+
// Reset height to auto to get the correct scrollHeight
826+
textarea.style.height = "auto";
827+
// Set height to scrollHeight to fit content
828+
textarea.style.height = `${textarea.scrollHeight}px`;
829+
}, [value]);
830+
831+
// Detect manual resize via mouseup on textarea
832+
useEffect(() => {
833+
const textarea = textareaRef.current;
834+
if (!textarea) return;
835+
836+
let startHeight = textarea.offsetHeight;
837+
838+
const handleMouseDown = () => {
839+
startHeight = textarea.offsetHeight;
840+
};
841+
842+
const handleMouseUp = () => {
843+
// If height changed without value change, user manually resized
844+
if (textarea.offsetHeight !== startHeight) {
845+
userResizedRef.current = true;
846+
}
847+
};
848+
849+
textarea.addEventListener("mousedown", handleMouseDown);
850+
textarea.addEventListener("mouseup", handleMouseUp);
851+
852+
return () => {
853+
textarea.removeEventListener("mousedown", handleMouseDown);
854+
textarea.removeEventListener("mouseup", handleMouseUp);
855+
};
856+
}, []);
857+
809858
// Switch back to write mode when content is cleared externally (like after submit)
810859
const prevValueRef = useRef(value);
811860
useEffect(() => {
812861
// Only switch if value was cleared (had content before, empty now)
813862
if (prevValueRef.current && !value && activeTab === "preview") {
814863
setActiveTab("write");
815864
}
865+
// Reset manual resize flag when content is cleared (new comment)
866+
if (prevValueRef.current && !value) {
867+
userResizedRef.current = false;
868+
}
816869
prevValueRef.current = value;
817870
}, [value, activeTab]);
818871

@@ -1370,11 +1423,19 @@ export const MarkdownEditor = memo(function MarkdownEditor({
13701423
placeholder={placeholder}
13711424
disabled={disabled}
13721425
className={cn(
1373-
"w-full px-3 py-2 text-sm bg-transparent resize-none focus:outline-none",
1426+
"w-full px-3 py-2 text-sm bg-transparent resize-vertical focus:outline-none",
13741427
"placeholder:text-muted-foreground",
13751428
disabled && "opacity-50 cursor-not-allowed"
13761429
)}
1377-
style={{ minHeight }}
1430+
style={{
1431+
minHeight,
1432+
maxHeight,
1433+
// Use field-sizing for browsers that support it (Chrome 123+, Safari 26.2+)
1434+
// Falls back to JS auto-resize for others
1435+
fieldSizing: "content",
1436+
// overflow: auto is required for resize handle to appear
1437+
overflowY: "auto",
1438+
}}
13781439
/>
13791440
</div>
13801441
<PopoverContent
@@ -1427,7 +1488,10 @@ export const MarkdownEditor = memo(function MarkdownEditor({
14271488
</PopoverContent>
14281489
</Popover>
14291490
) : (
1430-
<div className="px-3 py-2 overflow-auto" style={{ minHeight }}>
1491+
<div
1492+
className="px-3 py-2 overflow-auto"
1493+
style={{ minHeight, maxHeight }}
1494+
>
14311495
{value.trim() ? (
14321496
<Markdown className="text-sm">{value}</Markdown>
14331497
) : (

0 commit comments

Comments
 (0)