# Quick start (/docs/shuffle/quick-start)



<CodeBlockTabs defaultValue="npm">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npm i @pitter-patter/shuffle prosemirror-view@1.41.7
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm add @pitter-patter/shuffle prosemirror-view@1.41.7
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn add @pitter-patter/shuffle prosemirror-view@1.41.7
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun add @pitter-patter/shuffle prosemirror-view@1.41.7
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Update your schema [#update-your-schema]

Shuffle requires a few schema modifications in order to work as expected:

* A `row` node spec. `row` is a block node that should allow other top level blocks as children.
  When a node is dragged alongside an existing node, they will be automatically wrapped in a `row`
  parent node, which allows them to live on the same grid row.
* A `container` node spec. `container` is an optional vertical grouping of block nodes.
* A `pitterPatter.shuffle` configuration for any existing node specs that should be draggable and/or
  resizable.

Shuffle can automatically extend your schema for you, or you can modify your schema yourself to add
Shuffle support.

To extend your schema automatically:

```ts
import { addShuffleNodes } from "@pitter-patter/shuffle";

// Adds row and container nodes to your schema with
// content: "block+", and configures each node with
// the group "block" to be resizable and draggable.
const shuffledSchema = addShuffleNodes(schema, "block+", "block");
```

Or, to manually update your schema, just add the row and container nodes yourself, and configure
nodes to be resizable and draggable as needed:

```ts
import { container, row, shuffleAttrs } from "@pitter-patter/shuffle";

const schema = new Schema({
  nodes: {
    doc: {
      content: "block+",
    },
    text: {
      group: "inline",
      inline: true,
    },
    paragraph: {
      group: "block",
      content: "inline*",
      attrs: {
        ...shuffleAttrs,
      },
      pitterPatter: {
        shuffle: {
          resizable: true,
          draggablue: true,
        },
      },
    },
    row: {
      ...row,
      group: "block",
      content: "block+",
    },
    container: {
      ...container,
      group: "block",
      content: "block+",
    },
  },
});
```

## Add the plugin [#add-the-plugin]

Most of Shuffle’s logic lives in the `shuffle()` ProseMirror plugin. This should be added to your
EditorState:

```ts
import { reactKeys } from "@handlewithcare/react-prosemirror";
import { shuffle } from "@pitter-patter/shuffle";

const editorState = EditorState.create({
  schema,
  doce,
  plugins: [reactKeys(), shuffle()],
});
```

## Configure hover decorations [#configure-hover-decorations]

In schemas that can have deeply nested nodes, it can be helpful to use borders or highlights to
indicate to the user which nodes are being hovered over.

The `hoverDecorations` argument to the shuffle plugin creator will be called with each hovered node
to determine whether to render a node decoration.

```tsx
import { reactKeys } from "@handlewithcare/react-prosemirror";
import { shuffle } from "@pitter-patter/shuffle";
import { Decoration } from "prosemirror-view";
import { Node } from "prosemirror-model";

function hoverDecorations(from: number, to: number, node: Node) {
  // Return null to skip decorations for a given node
  if (node.type.name === "image") return null;

  return Decoration.node(from, to, {
    class: "shuffle-hover-block",
  });
}

const editorState = EditorState.create({
  schema,
  doc,
  plugins: [
    reactKeys(),
    shuffle({
      hoverDecorations,
    }),
  ],
});
```

## Wrap your ProseMirror component with the `ShuffleSkeleton` and add `ResizeHandles` and `DragHandles` [#wrap-your-prosemirror-component-with-the-shuffleskeleton-and-add-resizehandles-and-draghandles]

Shuffle provides a `ShuffleSkeleton` component that wraps your `ProseMirrorDoc`. It renders
Shuffle's grid skeleton, and must be rendered for resize and reposition behaviors to work correctly.
The component should be a direct parent of the `ProseMirrorDoc` component.

To add resize handles to your elements, include the `ResizeHandles` component as a child of your
`ShuffleSkeleton`. Likewise, include the `DragHandles` component to render drag handles.

```tsx
function Editor() {
  return (
    <ProseMirror defaultState={editorState}>
      <ShuffleSkeleton>
        <ProseMirrorDoc />
        <ResizeHandles />
        <DragHandles />
      </ShuffleSkeleton>
    </ProseMirror>
  );
}
```

## Import the styles [#import-the-styles]

Shuffle provides a small functional stylesheet for visualizing and positioning nodes on the grid.

```ts
import "@pitter-patter/shuffle/styles.css";
```

## Customizing [#customizing]

The appearance of the skeleton can be customized with CSS variables:

```css
:root {
  --shuffle-column-width: 3rem; /* The width of an individual grid column */
  --shuffle-gutter-width: 1.5rem; /* The visual gap between grid columns in the skeleton */
  --shuffle-row-gap: 1rem; /* The gap between rows in the grid */
  --shuffle-skeleton-color: lightgray /* The color of the grid columns in the skeleton */;
}
```

The `ResizeHandles` component optionally takes a `handleComponent` prop that will be used instead of
the default light blue button:

```tsx
import { ResizeHandles } from "@pitter-patter/shuffle";
import { EventHandler, PointerDown } from "react";

interface Props {
  style: { top: number; left: number };
  onPointerDown: EventHandler<PointerDown>;
}

function ResizeHandle({ styles, onPointerDown }) {
  return (
    <button type="button" className="resize-handle" styles={styles} onPointerDown={onPointerDown} />
  );
}

function Editor() {
  return (
    <ProseMirror defaultState={editorState}>
      <ShuffleSkeleton>
        <ProseMirrorDoc />
        <ResizeHandles handleComponent={ResizeHandle} />
      </ShuffleSkeleton>
    </ProseMirror>
  );
}
```

Similarly, the `DragHandles` component optionally takes a `handleComponent` prop that will be used
instead of the default light blue button:

```tsx
import { DragHandles } from "@pitter-patter/shuffle";
import { EventHandler, PointerDown } from "react";

interface Props {
  style: { top: number; left: number };
  onPointerDown: EventHandler<PointerDown>;
}

function DragHandle({ styles, onPointerDown, node }) {
  return (
    <button type="button" className="drag-handle" styles={styles} onPointerDown={onPointerDown}>
      {node.type.name[0].toUpperCase() + node.type.name.slice(1)}
    </button>
  );
}

function Editor() {
  return (
    <ProseMirror defaultState={editorState}>
      <ShuffleSkeleton>
        <ProseMirrorDoc />
        <DragHandles handleComponent={DragHandle} />
      </ShuffleSkeleton>
    </ProseMirror>
  );
}
```
