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

@editora/ui-react

v0.1.28

Published

Enterprise React component library with SaaS UI components, modern design system patterns, and wrappers for Editora UI Core.

Readme

Features

  • A broad catalog: inputs and forms, pickers, tables, tabs, menus, dialogs, drawers, charts, a calendar and a Gantt planner. The full list.
  • Typed props and events. Callbacks receive a typed detail, not a raw DOM event.
  • Forms that validate: <Form> collects the values of the controls inside it, and useForm() drives it.
  • Promise dialogs: await dialog.confirm({...}), with providers that work in React StrictMode.
  • Theming with design tokens, persisted if you want.
  • Floating layers: hooks for popovers, menus and tooltips with flip, shift and focus management.
  • Wrappers for @editora/ui-core, so the components are standard custom elements underneath.

Install

npm install @editora/ui-react

@editora/ui-core comes with the package. It is tested with React 18 and 19 (the peer range is React 16.8 to 20). These components render custom elements, which are defined in the browser only: see Server rendering.

Quick start

import { Button, Input, ThemeProvider } from '@editora/ui-react';

export default function App() {
  return (
    <ThemeProvider>
      <div style={{ display: 'grid', gap: 12, maxWidth: 360 }}>
        <Input name="title" label="Title" placeholder="Untitled" clearable />
        <Button variant="primary">Save</Button>
      </div>
    </ThemeProvider>
  );
}

The picture at the top of this page is made of these components:

import { Alert, Avatar, Badge, Button, Checkbox, DatePicker, Input, Progress, Switch } from '@editora/ui-react';

export default function Gallery() {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24, alignItems: 'start' }}>
      <div style={{ display: 'grid', gap: 16 }}>
        <Input label="Project name" description="Shown in the sidebar" placeholder="Untitled" clearable />

        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          <Button variant="primary">Save</Button>
          <Button variant="secondary">Cancel</Button>
          <Button variant="ghost">Skip</Button>
          <Button variant="danger">Delete</Button>
        </div>

        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <Badge tone="success" text="Live" />
          <Badge tone="warning" text="Review" />
          <Badge tone="danger" text="Failed" />
          <Avatar initials="AM" />
          <Avatar initials="JK" />
        </div>

        <Alert title="Heads up" description="Your trial ends in 3 days." tone="warning" />
      </div>

      <div style={{ display: 'grid', gap: 16 }}>
        <Switch checked label="Email notifications" description="A summary every Monday" />
        <Checkbox checked>Remember me</Checkbox>
        <Progress value={64} />
        <DatePicker label="Start date" clearable />
      </div>
    </div>
  );
}

Import from the package or from one component

Importing from the package root is the simplest, and it is the heaviest: every wrapper and every component's custom element is in the bundle, and the bundler cannot drop the ones you do not use. Each component also has its own entry:

// Everything from the package root: the simplest, and the heaviest.
import { Button, Input } from '@editora/ui-react';
// One component per entry: a lighter first load.
import ButtonOnly from '@editora/ui-react/Button';
import InputOnly from '@editora/ui-react/Input';

export default function Imports() {
  return (
    <div style={{ display: 'grid', gap: 12, maxWidth: 360 }}>
      <Input label="From the root" />
      <InputOnly label="From its own entry" />
      <Button>Root</Button>
      <ButtonOnly>Own entry</ButtonOnly>
    </div>
  );
}

Measured with esbuild, minified, with code splitting, importing one component and leaving React out. This is everything the import needs:

| Import | Gzipped | | --- | --- | | import { Button } from '@editora/ui-react' | 1 MB | | import Button from '@editora/ui-react/Button' | 55 KB | | import Input from '@editora/ui-react/Input' | 8 KB | | import DataTable from '@editora/ui-react/DataTable' | 14 KB |

Each entry registers only the custom elements its component renders, so nothing else is fetched later. The package root registers the whole catalog on purpose, which is why it is the heavy one.

Forms

<Form> finds the named controls inside it. useForm() gives you its methods; onDirtyChange tells you when the values differ from the starting ones:

