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

@omidtz/react-editor

v0.3.6

Published

Modern Lexical-based React rich text editor

Readme

@omidtz/react-editor

A modern Lexical-based rich text editor for React.

v0.3.1 · React 18 / 19 · JavaScript & TypeScript · Vite · Next.js · SSR-friendly

npm install @omidtz/react-editor
import { Editor } from "@omidtz/react-editor";

export default function App() {
  return <Editor direction="rtl" onChange={({ html, json }) => console.log(html, json)} />;
}

Styles load automatically with the package import. Optional explicit import:

import "@omidtz/react-editor/style.css";

Features

Editing

  • Headings H1–H6, paragraph
  • Bold, italic, underline, strikethrough
  • Text color, highlight, background color
  • Bullet / numbered / checklist
  • Quote, divider, links
  • Undo / redo
  • Slash commands (/)
  • Markdown shortcuts (# , ## , > , - , 1. , ```, **bold**, _italic_)
  • Mentions (@)
  • Emoji picker
  • Code blocks
  • Tables (insert, add/delete rows & columns)
  • File attachments (PDF, DOC, DOCX, ZIP, TXT)
  • Video embed (YouTube, Vimeo, generic embed URL)

Media (host-owned)

The editor does not own your File Manager. It only exposes clean hooks:

| Path | Purpose | | --- | --- | | onImageUpload(file) | Direct upload from toolbar / drag-drop / paste | | onOpenMediaLibrary() | Open your external media picker | | ref.insertImage({ url, alt, align? }) | Insert the selected asset into the document |

Also built into images:

  • Resize handles when selected
  • Caption field under the image
  • Alignment toolbar: left / center / right (persisted in JSON + HTML)

Built-in localStorage Media Library is optional and turns off automatically when you pass onOpenMediaLibrary (or set features.mediaLibrary: false).

UX / UI

  • Responsive toolbar (desktop / tablet / mobile)
  • Mobile sticky toolbar + More menu
  • RTL / LTR toggle in the toolbar (desktop & mobile)
  • Direction-aware placeholder: RTL → بنویسید · LTR → Write
  • Light / dark / custom theme (CSS variables)
  • Loading overlay
  • Auto-save with status indicator
  • Word / character count & reading time
  • Character limit (maxCharacters)
  • Floating UI tooltips
  • Keyboard navigation & ARIA

Output

  • Semantic HTML (h1–h6, p, ul/ol, blockquote, a, img/figure, table, …)
  • Lexical JSON
  • onChange({ html, json })

Quick start

import { Editor } from "@omidtz/react-editor";

function App() {
  return (
    <Editor
      direction="rtl"
      theme="light"
      onChange={({ html, json }) => {
        // persist html / json
      }}
    />
  );
}

Props

| Prop | Type | Default | Description | | --- | --- | --- | --- | | value | object \| string \| null | — | Initial Lexical JSON (or HTML string) | | placeholder | string | auto | Custom placeholder; if omitted, uses بنویسید (RTL) or Write (LTR) | | theme | "light" \| "dark" \| object | "light" | Theme preset or custom tokens (primary, radius, mode, …) | | direction | "ltr" \| "rtl" \| "auto" | "ltr" | Initial text direction (toggleable in toolbar) | | loading | boolean | false | Full-surface loading overlay | | loadingLabel | string | "Loading…" | Overlay label | | onChange | ({ html, json }) => void | — | Fires on every content update | | onJSONChange | (json) => void | — | JSON-only callback | | onHTMLChange | (html) => void | — | HTML-only callback | | autoSave | { enabled?, delay?, onSave } | — | Debounced save (see below) | | mentions | { id, name, username }[] | [] | Users for @ mention menu | | maxCharacters | number | — | Soft character limit + stats display | | features | object | see below | Feature flags (preferred over legacy booleans) | | video | boolean | true | Legacy alias for features.video | | files | boolean | true | Legacy alias for features.files | | showStats | boolean | true | Legacy alias for features.stats | | onImageUpload | (file) => Promise<string> | — | Direct file upload → returns image URL | | onFileUpload | (file) => Promise<string \| FileInfo> | — | Returns URL or file info | | onOpenMediaLibrary | () => void | — | Toolbar Images icon calls this; host opens its picker | | onReady | (api) => void | — | Receives { insertImage, getEditor, focus } | | toolbarExtra | { id, icon?, label?, onClick?, active? }[] | — | Extra toolbar buttons | | toolbar | ReactNode \| false | default | Custom toolbar or false to hide | | mobileToolbar | ReactNode \| false | default | Custom mobile toolbar or false | | className | string | — | Container class | | contentClassName | string | — | ContentEditable class | | ref | EditorHandle | — | Imperative API (insertImage, …) |

features

features={{
  mediaLibrary: false, // built-in localStorage library
  imageUpload: true,   // direct upload button (ImageUploader)
  files: false,
  video: false,
  emoji: true,
  slashCommands: true,
  stats: false,
  mentions: true,
  table: true,
}}

When onOpenMediaLibrary is passed, mediaLibrary defaults to false unless you explicitly set features.mediaLibrary: true.


External media library (recommended for Surena / apps with a File Manager)

import { useRef, useState } from "react";
import { Editor } from "@omidtz/react-editor";

function ArticleEditor() {
  const editorRef = useRef(null);
  const [libraryOpen, setLibraryOpen] = useState(false);

  return (
    <>
      <Editor
        ref={editorRef}
        features={{ mediaLibrary: false, imageUpload: true }}
        onImageUpload={async (file) => {
          // upload to your File Manager API
          const uploaded = await uploadToFileManager(file);
          return uploaded.url;
        }}
        onOpenMediaLibrary={() => setLibraryOpen(true)}
      />

      <YourMediaLibraryModal
        open={libraryOpen}
        onClose={() => setLibraryOpen(false)}
        onSelect={(media) => {
          editorRef.current?.insertImage({
            url: media.url,
            alt: media.altText,
            align: "center", // optional: "left" | "center" | "right"
          });
          setLibraryOpen(false);
        }}
      />
    </>
  );
}

Flow

Toolbar Images icon
        │
        ▼
onOpenMediaLibrary()
        │
        ▼
Host MediaLibraryModal
        │
        ▼
editorRef.insertImage({ url, alt, align? })

Alternatively, use onReady:

<Editor
  onOpenMediaLibrary={openModal}
  onReady={(api) => {
    mediaApiRef.current = api; // api.insertImage(...)
  }}
/>

insertImage payload

editorRef.current.insertImage({
  url: string,       // or src
  alt?: string,      // or altText / title
  width?: number | "inherit",
  height?: number | "inherit",
  caption?: string,
  align?: "left" | "center" | "right", // default: "left"
})

Image alignment

Select an image in the editor to show a floating toolbar with left / center / right.

  • Stored on the node as align in Lexical JSON
  • Exported HTML uses text-align (+ data-align) on the wrapping div / figure
  • Default: "left"
editorRef.current.insertImage({
  url: "https://cdn.example.com/a.jpg",
  alt: "Demo",
  align: "center",
})

Optional toolbarExtra

<Editor
  toolbarExtra={[
    {
      id: "custom-action",
      icon: "FolderOpen", // lucide name or component
      label: "Custom",
      onClick: () => doSomething(),
    },
  ]}
/>

Built-in icon names: Images, Image, FolderOpen, FileText, Smile, Video, Table, Link, …


Examples

Controlled / persisted content

<Editor
  value={savedJson}
  onChange={({ json, html }) => {
    localStorage.setItem("doc", JSON.stringify(json));
  }}
/>

Auto save

<Editor
  autoSave={{
    enabled: true,
    delay: 2000,
    onSave: async ({ json, html }) => {
      await fetch("/api/docs", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ json, html }),
      });
    },
  }}
