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

12 KiB
Raw Blame History

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: 统一数据模型

所有列表/表格/图表组件遵循同一数据接口:

// ✅ 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: 统一尺寸体系

// 所有组件共用 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 命名

// ✅ 统一用动词/形容词 + 肯定形式
disabled: boolean      // 禁用
loading: boolean       // 加载中
readOnly: boolean      // 只读
showSearch: boolean    // 显示搜索
showClear: boolean     // 显示清除
allowMultiple: boolean // 允许多选

// ❌ 避免 antd 风格的不一致
// allowClear / showSearch / bordered / dropdownMatchSelectWidth

规则 5: 自包含组件 + 确定导入路径

// ✅ 每个组件独立导入,路径即名字
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 看到所有选项:

// ✅ 明确枚举,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" 是什么关系?
// 每个组件导出完整类型
export interface ButtonProps {
  variant: ButtonVariant
  size: ButtonSize
  // ...
}

规则 7: 远离魔法字符串

// ✅ 枚举/常量,AI 不会拼错
<Table scrollBar={{ placement: 'overlay' }} />

// ❌ 魔法字符串
<Input status="error" />  // "error" 还是 "Error"?还有哪些值?

规则 8: 组件原子化

一个组件只做一件事,不做"大而全"的上帝组件:

// ✅ 拆分
<Table />         // 纯表格
<TableFilter />   // 表格筛选
<TableSort />     // 表格排序

// ❌ 不拆分
<Table filter sort pagination editable />  // 30+ props 的巨无霸

三、结合 AntV 的可视化组件规范

3.1 图表组件统一接口

所有图表组件共享以下 props 契约:

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 支持的图表类型

// 基础图表
<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 可视化交互

// 所有图表支持标准交互
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 组件(数值 + 迷你图)

// AI 生成看板时最常用的组件
<Stat
  title="本月收入"
  value={128000}
  prefix="¥"
  trend={12.5}          // 正数上升,负数下降
  trendDirection="up"
  chart={<MiniLine data={trendData} />}
/>

四、可落地的 Token 体系

所有值用 TypeScript 常量 + CSS 自定义属性双重暴露:

// 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 变量注入:

:root {
  --nv-color-primary: #6C5CE7;
  --nv-radius-sm: 8px;
  --nv-space-md: 12px;
  /* ... 每个 token 对应一个 --nv-* 变量 */
}

五、组件文件脚手架(AI 生成模板)

每个新组件创建时,AI 按此模板生成:

// 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 声明规范

// ✅ 正确做法
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 硬编码)

// ✅ 用对象映射,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 条件渲染

// ✅ 提前 return / 三元表达式
if (!data.length) return <Empty />
return <div>{data.map(render)}</div>

// ❌ 复杂 && 链
{!!data && data.length > 0 && data.map(item => item.active && <Item />)}

6.4 样式绑定

// ✅ 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 写的代码,跟人写的一样规范。