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

@heojeongbo/react-mosaic-ui

v2.2.6

Published

A modern React tiling window manager with FSD architecture, Rollup bundling, and Tailwind CSS v4

Readme

react-mosaic-ui

A modern React tiling window manager with drag-and-drop, resizable splits, and full TypeScript support.

Inspired by react-mosaic

Installation

# npm
npm install @heojeongbo/react-mosaic-ui

# yarn
yarn add @heojeongbo/react-mosaic-ui

# pnpm
pnpm add @heojeongbo/react-mosaic-ui

# bun
bun add @heojeongbo/react-mosaic-ui

Peer dependencies (if not already installed):

bun add react react-dom

Quick Start

import { useState } from 'react';
import { Mosaic, MosaicWindow, type MosaicNode } from '@heojeongbo/react-mosaic-ui';
import '@heojeongbo/react-mosaic-ui/styles.css';

type ViewId = 'editor' | 'preview' | 'terminal';

const TITLES: Record<ViewId, string> = {
  editor: 'Editor',
  preview: 'Preview',
  terminal: 'Terminal',
};

export default function App() {
  const [tree, setTree] = useState<MosaicNode<ViewId> | null>({
    direction: 'row',
    first: 'editor',
    second: {
      direction: 'column',
      first: 'preview',
      second: 'terminal',
      splitPercentage: 60,
    },
    splitPercentage: 60,
  });

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Mosaic<ViewId>
        value={tree}
        onChange={setTree}
        renderTile={(id, path) => (
          <MosaicWindow title={TITLES[id]} path={path} createNode={() => 'editor'}>
            <div style={{ padding: 16 }}>Content: {id}</div>
          </MosaicWindow>
        )}
      />
    </div>
  );
}

Core Concepts

Tree Structure

A layout is represented as a binary tree. Leaf nodes are tile IDs (string | number), and parent nodes describe how to split the space.

type MosaicNode<T> = T | MosaicParent<T>;

interface MosaicParent<T> {
  direction: 'row' | 'column'; // row = left/right split, column = top/bottom split
  first: MosaicNode<T>;
  second: MosaicNode<T>;
  splitPercentage?: number;    // 0–100, defaults to 50
}

Example tree (3 tiles):

const tree: MosaicNode<string> = {
  direction: 'row',
  first: 'a',
  second: {
    direction: 'column',
    first: 'b',
    second: 'c',
  },
};

Controlled vs Uncontrolled

Controlled — you own the state:

const [tree, setTree] = useState<MosaicNode<string> | null>(initialTree);

<Mosaic value={tree} onChange={setTree} renderTile={renderTile} />

Uncontrolled — the component manages state internally:

<Mosaic initialValue={initialTree} renderTile={renderTile} />

API Reference

<Mosaic>

| Prop | Type | Default | Description | |------|------|---------|-------------| | renderTile | (id: T, path: MosaicPath) => JSX.Element | required | Renders each leaf tile | | value | MosaicNode<T> \| null | — | Controlled tree value | | initialValue | MosaicNode<T> \| null | — | Uncontrolled initial value | | onChange | (node: MosaicNode<T> \| null) => void | — | Called on every tree change | | onRelease | (node: MosaicNode<T> \| null) => void | — | Called when drag/resize is released | | className | string | — | Extra class on the root element | | zeroStateView | JSX.Element | built-in | Shown when tree is null | | mosaicId | string | auto | ID for multi-mosaic DnD isolation | | createNode | () => T \| Promise<T> | — | Factory for new tiles (enables split/replace) | | resize | ResizeOptions | — | Override minimum pane size |

<MosaicWindow>

| Prop | Type | Default | Description | |------|------|---------|-------------| | title | string | required | Toolbar title | | path | MosaicPath | required | Position in the tree (passed from renderTile) | | children | ReactNode | required | Window body content | | createNode | () => T \| Promise<T> | — | Enables Split and Replace toolbar buttons | | draggable | boolean | true | Whether the window can be dragged | | toolbarControls | ReactNode | — | Replaces the default toolbar buttons entirely | | additionalControls | ReactNode | — | Extra controls shown in a collapsible drawer | | renderToolbar | (props, defaultToolbar) => ReactNode | — | Full toolbar override (receives default as second arg) | | onDragStart | () => void | — | Called when drag begins | | onDragEnd | (type: 'drop' \| 'reset') => void | — | Called when drag ends | | className | string | — | Extra class on the window element |

Built-in toolbar buttons (visible when createNode is provided):

| Button | Description | |--------|-------------| | Split | Splits the window in half | | Replace | Replaces the window with a new tile | | Expand | Expands to 70% of the parent | | Close | Removes the window from the layout |

Utility Functions

import {
  // Tree inspection
  getLeaves,                    // Get all leaf IDs in order
  isParent,                     // Check if a node is a parent
  getNodeAtPath,                // Get node at a given path
  getAndAssertNodeAtPathExists, // Same, throws if not found
  countNodes,                   // Count total nodes in the tree
  getTreeDepth,                 // Get maximum depth
  arePathsEqual,                // Compare two MosaicPath arrays
  getPathToCorner,              // Get path to a corner tile
  getOtherDirection,            // 'row' ↔ 'column'
  getOtherBranch,               // 'first' ↔ 'second'

  // Tree building
  createBalancedTreeFromLeaves, // Build balanced tree from array of IDs

  // Update generators (use with updateTree or mosaicActions.updateTree)
  updateTree,                   // Apply an array of MosaicUpdate to a tree
  createRemoveUpdate,           // Remove a tile
  createExpandUpdate,           // Expand a tile to a percentage
  createHideUpdate,             // Hide a tile (DnD internal)
  createReplaceUpdate,          // Replace a tile with another node
  createSplitUpdate,            // Split a tile into two
  createDragToUpdates,          // Move a tile via drag
} from '@heojeongbo/react-mosaic-ui';

