84 lines
4.0 KiB
Markdown
84 lines
4.0 KiB
Markdown
# 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 ? (
|
||
<ErrorState title="数据加载失败" description={error} onRetry={() => setTick((t) => t + 1)} />
|
||
) : items === null ? (
|
||
<LoadingState label="正在拉取数据…" />
|
||
) : items.length === 0 ? (
|
||
<EmptyState title="暂无数据" description="检查数据源后点击重新加载" />
|
||
) : (
|
||
items.map((it) => <SettingRow key={it} label={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)——超时、重试与机器关卡。
|