@json-render/ink
JSONスペックをインタラクティブなターミナルコンポーネントツリーに変換するInkターミナルレンダラー。標準コンポーネント、データバインディング、可視性、アクション、動的プロパティをサポート。
クイックスタート
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";
// 標準+カスタムコンポーネントでカタログを作成
const catalog = defineCatalog(schema, {
components: {
...standardComponentDefinitions,
CustomWidget: {
props: z.object({ title: z.string() }),
slots: [],
description: "カスタムウィジェット",
},
},
actions: standardActionDefinitions,
});
// カスタムコンポーネントのみ登録(標準コンポーネントは組み込み済み)
const { registry } = defineRegistry(catalog, {
components: {
CustomWidget: ({ props }) => <Text>{props.title}</Text>,
} as Components<typeof catalog>,
});
// レンダリング
function App({ spec }) {
return (
<JSONUIProvider initialState={{}}>
<Renderer spec={spec} registry={registry} />
</JSONUIProvider>
);
}
スペック構造(フラットな要素マップ)
Inkスキーマはルートキーを持つフラットな要素マップを使用します:
{
"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": "Hello from the terminal!" },
"children": []
}
}
}
標準コンポーネント
レイアウト
Box- Flexboxレイアウトコンテナ(ターミナル用<div>)。グループ化、スペーシング、ボーダー、配置に使用。デフォルトのflexDirectionは row。Text- オプションのスタイリング(color、bold、italic など)でテキストを出力Newline- 空行を挿入。flexDirection が column の Box 内で使用する必要があるSpacer- 主軸に沿って伸縮するフレキシブルな空スペース
コンテンツ
Heading- セクション見出し(h1: bold+underlined、h2: bold、h3: bold+dimmed、h4: dimmed)Divider- オプションの中央タイトル付き水平区切り線Badge- 色付きインラインラベル(バリアント: default、info、success、warning、error)Spinner- オプションラベル付きアニメーション読み込みスピナーProgressBar- 水平プログレスバー(0~1)Sparkline- Unicodeブロック文字を使用したインラインチャートBarChart- ラベルと値付きの水平棒グラフTable- ヘッダーと行を持つ表形式データList- 箇条書きまたは番号付きリストListItem- タイトル、サブタイトル、先頭/末尾テキストを含む構造化リスト行Card- オプションタイトル付きボーダーコンテナKeyValue- キーと値のペア表示Link- オプションラベル付きクリック可能なURLStatusLine- 色付きアイコン付きステータスメッセージ(info、success、warning、error)Markdown- ターミナルスタイルでマークダウンテキストをレンダリング
インタラクティブ
TextInput- テキスト入力フィールド(イベント: submit、change)Select- 矢印キーナビゲーションでの選択メニュー(イベント: change)MultiSelect- スペースキーでトグルする複数選択(イベント: change、submit)ConfirmInput- Yes/No確認プロンプト(イベント: confirm、deny)Tabs- 左右矢印キーで操作するタブバーナビゲーション(イベント: change)
可視性条件
要素に visible を使用して、状態に基づいて表示/非表示を切り替えます。構文: { "$state": "/path" }、{ "$state": "/path", "eq": value }、{ "$state": "/path", "not": true }、AND条件には { "$and": [cond1, cond2] }、OR条件には { "$or": [cond1, cond2] }。
動的プロパティ式
任意のプロパティ値は、レンダリング時に解決されるデータ駆動型の式にできます:
{ "$state": "/state/key" }- 状態モデルから読み取る(一方向読み取り){ "$bindState": "/path" }- 双方向バインディング。フォームコンポーネントの自然な値プロパティで使用{ "$bindItem": "field" }- リピートアイテムフィールドへの双方向バインディング{ "$cond": <条件>, "$then": <値>, "$else": <値> }- 条件付き値{ "$template": "Hello, ${/name}!" }- 状態値を文字列に補間
コンポーネントは双方向バインディングに statePath プロパティを使用しません。代わりに、自然な値プロパティに { "$bindState": "/path" } を使用します。
イベントシステム
コンポーネントは emit を使用して名前付きイベントを発行します。要素の on フィールドはイベントをアクションバインディングにマッピングします:
CustomButton: ({ props, emit }) => (
<Box>
<Text>{props.label}</Text>
{/* emit("press") でスペックの on.press にバインドされたアクションをトリガー */}
</Box>
),
{
"type": "CustomButton",
"props": { "label": "Submit" },
"on": { "press": { "action": "submit" } },
"children": []
}
組み込みアクション
setState、pushState、removeState は組み込みで自動処理されます:
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
Repeat(動的リスト)
コンテナ要素に repeat フィールドを使用して、状態配列からアイテムをレンダリングします:
{
"type": "Box",
"props": { "flexDirection": "column" },
"repeat": { "statePath": "/items", "key": "id" },
"children": ["item-row"]
}
繰り返し子要素内では、{ "$item": "field" } で現在のアイテムを読み取り、{ "$index": true } で現在のインデックスを取得します。
ストリーミング
useUIStream を使用して、JSONLパッチストリームからスペックを段階的にレンダリングします:
import { useUIStream } from "@json-render/ink";
const { spec, send, isStreaming } = useUIStream({ api: "/api/generate" });
サーバーサイドプロンプト生成
./server エクスポートを使用して、カタログからAIシステムプロンプトを生成します:
import { catalog } from "./catalog";
const systemPrompt = catalog.prompt({ system: "You are a terminal assistant." });
プロバイダ
| プロバイダ | 目的 |
|---|---|
StateProvider | コンポーネント間で状態を共有(JSON Pointer パス)。制御モード用にオプションの store プロパティを受け入れます。 |
ActionProvider | イベントシステムを介してディスパッチされたアクションを処理 |
VisibilityProvider | 状態に基づく条件付きレンダリングを有効化 |
ValidationProvider | フォームフィールドのバリデーション |
FocusProvider | インタラクティブコンポーネント間のフォーカス管理 |
JSONUIProvider | すべてのコンテキストを統合したプロバイダ |
外部ストア(制御モード)
StateStore を StateProvider(または JSONUIProvider)に渡して外部状態管理を使用します:
import { createStateStore, type StateStore } from "@json-render/ink";
const store = createStateStore({ count: 0 });
<StateProvider store={store}>{children}</StateProvider>
store.set("/count", 1); // Reactが自動的に再レンダリング
store が指定された場合、initialState と onStateChange は無視されます。
createRenderer(高レベルAPI)
import { createRenderer } from "@json-render/ink";
import { standardComponents } from "@json-render/ink";
import { catalog } from "./catalog";
const InkRenderer = createRenderer(catalog, {
...standardComponents,
// ここでカスタムコンポーネントを上書き
});
// InkRenderer はすべてのプロバイダを含む(状態、可視性、アクション、フォーカス)
render(
<InkRenderer spec={spec} state={{ activeTab: "overview" }} />
);
主要エクスポート
| エクスポート | 目的 |
|---|---|
defineRegistry | カタログから型安全なコンポーネントレジストリを作成 |
Renderer | レジストリを使用してスペックをレンダリング |
createRenderer | 高レベル: 組み込みプロバイダ付きコンポーネントを作成 |
JSONUIProvider | すべてのコンテキストを統合したプロバイダ |
schema | Inkフラット要素マップスキーマ(組み込み状態アクションを含む) |
standardComponentDefinitions | すべての標準コンポーネントのカタログ定義 |
standardActionDefinitions | 標準アクションのカタログ定義 |
standardComponents | 事前構築済みコンポーネント実装 |
useStateStore | 状態コンテキストにアクセス |
useStateValue | 状態から単一の値を取得 |
useBoundProp | $bindState/$bindItem 式の双方向バインディング |
useActions | アクションコンテキストにアクセス |
useAction | 単一のアクションディスパッチ関数を取得 |
useOptionalValidation | useValidation の非スロー版 |
useUIStream | APIエンドポイントからスペックをストリーミング |
createStateStore | フレームワーク非依存のインメモリ StateStore を作成 |
StateStore | 外部状態管理をプラグインするためのインターフェース |
Components | 型付きコンポーネントマップ(カタログ対応) |
Actions | 型付きアクションマップ(カタログ対応) |
ComponentContext | 型付きコンポーネントコンテキスト(カタログ対応) |
flatToTree | フラットな要素マップをツリー構造に変換 |
ターミナルUIデザインガイドライン
- レイアウトには Box を使用(flexDirection、padding、gap)。デフォルトのflexDirectionは row。
- ターミナル幅は約80~120カラム。メイン構造には垂直レイアウト(flexDirection: column)を推奨。
- Box で視覚的なグループ化に borderStyle を使用(single、double、round、bold)。
- 名前付きターミナルカラーを使用: red、green、yellow、blue、magenta、cyan、white、gray。
- セクションタイトルには Heading、セクション区切りには Divider、ステータス表示には Badge、ラベル付きデータには KeyValue、ボーダーグループには Card を使用。
- マルチビューUIには、子コンテンツに可視条件を設定した Tabs を使用。
- インライントレンドには Sparkline、値の比較には BarChart を使用。