@json-render/ink
Moteur de rendu terminal Ink qui convertit des spécifications JSON en arbres de composants interactifs avec composants standard, liaison de données, visibilité, actions et propriétés dynamiques.
Démarrage Rapide
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";
// Créer un catalogue avec des composants standard + personnalisés
const catalog = defineCatalog(schema, {
components: {
...standardComponentDefinitions,
CustomWidget: {
props: z.object({ title: z.string() }),
slots: [],
description: "Widget personnalisé",
},
},
actions: standardActionDefinitions,
});
// Enregistrer uniquement les composants personnalisés (les standard sont intégrés)
const { registry } = defineRegistry(catalog, {
components: {
CustomWidget: ({ props }) => <Text>{props.title}</Text>,
} as Components<typeof catalog>,
});
// Rendu
function App({ spec }) {
return (
<JSONUIProvider initialState={{}}>
<Renderer spec={spec} registry={registry} />
</JSONUIProvider>
);
}
Structure de la Spécification (Plan d'Éléments Plat)
Le schéma Ink utilise un plan d'éléments plat avec une clé racine :
{
"root": "main",
"elements": {
"main": {
"type": "Box",
"props": { "flexDirection": "column", "padding": 1 },
"children": ["heading", "content"]
},
"heading": {
"type": "Heading",
"props": { "text": "Tableau de bord", "level": "h1" },
"children": []
},
"content": {
"type": "Text",
"props": { "text": "Bonjour depuis le terminal !" },
"children": []
}
}
}
Composants Standard
Disposition
Box- Conteneur de disposition Flexbox (comme un<div>terminal). Utiliser pour le regroupement, l'espacement, les bordures, l'alignement. Par défaut, flexDirection est row.Text- Sortie de texte avec style optionnel (couleur, gras, italique, etc.)Newline- Insère des lignes vides. Doit être à l'intérieur d'un Box avec flexDirection column.Spacer- Espace vide flexible qui s'étend le long de l'axe principal.
Contenu
Heading- Titre de section (h1 : gras+souligné, h2 : gras, h3 : gras+atténué, h4 : atténué)Divider- Séparateur horizontal avec titre centré optionnelBadge- Étiquette en ligne colorée (variantes : default, info, success, warning, error)Spinner- Spinner de chargement animé avec étiquette optionnelleProgressBar- Barre de progression horizontale (0-1)Sparkline- Graphique en ligne utilisant des caractères Unicode de blocBarChart- Diagramme à barres horizontal avec étiquettes et valeursTable- Données tabulaires avec en-têtes et lignesList- Liste à puces ou numérotéeListItem- Ligne de liste structurée avec titre, sous-titre, texte de début/finCard- Conteneur avec bordure et titre optionnelKeyValue- Affichage de paire clé-valeurLink- URL cliquable avec étiquette optionnelleStatusLine- Message de statut avec icône colorée (info, success, warning, error)Markdown- Affiche du texte Markdown avec style terminal
Interactif
TextInput- Champ de saisie de texte (événements : submit, change)Select- Menu de sélection avec navigation par touches fléchées (événements : change)MultiSelect- Sélection multiple avec espace pour basculer (événements : change, submit)ConfirmInput- Invite de confirmation Oui/Non (événements : confirm, deny)Tabs- Navigation par onglets avec touches fléchées gauche/droite (événements : change)
Conditions de Visibilité
Utiliser visible sur les éléments pour afficher/masquer en fonction de l'état. Syntaxe : { "$state": "/chemin" }, { "$state": "/chemin", "eq": valeur }, { "$state": "/chemin", "not": true }, { "$and": [cond1, cond2] } pour ET, { "$or": [cond1, cond2] } pour OU.
Expressions de Propriétés Dynamiques
Toute valeur de propriété peut être une expression pilotée par les données résolue au moment du rendu :
{ "$state": "/etat/cle" }- lecture depuis le modèle d'état (lecture unidirectionnelle){ "$bindState": "/chemin" }- liaison bidirectionnelle : à utiliser sur la propriété de valeur naturelle des composants de formulaire{ "$bindItem": "champ" }- liaison bidirectionnelle à un champ d'élément de répétition{ "$cond": <condition>, "$then": <valeur>, "$else": <valeur> }- valeur conditionnelle{ "$template": "Bonjour, ${/nom} !" }- interpolation de valeurs d'état dans des chaînes
Les composants n'utilisent pas de propriété statePath pour la liaison bidirectionnelle. Utilisez { "$bindState": "/chemin" } sur la propriété de valeur naturelle à la place.
Système d'Événements
Les composants utilisent emit pour déclencher des événements nommés. Le champ on de l'élément mappe les événements aux liaisons d'actions :
CustomButton: ({ props, emit }) => (
<Box>
<Text>{props.label}</Text>
{/* emit("press") déclenche l'action liée dans on.press de la spécification */}
</Box>
),
{
"type": "CustomButton",
"props": { "label": "Soumettre" },
"on": { "press": { "action": "submit" } },
"children": []
}
Actions Intégrées
setState, pushState et removeState sont intégrées et gérées automatiquement :
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "accueil" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "Nouveau" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
Répétition (Listes Dynamiques)
Utilisez le champ repeat sur un élément conteneur pour afficher les éléments d'un tableau d'état :
{
"type": "Box",
"props": { "flexDirection": "column" },
"repeat": { "statePath": "/items", "key": "id" },
"children": ["item-row"]
}
À l'intérieur des enfants répétés, utilisez { "$item": "champ" } pour lire depuis l'élément courant et { "$index": true } pour l'index courant.
Streaming
Utilisez useUIStream pour rendre progressivement des spécifications à partir de flux de correctifs JSONL :
import { useUIStream } from "@json-render/ink";
const { spec, send, isStreaming } = useUIStream({ api: "/api/generate" });
Génération de Prompts Côté Serveur
Utilisez l'export ./server pour générer des prompts système IA à partir de votre catalogue :
import { catalog } from "./catalog";
const systemPrompt = catalog.prompt({ system: "Vous êtes un assistant terminal." });
Fournisseurs
| Fournisseur | Objectif |
|---|---|
StateProvider | Partager l'état entre les composants (chemins JSON Pointer). Accepte une propriété store optionnelle pour le mode contrôlé. |
ActionProvider | Gérer les actions déclenchées via le système d'événements |
VisibilityProvider | Activer le rendu conditionnel basé sur l'état |
ValidationProvider | Validation des champs de formulaire |
FocusProvider | Gérer le focus entre les composants interactifs |
JSONUIProvider | Fournisseur combiné pour tous les contextes |
Store Externe (Mode Contrôlé)
Passez un StateStore à StateProvider (ou JSONUIProvider) pour utiliser une gestion d'état externe :
import { createStateStore, type StateStore } from "@json-render/ink";
const store = createStateStore({ count: 0 });
<StateProvider store={store}>{children}</StateProvider>
store.set("/count", 1); // React se re-rend automatiquement
Lorsque store est fourni, initialState et onStateChange sont ignorés.
createRenderer (API de plus Haut Niveau)
import { createRenderer } from "@json-render/ink";
import { standardComponents } from "@json-render/ink";
import { catalog } from "./catalog";
const InkRenderer = createRenderer(catalog, {
...standardComponents,
// remplacements de composants personnalisés ici
});
// InkRenderer inclut tous les fournisseurs (état, visibilité, actions, focus)
render(
<InkRenderer spec={spec} state={{ activeTab: "aperçu" }} />
);
Exports Clés
| Export | Objectif |
|---|---|
defineRegistry | Créer un registre de composants typé à partir d'un catalogue |
Renderer | Rendre une spécification à l'aide d'un registre |
createRenderer | Plus haut niveau : crée un composant avec fournisseurs intégrés |
JSONUIProvider | Fournisseur combiné pour tous les contextes |
schema | Schéma de plan d'éléments plat Ink (inclut les actions d'état intégrées) |
standardComponentDefinitions | Définitions du catalogue pour tous les composants standard |
standardActionDefinitions | Définitions du catalogue pour les actions standard |
standardComponents | Implémentations de composants pré-construites |
useStateStore | Accéder au contexte d'état |
useStateValue | Obtenir une seule valeur de l'état |
useBoundProp | Liaison bidirectionnelle pour les expressions $bindState/$bindItem |
useActions | Accéder au contexte d'actions |
useAction | Obtenir une fonction de dispatch d'action unique |
useOptionalValidation | Variante non bloquante de useValidation |
useUIStream | Streamer des spécifications à partir d'un point de terminaison API |
createStateStore | Créer un StateStore en mémoire indépendant du framework |
StateStore | Interface pour brancher une gestion d'état externe |
Components | Carte de composants typée (tenant compte du catalogue) |
Actions | Carte d'actions typée (tenant compte du catalogue) |
ComponentContext | Contexte de composant typé (tenant compte du catalogue) |
flatToTree | Convertir un plan d'éléments plat en structure arborescente |
Directives de Conception d'IU Terminal
- Utiliser Box pour la disposition (flexDirection, padding, gap). Par défaut, flexDirection est row.
- La largeur du terminal est d'environ 80 à 120 colonnes. Privilégier les dispositions verticales (flexDirection: column) pour la structure principale.
- Utiliser borderStyle sur Box pour le regroupement visuel (single, double, round, bold).
- Utiliser des couleurs terminal nommées : red, green, yellow, blue, magenta, cyan, white, gray.
- Utiliser Heading pour les titres de section, Divider pour séparer les sections, Badge pour le statut, KeyValue pour les données étiquetées, Card pour les groupes encadrés.
- Utiliser Tabs pour les IU à plusieurs vues avec conditions de visibilité sur le contenu enfant.
- Utiliser Sparkline pour les tendances en ligne et BarChart pour comparer des valeurs.