# 设计系统规则

凡是任务涉及 UI 实现、设计稿转代码、样式、设计 Token 或组件复用时，都应用本文件。

## 核心优先级

按以下顺序优先处理：

1. 复用已有组件
2. 在已有组件上扩展
3. 创建新的叶子组件
4. 只有在理由非常充分时，才创建新的共享基础组件

## 设计稿转代码规则

当存在设计上下文时：

- 先通过 MCP 读取设计上下文（Figma / MasterGo / Pixso / 墨刀 / Sketch，摹客则使用截图/标注）
- 设计稿中的颜色/间距 token 必须从 design token 文件中引用
- 如果已有设计上下文，不要靠猜测来决定布局、间距、字体、颜色或状态
- 在创建新组件前，先检查现有组件，优先复用现有组件库中的组件
- 响应式断点: mobile(375px) / tablet(768px) / desktop(1440px)
- 像素还原精度要求: ≥ 95%
- 在改文件前先给出简短实现计划

始终要考虑：

- loading 状态
- empty 状态
- error 状态
- disabled 状态
- hover 状态
- focus 状态
- 在相关场景下的 selected 状态
- 响应式行为

## 设计 Token 规则

- 优先使用已有 design token、CSS 变量或主题变量
- 当已有 Token 可用时，不要硬编码颜色、圆角、间距、阴影和字体取值
- 如果缺少所需 Token，要明确指出，不要悄悄到处硬编码
- Token 使用方式要与周边模块保持一致

## 样式规则

- 优先使用局部、受控的样式，而不是大范围全局覆盖
- 保持当前断点下的响应式行为
- 与项目现有样式体系保持一致
- 检查 hover、active、focus、disabled、selected 等状态
- 除非没有合适 Token 或刻度值，否则不要使用 magic number

## 暗色模式

提供与系统主题同步的手动切换能力，减少视觉疲劳并满足可访问性要求。

### CSS 变量方案

```css
:root {
  --color-bg-primary: #ffffff;
  --color-bg-secondary: #f5f5f5;
  --color-text-primary: #1a1a1a;
  --color-text-secondary: #666666;
  --color-border: #e0e0e0;
}

[data-theme="dark"] {
  --color-bg-primary: #1a1a1a;
  --color-bg-secondary: #2a2a2a;
  --color-text-primary: #f0f0f0;
  --color-text-secondary: #a0a0a0;
  --color-border: #404040;
}

body {
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
}
```

### Tailwind 方案

```js
// tailwind.config.js
module.exports = {
  darkMode: "class", // 通过 .dark 类控制，而非 prefers-color-scheme
};
```

```html
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
  <!-- 内容 -->
</div>
```

### 主题切换 Hook

```tsx
import { useState, useEffect } from "react";

function useTheme() {
  const [theme, setTheme] = useState<"light" | "dark">("light");

  useEffect(() => {
    // 1. 优先读取用户保存的偏好
    const saved = localStorage.getItem("theme") as "light" | "dark" | null;
    if (saved) {
      setTheme(saved);
      return;
    }
    // 2. 回退到系统偏好
    const prefersDark = window.matchMedia(
      "(prefers-color-scheme: dark)",
    ).matches;
    setTheme(prefersDark ? "dark" : "light");
  }, []);

  useEffect(() => {
    const root = document.documentElement;
    if (theme === "dark") {
      root.classList.add("dark");
      root.setAttribute("data-theme", "dark");
    } else {
      root.classList.remove("dark");
      root.setAttribute("data-theme", "light");
    }
    localStorage.setItem("theme", theme);
  }, [theme]);

  const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));

  return { theme, toggle };
}
```

### FOUC 防护（主题闪烁）

在 `<head>` 中同步执行主题初始化脚本（阻塞渲染），避免首次加载时出现错误的主题色：

```html
<head>
  <script>
    // 同步执行 — 在 body 渲染前确定主题
    (function () {
      const theme =
        localStorage.getItem("theme") ||
        (window.matchMedia("(prefers-color-scheme: dark)").matches
          ? "dark"
          : "light");
      if (theme === "dark") {
        document.documentElement.classList.add("dark");
        document.documentElement.setAttribute("data-theme", "dark");
      }
    })();
  </script>
</head>
```

### 暗色模式注意事项

- **图片**: 亮色图片在暗色模式下刺眼，使用 `filter: brightness(0.8)` 降低亮度
- **阴影**: 暗色模式下阴影不明显，改用 `box-shadow: 0 0 15px rgba(255,255,255,0.05)` 或边框替代
- **颜色对比度**: 确保暗色模式下文本与背景的对比度 ≥ 4.5:1（WCAG AA）
- **表单控件**: 输入框、下拉框、开关等原生控件在暗色模式下需要自定义样式
- **第三方组件**: 检查 UI 组件库是否支持暗色模式，必要时用 CSS 变量覆盖

---

## 可访问性基线

优先使用语义化 HTML。

确保：

- 表单控件具备标签
- 纯图标按钮具有可访问名称
- 菜单、标签页、对话框和自定义控件支持键盘操作
- 焦点样式可见
- 编辑页面时标题层级正确
- 真实表格数据使用正确表格语义

## UI 任务的输出要求

对于较复杂的设计类任务，开始时先给出：

- 复用的组件
- 新建的组件
- 可能改动的文件
- 响应式策略
- Token 映射或样式策略

实现完成后，需要说明：

- 复用了什么
- 新建了什么
- 与设计稿有哪些偏差
- 缺少哪些 Token、资源或交互细节
