关于 inkjs-design
面向使用 Ink.js 构建终端界面的开发者的设计指南,围绕屏幕、可复用部件和输入组件整理基于 React 的 CLI 模式、示例与问题排查参考。
主要功能
- 01Screen、Part 和 Common 组件模式
- 02键盘输入与焦点处理指南
- 03多屏幕导航模式
- 04响应式终端布局指南
- 05性能与动画参考
- 06测试与问题排查参考
适用场景
- 01规划新 Ink.js CLI 的组件
- 02解决终端布局或输入冲突
- 03为 CLI 屏幕添加导航
安装
复制下面的命令,在终端中运行。
$
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 中的类似模式
- 添加类型定义
- 实现组件
- 编写测试
常见问题与解决方案
| 问题 | 参考 |
|---|---|
| Emoji 宽度错位 | ink-gotchas.md |
| Ctrl+C 被调用两次 | 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 的 Emoji 宽度计算问题:
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));
}
};
详细参考
核心模式
高级主题
故障排查
示例
请参阅 examples/ 获取实际实现示例。
评论
支持 Markdown