@zencemarketing/text-editor-sdk
v0.1.0-alpha.0
Published
A highly customizable rich text editor React component built with Slate.js - Features include mentions, tags, emoji picker, code blocks, image uploads, paste formatting, character limits, and fully configurable toolbars
Readme
@zencemarketing/text-editor-sdk
⚠️ Alpha Release: This is an early alpha version (0.1.0-alpha.0). APIs may change before the stable 1.0.0 release. Use in production at your own risk.
A highly customizable, feature-rich React text editor built with Slate.js. Perfect for content management systems, messaging apps, comment sections, and any application requiring rich text editing capabilities.
🚀 Features
- 📝 Rich Text Formatting - Bold, italic, underline, strikethrough, headings (H1-H3)
- 🎨 Lists - Bulleted and numbered lists
- 🔗 Links & Media - Insert links and upload images/videos
- 👥 Mentions - @mention users with autocomplete
- #️⃣ Tags/Variables - Insert dynamic placeholders for content personalization
- 😊 Emoji Picker - Built-in emoji selector
- 💻 Code Blocks - Syntax-highlighted code snippets
- 📋 Smart Paste - Keep formatting, match style, or paste as plain text
- 🔢 Character Counter - Track and limit character count
- ⚙️ Fully Customizable - Control height, width, colors, borders, and more
- 🎯 Configurable Toolbars - Top, bottom, or both with custom button selection
- ✅ Action Buttons - Built-in Cancel/Submit buttons with callbacks
- 📱 Responsive - Works on desktop and mobile
- ♿ Accessible - Keyboard navigation and ARIA support
- 📦 TypeScript - Full type definitions included
📦 Installation
npm install @zencemarketing/text-editor-sdkOr with yarn:
yarn add @zencemarketing/text-editor-sdkFor alpha version specifically:
npm install @zencemarketing/text-editor-sdk@alphaPeer Dependencies
Make sure you have React installed:
npm install react react-dom🎯 Quick Start
import React, { useState } from 'react';
import RichTextEditor from '@zencemarketing/text-editor-sdk';
function App() {
const [content, setContent] = useState('');
return (
<RichTextEditor
value={content}
onChange={setContent}
placeholder="Start typing..."
features={{
bold: true,
italic: true,
underline: true,
links: true,
bulletList: true
}}
/>
);
}📖 Documentation
Props
Content Management
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| value | string | undefined | Controlled value (JSON string of Slate nodes) |
| defaultValue | string | '[]' | Initial value for uncontrolled component |
| onChange | (value: string) => void | undefined | Callback when content changes |
| placeholder | string | "Start typing..." | Placeholder text |
Styling
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| width | string \| number | "100%" | Editor width |
| minHeight | string \| number | "200px" | Minimum height |
| maxHeight | string \| number | "400px" | Maximum height |
| borderColor | string | "#ddd" | Border color |
| borderRadius | string \| number | "4px" | Border radius |
| backgroundColor | string | "#ffffff" | Background color |
| textColor | string | "inherit" | Text color |
Features
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| features | EditorFeatures | {} | Object to enable/disable features |
Features Object
{
textFormatting?: boolean; // Enable text formatting
headings?: boolean; // Enable H1, H2, H3
bold?: boolean; // Enable bold
italic?: boolean; // Enable italic
underline?: boolean; // Enable underline
strikethrough?: boolean; // Enable strikethrough
textColor?: boolean; // Enable text color
highlightColor?: boolean; // Enable highlight
bulletList?: boolean; // Enable bulleted lists
numberedList?: boolean; // Enable numbered lists
links?: boolean; // Enable link insertion
mediaUpload?: boolean; // Enable media upload
mentions?: boolean; // Enable @mentions
emoji?: boolean; // Enable emoji picker
codeBlock?: boolean; // Enable code blocks
}Mentions
| Prop | Type | Description |
|------|------|-------------|
| mentionUsers | User[] | Array of users for @mentions |
| onMentionSelect | (user: User) => void | Callback when user is mentioned |
User Type:
interface User {
id: string;
name: string;
avatar?: string;
email?: string;
}Tags/Variables
| Prop | Type | Description |
|------|------|-------------|
| tags | Tag[] | Array of tags/variables |
| onTagSelect | (tag: Tag) => void | Callback when tag is inserted |
| tagPlaceholder | string | Placeholder for tag search |
Tag Type:
interface Tag {
id: string;
label: string; // Display name
value: string; // Actual value (e.g., "{{userName}}")
description?: string; // Optional description
color?: string; // Text color
backgroundColor?: string; // Background color
}Media Upload
| Prop | Type | Description |
|------|------|-------------|
| onMediaUpload | (file: File) => Promise<string \| void> | Async function to handle file upload |
Character Counter
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| showCharCount | boolean | false | Show character count |
| maxCharCount | number | undefined | Maximum characters allowed |
| charCountPosition | 'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' | 'bottom-right' | Position of counter |
Toolbar Configuration
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| toolbarPosition | 'top' \| 'bottom' \| 'both' | 'top' | Toolbar position |
| topToolbarButtons | string[] | [] | Buttons for top toolbar |
| bottomToolbarButtons | string[] | [] | Buttons for bottom toolbar |
Available Toolbar Buttons:
'headings', 'bold', 'italic', 'underline', 'strikethrough', 'bulletList', 'numberedList', 'link', 'mediaUpload', 'mentions', 'tags', 'emoji', 'codeBlock', 'more'
Action Buttons
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| actionButtonsPosition | 'top-right' \| 'bottom-right' \| 'none' | 'none' | Position of action buttons |
| cancelButton | CancelButtonConfig | undefined | Cancel button configuration |
| submitButton | SubmitButtonConfig | undefined | Submit button configuration |
Button Configuration:
interface CancelButtonConfig {
show?: boolean;
label?: string; // Max 15 characters
position?: 1 | 2; // Order (1=first, 2=second)
style?: React.CSSProperties;
onClick?: () => void;
}
interface SubmitButtonConfig {
show?: boolean;
label?: string;
position?: 1 | 2;
style?: React.CSSProperties;
onClick?: (content: string) => void; // Receives JSON string
}💡 Usage Examples
Basic Editor
import RichTextEditor from '@zencemarketing/text-editor-sdk';
function BasicEditor() {
const [content, setContent] = useState('');
return (
<RichTextEditor
value={content}
onChange={setContent}
features={{
bold: true,
italic: true,
underline: true
}}
/>
);
}With Mentions & Tags
const users = [
{ id: '1', name: 'John Doe', email: '[email protected]' },
{ id: '2', name: 'Jane Smith', email: '[email protected]' }
];
const tags = [
{
id: '1',
label: 'User Name',
value: '{{userName}}',
backgroundColor: '#fff3cd',
color: '#856404'
}
];
function EditorWithMentions() {
const [content, setContent] = useState('');
return (
<RichTextEditor
value={content}
onChange={setContent}
mentionUsers={users}
onMentionSelect={(user) => console.log('Mentioned:', user)}
tags={tags}
onTagSelect={(tag) => console.log('Tag inserted:', tag)}
features={{
mentions: true,
bold: true,
italic: true
}}
/>
);
}With Image Upload
function EditorWithUpload() {
const [content, setContent] = useState('');
const handleUpload = async (file: File) => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const { url } = await response.json();
return url;
};
return (
<RichTextEditor
value={content}
onChange={setContent}
onMediaUpload={handleUpload}
features={{
mediaUpload: true,
bold: true,
italic: true
}}
/>
);
}With Character Limit
function EditorWithLimit() {
const [content, setContent] = useState('');
return (
<RichTextEditor
value={content}
onChange={setContent}
showCharCount={true}
maxCharCount={1000}
charCountPosition="bottom-right"
features={{
bold: true,
italic: true
}}
/>
);
}With Custom Toolbars
function EditorWithCustomToolbars() {
const [content, setContent] = useState('');
return (
<RichTextEditor
value={content}
onChange={setContent}
toolbarPosition="both"
topToolbarButtons={['headings', 'bold', 'italic', 'link']}
bottomToolbarButtons={['emoji', 'mentions', 'mediaUpload']}
features={{
headings: true,
bold: true,
italic: true,
links: true,
emoji: true,
mentions: true,
mediaUpload: true
}}
/>
);
}With Submit/Cancel Buttons
function EditorWithActions() {
const [content, setContent] = useState('');
const handleSubmit = (editorContent: string) => {
const data = JSON.parse(editorContent);
console.log('Submitting:', data);
// Send to API
};
return (
<RichTextEditor
value={content}
onChange={setContent}
actionButtonsPosition="bottom-right"
cancelButton={{
show: true,
label: 'Cancel',
onClick: () => console.log('Cancelled')
}}
submitButton={{
show: true,
label: 'Send',
onClick: handleSubmit,
style: {
backgroundColor: '#28a745',
color: '#fff'
}
}}
features={{
bold: true,
italic: true,
emoji: true
}}
/>
);
}Complete Example (Kitchen Sink)
function CompleteEditor() {
const [content, setContent] = useState('');
const users = [
{ id: '1', name: 'John Doe', email: '[email protected]' },
{ id: '2', name: 'Jane Smith', email: '[email protected]' }
];
const tags = [
{
id: '1',
label: 'User Name',
value: '{{userName}}',
description: 'Current user name',
backgroundColor: '#fff3cd',
color: '#856404'
}
];
const handleMediaUpload = async (file: File) => {
// Upload logic
return 'https://example.com/uploaded-image.jpg';
};
const handleSubmit = (content: string) => {
console.log('Content:', JSON.parse(content));
};
return (
<RichTextEditor
value={content}
onChange={setContent}
placeholder="Start typing..."
// Styling
minHeight="200px"
maxHeight="500px"
width="100%"
borderRadius="8px"
backgroundColor="#ffffff"
// Features
features={{
headings: true,
bold: true,
italic: true,
underline: true,
strikethrough: true,
bulletList: true,
numberedList: true,
links: true,
mediaUpload: true,
mentions: true,
emoji: true,
codeBlock: true
}}
// Mentions
mentionUsers={users}
onMentionSelect={(user) => console.log('Mentioned:', user)}
// Tags
tags={tags}
onTagSelect={(tag) => console.log('Tag:', tag)}
tagPlaceholder="Search variables..."
// Media
onMediaUpload={handleMediaUpload}
// Character count
showCharCount={true}
maxCharCount={5000}
charCountPosition="bottom-right"
// Toolbar
toolbarPosition="both"
topToolbarButtons={[
'headings', 'bold', 'italic', 'underline',
'bulletList', 'numberedList', 'link'
]}
bottomToolbarButtons={[
'mediaUpload', 'mentions', 'tags', 'emoji', 'codeBlock'
]}
// Action buttons
actionButtonsPosition="bottom-right"
cancelButton={{
show: true,
label: 'Clear',
position: 1
}}
submitButton={{
show: true,
label: 'Send',
position: 2,
onClick: handleSubmit,
style: {
backgroundColor: '#28a745',
color: '#fff'
}
}}
/>
);
}🎨 Styling
The editor comes with default styling, but you can customize it extensively:
<RichTextEditor
className="my-custom-editor"
style={{ border: '2px solid blue' }}
backgroundColor="#f9f9f9"
borderColor="#cccccc"
borderRadius="12px"
toolbarBackgroundColor="#f0f0f0"
headerBackgroundColor="#e0e0e0"
textColor="#333333"
/>CSS Classes
You can also override styles using CSS:
.my-custom-editor {
font-family: 'Your Custom Font';
}
.rte-toolbar {
/* Toolbar styles */
}
.rte-toolbar-button {
/* Button styles */
}
.rte-editor-wrapper {
/* Editor content area */
}📝 Content Format
The editor stores content as a JSON string of Slate nodes. Example:
[
{
"type": "paragraph",
"children": [
{ "text": "Hello " },
{ "text": "world", "bold": true },
{ "text": "!" }
]
}
]Converting to HTML
To convert editor content to HTML, parse the JSON and process the nodes:
const content = JSON.parse(editorContent);
// Process nodes and convert to HTML based on your needsConverting from HTML
Use the built-in paste functionality to convert HTML to editor format automatically.
🔧 Advanced Usage
Programmatically Control Editor
function ControlledEditor() {
const [content, setContent] = useState('');
const insertText = () => {
const nodes = JSON.parse(content || '[]');
nodes.push({
type: 'paragraph',
children: [{ text: 'Programmatically added text' }]
});
setContent(JSON.stringify(nodes));
};
return (
<>
<button onClick={insertText}>Insert Text</button>
<RichTextEditor value={content} onChange={setContent} />
</>
);
}🐛 Troubleshooting
Editor not showing
Make sure you've installed peer dependencies:
npm install react react-domTypeScript errors
Ensure TypeScript is configured correctly and includes node_modules types.
Styles not applying
The editor includes its own CSS. Make sure your build tool processes CSS imports.
⚠️ Alpha Version Notice
This is an alpha release (0.1.0-alpha.0). This means:
- ✅ Core features are implemented and tested
- ⚠️ API may change before stable 1.0.0 release
- ⚠️ Some features may have bugs or limitations
- ⚠️ Not recommended for production use yet
- 📝 Breaking changes possible in future alpha releases
Roadmap to 1.0.0
- [ ] Complete testing across different browsers
- [ ] Performance optimizations
- [ ] API stabilization
- [ ] Additional features (tables, text/highlight colors)
- [ ] Comprehensive test suite
- [ ] Migration guides and documentation
- [ ] Community feedback integration
We encourage you to try it out, provide feedback, and report issues!
📄 License
MIT © ZenceMarketing
🤝 Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
📞 Support
For support, please contact us at [email protected]
Made with ❤️ by ZenceMarketing
