# Selection toolbar

The selection toolbar is a row of glass controls that floats above whatever is selected. It holds controls
for the selection: a font size for text, a colour for a sticky note, a width for a pen stroke, or
buttons you write yourself. You decide which entity types get a toolbar and what goes in it.

## Show the toolbar

Render `Toolbar` as a child of `KookieFlow`, then give each entity type that should have a toolbar
a `toolbar` entry in `entityTypes`. A type without an entry shows no toolbar.
```tsx
'use client';

import { Button } from '@kushagradhawan/kookie-ui-react';
import {
  KookieFlow,
  Toolbar,
  useGraph,
  type Entity,
  type EntityTypeDefinition,
} from '@kushagradhawan/kookie-flow';

// Outside the component, so the table keeps one identity across renders.
const entityTypes: Record<string, EntityTypeDefinition> = {
  text: { type: 'text', toolbar: true },
  comment: { type: 'comment', toolbar: true },
  step: {
    type: 'step',
    toolbar: {
      // Align and distribute, then one control of your own.
      defaults: ['arrange'],
      extra: ({ entities, update }) => {
        const flagged = entities.every((entity) => entity.data.status === 'warning');
        const toggle = () =>
          entities.forEach((entity) => update(entity.id, { status: flagged ? undefined : 'warning' }));
        return (
          <Button size="2" emphasis="quiet" onClick={toggle}>
            {flagged ? 'Clear flag' : 'Flag'}
          </Button>
        );
      },
    },
  },
};

const initialEntities: Entity[] = [
  { id: 'title', type: 'text', position: { x: 40, y: 40 }, width: 320, data: { content: 'Image pipeline' } },
  { id: 'note', type: 'comment', position: { x: 440, y: 30 }, width: 220, height: 90, data: { content: 'Drag a box around all three steps.' } },
  { id: 'load', type: 'step', position: { x: 40, y: 180 }, data: { label: 'Load' } },
  { id: 'resize', type: 'step', position: { x: 340, y: 230 }, data: { label: 'Resize' } },
  { id: 'export', type: 'step', position: { x: 640, y: 200 }, data: { label: 'Export' } },
];

export default function ToolbarExample() {
  const { entities, edges, onEntitiesChange, onEdgesChange } = useGraph({ initialEntities });

  return (
    <div style={{ width: '100%', height: 400 }}>
      <KookieFlow
        entities={entities}
        edges={edges}
        entityTypes={entityTypes}
        onEntitiesChange={onEntitiesChange}
        onEdgesChange={onEdgesChange}
      >
        <Toolbar />
      </KookieFlow>
    </div>
  );
}
```
Every control in the toolbar edits the graph through `onEntitiesChange`, the same callback a drag
uses. Pass it: without it, a control shows the current value, and changing the value does nothing.
`useGraph` applies these changes for you, as the example does.

`Toolbar` has to sit inside `KookieFlow`, because it reads the selection from the flow's store.
Rendered anywhere else, it throws.

The toolbar centres itself over the bounding box of the selection, 8 pixels above it. When that
would push it past the top of the canvas, it sits 8 pixels below the selection instead. It follows the selection while you pan and zoom, and it hides
while you drag, resize, draw a wire or drag a selection box, then comes back when the gesture
ends.

### How it looks

The toolbar has no card around it. It is a KookieUI `Toolbar` row, and every control in it is glass,
so each one draws its own capsule over the canvas: a segmented control, a field, or a
`ToolbarGroup` holding buttons that belong together. There are no dividers; the space between
capsules separates them. The row tells its controls that the canvas passes behind them, so they
show your theme's glass material. The whole row is one tab stop, and the arrow keys move inside it.

```tsx
<KookieFlow entities={entities} edges={edges} onEntitiesChange={onEntitiesChange}>
  <Toolbar size="2" />
</KookieFlow>
```