import { useState } from 'react';
import { Form, Field, Input, Button, useForm } from '@editora/ui-react';

export default function ProfileForm() {
  const { ref, submit, validate, getValues, reset } = useForm();
  const [dirty, setDirty] = useState(false);

  return (
    <Form
      ref={ref}
      autosave
      guardUnsaved
      onSubmit={(values) => console.log('submit', values)}
      onInvalid={(errors) => console.log('invalid', errors)}
      onDirtyChange={(isDirty) => setDirty(isDirty)}
      style={{ display: 'grid', gap: 12, maxWidth: 520 }}
    >
      <Field label="Full name" required>
        <Input name="fullName" required />
      </Field>

      <Field label="Email" required>
        <Input name="email" type="email" required />
      </Field>

      <div style={{ display: 'flex', gap: 8 }}>
        <Button variant="secondary" onClick={() => validate()}>Validate</Button>
        <Button variant="secondary" onClick={() => console.log(getValues())}>Values</Button>
        <Button variant="secondary" onClick={() => reset()}>Reset</Button>
        <Button onClick={() => submit()} disabled={!dirty}>Submit</Button>
      </div>
    </Form>
  );
}

useForm() returns ref, submit(), validate(), getValues(), reset(values?), isDirty() and markClean(). autosave reports the values through onAutosave after they stop changing, and guardUnsaved warns before the page is left with unsaved changes.

Promise dialogs

Wrap your app in the providers, then await the answer from anywhere inside:

import { DialogProvider, useDialog, AlertDialogProvider, useAlertDialog, Button } from '@editora/ui-react';

function Actions() {
  const dialog = useDialog();
  const alerts = useAlertDialog();

  return (
    <div style={{ display: 'flex', gap: 8 }}>
      <Button
        onClick={async () => {
          const res = await dialog.confirm({
            title: 'Archive project?',
            description: 'You can restore it later.',
            submitText: 'Archive'
          });
          console.log(res);
        }}
      >
        Confirm
      </Button>

      <Button
        variant="secondary"
        onClick={async () => {
          const res = await alerts.prompt({
            title: 'Rename',
            input: { required: true, placeholder: 'New name' }
          });
          console.log(res);
        }}
      >
        Prompt
      </Button>
    </div>
  );
}

export default function DialogExample() {
  return (
    <DialogProvider>
      <AlertDialogProvider>
        <Actions />
      </AlertDialogProvider>
    </DialogProvider>
  );
}

useDialog() has open() and confirm(). useAlertDialog() is for confirmations and prompts that need an answer. Both resolve with an object whose action says what the user did. The providers work in StrictMode.

Data tables

DataTable gives a table sorting, selection, column resizing and paging. Link a Pagination to it with paginationId:

import { DataTable, Pagination } from '@editora/ui-react';
import { useState } from 'react';

export default function UsersTable() {
  const [page, setPage] = useState(1);

  return (
    <div style={{ display: 'grid', gap: 10 }}>
      <DataTable
        sortable
        selectable
        resizableColumns
        draggableColumns
        page={page}
        pageSize={2}
        paginationId="users-pager"
        onPageChange={(d) => setPage(d.page)}
        onSortChange={(d) => console.log('sort', d)}
        onRowSelect={(d) => console.log('rows', d.indices)}
      >
        <table>
          <thead>
            <tr>
              <th data-key="id">ID</th>
              <th data-key="name">Name</th>
              <th data-key="role">Role</th>
            </tr>
          </thead>
          <tbody>
            <tr><td>1</td><td>Asha</td><td>Admin</td></tr>
            <tr><td>2</td><td>Marco</td><td>Editor</td></tr>
            <tr><td>3</td><td>Lena</td><td>Viewer</td></tr>
            <tr><td>4</td><td>Tariq</td><td>Editor</td></tr>
          </tbody>
        </table>
      </DataTable>

      <Pagination id="users-pager" page={page} />
    </div>
  );
}

Pickers

import { DatePicker, DateRangePicker, DateTimePicker, ColorPicker } from '@editora/ui-react';

