diff --git a/package.json b/package.json index aecf7223..e27dc15c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "classnames": "^2.3.1", "echarts": "^5.4.0", "echarts-for-react": "^3.0.2", + "emoji-picker-react": "^4.11.1", "env-cmd": "^10.1.0", "eslint": "^7.11.0", "framer-motion": "^10.12.18", diff --git a/src/components/atoms/markdown/MarkdownEditor.tsx b/src/components/atoms/markdown/MarkdownEditor.tsx new file mode 100644 index 00000000..dcd03aac --- /dev/null +++ b/src/components/atoms/markdown/MarkdownEditor.tsx @@ -0,0 +1,251 @@ +import React, { + useState, + TextareaHTMLAttributes, + useEffect, + useRef, +} from 'react'; +import ReactMarkdown from 'react-markdown'; +import styles from './markdown.module.scss'; +import { + Button, + Stack, + Menu, + MenuButton, + MenuList, + MenuItem, +} from '@chakra-ui/react'; +import { MdInsertLink } from 'react-icons/md'; +import EmojiPicker, { EmojiClickData, Theme } from 'emoji-picker-react'; + +interface MarkdownEditorProps + extends TextareaHTMLAttributes { + label?: string; + maxWidth?: number; + minWidth?: number; + error?: string[] | undefined; + resize?: boolean; + value?: string; +} + +const MarkdownEditor: React.FC = ({ + error, + maxWidth, + minWidth, + label, + value, + resize, + onChange, + ...rest +}) => { + const [input, setInput] = useState(value ? value : ''); + const [isFocused, setIsFocused] = useState(false); + // 2 is textarea default value for rows + const [rows, setRows] = useState(2); + const [view, setShowPreview] = useState(false); // Add state variable for toggle + const [showEmoji, setShowEmoji] = useState(false); + const textareaRef = useRef(null); + + const handleButtonClick = (callback: () => void) => { + callback(); + if (textareaRef.current) { + textareaRef.current.focus(); + } + }; + + const getLabelStyle = () => { + return !!input || isFocused + ? `${styles.label} ${styles.styledLabel}` + : styles.label; + }; + + useEffect(() => { + if (!resize) { + return; + } + const rowlen = input ? input.toString().split('\n').length : 2; + const max = 14; + setRows(rowlen < max ? rowlen : max); + }, [input, resize]); + + const insertText = (text: string, offset: number) => { + const textarea = textareaRef.current; + if (textarea === null) { + return; + } + const position = textarea.selectionStart; + const before = textarea.value.substring(0, position); + const after = textarea.value.substring(position, textarea.value.length); + + // Insert the new text at the cursor position + textarea.value = before + text + after; + setInput(textarea.value); + textarea.selectionStart = textarea.selectionEnd = + position + text.length - offset; + }; + + const insertLink = () => { + const text = '[Display text](https://www.example.com)'; + insertText(text, 1); + }; + + const insertBold = () => { + const text = '****'; + insertText(text, 2); + }; + + const insertItalic = () => { + const text = '**'; + insertText(text, 1); + }; + + const insertHeader = (level: number) => { + const text = '#'.repeat(level) + ' '; + insertText(text, 0); + }; + + const onClickEmoji = (EmojiObject: EmojiClickData) => { + insertText(EmojiObject.emoji, 0); + if (textareaRef.current) { + textareaRef.current.focus(); + } + }; + + return ( +
+
+ + + + + + + + + H + + + handleButtonClick(() => insertHeader(1))}> + H1 + + handleButtonClick(() => insertHeader(2))}> + H2 + + handleButtonClick(() => insertHeader(3))}> + H3 + + handleButtonClick(() => insertHeader(4))}> + H4 + + handleButtonClick(() => insertHeader(5))}> + H5 + + handleButtonClick(() => insertHeader(6))}> + H6 + + + + + +
+
+ {label && } + + {view ? ( // Conditionally render markdown or preview based on toggle state + {input} + ) : ( +
+