@bunli/图伊
一个基于反应的终端用户界面库,用于Bunli CLI框架,由OpenTUI的React渲染器提供动力.
特征
- 基于反应的组件: 使用熟悉的反应模式和JSX构建 TUIs
- 构成部分库: 交替缓冲 TUI 的格式、布局、反馈、数据显示和图表组件
- OpenTUI 整合: 完全访问 OpenTUI 的回放钩和组件
- 类型安全: 完整类型脚本支持,并有适当的类型推论
- 动画支持:平滑动画的内置时间系统
- 键盘处理: 方便键盘事件管理
useKeyboard - 第一级别 TUI 支持: TUI 渲染是一个一等特性, 不是插件
- 主题系统: 预设主题并带有符号覆盖
ThemeProvider编号 :createTheme
安装
bun add @bunli/tui react
快速启动
import { createCLI, defineCommand } from '@bunli/core'
const cli = await createCLI({
name: 'my-app',
version: '1.0.0'
})
// Define a command with TUI using the render property
const myCommand = defineCommand({
name: 'deploy',
description: 'Deploy application',
render: () => (
<box title="Deployment" style={{ border: true, padding: 2 }}>
<text>Deploying...</text>
</box>
),
handler: async () => {
// Non-TUI fallback when render is skipped
console.log('Deploying application...')
}
})
cli.command(myCommand)
await cli.run()
Bunli自动接通OpenTUI运行时间. 运行时间/文本 API 现在来自 @bunli/runtime/app,即时/会话API来自 @bunli/runtime/prompt,以及 @bunli/tui 专注于UI组件和钩.
TUI 执行语义
- 带有
render在交互式终端中运行. - 非交互终端返回到
handler当在现相. - 明确配置全屏流量
bufferMode: 'alternate'。 。 。 .
Render 寿命周期( 运行时退出)
使用的命令 render 最终必须呼叫 useRuntime().exit() (例如,在提交/取消/请求时),或命令不会退出.
import { useRuntime } from '@bunli/runtime/app'
import { useKeyboard } from '@bunli/tui'
function DeployTUI() {
const runtime = useRuntime()
useKeyboard((key) => {
if (key.name === 'q' || (key.ctrl && key.name === 'c')) {
runtime.exit()
}
})
return <text>Press q to quit</text>
}
缓冲模式( 替代对标准)
OpenTUI可以使用可替换的屏幕缓冲器(全屏TUI)或标准终端缓冲器(以回滚方式输出)来渲染.
配置此内容 createCLI() 配置 :
const cli = await createCLI({
name: 'my-app',
version: '1.0.0',
tui: {
renderer: {
bufferMode: 'alternate' // or 'standard'
}
}
})
注释:
bufferMode: 'standard'默认值.- 使用
bufferMode: 'alternate'用于全屏/屏蔽体验. - 鼠标跟踪默认已禁用( S)
useMouse: false),以避免在标准缓冲工作流程中退出后泄露生鼠逃生序列. 需要时明确启用 .
使用量
模块分割
根据模式使用子路径导出 :
@bunli/tui/interactive: 交替缓冲交互组件.@bunli/tui/charts:终端-本土图原始.@bunli/tui: root 导出后再出口共享组件/套接字.@bunli/runtime/app:运行时间生命周期/文本 API.@bunli/runtime/prompt: 即时/会话API.
Clack 迁移快速地图
用 Bunli- native API 替换 clack 智能模型时使用此功能 :
// clack
// import { intro, outro, confirm, select, multiselect, log } from '@clack/prompts'
// bunli
// prompt is provided via handler args by Bunli
prompt.intro('Setup')
const confirmed = await prompt.confirm('Continue?', { default: true })
const env = await prompt.select('Environment', {
options: [
{ label: 'Development', value: 'dev' },
{ label: 'Production', value: 'prod' }
]
})
const features = await prompt.multiselect('Features', {
options: [
{ label: 'Testing', value: 'testing' },
{ label: 'Docker', value: 'docker' }
],
initialValues: ['testing']
})
prompt.log.success(`Selected ${env}`)
prompt.outro('Done')
基本TUI组件
import { defineCommand } from '@bunli/core'
function MyTUI() {
return (
<box title="My App" style={{ border: true, padding: 2 }}>
<text>Hello from My App!</text>
</box>
)
}
export const myCommand = defineCommand({
name: 'my-command',
description: 'My command with TUI',
render: () => <MyTUI />,
handler: async () => {
console.log('Running my-command in CLI mode')
}
})
使用窗体组件
import { defineCommand } from '@bunli/core'
import { useRuntime } from '@bunli/runtime/app'
import { SchemaForm } from '@bunli/tui'
import { z } from 'zod'
const configSchema = z.object({
apiUrl: z.string().url('Enter a valid URL'),
region: z.enum(['us-east', 'us-west'])
})
function ConfigTUI() {
const runtime = useRuntime()
const regions = [
{ label: 'US East', value: 'us-east', hint: 'US East region' },
{ label: 'US West', value: 'us-west', hint: 'US West region' }
]
return (
<SchemaForm
title="Configure Settings"
schema={configSchema}
fields={[
{
kind: 'text',
name: 'apiUrl',
label: 'API URL',
placeholder: 'https://api.example.com',
required: true
},
{
kind: 'select',
name: 'region',
label: 'Region',
options: regions
}
]}
onSubmit={(values) => {
console.log('Validated form values:', values)
}}
onCancel={() => runtime.exit()}
/>
)
}
export const configureCommand = defineCommand({
name: 'configure',
description: 'Configure application settings',
render: () => <ConfigTUI />
})
使用 OpenTUI 钩
import { useRuntime } from '@bunli/runtime/app'
import { useKeyboard, useTimeline, useTerminalDimensions } from '@bunli/tui'
function InteractiveTUI({ command }) {
const [count, setCount] = useState(0)
const { width, height } = useTerminalDimensions()
const runtime = useRuntime()
const timeline = useTimeline({ duration: 2000 })
useKeyboard((key) => {
if (key.name === 'q') {
runtime.exit()
}
if (key.name === 'space') {
setCount(prev => prev + 1)
}
})
useEffect(() => {
timeline.add({ count: 0 }, {
count: 100,
duration: 2000,
onUpdate: (anim) => setCount(anim.targets[0].count)
})
}, [])
return (
<box title="Interactive Demo" style={{ border: true, padding: 2 }}>
<text>Count: {count}</text>
<text>Terminal: {width}x{height}</text>
<text>Press SPACE to increment, Q to quit</text>
</box>
)
}
构成部分库
交互式组件可从 @bunli/tui/interactive 和根出口.
包含原始人 :
- 表单 :
Form, (中文(简体) ),SchemaForm, (中文(简体) ),FormField, (中文(简体) ),SelectField - 表单 v2:
NumberField, (中文(简体) ),PasswordField, (中文(简体) ),TextareaField, (中文(简体) ),CheckboxField, (中文(简体) ),MultiSelectField - 布局 :
Container, (中文(简体) ),Stack, (中文(简体) ),Grid, (中文(简体) ),Panel, (中文(简体) ),Card, (中文(简体) ),Divider, (中文(简体) ),SectionHeader - 导航/流程 :
Tabs, (中文(简体) ),Menu, (中文(简体) ),CommandPalette, (中文(简体) ),Modal - 反馈 :
Alert, (中文(简体) ),Badge, (中文(简体) ),Toast, (中文(简体) ),ProgressBar, (中文(简体) ),EmptyState - 数据显示 :
List, (中文(简体) ),Table, (中文(简体) ),DataTable, (中文(简体) ),KeyValueList, (中文(简体) ),Stat, (中文(简体) ),Markdown, (中文(简体) ),Diff - 图:
BarChart, (中文(简体) ),LineChart, (中文(简体) ),Sparkline从@bunli/tui/charts - 运行时管弦乐/吼: 从
@bunli/runtime/app(单位:千美元)DialogProvider, (中文(简体) ),useDialogManager, (中文(简体) ),FocusScopeProvider, (中文(简体) ),useScopedKeyboard页:1
键盘合同
交互式原始文件的默认键盘绑定 :
Modal数字 :Esc编号 :Ctrl+C关上,Tab编号 :Shift+Tab焦点陷阱Dialog confirm数字 :Left编号 :h编号 :y- > 确认,Right编号 :l编号 :n- > 取消选择,Tab切换,Enter提交Dialog choose数字 :Up编号 :k先前,Down编号 :j接下来,Enter提交。 为选择/导航而跳过已禁用的选项 .Menu数字 :Up编号 :k, (中文(简体) ),Down编号 :j, (中文(简体) ),EnterTabs数字 :Left编号 :h上一个标签,Right编号 :l下一个标签CommandPalette数字 :Up编号 :k, (中文(简体) ),Down编号 :j, (中文(简体) ),EnterDataTable数字 :Left编号 :h上一个排序列,Right编号 :l下一个排序列,Up编号 :k上一行,请,Down编号 :j下一个行,Enter选择行Form数字 :Tab编号 :Shift+Tab实地导航,Ctrl+S提交,Ctrl+R重设,F8编号 :Shift+F8跳转出错字段,Esc取消( e)
对话框管理器
使用对话框管理器来堆放确认/选择流 , 具有一致的优先处理和解职语义 .
import { useDialogManager, DialogDismissedError } from '@bunli/runtime/app'
function Screen() {
const dialogs = useDialogManager()
async function deploy() {
try {
const confirmed = await dialogs.confirm({
title: 'Deploy',
message: 'Ship this release now?'
})
if (!confirmed) return
const target = await dialogs.choose({
title: 'Target',
options: [
{ label: 'Staging', value: 'staging', section: 'General' },
{ label: 'Production', value: 'production', section: 'Protected', disabled: true }
]
})
console.log('Deploying to', target)
} catch (error) {
if (error instanceof DialogDismissedError) {
console.log('Dialog dismissed')
}
}
}
return <box />
}
表单
用于受控交互式形式的由计划驱动的容器.
const runtime = useRuntime()
<Form
title="My Form"
schema={schema}
onSubmit={(values) => console.log(values)}
onCancel={() => runtime.exit()}
>
{/* Form fields */}
</Form>
道具 :
title: string- 表格标题schema: StandardSchemaV1- 验证计划(支持Zod和其他标准Schema适配器)onSubmit: (values) => void | Promise<void>- 提交具有计划验证值的处理器onCancel?: () => void- 取消处理器(可选)onValidationError?: (errors: Record<string, string>) => void- 验证错误召回initialValues?: Partial<InferOutput<schema>>- 初始控制值validateOnChange?: boolean- 输入/选择时验证(默认)true(中文(简体) )submitHint?: string- 页脚提示覆盖onReset?: () => void- 重置回调onDirtyChange?: (isDirty, dirtyFields) => void- 肮脏的状态召回onSubmitStateChange?: ({ isSubmitting, isValidating }) => void- 自动调用状态scopeId?: string- 嵌入式交互式流动的键盘范围边界 id
形状
更高层次的造型建构器,使字段来自描述符.
<SchemaForm
title="Deploy"
schema={schema}
fields={[
{ kind: 'text', name: 'service', label: 'Service' },
{ kind: 'select', name: 'env', label: 'Environment', options: envOptions },
{ kind: 'checkbox', name: 'telemetry', label: 'Enable telemetry' },
{
kind: 'textarea',
name: 'notes',
label: 'Release notes',
visibleWhen: (values) => values.env === 'production'
}
]}
onSubmit={(values) => console.log(values)}
/>
支助 SchemaForm 字段类型 :
textselectmultiselectnumberpasswordtextareacheckbox
场级行为 :
visibleWhen(values) => boolean用于有条件渲染.deriveDefault(values) => unknown用于依赖性默认初始化.
表单字段
受控文本字段必须形成上下文 .
<FormField
label="Username"
name="username"
placeholder="Enter username"
required
defaultValue=""
/>
道具 :
label: string- 实地标签name: string- 字段名称placeholder?: string- 占位符文本required?: boolean- 是否需要字段description?: string- 帮助文本defaultValue?: string- 形式状态初始值onChange?: (value: string) => void- 更改处理器onSubmit?: (value: string) => void- 提交处理器
选择字段
受控的选定字段将形成上下文 .
<SelectField
label="Environment"
name="env"
options={[
{ label: 'Development', value: 'dev', hint: 'Development environment' },
{ label: 'Production', value: 'prod', hint: 'Production environment' }
]}
defaultValue="dev"
onChange={setEnvironment}
/>
道具 :
label: string- 实地标签name: string- 字段名称options: SelectOption[]- 可用的选项required?: boolean- 是否需要字段description?: string- 帮助文本defaultValue?: SelectOption['value']- 初始选定值onChange?: (value: SelectOption['value']) => void- 更改处理器
进度栏
显示完成状态的进度栏组件 .
<ProgressBar
value={75}
label="Upload Progress"
color="#00ff00"
/>
道具 :
value: number- 进度值(0-100)label?: string- 进度标签color?: string- 进步栏颜色
图表初级
@bunli/tui/charts 支持负和稀有值,轴标签,以及多序列调色板.
import { BarChart, LineChart } from '@bunli/tui/charts'
<BarChart
series={[
{ name: 'build', points: [{ label: 'Mon', value: -2 }, { label: 'Tue', value: 6 }] },
{ name: 'test', points: [{ label: 'Mon', value: null }, { label: 'Tue', value: 4 }] }
]}
axis={{ yLabel: 'Jobs', xLabel: 'Day', showRange: true }}
/>
<LineChart
series={{ name: 'latency', points: [{ value: 120 }, { value: 98 }, { value: null }, { value: 104 }] }}
axis={{ yLabel: 'ms' }}
/>
主题提供和托肯斯
使用 ThemeProvider 以应用内置主题预设或符号覆盖.
import { ThemeProvider, createTheme } from '@bunli/tui/interactive'
const customTheme = createTheme({
preset: 'dark',
tokens: {
accent: '#3ec7ff',
textSuccess: '#3cd89b'
}
})
function App() {
return (
<ThemeProvider theme={customTheme}>
<Panel title="Deploy status">
<Alert tone="success" message="Ready to ship" />
</Panel>
</ThemeProvider>
)
}
OpenTUI 钩
套件再导出有用的 OpenTUI React hooks :
使用键盘
处理键盘事件.
import { useRuntime } from '@bunli/runtime/app'
import { useKeyboard } from '@bunli/tui'
const runtime = useRuntime()
useKeyboard((key) => {
if (key.name === 'escape') {
runtime.exit()
}
})
使用渲染器
访问 OpenTUI 渲染器实例 .
import { useRenderer } from '@bunli/tui'
const renderer = useRenderer()
renderer.console.show()
使用 useRenderer() 仅限高级出纳器检查/控制。 使用 useRuntime().exit() 用于完成正常命令.
使用终端数字
获取当前终端维度 .
import { useTerminalDimensions } from '@bunli/tui'
const { width, height } = useTerminalDimensions()
使用时间线
创建和管理动画.
import { useTimeline } from '@bunli/tui'
const timeline = useTimeline({ duration: 2000 })
timeline.add({ x: 0 }, {
x: 100,
duration: 2000,
onUpdate: (anim) => setX(anim.targets[0].x)
})
仅使用回复
处理终端调整事件大小 .
import { useOnResize } from '@bunli/tui'
useOnResize((width, height) => {
console.log(`Terminal resized to ${width}x${height}`)
})
渲染器配置
通过 createCLI({ tui: { renderer } }) 和可选命令级别覆盖 command.tui.renderer。 。 。 .
OpenTUI 组件
您可以直接使用任何 OpenTUI 反应组件 :
import { render } from '@opentui/react'
function MyComponent() {
return (
<box style={{ border: true, padding: 2 }}>
<text>Hello World</text>
<input placeholder="Type here..." />
<select options={options} />
</box>
)
}
可用组件 :
<box>- 有边界和布局的集装箱<text>- 有样式的文本显示<input>- 文本输入字段<select>- 下拉选择<scrollbox>- 可滚动容器<ascii-font>- ASCII 艺术文本<tab-select>- 基于标签的选择
实例
见 examples/tui-demo 完整示例目录 :
- 部署指挥: 有时间表的动画进度栏
- 配置命令: 带有输入和选择字段的窗体
类型脚本支持
软件包提供了完整的TypeScript支持:
import type { TuiComponent, TuiComponentProps } from '@bunli/tui'
const MyTUI: TuiComponent = ({ command, args, store }) => {
// Fully typed props
return <box>{command.name}</box>
}
许可证
麻省理工学院