export default function PickersDemo() {
  return (
    <div style={{ display: 'grid', gap: 12, maxWidth: 420 }}>
      <DatePicker label="Start date" clearable onValueChange={(v) => console.log(v)} />
      <DateRangePicker label="Window" closeOnSelect />
      <DateTimePicker label="Publish at" />
      <ColorPicker mode="popover" format="hex" alpha presets={['#2563eb', '#16a34a', '#dc2626']} />
    </div>
  );
}

Theming

ThemeProvider applies the design tokens of @editora/ui-core as CSS variables. With storageKey it remembers the theme in localStorage:

import { Button, ThemeProvider } from '@editora/ui-react';

export default function Themed() {
  return (
    <ThemeProvider
      tokens={{
        colors: { primary: '#0f766e', text: '#0f172a', background: '#ffffff' },
        radius: '10px'
      }}
      storageKey="my-app.theme"
    >
      <Button variant="primary">Save</Button>
    </ThemeProvider>
  );
}

Gantt planner

Gantt is a planning timeline: tasks, summary rows and milestones, dependencies, baselines, critical tasks, drag and resize, keyboard selection and a toolbar for zoom, filter and sort.

import { Gantt, type GanttLink, type GanttTask } from '@editora/ui-react';
import { useState } from 'react';

const initialTasks: GanttTask[] = [
  {
    id: 'foundation',
    label: 'Foundation',
    start: '2026-02-01',
    end: '2026-02-18',
    progress: 68,
    baselineStart: '2026-01-29',
    baselineEnd: '2026-02-14',
    critical: true
  },
  {
    id: 'handoff',
    label: 'Handoff milestone',
    start: '2026-02-20',
    type: 'milestone',
    tone: 'success'
  }
];

const links: GanttLink[] = [
  { id: 'foundation-handoff', source: 'foundation', target: 'handoff', type: 'e2s' }
];

export default function PlanningDemo() {
  const [tasks, setTasks] = useState(initialTasks);

  return (
    <Gantt
      tasks={tasks}
      links={links}
      zoom="week"
      sort="start"
      barVariant="soft"
      onTaskChange={({ id, start, end }) => {
        setTasks((items) => items.map((task) => task.id === id ? { ...task, start: start ?? task.start, end: end ?? task.end } : task));
      }}
      onTaskDelete={({ id }) => setTasks((items) => items.filter((task) => task.id !== id))}
      onLinkSelect={(detail) => console.log('dependency selected', detail)}
    />
  );
}
  • Dependency types: e2s, s2s, e2e and s2e. Bar designs: solid, soft, striped, outline and glass.
  • Also in the data: baselines, critical tasks, milestones, parent and child summaries, assignees, progress and split segments. Row virtualization turns on for large task sets; pass virtualized={false} for small custom layouts.
  • Planner features are props: zoom (including fit), weekStart, locale, labels, columns, gridWidth, fill, maxHeight, snap, nonWorkingDays, holidays, autoSummary, cascade, schedule="auto", criticalPath, linkable, creatable and initialScroll. The chart reports what the user does (onBeforeTaskChange, which can refuse a change with preventDefault(), onTaskChange, onTaskRename, onTaskCreate, onLinkCreate, onLinkDelete, onTaskToggle, onGridResize) and your state stays the source of truth.
  • A ref gives the element's methods: scrollToDate, scrollToTask, toggleTask, expandAll, collapseAll and exportSvg (the whole chart as an SVG string). The chart also prints as a whole.
  • The attribute-by-attribute guide, with a full planner example and screenshot, is in the @editora/ui-core README; the props above are the same options.
  • The types GanttTask, GanttLink, GanttTaskChangeDetail, GanttLinkSelectDetail, GanttLabels, GanttColumn, GanttBeforeTaskChangeDetail, GanttTaskRenameDetail, GanttTaskCreateDetail, GanttLinkCreateDetail and GanttElement are exported.

Floating layers

useFloating() positions a layer next to its anchor; useInteractions() combines the interaction hooks. This is the layer the menus, popovers and tooltips are built on:

import {
  FloatingArrow,
  FloatingFocusManager,
  FloatingPortal,
  useClick,
  useDismiss,
  useFloating,
  useInteractions
} from '@editora/ui-react';

