inkjs-design について
Ink.js でターミナル UI を構築する開発者向けの設計ガイドです。画面、再利用可能なパーツ、入力コンポーネントに分け、React ベースの CLI パターンや実例、トラブルシューティング資料をまとめています。
主な機能
- 01Screen、Part、Common のコンポーネントパターン
- 02キーボード入力とフォーカスの指針
- 03複数画面のナビゲーションパターン
- 04ターミナルのレスポンシブレイアウト指針
- 05パフォーマンスとアニメーションの資料
- 06テストとトラブルシューティングの資料
活用シーン
- 01新しい Ink.js CLI のコンポーネント設計
- 02ターミナルのレイアウトや入力競合の解決
- 03CLI の画面間ナビゲーションの追加
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ akiojin/ skills --skill inkjs-designインストール後、Claude Code で/inkjs-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples/component-examples.mdreferences/animation-patterns.mdcomponent-patterns.mdhooks-guide.mdink-gotchas.mdinput-handling.mdmulti-screen-navigation.mdperformance-optimization.mdresponsive-layout.mdstate-management.mdtesting-patterns.md詳細
Ink.js の設計
Ink.js(CLI 向け React)でターミナル UI を構築するための包括的なガイドです。
クイックスタート
新しいコンポーネントの作成
- コンポーネントの種類を決定します:Screen/Part/Common
- 類似するパターンについてはcomponent-patterns.mdを参照します
- 型定義を追加します
- コンポーネントを実装します
- テストを作成します
よくある問題と解決策
| 問題 | 参照先 |
|---|---|
| 絵文字の幅がずれる | ink-gotchas.md |
| Ctrl+C が 2 回呼び出される | ink-gotchas.md |
| useInput の競合 | ink-gotchas.md |
| レイアウトが崩れる | responsive-layout.md |
| 画面遷移 | multi-screen-navigation.md |
ディレクトリの規約
src/cli/ui/
├── components/
│ ├── App.tsx # Root component with screen management
│ ├── common/ # Common input components (Select, Input)
│ ├── parts/ # Reusable UI parts (Header, Footer)
│ └── screens/ # Full-screen components
├── hooks/ # Custom hooks
├── utils/ # Utility functions
└── types.ts # Type definitions
コンポーネントの分類
Screen(全画面ビュー)
- 完全な画面/ページを表します
useInputを介してキーボード入力を処理します- Header/Content/Footer のレイアウトを実装します
- 画面レベルの状態を管理します
Part(再利用可能な要素)
- 再利用可能な UI の構成要素です
React.memoで最適化します- ステートレス/純粋なコンポーネントを推奨します
- props で設定を受け取ります
Common(入力コンポーネント)
- 基本的な入力コンポーネントです
- 制御モードと非制御モードの両方をサポートします
- フォーカス管理を処理します
- 一貫した UX を提供します
重要なパターン
1. アイコンの幅の上書き
string-width v8 の絵文字幅計算に関する問題を修正します:
const WIDTH_OVERRIDES: Record<string, number> = {
"⚡": 1, "✨": 1, "🐛": 1, "🔥": 1, "🚀": 1,
"🟢": 1, "🟠": 1, "✅": 1, "⚠️": 1,
};
const getIconWidth = (icon: string): number => {
const baseWidth = stringWidth(icon);
const override = WIDTH_OVERRIDES[icon];
return override !== undefined ? Math.max(baseWidth, override) : baseWidth;
};
2. useInput の競合回避
複数の useInput フックがすべて実行されるため、早期リターンまたは isActive を使用します:
useInput((input, key) => {
if (disabled) return; // Early return when inactive
// Handle input...
}, { isActive: isFocused });
3. Ctrl+C の処理
render(<App />, { exitOnCtrlC: false });
// In component
const { exit } = useApp();
useInput((input, key) => {
if (key.ctrl && input === "c") {
cleanup();
exit();
}
});
4. 動的な高さの計算
const { rows } = useTerminalSize();
const HEADER_LINES = 3;
const FOOTER_LINES = 2;
const contentHeight = rows - HEADER_LINES - FOOTER_LINES;
const visibleItems = Math.max(5, contentHeight);
5. カスタム比較関数を使った React.memo
function arePropsEqual<T>(prev: Props<T>, next: Props<T>): boolean {
if (prev.items.length !== next.items.length) return false;
for (let i = 0; i < prev.items.length; i++) {
if (prev.items[i].value !== next.items[i].value) return false;
}
return prev.selectedIndex === next.selectedIndex;
}
export const Select = React.memo(SelectComponent, arePropsEqual);
6. 複数画面のナビゲーション
type ScreenType = "main" | "detail" | "settings";
const [screenStack, setScreenStack] = useState<ScreenType[]>(["main"]);
const currentScreen = screenStack[screenStack.length - 1];
const navigateTo = (screen: ScreenType) => {
setScreenStack(prev => [...prev, screen]);
};
const goBack = () => {
if (screenStack.length > 1) {
setScreenStack(prev => prev.slice(0, -1));
}
};
詳細なリファレンス
コアパターン
- コンポーネントパターン - Screen/Part/Common のアーキテクチャ
- フックガイド - カスタムフックの設計パターン
発展的なトピック
- 複数画面のナビゲーション - 画面スタックの管理
- アニメーションパターン - スピナーとプログレスバー
- 状態管理 - 複雑な状態のパターン
- レスポンシブレイアウト - ターミナルサイズの処理
- パフォーマンス最適化 - 最適化の手法
- 入力処理 - キーボード入力のパターン
トラブルシューティング
例
実装例についてはexamples/を参照してください。
コメント
Markdown 対応