UNPKG

@boomerang-io/carbon-addons-boomerang-react

Version:
165 lines (158 loc) 10.4 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var React = require('react'); var ibmProducts = require('@carbon/ibm-products'); require('quill/dist/quill.snow.css'); var settings = require('../../internal/settings.js'); var icons = require('@carbon/react/icons'); var react = require('@carbon/react'); var cx = require('classnames'); var sanitizeHtml = require('sanitize-html'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var React__default = /*#__PURE__*/_interopDefault(React); var cx__default = /*#__PURE__*/_interopDefault(cx); var sanitizeHtml__default = /*#__PURE__*/_interopDefault(sanitizeHtml); /* IBM Confidential 694970X, 69497O0 © Copyright IBM Corp. 2022, 2025 */ const RichTextAreaComponent = React__default.default.forwardRef(function RichTextAreaComponent({ label, labelText, maxWordCount, value, helperText, placeholder, onChange, setError, invalid, customError, readOnly, quillProps, }, ref) { ibmProducts.pkg.component.ToolbarGroup = ibmProducts.pkg.component.Toolbar = ibmProducts.pkg.component.ToolbarButton = true; const labelValue = label || labelText; const editorRef = React.useRef(null); const [url, setUrl] = React.useState(""); const [wordCount, setWordCount] = React.useState(0); const [savedSelection, setSavedSelection] = React.useState(null); const [showUrlInput, setShowUrlInput] = React.useState(false); const [noSelection, setNoSelection] = React.useState(false); const quillRef = React.useRef(null); React.useEffect(() => { (async () => { const { default: Quill } = await import('quill'); if (editorRef.current) { const quill = new Quill(editorRef.current, { theme: "snow", modules: { toolbar: false, }, placeholder: !readOnly ? placeholder : "", readOnly, ...quillProps, }); quillRef.current = quill; if (value) { const cleanHtml = sanitizeHtml__default.default(value, { allowedTags: ["p", "strong", "b", "i", "em", "u", "ol", "ul", "li", "a"], allowedAttributes: { a: ["href", "rel", "target"], }, }); quillRef.current.clipboard.dangerouslyPasteHTML(cleanHtml); const length = quillRef.current.getLength(); quillRef.current.setSelection(length, 0); setWordCount(getWordCount()); if (setError) { setError(maxWordCount && getWordCount() > maxWordCount); } } quill.on("text-change", () => { const currentCount = getWordCount(); setWordCount(currentCount); const wordCountExceeded = maxWordCount && currentCount > maxWordCount; if (onChange && !wordCountExceeded) { onChange(quill.getSemanticHTML()); } if (setError) { setError(wordCountExceeded); } }); } })() .catch(error => { console.error(error); }); }, []); const handleBold = () => { quillRef.current?.format("bold", !quillRef.current.getFormat().bold); }; const handleItalic = () => { quillRef.current?.format("italic", !quillRef.current.getFormat().italic); }; const handleUnderline = () => { quillRef.current?.format("underline", !quillRef.current.getFormat().underline); }; const handleOrderedList = () => { quillRef.current?.format("list", "ordered"); }; const handleBulletList = () => { quillRef.current?.format("list", "bullet"); }; const handleLinkBtn = () => { const selection = quillRef.current?.getSelection(); if (selection && selection.length > 0) { setSavedSelection(selection); setShowUrlInput(true); } else { setNoSelection(true); } }; const handleLinkInsert = () => { if (url) { const range = savedSelection || quillRef.current?.getSelection(); if (range?.length) { quillRef.current?.formatText(range.index, range.length, "link", url); } } setShowUrlInput(false); setSavedSelection(null); setUrl(""); }; const handleLinkCancel = () => { setShowUrlInput(false); setSavedSelection(null); setUrl(""); }; const getWordCount = () => { if (quillRef.current && maxWordCount) { const text = quillRef.current.getText(); const words = text.trim().split(/\s+/); return words.filter((word) => word.length > 0).length; } return 0; }; const wordCountExceeded = maxWordCount && wordCount > maxWordCount; return (React__default.default.createElement(React__default.default.Fragment, null, React__default.default.createElement("div", { className: `${settings.prefix}--rich-text-editor-labels` }, labelValue ? (React__default.default.createElement("div", { "data-testid": "rich-text-editor-label", className: `${settings.prefix}--label` }, labelValue)) : null, maxWordCount ? (React__default.default.createElement("div", { "data-testid": "rich-text-editor-word-count", className: cx__default.default(`${settings.prefix}--label`, { [`${settings.prefix}--rich-text-editor-error`]: wordCount > maxWordCount }) }, `${wordCount}/${maxWordCount}`)) : null), (!readOnly) ? React__default.default.createElement(ibmProducts.Toolbar, { className: `${settings.prefix}--rich-text-editor-toolbar` }, React__default.default.createElement(ibmProducts.ToolbarGroup, null, React__default.default.createElement(ibmProducts.ToolbarButton, { "data-testid": "rich-text-editor-bold-btn", onClick: handleBold, label: "Bold", renderIcon: (props) => React__default.default.createElement(icons.TextBold, { size: 16, ...props }) }), React__default.default.createElement(ibmProducts.ToolbarButton, { "data-testid": "rich-text-editor-italic-btn", onClick: handleItalic, label: "Italic", renderIcon: (props) => React__default.default.createElement(icons.TextItalic, { size: 16, ...props }) }), React__default.default.createElement(ibmProducts.ToolbarButton, { "data-testid": "rich-text-editor-underline-btn", onClick: handleUnderline, label: "Underline", renderIcon: (props) => React__default.default.createElement(icons.TextUnderline, { size: 16, ...props }) }), React__default.default.createElement(ibmProducts.ToolbarButton, { "data-testid": "rich-text-editor-bullet-list-btn", onClick: handleBulletList, label: "Bulleted List", renderIcon: (props) => React__default.default.createElement(icons.ListBulleted, { size: 16, ...props }) }), React__default.default.createElement(ibmProducts.ToolbarButton, { "data-testid": "rich-text-editor-numbered-list-btn", onClick: handleOrderedList, label: "Numbered List", renderIcon: (props) => React__default.default.createElement(icons.ListNumbered, { size: 16, ...props }) }), React__default.default.createElement(ibmProducts.ToolbarButton, { "data-testid": "rich-text-editor-hyperlink-btn", onClick: () => handleLinkBtn(), label: "Hyperlink", renderIcon: (props) => React__default.default.createElement(icons.Link, { size: 16, ...props }) }))) : null, showUrlInput && (React__default.default.createElement("div", { className: `${settings.prefix}--rich-text-editor-url-input` }, React__default.default.createElement(react.TextInput, { onChange: (e) => { setUrl(e.target.value); }, id: "hyperlink-input", "data-testid": "rich-text-editor-hyperlink-input", placeholder: "Enter URL", size: "sm", type: "url", labelText: "" }), React__default.default.createElement(react.Button, { "data-testid": "rich-text-editor-hyperlink-input-ok-btn", onClick: () => handleLinkInsert(), kind: "ghost", size: "sm" }, "OK"), React__default.default.createElement(react.Button, { "data-testid": "rich-text-editor-hyperlink-input-cancel-btn", onClick: () => handleLinkCancel(), kind: "ghost", size: "sm" }, "Cancel"))), React__default.default.createElement("div", { "data-testid": "rich-text-editor-text-area", className: cx__default.default({ [`${settings.prefix}--rich-text-editor-error-border`]: wordCountExceeded, }) }, React__default.default.createElement("div", { className: cx__default.default(`${settings.prefix}--rich-text-editor`, { [`${settings.prefix}--rich-text-editor-disabled`]: readOnly, }), onFocus: () => setNoSelection(false), ref: editorRef })), React__default.default.createElement("div", { "data-testid": "rich-text-editor-footer", className: `${settings.prefix}--rich-text-editor-footer` }, !noSelection && !wordCountExceeded && helperText ? (React__default.default.createElement("div", { "data-testid": "rich-text-editor-helper-text", className: `${settings.prefix}--label` }, helperText)) : null, wordCountExceeded && !customError ? (React__default.default.createElement("div", { "data-testid": "rich-text-editor-wordCount-error", className: cx__default.default(`${settings.prefix}--label`, `${settings.prefix}--rich-text-editor-error`) }, "Exceeded Word Count")) : null, noSelection ? (React__default.default.createElement("div", { "data-testid": "rich-text-editor-link-input-error", className: cx__default.default(`${settings.prefix}--label`, `${settings.prefix}--rich-text-editor-error`) }, "Select text before adding link")) : null, invalid ? (React__default.default.createElement("div", { className: cx__default.default(`${settings.prefix}--label`, `${settings.prefix}--rich-text-editor-error`) }, customError)) : null))); }); exports.default = RichTextAreaComponent;