# DiagramToolbar

A bar of actions belonging to the surface underneath them.

- Group: Diagrams
- Import: `import { DiagramToolbar } from '@misoto22/folio/diagrams'`
- Page: https://ui.misoto22.com/components/diagram-toolbar/
- Related: floating-icon-button, diagram-export-menu

## When to reach for it

FloatingIconButton is one pinned action. This is the container for several, so they read as one object rather than as a scatter.

## Anatomy

- **Bar** (required) — role="toolbar" with a horizontal orientation, on a panel plate with a hairline and the system’s corner. One plate and one border is what makes six pinned buttons read as one object instead of as six.
- **Name** (required) — label, which becomes the bar’s accessible name. It is a required prop with no default, because a toolbar that announces its role and nothing else is a group a screen reader cannot tell from the next one.
- **Group** (required) — DiagramToolbarGroup: one run of related controls, divided by a hairline on its leading edge rather than by space. The first group carries no rule, so the bar does not open with a divider.
- **Controls** (required) — The children, at whatever size the caller gives them. The bar sets the gap and paints no state of its own — a button in here is the same button it is anywhere else.
- **Pin** — placement="floating" takes the bar out of the flow and pins it to the top edge of the nearest POSITIONED ancestor, at the sticky rank — under every anchored panel and every dialog — with align choosing the corner. placement="inline" leaves it in the flow and pins nothing.

## Best practices

### Do

- Give label the bar’s job rather than its shape — "Diagram actions", not "Toolbar" — because a page with a figure and a table otherwise announces two toolbars and distinguishes neither.
- Divide with DiagramToolbarGroup rather than with a gap. The rule is drawn on the group, and a gap wide enough to read as a boundary is also wide enough to stop the bar reading as one object, which is the reason the controls were collected at all.
- Keep it to a handful of controls. There is no roving focus here, so Tab visits every one of them: ten actions in the bar is ten stops between the reader and the rest of the page.

### Don’t

- A floating bar sits over the surface, not beside it. Whatever is in that corner of the figure is underneath it — pad the surface, or use placement="inline" and let the bar take its own row.
- role="toolbar" is a promise about the keyboard as well as a name: a reader is told this is a toolbar and expects the arrow keys to move inside it. For two buttons that merely sit next to each other, a plain div makes no promise this component then has to keep.
- A floating bar is a SIBLING of the canvas inside a positioned wrapper, never a child of it. Everything handed to DiagramCanvas renders inside the transformed stage, so a bar passed as a child zooms and pans away with the diagram it was put there to control — and with no positioned ancestor at all it pins to whichever box further up the page happens to be one.

## Accessibility

- role="toolbar" announces a toolbar rather than six unrelated buttons. It does not implement roving focus — Tab visits every control, which is honest for a bar of three.

## DiagramToolbar

A bar of actions that belong to the surface underneath them. The package already has `FloatingIconButton` for ONE pinned action. This is the container for several — which is a different problem, because several pinned buttons need to read as one object rather than as a scatter: one plate, one border, hairlines between the groups, and a single `role="toolbar"` so a screen reader announces a toolbar instead of six unrelated buttons. `role="toolbar"` also changes the keyboard contract, and callers should know what they are opting into: arrow keys are expected to move between the controls and Tab is expected to leave the bar. This component does not implement roving focus for you — a bar of three buttons where Tab visits all three is honest and fine — so pass `placement="inline"` and skip the role by using a plain `<div>` if that is not what you want.

### Props

- `children` (required) — `ReactNode`.
- `placement` — `'inline' | 'floating'` default `'inline'`. Where it sits. `floating` pins it over the surface it acts on.
- `align` — `'start' | 'end'` default `'end'`. Which corner a floating bar pins to.
- `label` (required) — `string`.

Also accepts: `Omit<ComponentProps<'div'>, 'children'>`.

## DiagramToolbarGroup

One run of related controls inside the bar. Separated by a hairline rather than by space, because a gap large enough to read as a group boundary is also large enough to stop the bar reading as one object — which is the whole reason the controls were collected into a bar.

## Example — default

```tsx
import { Button } from '@misoto22/folio'
import { DiagramToolbar, DiagramToolbarGroup } from '@misoto22/folio/diagrams'

<DiagramToolbar label="Diagram actions">
  <DiagramToolbarGroup>
    <Button size="sm" variant="ghost">
      Theme
    </Button>
    <Button size="sm" variant="ghost">
      Style
    </Button>
  </DiagramToolbarGroup>
  <DiagramToolbarGroup>
    <Button size="sm" variant="ghost">
      Present
    </Button>
    <Button size="sm" variant="secondary">
      Export
    </Button>
  </DiagramToolbarGroup>
</DiagramToolbar>
```

## Example — floating over a canvas

```tsx
import { Button } from '@misoto22/folio'

<div className="relative" ref={stage}>
  <DiagramCanvas ref={canvas} height="14rem" label="Request path">
    <ArchitectureFigure spec={SPEC} heading={false} legend="hidden" cards={false} />
  </DiagramCanvas>
  <DiagramToolbar label="Diagram actions" placement="floating" align="start">
    <DiagramToolbarGroup>
      <Button size="sm" variant="ghost" iconOnly aria-label="Reset the view" onClick={() => canvas.current?.reset()}>
        <RiResetLeftLine size={14} aria-hidden />
      </Button>
      <Button size="sm" variant="ghost" iconOnly aria-label="Centre the diagram" onClick={() => canvas.current?.centerOn(420, 84)}>
        <RiFullscreenLine size={14} aria-hidden />
      </Button>
    </DiagramToolbarGroup>
    <DiagramToolbarGroup>
      <DiagramExportMenu targetRef={stage} title="Request path" />
    </DiagramToolbarGroup>
  </DiagramToolbar>
</div>
```

## Example — a different set of controls

```tsx
import { Button } from '@misoto22/folio'
import { DiagramToolbar, DiagramToolbarGroup } from '@misoto22/folio/diagrams'

<DiagramToolbar label="Chapters">
  <DiagramToolbarGroup>
    {CHAPTERS.map((chapter) => (
      <Button
        key={chapter.id}
        size="sm"
        variant={chapter.id === active ? 'secondary' : 'ghost'}
        aria-pressed={chapter.id === active}
        onClick={() => setActive(chapter.id)}
      >
        {chapter.label}
      </Button>
    ))}
  </DiagramToolbarGroup>
  <DiagramToolbarGroup>
    <span className="mono-meta px-1.5 text-(--ink-3-aa)">
      {position} of {CHAPTERS.length}
    </span>
  </DiagramToolbarGroup>
</DiagramToolbar>
```
