Toolbar
Barra de ações flutuante por nó dentro de um Canvas. Ancorada abaixo do nó selecionado, expõe ações contextuais como editar, duplicar e deletar.
Visão geral
Toolbar é uma skin fina da Gremorie sobre o NodeToolbar de @xyflow/react. Ele renderiza uma pequena linha flutuante de ações que aparece ao lado de um nó quando o nó está selecionado (ou sempre, dependendo de isVisible).
Coloque-o dentro do corpo de um <Node>. O xyflow o posiciona em relação ao nó automaticamente (usando Position.Bottom por padrão) e o reposiciona conforme o nó se move ou o canvas dá pan. O chrome é intencionalmente mínimo: uma borda arredondada, fundo claro e uma linha flex gap-1 que segura os botões que você quiser.
Use-o para as affordances de workflow mais comuns: editar, duplicar, deletar, configurar. Para controles mais elaborados que devem estar sempre visíveis (como zoom e fit no nível do canvas), recorra a Controls.
Preview
'use client';import '@xyflow/react/dist/style.css';import { Toolbar } from '@gremorie/rx-ai';import { Button } from '@gremorie/rx-forms';import { type NodeProps, type NodeTypes, Position, ReactFlow,} from '@xyflow/react';import { Bold, Italic, Underline } from 'lucide-react';import type { ReactNode } from 'react';const ToolbarNode = ({ id, data }: NodeProps) => ( <> <Toolbar isVisible nodeId={id} position={Position.Bottom}> {(data as { toolbar: ReactNode }).toolbar} </Toolbar> <div className="rounded-md border bg-background px-6 py-3 text-sm"> Node </div> </>);const toolbarNodeTypes: NodeTypes = { toolbarNode: ToolbarNode };export function ToolbarPreview() { return ( <div className="h-[220px] w-full"> <ReactFlow edges={[]} fitView nodeTypes={toolbarNodeTypes} nodes={[ { id: '1', type: 'toolbarNode', position: { x: 110, y: 70 }, data: { toolbar: ( <div aria-label="Formatting" className="flex items-center gap-1" role="group" > <Button aria-label="Bold" size="icon-sm" variant="ghost"> <Bold aria-hidden="true" className="size-4" /> </Button> <Button aria-label="Italic" size="icon-sm" variant="ghost"> <Italic aria-hidden="true" className="size-4" /> </Button> <Button aria-label="Underline" size="icon-sm" variant="ghost"> <Underline aria-hidden="true" className="size-4" /> </Button> </div> ), }, }, ]} nodesConnectable={false} nodesDraggable={false} panOnDrag={false} proOptions={{ hideAttribution: true }} zoomOnScroll={false} /> </div> );}Anatomia
Toolbar node action toolbar — leaf primitive (rendered on a Canvas node)Instalação
bash npx gremorie@latest add rx-toolbar bash pnpm dlx gremorie@latest add rx-toolbar bash yarn dlx gremorie@latest add rx-toolbar bash bunx --bun gremorie@latest add rx-toolbar Requer @xyflow/react como peer dependency. Já é uma dep transitiva da família workflow, então instalar rx-canvas, rx-node ou qualquer outro primitivo de workflow já a traz junto.
Importe o stylesheet do xyflow uma vez no entry da app:
import '@xyflow/react/dist/style.css';Uso
import { Node, NodeContent, Toolbar } from "@gremorie/rx-ai";
import { Button } from "@gremorie/rx-forms";
import { PencilIcon, TrashIcon } from "lucide-react";
export function StartNode({ data, ...props }) {
return (
<Node {...props} handles={{ target: false, source: true }}>
<Toolbar>
<Button size="icon" variant="ghost" aria-label="Edit">
<PencilIcon className="size-4" />
</Button>
<Button size="icon" variant="ghost" aria-label="Delete">
<TrashIcon className="size-4" />
</Button>
</Toolbar>
<NodeContent>{data.label}</NodeContent>
</Node>
);
}import { Component } from "@angular/core";
import {
Toolbar,
ToolbarGroup,
ToolbarButton,
} from "@gremorie/ng-ai";
@Component({
selector: "app-example",
standalone: true,
imports: [Toolbar, ToolbarGroup, ToolbarButton],
template: ` <toolbar>
<toolbar-group ariaLabel="Formatting">
<toolbar-button [pressed]="bold" ariaLabel="Bold" (clicked)="bold = !bold">
<span class="font-bold">B</span>
</toolbar-button>
<toolbar-button ariaLabel="Italic">
<span class="italic">I</span>
</toolbar-button>
</toolbar-group>
</toolbar>
`,
})
export class ExampleComponent {
bold = false;
}
O
<toolbar>do Angular é o primitivo genérico (uma<div role="toolbar">). Combine-o com@xyflow/angularno nível da app se você precisar de um toolbar ancorado no canvas.
API
<Toolbar>
Envolve o NodeToolbar de @xyflow/react. Todas as props de NodeToolbar são encaminhadas.
| Prop | Type | Default | Description |
|---|---|---|---|
position | Position | Position.Bottom | Lado de ancoragem. A Gremorie sobrescreve o default do xyflow para bottom. |
isVisible | boolean | controlado pelo xyflow | Força visível independentemente do estado de seleção. |
offset | number | 10 | Distância (px) da borda do nó. |
nodeId | string | string[] | nó pai | Vincula a um nó específico quando renderizado fora de um. |
align | "start" | "center" | "end" | "center" | Alinhamento ao longo da borda escolhida. |
className | string | - | Classes extras na superfície do toolbar. |
Chrome padrão: flex items-center gap-1 rounded-sm border bg-background p-1.5.
Composição
<Toolbar>vive dentro de um<Node>. O xyflow infere o ID do nó a partir do contextoNodePropsao redor, então você normalmente não passanodeIdmanualmente.- Os children costumam ser
Button size="icon"de@gremorie/rx-forms. Cada botão precisa do seu próprioaria-labelporque são só ícone. - A visibilidade segue a seleção do nó por padrão. Clique no nó e o toolbar surge; clique em qualquer outro lugar e ele desaparece.
- Para controles no nível do canvas (zoom in, zoom out, fit view), use
Controls, que ancora em um canto do canvas.
Variações
Só editar e deletar
O toolbar mínimo padrão. Visível apenas quando o nó está selecionado.
<Toolbar>
<Button size="icon" variant="ghost" aria-label="Edit">
<PencilIcon className="size-4" />
</Button>
<Button size="icon" variant="ghost" aria-label="Delete">
<TrashIcon className="size-4" />
</Button>
</Toolbar>Sempre visível
Passe isVisible para manter o toolbar montado independentemente da seleção. Útil para affordances de "drag handle" em fluxos read-only.
<Toolbar isVisible>
<Button size="icon" variant="ghost" aria-label="Drag">
<GripVerticalIcon className="size-4" />
</Button>
</Toolbar>Ancorado no topo
Sobrescreva position quando a região do canvas abaixo do nó estiver cheia demais.
import { Position } from '@xyflow/react';
<Toolbar position={Position.Top}>
<Button size="icon" variant="ghost" aria-label="Configure">
<SettingsIcon className="size-4" />
</Button>
</Toolbar>;Acessibilidade
- Keyboard: os botões são
Buttons de verdade, então a ordem de tab segue o DOM e Enter / Space os ativam. Selecionar um nó com o teclado (depois que o canvas está focado) também expõe seu toolbar. - Botões só de ícone exigem
aria-label: não há tooltip embutido nas ações doToolbar. Ou forneçaaria-labeldiretamente ou envolva cada botão em umTooltip+TooltipTriggerde@gremorie/rx-overlays. - Reduced motion: a animação de posicionamento do xyflow é puramente baseada em transform. Respeite as preferências do usuário com
@media (prefers-reduced-motion: reduce)se você adicionar transições custom. - Screen readers: como o toolbar aparece na seleção, anuncie a seleção do nó via
aria-live(ou dependa dos eventos de seleção do xyflow) para que a tecnologia assistiva saiba que novas ações estão disponíveis.