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

@dannysir/floating-components

v0.3.0

Published

VS Code-style tree-based panel layout for React

Readme

@dannysir/floating-components

한국어 README · Live Demo · API Reference

Tree-based resizable and reorderable panel layout for React. Split panels horizontally or vertically, resize borders by dragging, and reorder panels via drag and drop — just like VS Code or any modern IDE.


Features

  • N-ary tree structureSplitNode can hold two or more children, keeping the tree flat without unnecessary nesting
  • Border drag resize — drag panel borders to resize (requestAnimationFrame optimized)
  • Drag-and-drop panel move — reorder panels via HTML5 Drag & Drop API
  • Multi-level drop target — distinguishes panel edge, parent split edge, and root edge for depth-aware placement
  • Immutable state — all tree updates produce new objects via spread
  • View / State separationTreeLayout (rendering) and useLayoutTree (state) can be used independently
  • TypeScript — full type declarations included
  • ESM + CJS — dual-format bundle output

Installation

npm install @dannysir/floating-components

Peer dependencies: react >= 18


Quick Start

import {
  TreeLayout,
  useLayoutTree,
  createComponentStore,
  type LayoutNode,
} from "@dannysir/floating-components";

// 1. Map string keys to the React nodes they render.
const store = createComponentStore({
  "panel-a": <div style={{ padding: 16, background: "#dbeafe", height: "100%" }}>Panel A</div>,
  "panel-b": <div style={{ padding: 16, background: "#dcfce7", height: "100%" }}>Panel B</div>,
  "panel-c": <div style={{ padding: 16, background: "#ffedd5", height: "100%" }}>Panel C</div>,
});

// 2. The tree stores only string componentKeys — no React elements.
const initialTree: LayoutNode = {
  type: "split",
  direction: "horizontal",
  size: 1,
  children: [
    { type: "panel", id: "panel-a", size: 1, componentKey: "panel-a" },
    {
      type: "split",
      direction: "vertical",
      size: 1,
      children: [
        { type: "panel", id: "panel-b", size: 1, componentKey: "panel-b" },
        { type: "panel", id: "panel-c", size: 1, componentKey: "panel-c" },
      ],
    },
  ],
};

const App = () => {
  const { tree, resizeBorder, movePanel } = useLayoutTree(initialTree);

  return (
    <div style={{ width: "100vw", height: "100vh" }}>
      <TreeLayout tree={tree} components={store} onResizeBorder={resizeBorder} onMovePanel={movePanel} />
    </div>
  );
};

TreeLayout fills its parent by default (width: 100%, height: 100%). Use a sized parent as above, or pass width/height props to set explicit dimensions.

The tree holds only primitive values (id, size, direction, componentKey), so JSON.stringify(tree) round-trips cleanly. See Persistence below.


Recipes

Toggle panel visibility

const { panelIds, removePanel, insertPanel } = useLayoutTree(initialTree);

const togglePanel = (id: string, componentKey: string) => {
  if (panelIds.includes(id)) {
    removePanel(id);
  } else {
    insertPanel({ panel: { id, componentKey } });
  }
};

Persistence

Because the tree contains only primitive values, you can save and restore the layout with plain JSON.stringify / JSON.parse — no custom serializer needed. The ComponentStore (the key → React node mapping) lives separately in your code, so it never needs to be serialized.

const store = createComponentStore({
  sidebar: <Sidebar />,
  editor: <Editor />,
});

const STORAGE_KEY = "my-layout";

const load = (): LayoutNode => {
  const saved = localStorage.getItem(STORAGE_KEY);
  return saved ? (JSON.parse(saved) as LayoutNode) : defaultTree;
};

const App = () => {
  const { tree, resizeBorder, movePanel } = useLayoutTree(load());

  useEffect(() => {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(tree));
  }, [tree]);

  return <TreeLayout tree={tree} components={store} onResizeBorder={resizeBorder} onMovePanel={movePanel} />;
};

On restore, the tree's componentKeys are looked up in the store. If a key isn't registered, the panel renders empty and a dev-mode console warning is emitted — so keep your store keys stable across releases.

Create the store once and keep a stable reference (module-level or useMemo). Calling register/unregister mutates the internal Map but does not trigger a re-render — to change what's on screen dynamically, swap the tree (e.g. setTree) rather than relying on store mutation.


Drag & Drop

Drag any panel to reorder. A translucent preview of the drop target follows the cursor, and dropping near different regions produces different placements:

  • Drop on the panel center → split the hovered panel
  • Drop near the enclosing split's edge → place as a sibling of the parent split
  • Drop near the root's edge → place at the top level

Restrict to a single axis

By default TreeLayout uses 4-edge classification (direction="complex"). Pass direction to lock the layout to one axis:

<TreeLayout
  tree={tree}
  direction="vertical"
  onResizeBorder={resizeBorder}
  onMovePanel={movePanel}
/>
  • "vertical" — drops classified by the Y midline (top/bottom only); only vertical splits are produced
  • "horizontal" — drops classified by the X midline (left/right only); only horizontal splits are produced
  • "complex" (default) — 4-edge classification with both axes

If the input tree contains splits whose direction conflicts with the prop, they are auto-normalized and a dev-mode console warning is emitted. useLayoutTree.splitPanel(...) direct calls are not constrained.

See API Reference → direction.

Wire it up with useLayoutTree's movePanel:

<TreeLayout tree={tree} onResizeBorder={resizeBorder} onMovePanel={movePanel} />

See API Reference → Drag & Drop for the full placement rules and the depth parameter.


Documentation


License

ISC