feat(ui-refactor 2/4): DESIGN.md 重写为 beautifului 标准(token/圆角/阴影/动效/字号体系);插件组件库 Button 对齐 pill 体系
This commit is contained in:
+92
-54
@@ -1,26 +1,32 @@
|
|||||||
# DESIGN.md · 插件视觉一致性规范
|
# DESIGN.md · 插件视觉一致性规范(beautifului 审美)
|
||||||
|
|
||||||
> 读者是**写 UI 的 Agent/开发者**。目标:任何来源的插件,界面观感与宿主完全一致。
|
> 读者是**写 UI 的 Agent/开发者**。目标:任何来源的插件,界面观感与宿主完全一致,
|
||||||
|
> 达到 [beautifului.dev](https://www.beautifului.dev/)(MIT)的质感。
|
||||||
> 规则分两类:**强制**(可机器校验/一眼可辨)与**默认**(无充分理由不得偏离)。
|
> 规则分两类:**强制**(可机器校验/一眼可辨)与**默认**(无充分理由不得偏离)。
|
||||||
> 契约问题看 [../plugins.md](../plugins.md);本文件只管"长什么样"。
|
> 契约问题看 [../plugins.md](../plugins.md);本文件只管"长什么样"。
|
||||||
|
> Token 单一事实源:`apps/desktop/src/renderer/src/styles/beautifului.css`(= foundation 原版),
|
||||||
|
> 组件参考源码归档在 `docs/ui-ref/beautifului/*.json`(26 个 registry)。
|
||||||
|
|
||||||
## 0. 两条铁律
|
## 0. 两条铁律
|
||||||
|
|
||||||
1. **色值只允许来自 token**。任何 `#hex`/`rgb()` 硬编码(规范中列出的除外)都算违规;语义永远用 token 名表达(`text-ink-3` 而不是 `text-gray-400`)。
|
1. **色值只允许来自 token**。任何 `#hex`/`rgb()`/`oklch()` 硬编码(规范中列出的除外)都算违规;
|
||||||
2. **不发明新组件形态**。先查本文 §4 标准模式;确需新形态时,用 §1 的 token 组合拼装,不引入新色/新圆角/新阴影。
|
语义永远用 token 名表达(`text-ink-3` 而不是 `text-gray-400`)。
|
||||||
|
2. **不发明新组件形态**。先查本文 §4 标准模式与公共组件库;确需新形态时,
|
||||||
|
用 §1 的 token 组合拼装,不引入新色/新圆角/新阴影。
|
||||||
|
|
||||||
## 1. Token 表(单一事实源:`peer://assets/tokens.css` = 宿主 beautifului.css)
|
## 1. Token 表(单一事实源:`peer://assets/tokens.css` = 宿主 beautifului.css)
|
||||||
|
|
||||||
### 表面(背景)
|
### 表面(背景)——冷调近白,亮暗两套完整 token
|
||||||
|
|
||||||
| token | 意图 |
|
| token | 意图 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `--page` | 页面最底层背景 |
|
| `--page` | 页面最底层背景 |
|
||||||
| `--surface` | 卡片/面板表面 |
|
| `--canvas` | 画布(primary 按钮的文字色取这里) |
|
||||||
|
| `--surface` | 卡片/面板表面(纯白) |
|
||||||
| `--inset` | 内嵌区域(输入框底、预览底、代码块底) |
|
| `--inset` | 内嵌区域(输入框底、预览底、代码块底) |
|
||||||
| `--hover` / `--hover-2` | 悬停态一级/二级 |
|
| `--hover` / `--hover-2` | 悬停态一级/二级 |
|
||||||
|
|
||||||
### 文字
|
### 文字——中性 ink ramp(深灰,不用纯黑)
|
||||||
|
|
||||||
| token | 意图 |
|
| token | 意图 |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -28,51 +34,90 @@
|
|||||||
| `--ink-2` | 次级文字(正文说明) |
|
| `--ink-2` | 次级文字(正文说明) |
|
||||||
| `--ink-3` | 弱化文字(占位、提示、元信息、路径) |
|
| `--ink-3` | 弱化文字(占位、提示、元信息、路径) |
|
||||||
|
|
||||||
### 边线
|
### 边线——实色 hairline,不用 alpha
|
||||||
|
|
||||||
| token | 意图 |
|
| token | 意图 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `--line` | 常规边框(卡片、列表分隔) |
|
| `--line` | 常规边框(卡片、列表分隔) |
|
||||||
| `--line-strong` | 强边框(输入框、可点击描边按钮) |
|
| `--line-strong` | 强边框(输入框、可点击描边) |
|
||||||
| `--line-soft` | 极弱分隔 |
|
| `--line-soft` | 极弱分隔 |
|
||||||
|
|
||||||
### 强调与语义
|
### 强调与语义——蓝色 accent,语义色只作"调味料"
|
||||||
|
|
||||||
| token | 意图 |
|
| token | 意图 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `--accent` / `--accent-ink` / `--accent-tint` | 品牌强调(主按钮、选中、链接) |
|
| `--accent` / `--accent-ink` / `--accent-tint` | 焦点环/选区/选中态/链接(accent-ink 负责可交互文本,保 AA 对比) |
|
||||||
| `--green` / `--green-tint` | 成功/正常 |
|
| `--green` / `--green-tint` | 成功/正常 |
|
||||||
| `--orange` / `--orange-tint` | 警示 |
|
| `--orange` / `--orange-tint` | 警示 |
|
||||||
| `--red` / `--red-tint` | 错误/危险 |
|
| `--red` / `--red-tint` | 错误/危险 |
|
||||||
|
|
||||||
### 阴影(全部是描边式,禁止投影类阴影)
|
### 阴影——「ring + 分层柔影」,不是纯 hairline
|
||||||
|
|
||||||
`--shadow-hairline`(发丝线)· `--shadow-btn`(按钮)· `--shadow-card`(卡片)· `--shadow-raised`(浮起)· `--shadow-overlay`(弹层)· `--shadow-inset-field`(内嵌输入)
|
| token | 意图 |
|
||||||
|
|---|---|
|
||||||
|
| `--shadow-btn` | ring(line-strong) + xs——secondary 按钮 |
|
||||||
|
| `--shadow-card` | ring(line) + sm——卡片 |
|
||||||
|
| `--shadow-raised` | ring(line) + md——悬浮件 |
|
||||||
|
| `--shadow-overlay` | ring(line) + lg——弹层/模态 |
|
||||||
|
| `--shadow-inset-field` | 输入框内嵌阴影 |
|
||||||
|
| `--shadow-hairline` | 纯 ring(无投影)|
|
||||||
|
|
||||||
### 动效
|
暗色模式 ring 自动切换为低透明白渐进(0.10→0.15 按浮起高度),阴影加深——
|
||||||
|
**黑白主题不是简单反色**:暗色有一套专属 elevation 表达。
|
||||||
|
|
||||||
`--duration-fast/base/slow`(统一 400ms)+ `--ease-standard`。悬停/切换用 `transition-colors` 即可。
|
### 圆角体系(关键差异)
|
||||||
|
|
||||||
|
| token | 值 | 用途 |
|
||||||
|
|---|---|---|
|
||||||
|
| `--radius-chip` | 6px | chips、小徽标 |
|
||||||
|
| `--radius-control` | 8px | 输入框、菜单行、icon 按钮 |
|
||||||
|
| `--radius-card` | 10px | 卡片 |
|
||||||
|
| `--radius-window` | 14px | 窗口级容器(composer 外壳、弹窗) |
|
||||||
|
| `rounded-full` | 999px | **按钮(pill 是核心语言)**、状态点、头像 |
|
||||||
|
|
||||||
|
### 动效——强缓动曲线,内置 ease 太弱
|
||||||
|
|
||||||
|
| token | 值 | 用途 |
|
||||||
|
|---|---|---|
|
||||||
|
| `--ease-out-strong` | cubic-bezier(0.23, 1, 0.32, 1) | 大位移(滑层、展开、fade-up) |
|
||||||
|
| `--ease-in-out-strong` | cubic-bezier(0.77, 0, 0.175, 1) | 往返位移 |
|
||||||
|
| `--ease-link` | cubic-bezier(0.16, 1, 0.3, 1) | 下划线/微交互 |
|
||||||
|
| 控件默认 | 150ms ease-out | pill 按钮、悬停反馈 |
|
||||||
|
| 按压 | `active:scale-[0.96]` | 所有可点 pill |
|
||||||
|
|
||||||
## 2. 类名词汇(宿主 Tailwind 映射,renderer 组件直接用)
|
## 2. 类名词汇(宿主 Tailwind 映射,renderer 组件直接用)
|
||||||
|
|
||||||
背景 `bg-page / bg-surface / bg-inset / bg-hover / bg-accent / bg-accent/10` ·
|
背景 `bg-page / bg-canvas / bg-surface / bg-inset / bg-hover / bg-hover-2 / bg-accent / bg-accent/10` ·
|
||||||
文字 `text-ink / text-ink-2 / text-ink-3 / text-accent / text-red` ·
|
文字 `text-ink / text-ink-2 / text-ink-3 / text-accent / text-accent-ink / text-red` ·
|
||||||
边框 `border-line / border-line-strong / border-dashed border-line` ·
|
边框 `border-line / border-line-strong / border-dashed border-line` ·
|
||||||
圆角 `rounded-card`(卡片/输入块)`rounded-control`(按钮/输入框/徽标)。
|
圆角 `rounded-chip`(6)`rounded-control`(8)`rounded-card`(10)`rounded-window`(14)`rounded-full`(pill)·
|
||||||
|
阴影 `shadow-btn / shadow-card / shadow-raised / shadow-overlay`。
|
||||||
|
|
||||||
## 3. 字号与间距(默认)
|
## 3. 字号与间距
|
||||||
|
|
||||||
- 字号阶:`10px`(徽标)/ `11px`(辅助按钮)/ `11.5px`(次要按钮、说明)/ `12px`(正文说明) / `12.5px`(行标题/正文)/ `16px`(页面标题)。**不使用其他字号**;行高 `leading-4/5`。
|
- 基准 14px/1.5,`letter-spacing -0.01em`;字号阶:`11px`(元信息/徽标)→ `12px`(chips、正文说明)→ `12.5/13px`(行标题、按钮)→ `14px`(正文)→ `16px`(页面标题)。**不使用其他字号**。
|
||||||
- 间距基数为 4px:卡片内边距 `px-3 py-2.5`,区块间 `space-y-1.5`,小节间 `pt-3`。
|
- 间距基数 4px:卡片内边距 `px-3 py-2.5`(紧凑)或 `primitive-card-pad`(12px),区块间 `space-y-1.5`,小节间 `pt-3`。
|
||||||
- 图标一律 **lucide**(宿主白名单 `plugin-icons.tsx`,约 100 个常用名):`manifest.icon` / 状态胶囊 `icon` 填 lucide 图标名(如 `"Globe"`,kebab-case 亦可),12–16px、`currentColor` 由宿主保证;**禁止自绘 svg**(旧内联 svg 仅对存量插件兼容),未知名回落拼图图标
|
- 图标 12–16px 线条风格,`currentColor`;**一律 lucide**(宿主白名单 `plugin-icons.tsx`),禁止自绘 svg。
|
||||||
|
- 颜色只作"调味料":结构永远是中性 ink + line,彩色仅出现在状态/数值/选中。
|
||||||
|
|
||||||
## 4. 标准模式(复制即用)
|
## 4. 标准模式
|
||||||
|
|
||||||
> **renderer 组件优先用组件库**:本节模式已在 `@aso/plugin-sdk/ui` 代码化
|
> **renderer 组件优先用组件库**:`@aso/plugin-sdk/ui` 的 `Button`/`SettingRow`/`LoadingState`/
|
||||||
> (`Button`/`SettingRow`/`EmptyState`/`LoadingState`/`ErrorState`/`Badge`…,
|
> `ErrorState`/`EmptyState`/`Badge`… 已按本规范实现(见 [L6](./07-ui-components.md))。
|
||||||
> 见 [L6](./07-ui-components.md)),直接 import,不要手写。
|
|
||||||
> 下面的 HTML 形态留给 iframe 插件页作者。
|
> 下面的 HTML 形态留给 iframe 插件页作者。
|
||||||
|
|
||||||
|
### 按钮(pill 是核心语言)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- primary:墨底 canvas 字 + 内高光;accent:蓝底白字;secondary:白底 ring+xs -->
|
||||||
|
<button class="inline-flex h-[27px] items-center justify-center gap-1.5 rounded-full px-3
|
||||||
|
text-[13px] font-medium leading-none transition-[transform,background-color,opacity]
|
||||||
|
duration-150 ease-out active:scale-[0.96] bg-accent text-white hover:bg-accent-ink
|
||||||
|
shadow-[inset_0_1px_0_rgba(255,255,255,0.14)]">主操作</button>
|
||||||
|
<!-- quiet:透明直到悬停(密集工具行) -->
|
||||||
|
<button class="h-7 rounded-full px-2.5 text-[12px] text-ink hover:bg-hover">次要</button>
|
||||||
|
```
|
||||||
|
|
||||||
### 设置行(label 左 / 控件右)
|
### 设置行(label 左 / 控件右)
|
||||||
|
|
||||||
```html
|
```html
|
||||||
@@ -85,52 +130,45 @@
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 按钮
|
### 卡片(window 级容器用 14px)
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!-- primary -->
|
<div class="rounded-window border border-line bg-surface shadow-card">…</div>
|
||||||
<button class="rounded-control bg-accent px-3 py-1.5 text-[11.5px] font-medium text-white hover:opacity-90">主操作</button>
|
|
||||||
<!-- ghost -->
|
|
||||||
<button class="rounded-control border border-line-strong px-3 py-1.5 text-[11.5px] text-ink hover:bg-hover">次要操作</button>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 徽标
|
### 徽标 / 空态 / 开关
|
||||||
|
|
||||||
```html
|
徽标 `rounded-chip bg-hover px-1.5 py-0.5 text-[10px] text-ink-3`;
|
||||||
<span class="rounded-control bg-hover px-1.5 py-0.5 text-[10px] text-ink-3">全局 v0.1.0</span>
|
空态 = 虚线卡(同旧版);开关 `h-5 w-9`(行内)/ `h-6 w-10`(表单级),
|
||||||
```
|
滑块 `size-4/5 rounded-full bg-white shadow`,开 `bg-accent`、关 `bg-line-strong`。
|
||||||
|
|
||||||
### 空态
|
## 5. 动效范式
|
||||||
|
|
||||||
```html
|
- 悬停反馈:150ms ease-out,只变 background-color/text(配 `transition-colors`)
|
||||||
<div class="flex flex-col items-center justify-center gap-1 rounded-card border border-dashed border-line py-12 text-center">
|
- 内容进场:`fade-up 400ms var(--ease-out-strong)`(聊天消息、卡片)
|
||||||
<div class="text-[13px] font-medium text-ink-2">暂无数据</div>
|
- 弹层:`pop-in`(scale 0.95→1)+ `shadow-overlay`
|
||||||
<div class="text-[11.5px] text-ink-3">怎么产生的提示</div>
|
- 滑动高亮层(菜单/标签条):220ms `cubic-bezier(0.23,1,0.32,1)`(GlideMenu)
|
||||||
</div>
|
- 流式输出:光标 2px `--ink` 常亮→结束后闪烁;`prefers-reduced-motion` 全局降级
|
||||||
```
|
|
||||||
|
|
||||||
### 开关(switch)
|
## 6. iframe 插件页附加规则
|
||||||
|
|
||||||
宿主规格:`h-5 w-9`(行内)/ `h-6 w-10`(表单级),滑块 `size-4/5 rounded-full bg-white shadow`,开 `bg-accent`、关 `bg-line-strong`。
|
- `<head>` 必须引入 `<link rel="stylesheet" href="peer://assets/tokens.css" />`,并用同名 token 编译自己的 Tailwind
|
||||||
|
- 主题跟随宿主推送(`useHarnessTheme`),**禁止** `prefers-color-scheme` 自测;亮暗两套 token 都要核对
|
||||||
|
- 与宿主的视觉差异应趋近于零:同 token + 同类名词汇 + 同字号阶 + 同圆角/阴影体系
|
||||||
|
|
||||||
## 5. iframe 插件页附加规则
|
## 7. renderer 组件附加规则
|
||||||
|
|
||||||
- `<head>` 必须引入 `<link rel="stylesheet" href="peer://assets/tokens.css" />`,并用同名 token 编译自己的 Tailwind(token 名 → 变量:`page/ink/ink-2/ink-3/line/line-strong/accent…`)
|
|
||||||
- 主题跟随宿主推送(`useHarnessTheme`),**禁止** `prefers-color-scheme` 自测
|
|
||||||
- 与宿主的视觉差异应趋近于零:同 token + 同类名词汇 + 同字号阶
|
|
||||||
|
|
||||||
## 6. renderer 组件附加规则
|
|
||||||
|
|
||||||
- 类名直接用宿主词汇(§2),无需引样式;**优先 import 公共组件库**([L6](./07-ui-components.md)),不要手写标准模式
|
- 类名直接用宿主词汇(§2),无需引样式;**优先 import 公共组件库**([L6](./07-ui-components.md)),不要手写标准模式
|
||||||
- React 必须来自 `ctx.react`(见 [L4](./05-renderer-components.md)),否则 hooks 失效
|
- React 必须来自 `ctx.react`(见 [L4](./05-renderer-components.md)),否则 hooks 失效
|
||||||
- 禁止操作宿主 DOM(document 全局查询/改样式),一切交互限制在自己的组件子树
|
- 禁止操作宿主 DOM(document 全局查询/改样式),一切交互限制在自己的组件子树
|
||||||
- 机器关卡:`pnpm plugin:check <插件目录>`(hex 色值 / React 副本 / 三态缺失等,见 [L7](./08-performance.md))
|
- 机器关卡:`pnpm plugin:check <插件目录>`(hex 色值 / React 副本 / 三态缺失等,见 [L7](./08-performance.md))
|
||||||
|
|
||||||
## 7. 自检清单(提发布前)
|
## 8. 自检清单(提发布前)
|
||||||
|
|
||||||
- [ ] `pnpm plugin:check <插件目录>` 0 error(机器关卡,见 [L7](./08-performance.md))
|
- [ ] `pnpm plugin:check <插件目录>` 0 error(机器关卡,见 [L7](./08-performance.md))
|
||||||
- [ ] 无硬编码色值/阴影/圆角(铁律 1)
|
- [ ] 无硬编码色值/阴影/圆角(铁律 1)
|
||||||
- [ ] 字号都在 §3 的阶上
|
- [ ] 圆角落在体系上:chip 6 / control 8 / card 10 / window 14 / pill 999
|
||||||
- [ ] 深浅色都检查过(切主题看一眼;token 正确时自动成立)
|
- [ ] 字号都在 §3 的阶上;按钮是 pill(rounded-full)
|
||||||
|
- [ ] 深浅色都检查过(暗色不是反色:ring 是低透白、阴影更深)
|
||||||
- [ ] 复用了 §4 模式(renderer 直接用组件库)而非新造形态
|
- [ ] 复用了 §4 模式(renderer 直接用组件库)而非新造形态
|
||||||
- [ ] iframe 页引了 tokens.css 且无 `prefers-color-scheme`
|
- [ ] iframe 页引了 tokens.css 且无 `prefers-color-scheme`
|
||||||
|
|||||||
@@ -118,26 +118,28 @@ export function EmptyState({ title, description, action, className }: EmptyState
|
|||||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
/** primary=强调实心 / ghost=描边(默认)/ danger=危险描边 */
|
/** primary=强调实心 / ghost=描边(默认)/ danger=危险描边 */
|
||||||
variant?: 'primary' | 'ghost' | 'danger'
|
variant?: 'primary' | 'ghost' | 'danger'
|
||||||
/** sm=辅助按钮 11px / md=标准 11.5px(默认) */
|
/** sm=辅助 pill(h-7 12px)/ md=标准 pill(13px)(默认) */
|
||||||
size?: 'sm' | 'md'
|
size?: 'sm' | 'md'
|
||||||
/** true 时禁用并显示旋转指示器(异步提交防重复点击) */
|
/** true 时禁用并显示旋转指示器(异步提交防重复点击) */
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Button({ variant = 'ghost', size = 'md', loading = false, disabled, className, children, ...rest }: ButtonProps): ReactElement {
|
export function Button({ variant = 'ghost', size = 'md', loading = false, disabled, className, children, ...rest }: ButtonProps): ReactElement {
|
||||||
|
/* beautifului pill 体系:rounded-full + 150ms ease-out + 按压回缩 0.96;
|
||||||
|
* filled 变体带 1px 内高光(类已在宿主 Button.tsx 中生成,插件可直接复用)。 */
|
||||||
const variantCls =
|
const variantCls =
|
||||||
variant === 'primary'
|
variant === 'primary'
|
||||||
? 'bg-accent text-white hover:opacity-90'
|
? 'bg-ink text-canvas hover:opacity-90 shadow-[inset_0_1px_0_rgba(255,255,255,0.14)]'
|
||||||
: variant === 'danger'
|
: variant === 'danger'
|
||||||
? 'border border-line-strong text-red hover:bg-red/10'
|
? 'border border-line-strong text-red hover:bg-red/10'
|
||||||
: 'border border-line-strong text-ink hover:bg-hover'
|
: 'bg-surface text-ink shadow-btn hover:bg-inset'
|
||||||
const sizeCls = size === 'sm' ? 'px-2 py-1 text-[11px]' : 'px-3 py-1.5 text-[11.5px]'
|
const sizeCls = size === 'sm' ? 'h-7 px-2.5 text-[12px]' : 'h-[27px] px-3 text-[12.5px]'
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={disabled || loading}
|
disabled={disabled || loading}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center justify-center gap-1.5 rounded-control font-medium transition-colors disabled:pointer-events-none disabled:opacity-50',
|
'inline-flex items-center justify-center gap-1.5 rounded-full font-medium leading-none transition-[transform,background-color,opacity] duration-150 ease-out active:scale-[0.96] disabled:pointer-events-none disabled:opacity-50',
|
||||||
variantCls,
|
variantCls,
|
||||||
sizeCls,
|
sizeCls,
|
||||||
className
|
className
|
||||||
|
|||||||
Reference in New Issue
Block a user