# Create Your First Canvas

This guide builds a note board in a Phoenix LiveView app.

It covers the first-use path only:

- install the Elixir package and browser runtime;
- define one item-content LiveView;
- author one canvas item in HEEx;
- render the canvas runtime;
- add drag, resize, add, focus, and remove behavior.

Graph connectors, drawing tools, shared canvases, persistence, history, and custom
render loops come later.

## Install LivePanels

Add the package:

```elixir
# mix.exs
{:livepanels, "~> 0.1.0"}
```

Fetch dependencies:

```bash
mix deps.get
```

Install the browser package into your Phoenix assets:

```bash
mix livepanels.install
cd assets && npm install
```

Import the browser hooks and runtime CSS beside your existing LiveView setup:

```js
import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
import { LivePanelsHooks } from "livepanels";
import "livepanels/runtime.css";

const hooks = {
  ...LivePanelsHooks
};

const liveSocket = new LiveSocket("/live", Socket, { hooks });
```

If your Phoenix app imports CSS from `assets/css/app.css` instead of from
JavaScript, put the CSS import there:

```css
@import "livepanels/runtime.css";
```

## Generate Or Write The First Canvas

The fastest route is the generator:

```bash
mix livepanels.new canvas --yes
```

That creates a working route LiveView at
`lib/<app>_web/live/canvas_live.ex`. It is a good first file to inspect because
it contains the same pieces this guide explains manually.

If you are wiring by hand, start with a normal Phoenix route:

```elixir
scope "/", MyAppWeb do
  pipe_through :browser

  live "/board", BoardLive
end
```

## Define The Canvas LiveView

The canvas LiveView is an ordinary LiveView with `LivePanels.Canvas` attached.
Declare initial panels directly inside `canvas_runtime/1`:

```elixir
defmodule MyAppWeb.BoardLive do
  use MyAppWeb, :live_view

  use LivePanels.Canvas,
    pubsub: MyApp.PubSub

  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <main class="board-page">
      <.canvas_runtime {canvas_runtime_attrs(assigns, class: "board-canvas")}>
        <:item :let={context} id="welcome-note" at={{80, 80}} size={{640, 420}} resize>
          <section class={"board-item #{if context.selected?, do: "is-selected"}"}>
            <header {drag_handle_attrs(class: "board-item__title")}>
              <span>Welcome note</span>
            </header>

            <div class="board-item__body">
              <article class="note-content">
                <p>This content lives directly inside the item declaration.</p>
              </article>
            </div>

            <.resize_handles :if={context.view.resizable?} />
          </section>
        </:item>
      </.canvas_runtime>
    </main>
    """
  end
end
```

The important parts are:

- `use LivePanels.Canvas` attaches the canvas runtime to the LiveView;
- `<:item>` declares an item from markup;
- `id`, `at`, and `size` declare the item identity and initial frame;
- `canvas_runtime_attrs(assigns, ...)` passes runtime assigns to the component;
- `canvas_runtime/1` renders the canvas root, layers, and item loop;
- `drag_handle_attrs/1` and `resize_handles/1` connect your markup to the
  runtime protocol.

## Add Enough CSS To See It

LivePanels ships structural runtime CSS, not your app's item design. Add application
styles that fit your app. A starter board can begin with:

```css
.board-page {
  block-size: 100vh;
  display: grid;
  grid-template-rows: auto 1fr;
}

.board-toolbar {
  display: flex;
  gap: 0.5rem;
  padding: 0.75rem;
  border-bottom: 1px solid #d7dde7;
}

.board-canvas {
  min-block-size: 0;
  background: #f7f9fc;
}

.board-item {
  height: 100%;
  overflow: hidden;
  border: 1px solid #c7d2e0;
  border-radius: 6px;
  background: white;
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
}

.board-item__title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid #e2e8f0;
  cursor: grab;
}

.board-item__body {
  padding: 0.75rem;
}
```

## Try The First Interactions

Start the Phoenix server and visit the route:

```bash
mix phx.server
```

You should be able to:

- click `New note` to add an item;
- drag the titlebar to move the item;
- resize from the handles;
- click the item to focus it;
- remove it with the app-rendered close button.

If those interactions do not work, check these in order:

- the LivePanels browser hooks are included in `LiveSocket`;
- `runtime.css` is imported;
- the page renders `canvas_runtime_attrs(assigns, ...)`;
- controls use LivePanels attrs instead of hand-written `phx-click` payloads;
- `mix livepanels.check` passes.

## What To Read Next

Use `Find The API You Need` when you know the thing you want to do and need the
right module. Use `Add Items` when you want to understand item declarations and
placement. Use `Render Item Content With LiveViews` when item content needs to
communicate with the canvas.
