Skip to main content
Gremorie

Node

Primitivo de nó baseado em card para o Canvas. Envolve o Card da Gremorie com Handles xyflow de target / source e uma composição header / content / footer que combina com o resto do design system.

Visão geral

Node é o vértice em formato de card que você coloca dentro de um Canvas. Ele compõe um Card de @gremorie/rx-display com Handles do xyflow à esquerda (target) e à direita (source), e então expõe a mesma composição Header / Title / Description / Action / Content / Footer de um Card comum.

O wrapper customiza o Card para o contexto de canvas: remove o padding padrão para que os handles alinhem de forma limpa, estreita a largura para w-sm, e skina o header e o footer com uma superfície secondary para que se leiam distintos do corpo do conteúdo.

Use-o como a base para todo tipo de nó custom. Combine com Toolbar para ações ancoradas na seleção, Edge para conectar nós, e Connection para arrastes em progresso.

Preview

'use client';import '@xyflow/react/dist/style.css';import {  Canvas,  Controls,  Node,  NodeAction,  NodeContent,  NodeDescription,  NodeFooter,  NodeHeader,  NodeTitle,} from '@gremorie/rx-ai';import { Button } from '@gremorie/rx-forms';import {  type Edge as FlowEdge,  type Node as FlowNode,  type NodeProps,  type NodeTypes,  ReactFlowProvider,} from '@xyflow/react';import { MoreHorizontalIcon } from 'lucide-react';type AgentNodeData = {  title: string;  description: string;  body: string;  source: boolean;  target: boolean;};const AgentNode = ({ data }: NodeProps) => {  const { title, description, body, source, target } = data as AgentNodeData;  return (    <Node handles={{ source, target }}>      <NodeHeader>        <NodeTitle>{title}</NodeTitle>        <NodeDescription>{description}</NodeDescription>        <NodeAction>          <Button aria-label="Node actions" size="icon" variant="ghost">            <MoreHorizontalIcon className="size-4" />          </Button>        </NodeAction>      </NodeHeader>      <NodeContent>        <p className="text-muted-foreground text-sm">{body}</p>      </NodeContent>      <NodeFooter>        <span className="text-muted-foreground text-xs">Ready</span>      </NodeFooter>    </Node>  );};const agentNodeTypes: NodeTypes = { agent: AgentNode };const agentNodes: FlowNode[] = [  {    id: '1',    type: 'agent',    position: { x: 0, y: 0 },    data: {      title: 'Plan',      description: 'Break the task into steps',      body: 'Decompose the request and outline the actions to take.',      source: true,      target: false,    },  },  {    id: '2',    type: 'agent',    position: { x: 360, y: 0 },    data: {      title: 'Execute',      description: 'Run the planned tools',      body: 'Call each tool in order and collect the results.',      source: false,      target: true,    },  },];const agentEdges: FlowEdge[] = [{ id: 'e1-2', source: '1', target: '2' }];export function NodePreview() {  return (    <div className="h-[360px] w-full">      <ReactFlowProvider>        <Canvas          edges={agentEdges}          nodeTypes={agentNodeTypes}          nodes={agentNodes}        >          <Controls />        </Canvas>      </ReactFlowProvider>    </div>  );}

Anatomia

Node
├─ NodeHeader
│  ├─ NodeTitle
│  ├─ NodeDescription
│  └─ NodeAction      top-right slot
├─ NodeContent        body
└─ NodeFooter

Instalação

bash npx gremorie@latest add rx-node
bash pnpm dlx gremorie@latest add rx-node
bash yarn dlx gremorie@latest add rx-node
bash bunx --bun gremorie@latest add rx-node

Traz rx-display (para o Card) como dependência de registry. Requer @xyflow/react como peer.

Uso

import {
  Node,
  NodeAction,
  NodeContent,
  NodeDescription,
  NodeFooter,
  NodeHeader,
  NodeTitle,
} from "@gremorie/rx-ai";
import { Button } from "@gremorie/rx-forms";
import { PlayIcon } from "lucide-react";

const AgentNode = (props) => (

<Node {...props} handles={{ target: true, source: true }}>
  <NodeHeader>
    <NodeTitle>{props.data.name}</NodeTitle>
    <NodeDescription>{props.data.model}</NodeDescription>
    <NodeAction>
      <Button size="icon" variant="ghost" aria-label="Run">
        <PlayIcon className="size-4" />
      </Button>
    </NodeAction>
  </NodeHeader>
  <NodeContent>{props.data.prompt}</NodeContent>
  <NodeFooter>{props.data.tokenCount} tokens</NodeFooter>
</Node>
);

const nodeTypes = { agent: AgentNode };