export default function Example() {
  const floating = useFloating({
    placement: 'bottom-start',
    offset: 10,
    flip: true,
    shift: true,
    fitViewport: true,
    strategy: 'fixed'
  });

  const interactions = useInteractions([
    useClick(floating.context),
    useDismiss(floating.context)
  ]);

  return (
    <>
      <button ref={floating.referenceRef} {...interactions.getReferenceProps()}>
        Open
      </button>

      {floating.open ? (
        <FloatingPortal>
          <FloatingFocusManager context={floating.context}>
            <div
              ref={floating.floatingRef}
              {...interactions.getFloatingProps({
                style: {
                  position: 'fixed',
                  top: floating.coords.top,
                  left: floating.coords.left
                }
              })}
            >
              <FloatingArrow context={floating.context} fill="#fff" stroke="#111827" />
              Floating content
            </div>
          </FloatingFocusManager>
        </FloatingPortal>
      ) : null}
    </>
  );
}

More hooks: useHover, useFocus, useRole, useListNavigation and useTypeahead. More helpers: FloatingOverlay, FloatingTree, FloatingList and FloatingDelayGroup. For the same engine without React, see createPositioner in @editora/ui-core.

Components

Forms and inputs: Form, Field, Label, Input, Textarea, Select, Combobox, Checkbox, Radio, RadioGroup, Switch, Slider, DatePicker, DateRangePicker, TimePicker, DateTimePicker, DateRangeTimePicker, ColorPicker

Data and display: Table, DataTable, Pagination, Calendar, Chart, Timeline, Gantt, Badge, Alert, Skeleton, EmptyState, Progress, Avatar, AspectRatio

Overlays and interaction: Tooltip, HoverCard, Popover, Dropdown, Menu, Menubar, ContextMenu, Dialog, AlertDialog, Drawer, Portal, Presence, CommandPalette, QuickActions, Toolbar, FloatingToolbar, BlockControls, SelectionPopup, PluginPanel

Navigation and layout: Layout, Sidebar, AppHeader, Breadcrumb, NavigationMenu, Tabs, Box, Flex, Grid, Section, Container, Separator, Slot, VisuallyHidden, Accordion, Collapsible, Stepper, Wizard, DirectionProvider

Providers and hooks: DialogProvider, useDialog, AlertDialogProvider, useAlertDialog, ThemeProvider, useTheme, useForm, useFloating

Server rendering

The package can be imported on the server: loading a module does not touch the DOM, and the custom elements are defined only in the browser. A 'use client' file that uses these components is still rendered on the server first (Next.js does this), and that HTML carries the <ui-*> tags, which upgrade once the browser has loaded the package.

In React Server Components import from @editora/ui-react/server. It has no hooks, so a server component can load it, and it holds the components that need none: Box, Flex, Grid, Container, Section, Separator, Anchor, AspectRatio, Field, Label, Icon, Kbd, Shortcut, Stat, MetricCard, CodeBlock, CodeSnippet, DirectionProvider and VisuallyHidden. It only renders the markup. To define the elements, import @editora/ui-react/client (or the package root) from a client component; a page made of server components alone never loads the definitions, so its <ui-*> tags are not upgraded.

In development React can log a hydration warning for client components: the elements add their own inline styles and attributes as soon as they are defined, before React hydrates. The page works. The dialog providers create their host element on the client, in an effect, and clean it up when they unmount.

Troubleshooting

  • A component renders as an empty tag, or React warns that a tag is not registered. Import the wrapper from the package root or from its own entry (@editora/ui-react/Button); both register the element.
  • A callback does not fire as expected. Use the typed detail that wrapper callbacks receive, such as onPageChange={(d) => d.page}, instead of parsing the DOM event.

The rest of the family

| Package | | | --- | --- | | @editora/ui-core | The web components these wrap, for any framework. | | @editora/ui-sortable | Sortable lists and boards, with a React wrapper. | | @editora/toast | Toast notifications. | | @editora/react | The Editora rich text editor for React. |

License

MIT © Ajay Kumar