@json-render/ink
Renderizador de terminal Ink que converte especificações JSON em árvores de componentes de terminal interativas com componentes padrão, vinculação de dados, visibilidade, ações e propriedades dinâmicas.
Início Rápido
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/ink/schema";
import {
standardComponentDefinitions,
standardActionDefinitions,
} from "@json-render/ink/catalog";
import { defineRegistry, Renderer, type Components } from "@json-render/ink";
import { z } from "zod";
// Criar catálogo com componentes padrão + personalizados
const catalog = defineCatalog(schema, {
components: {
...standardComponentDefinitions,
CustomWidget: {
props: z.object({ title: z.string() }),
slots: [],
description: "Widget personalizado",
},
},
actions: standardActionDefinitions,
});
// Registrar apenas componentes personalizados (os padrão são embutidos)
const { registry } = defineRegistry(catalog, {
components: {
CustomWidget: ({ props }) => <Text>{props.title}</Text>,
} as Components<typeof catalog>,
});
// Renderizar
function App({ spec }) {
return (
<JSONUIProvider initialState={{}}>
<Renderer spec={spec} registry={registry} />
</JSONUIProvider>
);
}
Estrutura da Especificação (Mapa de Elementos Plano)
O esquema Ink usa um mapa de elementos plano com uma chave raiz:
{
"root": "main",
"elements": {
"main": {
"type": "Box",
"props": { "flexDirection": "column", "padding": 1 },
"children": ["heading", "content"]
},
"heading": {
"type": "Heading",
"props": { "text": "Dashboard", "level": "h1" },
"children": []
},
"content": {
"type": "Text",
"props": { "text": "Olá do terminal!" },
"children": []
}
}
}
Componentes Padrão
Layout
Box- Contêiner de layout Flexbox (como um<div>no terminal). Use para agrupamento, espaçamento, bordas, alinhamento. OflexDirectionpadrão érow.Text- Saída de texto com estilo opcional (cor, negrito, itálico, etc.)Newline- Insere linhas em branco. Deve estar dentro de umBoxcomflexDirectioncolumn.Spacer- Espaço vazio flexível que se expande ao longo do eixo principal.
Conteúdo
Heading- Título de seção (h1: negrito+sublinhado,h2: negrito,h3: negrito+escurecido,h4: escurecido)Divider- Separador horizontal com título centralizado opcionalBadge- Rótulo inline colorido (variantes:default,info,success,warning,error)Spinner- Spinner de carregamento animado com rótulo opcionalProgressBar- Barra de progresso horizontal (0-1)Sparkline- Gráfico inline usando caracteres Unicode de blocoBarChart- Gráfico de barras horizontal com rótulos e valoresTable- Dados tabulares com cabeçalhos e linhasList- Lista com marcadores ou numeradaListItem- Linha de lista estruturada com título, subtítulo, texto inicial/finalCard- Contêiner com borda e título opcionalKeyValue- Exibição de par chave-valorLink- URL clicável com rótulo opcionalStatusLine- Mensagem de status com ícone colorido (info,success,warning,error)Markdown- Renderiza texto markdown com estilização de terminal
Interativos
TextInput- Campo de entrada de texto (eventos:submit,change)Select- Menu de seleção com navegação por setas (evento:change)MultiSelect- Seleção múltipla com espaço para alternar (eventos:change,submit)ConfirmInput- Prompt de confirmação Sim/Não (eventos:confirm,deny)Tabs- Navegação por abas com teclas de seta esquerda/direita (evento:change)
Condições de Visibilidade
Use visible em elementos para mostrar/esconder com base no estado. Sintaxe: { "$state": "/caminho" }, { "$state": "/caminho", "eq": valor }, { "$state": "/caminho", "not": true }, { "$and": [cond1, cond2] } para E, { "$or": [cond1, cond2] } para OU.
Expressões de Propriedades Dinâmicas
Qualquer valor de propriedade pode ser uma expressão orientada por dados resolvida no momento da renderização:
{ "$state": "/state/key" }- lê do modelo de estado (leitura unidirecional){ "$bindState": "/caminho" }- vinculação bidirecional: use na propriedade de valor natural de componentes de formulário{ "$bindItem": "campo" }- vinculação bidirecional a um campo de item repetido{ "$cond": <condição>, "$then": <valor>, "$else": <valor> }- valor condicional{ "$template": "Olá, ${/nome}!" }- interpola valores de estado em strings
Os componentes não usam uma propriedade statePath para vinculação bidirecional. Use { "$bindState": "/caminho" } na propriedade de valor natural.
Sistema de Eventos
Os componentes usam emit para disparar eventos nomeados. O campo on do elemento mapeia eventos para ligações de ações:
CustomButton: ({ props, emit }) => (
<Box>
<Text>{props.label}</Text>
{/* emit("press") dispara a ação vinculada em on.press da especificação */}
</Box>
),
{
"type": "CustomButton",
"props": { "label": "Enviar" },
"on": { "press": { "action": "submit" } },
"children": []
}
Ações Embutidas
setState, pushState e removeState são embutidas e tratadas automaticamente:
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "Novo" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
Repetição (Listas Dinâmicas)
Use o campo repeat em um elemento contêiner para renderizar itens de um array de estado:
{
"type": "Box",
"props": { "flexDirection": "column" },
"repeat": { "statePath": "/items", "key": "id" },
"children": ["item-row"]
}
Dentro de filhos repetidos, use { "$item": "campo" } para ler do item atual e { "$index": true } para o índice atual.
Streaming
Use useUIStream para renderizar progressivamente especificações a partir de streams de patch JSONL:
import { useUIStream } from "@json-render/ink";
const { spec, send, isStreaming } = useUIStream({ api: "/api/generate" });
Geração de Prompt no Lado do Servidor
Use a exportação ./server para gerar prompts de sistema de IA a partir do seu catálogo:
import { catalog } from "./catalog";
const systemPrompt = catalog.prompt({ system: "Você é um assistente de terminal." });
Provedores
| Provedor | Propósito |
|---|---|
StateProvider | Compartilha estado entre componentes (caminhos JSON Pointer). Aceita propriedade store opcional para modo controlado. |
ActionProvider | Lida com ações despachadas através do sistema de eventos |
VisibilityProvider | Habilita renderização condicional baseada no estado |
ValidationProvider | Validação de campos de formulário |
FocusProvider | Gerencia foco entre componentes interativos |
JSONUIProvider | Provedor combinado para todos os contextos |
Armazenamento Externo (Modo Controlado)
Passe um StateStore para StateProvider (ou JSONUIProvider) para usar gerenciamento de estado externo:
import { createStateStore, type StateStore } from "@json-render/ink";
const store = createStateStore({ count: 0 });
<StateProvider store={store}>{children}</StateProvider>
store.set("/count", 1); // React re-renderiza automaticamente
Quando store é fornecido, initialState e onStateChange são ignorados.
createRenderer (API de Nível Superior)
import { createRenderer } from "@json-render/ink";
import { standardComponents } from "@json-render/ink";
import { catalog } from "./catalog";
const InkRenderer = createRenderer(catalog, {
...standardComponents,
// substituições de componentes personalizados aqui
});
// InkRenderer inclui todos os provedores (estado, visibilidade, ações, foco)
render(
<InkRenderer spec={spec} state={{ activeTab: "overview" }} />
);
Exportações Principais
| Exportação | Propósito |
|---|---|
defineRegistry | Cria um registro de componentes type-safe a partir de um catálogo |
Renderer | Renderiza uma especificação usando um registro |
createRenderer | Nível superior: cria um componente com provedores embutidos |
JSONUIProvider | Provedor combinado para todos os contextos |
schema | Esquema de mapa de elementos planos Ink (inclui ações de estado embutidas) |
standardComponentDefinitions | Definições de catálogo para todos os componentes padrão |
standardActionDefinitions | Definições de catálogo para ações padrão |
standardComponents | Implementações de componentes pré-construídos |
useStateStore | Acessa o contexto de estado |
useStateValue | Obtém um único valor do estado |
useBoundProp | Vinculação bidirecional para expressões $bindState/$bindItem |
useActions | Acessa o contexto de ações |
useAction | Obtém uma única função de despacho de ação |
useOptionalValidation | Variante que não lança exceções de useValidation |
useUIStream | Transmite especificações de um endpoint de API |
createStateStore | Cria um StateStore em memória independente de framework |
StateStore | Interface para conectar gerenciamento de estado externo |
Components | Mapa de componentes tipado (ciente do catálogo) |
Actions | Mapa de ações tipado (ciente do catálogo) |
ComponentContext | Contexto de componente tipado (ciente do catálogo) |
flatToTree | Converte mapa de elementos plano em estrutura de árvore |
Diretrizes de Design de UI para Terminal
- Use
Boxpara layout (flexDirection,padding,gap). OflexDirectionpadrão érow. - Largura do terminal é de ~80-120 colunas. Prefira layouts verticais (
flexDirection: column) para estrutura principal. - Use
borderStyleemBoxpara agrupamento visual (single,double,round,bold). - Use cores de terminal nomeadas:
red,green,yellow,blue,magenta,cyan,white,gray. - Use
Headingpara títulos de seção,Dividerpara separar seções,Badgepara status,KeyValuepara dados rotulados,Cardpara grupos com borda. - Use
Tabspara UIs de múltiplas visões com condições de visibilidade no conteúdo filho. - Use
Sparklinepara tendências inline eBarChartpara comparar valores.