Skip to content

Commit 5fedc6b

Browse files
konardclaude
andcommitted
Implement Monaco Editor for better message editing
- Created MonacoMarkdownEditor component with markdown language support - Replaced textarea with Monaco Editor in chat-with-markdown component - Added auto-resize functionality based on content height - Added keyboard shortcuts: Enter to send, Ctrl+Enter for new line - Fixed TypeScript errors in chakra-markdown component - Improved markdown editing experience with syntax highlighting 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent fb367a1 commit 5fedc6b

File tree

3 files changed

+159
-36
lines changed

3 files changed

+159
-36
lines changed

example/components/chakra-markdown.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@ function calculateChildrenHeight(parentDiv) {
1313
return childrenHeight;
1414
}
1515

16-
const ChakraMarkdown = React.memo<any>(({ content }) => {
16+
interface ChakraMarkdownProps {
17+
content: string;
18+
}
19+
20+
const ChakraMarkdown = React.memo<ChakraMarkdownProps>(({ content }) => {
1721
const renderers = {
18-
h1: ({ children }) => <Heading as="h1" size="xl" mb={2}>{children}</Heading>,
19-
h2: ({ children }) => <Heading as="h2" size="lg" mb={2}>{children}</Heading>,
20-
h3: ({ children }) => <Heading as="h3" size="md" mb={2}>{children}</Heading>,
21-
a: ({ href, children }) => (
22-
<ChakraLink href={href} color="blue.500" isExternal>
22+
h1: ({ children }: any) => <Heading as="h1" size="xl" mb={2}>{children}</Heading>,
23+
h2: ({ children }: any) => <Heading as="h2" size="lg" mb={2}>{children}</Heading>,
24+
h3: ({ children }: any) => <Heading as="h3" size="md" mb={2}>{children}</Heading>,
25+
a: ({ href, children, ...props }: any) => (
26+
<ChakraLink href={href} color="blue.500" isExternal {...props}>
2327
{children}
2428
</ChakraLink>
2529
),
26-
code: ({ inline, children, className }) => {
30+
code: ({ inline, children, className }: any) => {
2731
const { colorMode } = useColorMode();
2832
const monacoTheme = colorMode === 'dark' ? 'vs-dark' : 'vs-light';
2933

example/components/chat-with-markdown.tsx

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React, { useState, useRef, useEffect } from 'react';
2-
import { ChakraProvider, Box, Textarea, Button, VStack, HStack, Avatar, Text, useColorModeValue } from '@chakra-ui/react';
1+
import React, { useState } from 'react';
2+
import { ChakraProvider, Box, Button, VStack, HStack, Avatar, Text, useColorModeValue } from '@chakra-ui/react';
33
import ChakraMarkdown from './chakra-markdown'; // Import the renamed component
4+
import MonacoMarkdownEditor from './monaco-markdown-editor';
45

56
function ChatApp() {
67
const [messages, setMessages] = useState([]);
78
const [input, setInput] = useState("");
8-
const textareaRef = useRef(null);
99

1010
const bgColor = useColorModeValue("gray.100", "gray.900");
1111
const chatBgColor = useColorModeValue("white", "gray.800");
@@ -21,26 +21,9 @@ function ChatApp() {
2121
};
2222
setMessages([...messages, newMessage]);
2323
setInput("");
24-
if (textareaRef.current) {
25-
textareaRef.current.style.height = "auto"; // Reset textarea height after sending
26-
}
2724
}
2825
};
2926

30-
// Function to adjust the height of the textarea
31-
const autoResizeTextarea = () => {
32-
const maxTextareaHeight = window.innerHeight * 0.3; // 30% of screen height
33-
if (textareaRef.current) {
34-
textareaRef.current.style.height = "auto"; // Reset the height
35-
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, maxTextareaHeight)}px`; // Adjust the height, limiting to 30%
36-
}
37-
};
38-
39-
useEffect(() => {
40-
// Run the resize function when the input value changes
41-
autoResizeTextarea();
42-
}, [input]);
43-
4427
return (
4528
<ChakraProvider>
4629
<Box bg={bgColor} h="100vh" p={4} display="flex" flexDirection="column">
@@ -74,17 +57,13 @@ function ChatApp() {
7457

7558
{/* Input Section */}
7659
<HStack mt={4} spacing={2} align="end">
77-
<Textarea
78-
ref={textareaRef}
60+
<MonacoMarkdownEditor
7961
value={input}
80-
onChange={(e) => setInput(e.target.value)}
81-
onInput={autoResizeTextarea} // Call onInput to trigger auto-resize
62+
onChange={setInput}
63+
onEnter={sendMessage}
8264
placeholder="Type a message with Markdown..."
83-
bg={chatBgColor}
84-
resize="none"
85-
overflow="hidden"
86-
flex="1"
87-
maxHeight={`30vh`} // Max height: 30% of screen height
65+
maxHeight="30vh"
66+
minHeight="60px"
8867
/>
8968
<Button colorScheme="blue" onClick={sendMessage}>
9069
Send
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import React, { useRef, useState, useEffect } from 'react';
2+
import { Box, useColorMode } from '@chakra-ui/react';
3+
import MonacoEditor from '@monaco-editor/react';
4+
5+
interface MonacoMarkdownEditorProps {
6+
value: string;
7+
onChange: (value: string) => void;
8+
onEnter?: () => void;
9+
placeholder?: string;
10+
maxHeight?: string;
11+
minHeight?: string;
12+
}
13+
14+
const MonacoMarkdownEditor: React.FC<MonacoMarkdownEditorProps> = ({
15+
value,
16+
onChange,
17+
onEnter,
18+
placeholder = "Type your message with Markdown...",
19+
maxHeight = "30vh",
20+
minHeight = "100px"
21+
}) => {
22+
const { colorMode } = useColorMode();
23+
const editorRef = useRef(null);
24+
const [editorHeight, setEditorHeight] = useState(minHeight);
25+
26+
const monacoTheme = colorMode === 'dark' ? 'vs-dark' : 'vs-light';
27+
28+
// Auto-resize editor based on content
29+
const updateEditorHeight = () => {
30+
if (editorRef.current) {
31+
const editor = editorRef.current;
32+
const contentHeight = editor.getContentHeight();
33+
const maxHeightPx = parseInt(maxHeight.replace('vh', '')) * window.innerHeight / 100;
34+
const minHeightPx = parseInt(minHeight.replace('px', ''));
35+
36+
const newHeight = Math.min(Math.max(contentHeight, minHeightPx), maxHeightPx);
37+
setEditorHeight(`${newHeight}px`);
38+
}
39+
};
40+
41+
const handleEditorDidMount = (editor, monaco) => {
42+
editorRef.current = editor;
43+
44+
// Set up auto-resize
45+
editor.onDidContentSizeChange(updateEditorHeight);
46+
47+
// Initial height calculation
48+
updateEditorHeight();
49+
50+
// Configure editor for better markdown editing experience
51+
editor.updateOptions({
52+
automaticLayout: true,
53+
scrollBeyondLastLine: false,
54+
wordWrap: 'on',
55+
minimap: { enabled: false },
56+
lineNumbers: 'off',
57+
folding: false,
58+
selectOnLineNumbers: false,
59+
overviewRulerLanes: 0,
60+
hideCursorInOverviewRuler: true,
61+
renderLineHighlight: 'none',
62+
scrollbar: {
63+
vertical: 'auto',
64+
horizontal: 'auto',
65+
verticalScrollbarSize: 8,
66+
horizontalScrollbarSize: 8
67+
}
68+
});
69+
70+
// Show placeholder when empty
71+
if (!value) {
72+
editor.setValue('');
73+
}
74+
75+
// Add keyboard shortcut for Enter to send (Ctrl+Enter for new line)
76+
if (onEnter) {
77+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
78+
// Ctrl+Enter adds new line (default behavior)
79+
editor.trigger('keyboard', 'type', { text: '\n' });
80+
});
81+
82+
editor.addCommand(monaco.KeyCode.Enter, () => {
83+
// Enter sends message
84+
onEnter();
85+
});
86+
}
87+
};
88+
89+
const handleEditorChange = (newValue) => {
90+
onChange(newValue || '');
91+
// Trigger height update after content change
92+
setTimeout(updateEditorHeight, 0);
93+
};
94+
95+
useEffect(() => {
96+
updateEditorHeight();
97+
}, [value]);
98+
99+
return (
100+
<Box
101+
border="1px solid"
102+
borderColor={colorMode === 'dark' ? 'gray.600' : 'gray.200'}
103+
borderRadius="md"
104+
overflow="hidden"
105+
flex="1"
106+
>
107+
<MonacoEditor
108+
height={editorHeight}
109+
language="markdown"
110+
theme={monacoTheme}
111+
value={value}
112+
onChange={handleEditorChange}
113+
onMount={handleEditorDidMount}
114+
options={{
115+
placeholder: placeholder,
116+
automaticLayout: true,
117+
scrollBeyondLastLine: false,
118+
wordWrap: 'on',
119+
minimap: { enabled: false },
120+
lineNumbers: 'off',
121+
folding: false,
122+
selectOnLineNumbers: false,
123+
overviewRulerLanes: 0,
124+
hideCursorInOverviewRuler: true,
125+
renderLineHighlight: 'none',
126+
scrollbar: {
127+
vertical: 'auto',
128+
horizontal: 'auto',
129+
verticalScrollbarSize: 8,
130+
horizontalScrollbarSize: 8
131+
},
132+
fontSize: 14,
133+
fontFamily: 'ui-monospace, SFMono-Regular, "Roboto Mono", monospace'
134+
}}
135+
/>
136+
</Box>
137+
);
138+
};
139+
140+
export default MonacoMarkdownEditor;

0 commit comments

Comments
 (0)