| Prop | Type | Default | What it does |
| --- | --- | --- | --- |
| `size` | `ToolbarProps['size']` from `@kushagradhawan/kookie-ui-react` | `'3'` | The size every control in the toolbar takes, unless a control states its own. |
| `children` | `ToolbarRenderFn` | none | Replaces the toolbar's contents for every selection. See [Replace the toolbar's contents](#replace-the-toolbars-contents). |

## Turn on the built-in controls

Set `toolbar: true` on a built-in entity type to show its default controls. Text, comments, images
and ink each have a set.

```tsx
const entityTypes: Record<string, EntityTypeDefinition> = {
  text: { type: 'text', toolbar: true },
  comment: { type: 'comment', toolbar: true },
  image: { type: 'image', toolbar: true },
  draw: { type: 'draw', toolbar: true },
};
```

A control shows the value of the first selected entity. A change applies to every selected entity
at once, in one `onEntitiesChange` call. Number fields commit when you press Enter or move focus
away, so typing "24" doesn't write a font size of 2 on the way.

| Widget | Default for | Control | What it writes |
| --- | --- | --- | --- |
| `sizingMode` | `text` | Auto width, auto height or fixed | `data.sizingMode`, and the width and height that mode gives the text |
| `fontSize` | `text`, `comment` | Number | `data.fontSize` |
| `noteColor` | `comment` | A hue: yellow, orange, red, pink, violet, blue, cyan, green or grey | `data.color`, and clears `data.backgroundColor` and `data.textColor` |
| `backgroundColor` | none | Colour picker | `data.backgroundColor` |
| `fontFamily` | `text` | System, Serif or Mono | `data.fontFamily`: `'system-ui'`, `'serif'` or `'monospace'` |
| `fontWeight` | `text` | Regular, Semibold or Bold | `data.fontWeight`: `400`, `600` or `700` |
| `textColor` | `text` | Colour picker | `data.textColor` |
| `textAlign` | `text` | Left, centre or right | `data.textAlign` |
| `lineHeight` | `text` | Number | `data.lineHeight` |
| `letterSpacing` | `text` | Number | `data.letterSpacing` |
| `objectFit` | `image` | Fill, cover or contain | `data.objectFit` |
| `aspectLock` | `image` | Lock toggle | `data.aspectLocked` |
| `strokeColor` | `draw` | Colour picker | `data.strokeColor` |
| `strokeWidth` | `draw` | Number | `data.strokeWidth`, and a box refitted to the new width |
| `arrange` | none | Align and distribute buttons | New positions, reported as a drag reports them |

A comment's toolbar picks its hue and its font size. The hue tints the note's fill, edge and text
together and follows light and dark mode. List `backgroundColor` or `textColor` to also offer a
colour picker for a note that needs a colour no hue gives; the pickers start from the colour the
note is painted in. The widget types also include `color`, which draws no control.

`arrange` works on any entity type. It shows six align buttons once two or more entities are
selected, and two distribute buttons once three or more are. With one entity selected it shows
nothing. The [chapter on arranging entities](/data/arrange#align-a-selection) describes what each
button does, and the keys that do the same.

The widget names are typed in groups: `TextToolbarWidget`, `CommentToolbarWidget`,
`ImageToolbarWidget`, `DrawToolbarWidget` and `CommonToolbarWidget` (`color` and `arrange`).
`ToolbarWidget` is all of them together.

The `toolbar` field's own type is `ToolbarConfig`. It accepts `true`, `false`, an array of widget
names, an object with `defaults` and `extra`, or a render function.

## Pick which controls appear

Pass an array instead of `true` to choose the controls and their order. Each one stands on its own
in the row.

```tsx
const entityTypes: Record<string, EntityTypeDefinition> = {
  text: { type: 'text', toolbar: ['fontSize', 'fontWeight', 'textColor', 'arrange'] },
};
```

Only text, comments, images and ink have default controls. On any other type, including the types
you define, `toolbar: true` shows no toolbar, and an array is how you give that type built-in
controls. A list such as `['arrange']` works on any type. With only one entity selected it shows
nothing, because `arrange` has nothing to show for one entity.

To show no toolbar for a type, set `toolbar: false` or leave `toolbar` out.

## Add your own controls

Pass an object with `defaults` and `extra` to keep built-in controls and add yours after them.
`defaults` takes `true` or an array of widget names. `extra` is a function that returns the controls
to add after them.

Build your controls from KookieUI's toolbar parts. A `ToolbarButton` joins the row's keyboard, where
a plain `Button` would be a second tab stop. Put buttons that belong together in a `ToolbarGroup`
with `backdrop`, so they share one glass capsule like the built-in controls.

```tsx
import { ToolbarButton, ToolbarGroup } from '@kushagradhawan/kookie-ui-react';
import type { EntityTypeDefinition } from '@kushagradhawan/kookie-flow';

const entityTypes: Record<string, EntityTypeDefinition> = {
  step: {
    type: 'step',
    toolbar: {
      defaults: ['arrange'],
      extra: ({ entities, update }) => (
        <ToolbarGroup backdrop>
          <ToolbarButton onClick={() => entities.forEach((entity) => update(entity.id, { status: 'warning' }))}>
            Flag
          </ToolbarButton>
        </ToolbarGroup>
      ),
    },
  },
};
```

The function receives an object of type `ToolbarRenderProps`:

| Field | Type | What it is |
| --- | --- | --- |
| `entities` | `Entity[]` | Every selected entity. |
| `update` | `(entityId: string, data: Partial<EntityData>) => void` | Reports one `data` change for one entity through `onEntitiesChange`. |
| `bounds` | `{ x: number; y: number; width: number; height: number }` | The selection's bounding box, in world coordinates. |
| `align` | `(edge: AlignEdge) => void` | Lines the selection up on one edge or centre line, and reports the moves. |
| `distribute` | `(axis: DistributeAxis) => void` | Spaces the selection evenly along an axis, and reports the moves. |

`update` sends only the fields you pass. `useGraph` merges them into the entity's existing `data`,
so `update(id, { status: 'warning' })` keeps the label. If you apply changes yourself, merge the
same way. Each call to `update` is its own `onEntitiesChange` call, so a loop over five entities
reports five.

`ToolbarRenderFn` is the type of this function: `(props: ToolbarRenderProps) => React.ReactNode`.

## Replace the toolbar's contents

Set `toolbar` to a function to draw that type's toolbar entirely yourself. The toolbar shows no
built-in controls, only what your function returns.

```tsx
const entityTypes: Record<string, EntityTypeDefinition> = {
  step: {
    type: 'step',
    toolbar: ({ entities, update }) => (
      <ToolbarGroup backdrop>
        <ToolbarButton onClick={() => update(entities[0].id, { label: 'Renamed' })}>Rename</ToolbarButton>
      </ToolbarGroup>
    ),
  },
};
```

To replace the contents for every selection, whatever its types, pass the same kind of function
as the child of `Toolbar`. That function ignores every `toolbar` entry in `entityTypes`, and the
toolbar appears for any selection that holds at least one entity.

## Show controls for a mixed selection

The `toolbar` entries in `entityTypes` apply only when every selected entity has the same type.
When a selection mixes types, such as a text block and a step, no entry applies and no toolbar
appears.

To offer controls for a mixed selection, pass a function as the child of `Toolbar` and decide
from `entities` what to show. Return `null` to show nothing.

```tsx title="board.tsx"
'use client';

import { ToolbarButton, ToolbarGroup } from '@kushagradhawan/kookie-ui-react';
import { KookieFlow, Toolbar, useGraph } from '@kushagradhawan/kookie-flow';

export function Board() {
  const { entities, edges, onEntitiesChange, onEdgesChange } = useGraph();

  return (
    <div style={{ height: 480 }}>
      <KookieFlow entities={entities} edges={edges} onEntitiesChange={onEntitiesChange} onEdgesChange={onEdgesChange}>
        <Toolbar>
          {({ entities: selected, align }) =>
            selected.length < 2 ? null : (
              <ToolbarGroup backdrop>
                <ToolbarButton onClick={() => align('left')}>Align left</ToolbarButton>
                <ToolbarButton onClick={() => align('top')}>Align top</ToolbarButton>
              </ToolbarGroup>
            )
          }
        </Toolbar>
      </KookieFlow>
    </div>
  );
}
```

`align` and `distribute` move the selection in the canvas and then report every move through
`onEntitiesChange`, so a controlled graph keeps the new positions.
