Files
RustUI/DESIGN_SPEC.md
T
2026-05-28 14:18:56 +08:00

465 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Nova 设计规范
> AI-Native 组件库:专为大模型代码生成优化的 AntV 类可视化 + UI 组件库。
> 核心目标:**让 AI 第一次生成的代码就能跑、就能看、就能用。**
---
## 一、核心理念
### 1.1 AI 优先 (AI-First)
传统组件库(antd)面向人类开发者编写,Nova 面向 AI 编写。这意味着:
| 原则 | 说明 | 反例 (antd 风格) | 正例 (Nova 风格) |
|------|------|-------------------|------------------|
| **零歧义** | 每个 prop 名称自解释,无简称、无隐式行为 | `allowClear` | `showClear` |
| **零默认假设** | 所有视觉行为显式声明,不靠"常识" | `status` 默认 `undefined` 表示正常 | `status: 'default' \| 'error' \| 'warning' \| 'success'` |
| **一致性** | 相同概念用相同 prop 名,跨组件统一 | Table `dataSource` / Select `options` | 统一 `data` |
| **扁平化** | 最多一层嵌套,禁止深层 renderProps | `columns[].render()` | `column.renderCell` 函数 prop |
| **确定性** | 相同输入始终产生相同输出,无随机行为 | 部分组件默认不带 `key` 警告 | 所有组件强制 `key` 或无状态 |
### 1.2 可视化 + UI 一体化
Nova 融合 AntV 的数据可视化能力与 antd 的 UI 能力,两者共享同一套设计 Token、交互模式、主题系统。
---
## 二、AI 最友好的 API 设计规则
### 规则 1: 统一数据模型
所有列表/表格/图表组件遵循同一数据接口:
```tsx
// ✅ Nova 方式 — 所有数据组件统一
<Table data={items} />
<List data={items} />
<Chart data={items} />
<Timeline data={items} />
// ❌ antd 方式 — 不同名称增加 AI 混淆
<Table dataSource={items} /> // dataSource
<Select options={items} /> // options
<Tree treeData={items} /> // treeData
```
### 规则 2: 统一尺寸体系
```tsx
// 所有组件共用 size,无例外
size: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
// ❌ antd 不一致的尺寸名
// Button: large / middle / small
// Input: large / middle / small
// DatePicker: large / middle / small
// Tag: 无尺寸 prop
```
### 规则 3: 统一事件命名
```
// 所有事件回调 = on + 动词 + 名词
onClick // ✅
onChange // ✅
onSelect // ✅
onClose // ✅
// ❌ 避免 on + 名词 + 动词 (增加 AI 推理负担)
onMenuClick // ❌ → onClick 即可
onPanelChange // ❌ → onChange
```
### 规则 4: 统一布尔 prop 命名
```tsx
// ✅ 统一用动词/形容词 + 肯定形式
disabled: boolean // 禁用
loading: boolean // 加载中
readOnly: boolean // 只读
showSearch: boolean // 显示搜索
showClear: boolean // 显示清除
allowMultiple: boolean // 允许多选
// ❌ 避免 antd 风格的不一致
// allowClear / showSearch / bordered / dropdownMatchSelectWidth
```
### 规则 5: 自包含组件 + 确定导入路径
```tsx
// ✅ 每个组件独立导入,路径即名字
import { Button } from '@nova/ui/button'
import { Modal } from '@nova/ui/modal'
import { LineChart } from '@nova/ui/line-chart'
// ❌ 避免 AI 不确定路径
// import { Button } from '@nova/ui' // 可能 tree-shake 失败
// import { LineChart } from '@nova/charts' // 需要 AI 猜测包名
```
### 规则 6: 类型优先
所有 prop 使用联合类型字面量而非 boolean,让 AI 看到所有选项:
```tsx
// ✅ 明确枚举,AI 能看到所有可能性
type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'glass'
type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
type Status = 'default' | 'success' | 'warning' | 'error' | 'info'
// ❌ boolean 隐藏了其他可能性
<Button ghost /> // 为什么不是 outline
<Button primary /> // primary 和 type="primary" 是什么关系?
```
```tsx
// 每个组件导出完整类型
export interface ButtonProps {
variant: ButtonVariant
size: ButtonSize
// ...
}
```
### 规则 7: 远离魔法字符串
```tsx
// ✅ 枚举/常量,AI 不会拼错
<Table scrollBar={{ placement: 'overlay' }} />
// ❌ 魔法字符串
<Input status="error" /> // "error" 还是 "Error"?还有哪些值?
```
### 规则 8: 组件原子化
一个组件只做一件事,不做"大而全"的上帝组件:
```tsx
// ✅ 拆分
<Table /> // 纯表格
<TableFilter /> // 表格筛选
<TableSort /> // 表格排序
// ❌ 不拆分
<Table filter sort pagination editable /> // 30+ props 的巨无霸
```
---
## 三、结合 AntV 的可视化组件规范
### 3.1 图表组件统一接口
所有图表组件共享以下 props 契约:
```tsx
interface ChartBaseProps<T> {
data: T[] // 统一数据源
xField: keyof T // X 轴字段名
yField: keyof T // Y 轴字段名
seriesField?: keyof T // 分组/系列字段
color?: string | string[] | ((d: T) => string)
size?: number | ((d: T) => number)
tooltip?: boolean | TooltipConfig
legend?: boolean | LegendConfig
animation?: boolean | AnimationConfig
}
```
### 3.2 支持的图表类型
```tsx
// 基础图表
<LineChart data={data} xField="date" yField="value" />
<BarChart data={data} xField="category" yField="count" />
<PieChart data={data} angleField="value" colorField="name" />
<AreaChart data={data} xField="date" yField="value" />
<ScatterChart data={data} xField="age" yField="income" />
// 组合图表
<DualAxesChart data={data} left={{ yField: 'revenue' }} right={{ yField: 'rate' }} />
// 统计图表
<BoxPlot data={data} />
<Heatmap data={data} />
<RadarChart data={data} />
<Treemap data={data} />
// 地图
<MapChart data={geoData} />
```
### 3.3 可视化交互
```tsx
// 所有图表支持标准交互
interface ChartInteraction {
tooltip: boolean | { shared?: boolean; crosshairs?: boolean }
zoom: boolean | { type: 'x' | 'y' | 'both' }
brush: boolean | { type: 'rect' | 'polygon' }
slider: boolean | { start?: number; end?: number }
legendFilter: boolean
}
```
### 3.4 Stat 组件(数值 + 迷你图)
```tsx
// AI 生成看板时最常用的组件
<Stat
title="本月收入"
value={128000}
prefix="¥"
trend={12.5} // 正数上升,负数下降
trendDirection="up"
chart={<MiniLine data={trendData} />}
/>
```
---
## 四、可落地的 Token 体系
所有值用 TypeScript 常量 + CSS 自定义属性双重暴露:
```ts
// theme/tokens.ts
export const tokens = {
color: {
primary: '#6C5CE7',
primaryHover: '#A29BFE',
primaryActive: '#4A3DBF',
success: '#00B894',
warning: '#FDCB6E',
error: '#FF7675',
info: '#74B9FF',
text: '#1a1a2e',
bg: '#ffffff',
bgElevated: '#f8f9fa',
border: '#e2e8f0',
},
radius: {
xs: '4px',
sm: '8px',
md: '12px',
lg: '16px',
xl: '24px',
round: '9999px',
},
space: {
xs: '4px',
sm: '8px',
md: '12px',
lg: '16px',
xl: '24px',
'2xl': '32px',
'3xl': '48px',
},
font: {
family: '"Inter", "SF Pro Display", -apple-system, sans-serif',
familyMono: '"JetBrains Mono", "SF Mono", monospace',
size: { xs: 12, sm: 14, md: 16, lg: 20, xl: 24 },
weight: { regular: 400, medium: 500, semibold: 600, bold: 700 },
},
shadow: {
sm: '0 1px 3px rgba(0,0,0,0.08)',
md: '0 4px 12px rgba(0,0,0,0.1)',
lg: '0 8px 30px rgba(0,0,0,0.12)',
xl: '0 20px 60px rgba(0,0,0,0.15)',
},
motion: {
fast: '100ms',
normal: '200ms',
slow: '350ms',
easeOut: 'cubic-bezier(0.23, 1, 0.32, 1)',
easeInOut: 'cubic-bezier(0.65, 0, 0.35, 1)',
spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
},
} as const
```
CSS 变量注入:
```css
:root {
--nv-color-primary: #6C5CE7;
--nv-radius-sm: 8px;
--nv-space-md: 12px;
/* ... 每个 token 对应一个 --nv-* 变量 */
}
```
---
## 五、组件文件脚手架(AI 生成模板)
每个新组件创建时,AI 按此模板生成:
```tsx
// Button.tsx — AI 生成的模板示例
import React from 'react'
export interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {
variant?: 'solid' | 'outline' | 'ghost' | 'glass'
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
status?: 'default' | 'success' | 'warning' | 'error'
loading?: boolean
icon?: React.ReactNode
iconPosition?: 'left' | 'right'
fullWidth?: boolean
}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({
variant = 'solid',
size = 'md',
status = 'default',
loading = false,
icon,
iconPosition = 'left',
fullWidth = false,
children,
...rest
}, ref) => {
return (
<button
ref={ref}
data-variant={variant}
data-size={size}
data-status={status}
data-loading={loading || undefined}
data-full-width={fullWidth || undefined}
{...rest}
>
{loading ? <Spinner /> : icon && iconPosition === 'left' && icon}
{children}
{icon && iconPosition === 'right' && icon}
</button>
)
}
)
Button.displayName = 'Button'
```
### 每个组件必须包含:
```
Button/
├── index.ts # export { Button } from './Button'
├── Button.tsx # 组件实现 + Props 类型导出
├── Button.style.ts # 样式(vanilla-extract / CSS modules
├── Button.test.tsx # 测试(至少 3 个用例:渲染、交互、边界)
└── Button.stories.tsx # Storybook stories(至少展示所有 variant + size 组合)
```
---
## 六、AI 友好的代码约束
### 6.1 Props 声明规范
```tsx
// ✅ 正确做法
interface ButtonProps {
/** 按钮风格变体 */
variant?: 'solid' | 'outline' | 'ghost' | 'glass'
/** 按钮尺寸 */
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
}
// ❌ 禁止做法
interface BadProps {
type?: string // string 类型 — AI 不知道可选值
mode?: any // any 类型 — 无类型提示
[key: string]: any // 透传导致 AI 无法推理
}
```
### 6.2 状态映射(禁止 switch-case 硬编码)
```tsx
// ✅ 用对象映射,AI 容易理解
const STATUS_MAP = {
default: { bg: '#f5f5f5', color: '#333' },
success: { bg: '#f0fff4', color: '#00b894' },
warning: { bg: '#fffbeb', color: '#fdcb6e' },
error: { bg: '#fff5f5', color: '#ff7675' },
} as const
// ❌ switch-case 层层嵌套
switch (status) {
case 'success': return { bg: '#f0fff4', color: '#00b894' }
// ...
}
```
### 6.3 条件渲染
```tsx
// ✅ 提前 return / 三元表达式
if (!data.length) return <Empty />
return <div>{data.map(render)}</div>
// ❌ 复杂 && 链
{!!data && data.length > 0 && data.map(item => item.active && <Item />)}
```
### 6.4 样式绑定
```tsx
// ✅ data-* 属性 + CSS 属性选择器(AI 删改时不易出错)
<button data-variant="solid" data-size="lg" />
// ❌ classnames 动态拼接(AI 难以追踪所有组合)
<button className={cn('btn', `btn-${variant}`, `btn-${size}`)} />
```
---
## 七、项目结构
```
novaui/
├── packages/
│ ├── core/ # 核心组件
│ │ ├── Button/
│ │ ├── Modal/
│ │ └── ...
│ ├── charts/ # 可视化组件(基于 AntV/G2)
│ │ ├── LineChart/
│ │ ├── BarChart/
│ │ └── ...
│ ├── icons/ # 图标库
│ ├── theme/ # 主题 Token + 生成器
│ └── utils/ # 工具函数
├── apps/
│ └── docs/ # Storybook
├── packages.json # monorepo root
└── tsconfig.json
```
---
## 八、总结:Nova 给 AI 的"提示工程"
当 AI 生成 Nova 组件代码时,应当遵循的心智模型:
```
1. 导入路径 = @nova/ui/组件名(全小写 kebab-case
2. 数据 prop 始终叫 data,没有例外
3. 尺寸 prop 始终叫 size,值从 xs-xl 选
4. 布尔 prop 用动词开头,如 showSearch、allowMultiple
5. 所有 prop 类型用联合类型字面量定义
6. 状态样式用 data-* 属性,不用 class 拼接
7. 回调命名 = on + 动词 + 名词
8. 每个组件独立文件,组件 = 文件夹
9. 可视化组件统一 xField / yField / data
10. 拒绝魔法字符串、拒绝 any、拒绝深层嵌套
```
> **Nova 的核心竞争力:AI 写的代码,跟人写的一样规范。**