# Plane

An infinite, zoomable surface for boards, maps and diagrams — inertia, pinch, a minimap and a camera that follows keyboard focus.

- Family: Signature, imported from `mizu-ui` or `mizu-ui/signature`
- Docs: https://mizu.user64bit.world/components/plane
- Source: https://github.com/0xuser64bit/mizu/blob/main/packages/mizu/src/signature/Plane.tsx

## Usage

```tsx
"use client";

import { useState } from "react";
import { Plane, PlaneItem } from "mizu-ui";

export function Example() {
  const [notes, setNotes] = useState([
    { id: "a", x: 0, y: 0, text: "Research" },
    { id: "b", x: 260, y: 90, text: "Prototype" },
    { id: "c", x: 90, y: 260, text: "Ship it" },
  ]);
  return (
    <Plane label="Planning board">
      {notes.map((n) => (
        <PlaneItem
          key={n.id}
          id={n.id}
          x={n.x}
          y={n.y}
          width={200}
          label={n.text}
          onMove={(x, y) =>
            setNotes((all) =>
              all.map((a) => (a.id === n.id ? { ...a, x, y } : a)),
            )
          }
        >
          <p
            style={{
              margin: 0,
              padding: 18,
              background: "var(--mizu-ink-2)",
              border: "1px solid var(--mizu-line-bright)",
            }}
          >
            {n.text}
          </p>
        </PlaneItem>
      ))}
    </Plane>
  );
}
```

## Copy it with the shadcn CLI

```sh
npx shadcn@latest init 0xuser64bit/mizu/preset # once, where there is no components.json
npx shadcn@latest add 0xuser64bit/mizu/plane
```

## Props

- `label / children`: `string / ReactNode`. Accessible name and world content (usually PlaneItems).
- `view / defaultView / onViewChange`: `{ x, y, zoom }`. Camera: the world point at the centre and the zoom. Frames the content by default.
- `minZoom / maxZoom`: `number` (default `0.15 / 4`). Zoom limits.
- `grid / minimap / coordinates`: `"dots" | "lines" | "none" / boolean / boolean` (default `"dots" / true / true`). Surface texture and instruments.
- `tools`: `ReactNode`. Extra toolbar controls beside zoom and Fit.
- `ref`: `PlaneHandle`. fit(), zoomBy(factor), flyTo(box | point) and toWorld(clientX, clientY).
- `PlaneItem: id / x / y / width / height / label`: `string / number`. World position and size; measured when width or height is omitted.
- `PlaneItem: onMove / onMoveEnd`: `(x, y) => void`. Makes the item draggable and nudgeable.

## Keyboard

- ← → ↑ ↓: Travel (Shift for larger steps)
- + −: Zoom
- 0 · 1: Fit everything · 100%
- Tab: Next item — the view follows
- ← → ↑ ↓: On a draggable item: nudge it (Shift: 48px)
- Ctrl + scroll: Zoom at the pointer; double-click zooms in

## Accessibility

The viewport is a focusable canvas group with keyboard travel and zoom. Items are named groups in reading order; tabbing to one flies the camera to it. Draggable items nudge with arrow keys. Plain scrolling only pans once the canvas is active, so the page never traps the wheel; a hint explains Ctrl-scroll.

## Motion

The camera glides in log space and pulls back on long flights before settling in; drags coast to rest with inertia. The zoom-adaptive dot grid re-densifies as you scale. Reduced motion jumps directly and removes coasting.
