npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

npm version License: MIT

🚀 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-sdk

Or with yarn:

yarn add @zencemarketing/text-editor-sdk

For alpha version specifically:

npm install @zencemarketing/text-editor-sdk@alpha

Peer 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 needs

Converting 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-dom

TypeScript 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