A edição Angular deste componente está planejada; a edição React está production-ready hoje.

API

<Node>

PropTypeDefaultDescription
handles{ target: boolean; source: boolean }-Obrigatório. Quais lados do nó expõem handles de conexão. target adiciona um handle à esquerda, source adiciona um handle à direita.
classNamestring-Classes extras no Card subjacente.

Estende todas as props de Card de @gremorie/rx-display. Renderiza o Card com gap-0 rounded-md p-0 e a classe node-container para mira no nível do canvas. O xyflow encaminha props de seleção / arraste automaticamente quando usado dentro de uma entrada nodeTypes registrada.

<NodeHeader>

Header com border-bottom, fundo secondary e padding apertado p-3!. Hospeda o título, a descrição e a ação à direita.

Estende todas as props de CardHeader.

<NodeTitle> / <NodeDescription> / <NodeAction>

Wrappers pass-through em torno de CardTitle, CardDescription e CardAction. APIs idênticas - existem para que a identidade visual se mantenha consistente entre as edições e para que os consumidores possam trocar as implementações sem mexer em cada tipo de nó.

<NodeContent>

Slot de corpo com padding p-3. Estende todas as props de CardContent.

<NodeFooter>

Footer com border-top, fundo secondary e padding p-3!. Espelha NodeHeader.

Estende todas as props de CardFooter.

Composição

  1. Registre o tipo de nó no Canvas via a prop nodeTypes. A chave (por exemplo, "agent") é o que os dados do edge referenciam como type.
  2. Dentro do nó, declare a config handles para que o xyflow saiba onde as conexões podem cair.
  3. Componha com Header / Content / Footer para combinar com o resto da Gremorie. O header e o footer no tom secondary criam separação visual natural do corpo.
  4. Adicione um Toolbar para ações ancoradas na seleção (editar, deletar, configurar). Para ações permanentes dentro do card, use NodeAction no header.

Variações

Nó só de entrada (ponto de partida)

Apenas handle de source - sem conexões de entrada.

const StartNode = (props) => (
  <Node {...props} handles={{ target: false, source: true }}>
    <NodeContent>Start</NodeContent>
  </Node>
);

Nó só de saída (terminal)

Apenas handle de target - sem conexões de saída.

const EndNode = (props) => (
  <Node {...props} handles={{ target: true, source: false }}>
    <NodeContent>End</NodeContent>
  </Node>
);

Um nó de agente completo com metadados no header, prompt no corpo, status no footer e uma ação de executar.

<Node {...props} handles={{ target: true, source: true }}>
  <NodeHeader>
    <NodeTitle>{props.data.name}</NodeTitle>
    <NodeDescription>{props.data.model}</NodeDescription>
    <NodeAction>
      <Button size="icon" variant="ghost" aria-label="Run">
        <PlayIcon className="size-4" />
      </Button>
    </NodeAction>
  </NodeHeader>
  <NodeContent>{props.data.prompt}</NodeContent>
  <NodeFooter>{props.data.tokenCount} tokens</NodeFooter>
</Node>

Nó com toolbar ancorado na seleção

Combine com Toolbar para ações que só aparecem quando o nó está selecionado.

<Node {...props} handles={{ target: true, 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>{props.data.label}</NodeContent>
</Node>

Acessibilidade

  • Keyboard: o xyflow cuida da seleção do nó e do movimento por setas quando o canvas tem foco. Children interativos (NodeAction, botões de Toolbar) precisam dos seus próprios aria-labels, já que costumam ser só ícone.
  • Semântica: um Node renderiza como um <div> (via Card). Quando o nó representa algo semanticamente importante (um agente, uma tool), exponha esse papel via aria-label no próprio Card ou no NodeTitle.
  • Handles: o Handle do xyflow é um pequeno círculo-alvo sem nome acessível. Se o seu fluxo é para ser navegado com tecnologia assistiva, forneça aria-labels descrevendo que tipo de conexão o handle aceita ("Aceita input de mensagem").
  • Contraste de cor: o fundo secondary no NodeHeader e no NodeFooter atinge 4.5:1 contra o token de foreground em ambos os temas da Gremorie. Reverifique se você customizar os tokens.
  • Focus visible: não remova o focus ring padrão do xyflow em nós selecionados - usuários de navegação por teclado dependem dele.

Relacionados

  • Canvas - a superfície onde os nós vivem
  • Edge - as linhas que conectam os handles dos nós
  • Connection - a linha de arraste em progresso entre handles
  • Toolbar - barra de ações ancorada na seleção de um nó
  • Panel - overlay no canto do canvas para ações não específicas de nó

On this page