/>

Shows Saving… / Saved / error in the editor chrome.

Theme

<Editor theme="dark" />

<Editor
  theme={{
    mode: "light",
    primary: "#6366f1",
    radius: "12px",
  }}
/>

Direction (RTL / LTR)

<Editor direction="rtl" />
<Editor direction="ltr" />

Users can also toggle RTL ↔ LTR from the first control in the toolbar. Placeholder updates automatically unless you pass a custom placeholder.

Mentions

<Editor
  mentions={[
    { id: 1, name: "Omid", username: "omid" },
    { id: 2, name: "Admin", username: "admin" },
  ]}
/>

Type @ to open the mention menu (search + keyboard navigation).

Character limit & stats

<Editor maxCharacters={5000} showStats />

Direct image upload only

<Editor
  features={{ mediaLibrary: false }}
  onImageUpload={async (file) => {
    const formData = new FormData();
    formData.append("image", file);

    const res = await fetch("/api/upload", {
      method: "POST",
      headers: { Authorization: `Bearer ${token}` },
      body: formData,
    });

    const data = await res.json();
    return data.url; // Promise<string>
  }}
/>

Toolbar Upload image opens the ImageUploader. Paste / drag-drop still go through onImageUpload when provided.

Built-in Media Library (optional)

Default when you do not pass onOpenMediaLibrary:

<Editor features={{ mediaLibrary: true }} onImageUpload={uploadFn} />

Stores items in localStorage (omid-editor-media-library). Prefer an external library for production apps with a central File Manager.

File upload

<Editor
  onFileUpload={async (file) => {
    const uploaded = await uploadFile(file);
    return {
      src: uploaded.url,
      name: uploaded.name,
      size: uploaded.size,
      mimeType: uploaded.mimeType,
    };
  }}
/>

Or return a plain URL string.

Video

Paste a YouTube / Vimeo (or embed) URL — it becomes a responsive video block (replace / remove supported).

Disable with features={{ video: false }} or video={false}.

Slash commands

Type / for: Heading 1–2, lists, checklist, quote, divider, image, table, code block.

Disable with features={{ slashCommands: false }}.

Markdown shortcuts

While typing: # , ## , > , - , 1. , fenced code, **bold**, _italic_.


Public API

Main

import {
  Editor,
  EditorProvider,
  Toolbar,
  MediaLibraryButton,
  exportJSON,
  exportHTML,
  exportEditorValue,
  importJSON,
  importHTML,
  importEditorValue,
} from "@omidtz/react-editor";

Hooks & theme (advanced)

Also exported: useEditorCommands, useEditorValue, useTheme, useDirection, useEditorFeatures, useEditorUpload, useAutoSave, IMAGE_ALIGNS, normalizeImageAlign, plugins, nodes, and utils for extension.


HTML output (example)

<h1>Hello</h1>
<p>Paragraph with <strong>bold</strong> and <em>italic</em>.</p>
<ul>
  <li>Item</li>
</ul>
<blockquote>Quote</blockquote>
<figure data-omid-image="true" data-align="center" style="text-align: center;">
  <img src="https://cdn.example.com/a.jpg" alt="Demo" data-align="center" />
  <figcaption>Optional caption</figcaption>
</figure>

Framework notes

| Environment | Notes | | --- | --- | | Vite | Works out of the box | | Next.js | Use in Client Components ("use client"). CSS auto-imports; if needed: import "@omidtz/react-editor/style.css" | | CRA / Remix | Supported as a normal React dependency | | SSR | Editor UI is client-side; avoid calling browser-only APIs at import time |

Peer dependencies: react and react-dom >=18.


Development (this repo)

npm install
npm run dev          # playground
npm run build        # library → dist/
npm run build:demo   # demo app → dist-demo/

Roadmap

  • Stronger public plugin API
  • Collaboration / comments
  • Revision history
  • Official TypeScript .d.ts packaging polish

License

MIT

Author

Omid Taziki