Common patterns

// Get all tile IDs currently in the layout
const ids = getLeaves(tree); // ['editor', 'preview', 'terminal']

// Auto-arrange: rebuild a balanced layout from existing tiles
const balanced = createBalancedTreeFromLeaves(getLeaves(tree));
setTree(balanced);

// Remove a specific tile programmatically
const update = createRemoveUpdate(tree, pathToTile);
setTree(updateTree(tree, [update]));

Contexts

For advanced use cases you can read the mosaic state directly from context:

import { MosaicContext, MosaicWindowContext } from '@heojeongbo/react-mosaic-ui';
import { useContext } from 'react';

// Inside a tile rendered by renderTile:
function MyTile() {
  const { mosaicActions, mosaicId } = useContext(MosaicContext);
  const { mosaicWindowActions } = useContext(MosaicWindowContext);

  return (
    <button onClick={() => mosaicActions.remove(mosaicWindowActions.getPath())}>
      Close me
    </button>
  );
}

MosaicRootActions (via MosaicContext.mosaicActions):

| Method | Description | |--------|-------------| | expand(path, percentage?) | Expand node to percentage (default 70%) | | remove(path) | Remove node at path | | hide(path) | Hide node (used internally by DnD) | | replaceWith(path, node) | Replace node at path | | updateTree(updates, suppressOnRelease?) | Apply multiple updates atomically | | getRoot() | Get current root node |

MosaicWindowActions (via MosaicWindowContext.mosaicWindowActions):

| Method | Description | |--------|-------------| | split() | Split the current window | | replaceWithNew() | Replace current window with a new tile | | getPath() | Get current window's path in the tree |

Style Customization

Import the stylesheet once at your app entry point:

import '@heojeongbo/react-mosaic-ui/styles.css';

Override CSS variables to theme the layout:

:root {
  --rm-border-color: #cbd5e1;
  --rm-background: #ffffff;
  --rm-window-bg: #f8fafc;
  --rm-toolbar-bg: #f1f5f9;
  --rm-split-color: #94a3b8;
  --rm-split-hover: #64748b;
  --rm-split-size: 4px;       /* Width/height of the resize handle */
  --rm-toolbar-height: 40px;
}

All internal class names use the rm- prefix and are scoped under .react-mosaic, so they won't conflict with your own styles.

Custom toolbar

<MosaicWindow
  title="My Window"
  path={path}
  renderToolbar={(props, defaultToolbar) => (
    <div className="my-toolbar">
      <span>{props.title}</span>
      <div className="actions">{defaultToolbar}</div>
    </div>
  )}
>
  <div>Content</div>
</MosaicWindow>

Additional controls (drawer)

<MosaicWindow
  title="My Window"
  path={path}
  additionalControls={
    <>
      <button onClick={handleExport}>Export</button>
      <button onClick={handleSettings}>Settings</button>
    </>
  }
>
  <div>Content</div>
</MosaicWindow>

Advanced Examples

Dynamic tile creation

let nextId = 1;

function App() {
  const [tree, setTree] = useState<MosaicNode<number> | null>(1);

  return (
    <Mosaic<number>
      value={tree}
      onChange={setTree}
      createNode={() => ++nextId}
      renderTile={(id, path) => (
        <MosaicWindow title={`Window ${id}`} path={path} createNode={() => ++nextId}>
          <div>Content {id}</div>
        </MosaicWindow>
      )}
    />
  );
}

Adding a new window to an existing layout

import { createBalancedTreeFromLeaves, getLeaves } from '@heojeongbo/react-mosaic-ui';

function addWindow(tree: MosaicNode<string> | null, newId: string) {
  const current = getLeaves(tree ?? []);
  return createBalancedTreeFromLeaves([...current, newId]);
}

Multiple independent mosaics on one page

<Mosaic mosaicId="mosaic-left"  value={leftTree}  onChange={setLeft}  renderTile={renderTile} />
<Mosaic mosaicId="mosaic-right" value={rightTree} onChange={setRight} renderTile={renderTile} />

Tiles can only be dragged within the same mosaicId.

onRelease — respond after resize or drag completes

<Mosaic
  value={tree}
  onChange={setTree}
  onRelease={(newTree) => {
    // save layout to server/localStorage after user finishes dragging
    saveLayout(newTree);
  }}
  renderTile={renderTile}
/>

Development

# Install dependencies
bun install

# Build library
bun run build

# Run tests (200 tests)
bun run test

# Type check
bun run typecheck

# Lint
bun run lint

# Run all checks
bun run check

# Run the example app
cd example && bun install && bun run dev

Release

bun run release:patch   # 2.2.1 → 2.2.2
bun run release:minor   # 2.2.1 → 2.3.0
bun run release:major   # 2.2.1 → 3.0.0
bun run release:dry     # dry run (no publish)

The release process runs lint + typecheck + tests, builds, bumps the version, generates CHANGELOG, tags the commit, pushes to GitHub, and publishes to npm.

Tech Stack

| Tool | Purpose | |------|---------| | React 18 / 19 | UI | | TypeScript 5 | Type safety | | Rollup | Library bundler | | Tailwind CSS v4 | Styling (rm- prefix) | | React DnD | Drag and drop | | Immer | Immutable tree updates | | Vitest | Testing | | Bun | Package manager & scripts |

License

MIT