# L6 · 公共 UI 组件库(@aso/plugin-sdk/ui) > 前置:[L4 React 组件](./05-renderer-components.md)。约 15 分钟。 > 目标:不写一行样式代码,做出与宿主完全一致的界面;等待/失败/空态不再自造。 ## 为什么必须用它 [DESIGN.md](./DESIGN.md) 定了「长什么样」,组件库把它变成「直接拿来的代码」: - **token 同源**:组件内部只用宿主已核验的类词汇(`text-ink-3`、`rounded-control`…) 与 `var(--token)` 内联变量,深浅色自动跟随宿主主题 - **三态统一**:等待 `LoadingState` / 失败 `ErrorState`(可重试)/ 空 `EmptyState`, 任何插件的加载体验与宿主一致 - **零依赖**:`./ui` 是 sdk 源码子路径,随 esbuild 一起打进 renderer.mjs, 不引任何第三方 UI 库(shadcn/ui 的思路——无运行时、复制即用——由宿主承担) ## 导入与构建 ```tsx import { Button, LoadingState, ErrorState, EmptyState, SettingRow, Switch } from '@aso/plugin-sdk/ui' ``` 构建命令与 L4 完全相同——`ui` 随 sdk 源码一起被打包,react 别名照旧: ```bash esbuild src/renderer.tsx --bundle --format=esm --outfile=renderer.mjs \ --jsx=automatic --alias:react=./shims/react.js --alias:react/jsx-runtime=./shims/jsx-runtime.js ``` ## 组件目录 | 组件 | 用途 | 要点 | |---|---|---| | `Spinner` | 行内旋转指示 | `size` 默认 14px | | `LoadingState` | 区块等待态 | `label` 可换文案,默认「加载中…」 | | `ErrorState` | 区块失败态 | `title`/`description`(错误消息)、`onRetry` 提供即显示重试按钮 | | `EmptyState` | 区块空态 | `title` + `description` + `action`(如「去设置」按钮) | | `Button` | 按钮 | `variant: 'primary' \| 'ghost' \| 'danger'`;`loading` 置灰并显示 Spinner,防重复提交 | | `Input` / `Textarea` | 输入 | 与宿主设置页同款外观 | | `Select` | 下拉 | `options: { value, label }[]`,原生 select | | `Switch` | 开关 | 受控 `checked`/`onCheckedChange`,或非受控 `defaultChecked` | | `Card` | 卡片容器 | `rounded-card` + line 边框 | | `SettingRow` | 设置行 | label 左 / 控件右(DESIGN §4 标准模式) | | `Badge` | 徽标 | `tone: 'neutral' \| 'accent' \| 'danger'`,版本/来源/状态元信息 | 全部组件接受 `className` 追加布局类(flex/间距/尺寸),**不接受**颜色/字号覆写—— 那两条路(hex、新字号)被 [L7 检验器](./08-performance.md)拦截。 ## 三态铁律 任何异步区块的状态机:**loading → data | error | empty**,四选一,不允许第五种 (白屏、裸文本「加载中」、无反馈的失败)。模板(plugin-template)是完整示范: ```tsx {error !== null ? ( setTick((t) => t + 1)} /> ) : items === null ? ( ) : items.length === 0 ? ( ) : ( items.map((it) => ) )} ``` 配套铁律:异步调用必须 `withTimeout`/`withRetry`(见 L7),否则 loading 态可能永远挂着。 ## 宿主兜底 renderer 模块的读源与 import 宿主各限时 8 秒:超时或失败时,插件槽位自动呈现宿主侧 「插件界面装载失败」ErrorState(带重试按钮);组件渲染期崩溃由 PluginErrorBoundary 兜底。也就是说**装载失败不需要插件自己处理**,插件只需管好自己组件内部的异步。 ## 边界 - 仅 renderer 组件槽位可用(渲染在宿主 DOM,复用宿主样式表);iframe 插件页是物理 隔离文档,请按 [DESIGN.md](./DESIGN.md) §4 的 HTML 模式自行实现 - 需要新形态时:先用现有组件拼;确实不够,在 DESIGN.md 约束内组合 token, 并把新形态沉淀回团队公共代码,而不是留在单个插件里 --- **上一篇** [L5 自诊断](./06-self-diagnosis.md) · **下一篇**:[L7 性能与检验](./08-performance.md)——超时、重试与机器关卡。