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

@plim/react

v0.4.0

Published

Plim React bindings: define block descriptors with React components and host them inside a Plim editor.

Readme

@plim/react

React bindings for the Plim block editor: a <PlimEditor> component, the useEditorHandle() hook, ready-made slash-command and mention extensions with first-class React menus, the comments UI layer, and a bridge for defining blocks as real React components (toComponent) that persist into the document.

Install

pnpm add @plim/react @plim/editor @plim/core react react-dom

Import the editor stylesheet once at your app entry:

import '@plim/editor/styles.css';

Quickstart

import {
  PlimDriver,
  boldMark, italicMark, underlineMark, strikethroughMark, codeMark, linkMark,
  paragraphBlock, headingBlock, bulletedListBlock, numberedListBlock,
  todoListBlock, quoteBlock, horizontalRuleBlock,
} from '@plim/core';
import { contentFromMarkdown } from '@plim/markdown';
import {
  PlimEditor, useEditorHandle,
  SlashCommandMenu, slashCommandExtension, DEFAULT_SLASH_ITEMS,
} from '@plim/react';
import '@plim/editor/styles.css';

const plim = new PlimDriver({
  extensions: [slashCommandExtension()],
  registeredMarks: [boldMark, italicMark, underlineMark, strikethroughMark, codeMark, linkMark],
  registeredBlocks: [
    paragraphBlock, headingBlock,
    bulletedListBlock, numberedListBlock, todoListBlock,
    quoteBlock, horizontalRuleBlock,
  ],
});

export function App() {
  const handle = useEditorHandle();
  return (
    <>
      <PlimEditor
        plim={plim}
        handle={handle}
        initialContent={contentFromMarkdown('# Hello, Plim', 'Press `/` for the slash menu.')}
        autoFocus
      />
      <SlashCommandMenu editor={handle} items={DEFAULT_SLASH_ITEMS} />
    </>
  );
}

<PlimEditor>

type PlimEditorProps = {
  plim: PlimDriver;
  handle?: EditorHandle;          // from useEditorHandle(); lets sibling menus address the editor
  initialContent?: DocumentNode;
  readonly?: boolean;
  autoFocus?: boolean;
  onTransaction?: (tx, state) => void;
  whenReady?: () => void;
  asyncEventListeners?: AsyncListenerRegistration[];
  className?: string;
  style?: React.CSSProperties;
};

useEditorHandle() returns a stable EditorHandle you pass to <PlimEditor handle={...}> and to the menu components so they share the same live editor.

Built-in extensions & menus

Two extensions ship ready to register on your driver, each paired with a React menu:

  • Slash commandsslashCommandExtension({ character?, eventName? }) + <SlashCommandMenu editor={handle} items={DEFAULT_SLASH_ITEMS} />. Items are SlashCommandItems (id, label, hint, icon, keywords, and either blockType/attrs or a custom apply).
  • MentionsmentionExtension({ character?, eventName?, priority? }) + <MentionMenu />. DEFAULT_MENTION_USERS is a starter dataset; supply your own MentionUser[].

Both extensions trigger an async event you respond to by rendering a menu; ActionPanel / HoverMenu are the positioned primitives the menus build on (currentCaretRect, currentSelectionRect, currentBlockAnchor help you anchor custom UI).

React blocks (toComponent)

Define a block whose body is a real React component, persisted into the document. For an atomic block (no editable text), just return JSX:

import { defineBlock } from '@plim/core';

export const counterBlock = defineBlock((editor) => ({
  name: 'counter',
  type: 'standalone',
  atomic: true,
  supportsDecoration: false,
  toComponent: (payload) => (
    <CounterCard
      count={Number(payload.attrs.count ?? 0)}
      onChange={(next) => {
        const tx = editor.createTransaction();
        tx.setBlockAttrs(/* path for payload.id */ [], { count: next });
        tx.commit();
      }}
    />
  ),
}));

For an editable React block, render ContentSlot where the editor's [data-block-content] element should land — the editor owns the text inside it, React owns everything around it:

import { defineBlock, type BlockPayload } from '@plim/core';
import { ContentSlot } from '@plim/react';

export const calloutBlock = defineBlock({
  name: 'callout',
  type: 'standalone',
  toComponent: (payload: BlockPayload) => (
    <div className="plim-callout">
      <span contentEditable={false}>💡</span>
      <ContentSlot el={(payload.content as HTMLElement[])[0]} />
    </div>
  ),
});

ContentSlot mounts the slot with display: contents (no extra layout) and no-ops once attached, so React's reconciliation never fights the editor's in-place text updates.

Comments

@plim/react provides the UI for the comment system that lives in @plim/collaboration. Mount one component:

import { CommentsLayer } from '@plim/react';
<CommentsLayer editor={handle} store={store} currentUser={{ id: 'me', name: 'You' }} />;

Or compose your own panel from the exported building blocks: CommentThreadCard, CommentCard, CommentComposer, and the useComments(store) hook.

Where to go next

License

See the LICENSE file in this package.