@boomerang-io/carbon-addons-boomerang-react
Version:
Carbon Addons for Boomerang apps
165 lines (158 loc) • 10.5 kB
JavaScript
;
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;