diff --git a/package.json b/package.json
index 16cb7e7..1b38dcf 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,8 @@
"@prisma/client": "^7.2.0",
"bcryptjs": "^3.0.3",
"better-auth": "^1.4.10",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
"dotenv": "^17.2.3",
"lucide-react": "^0.562.0",
"next": "16.1.1",
@@ -23,6 +25,7 @@
"react": "19.2.3",
"react-dom": "19.2.3",
"sonner": "^2.0.7",
+ "tailwind-merge": "^3.4.0",
"unstorage": "^1.17.3",
"zod": "^4.3.5"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d0824bd..c10ac22 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -24,6 +24,12 @@ importers:
better-auth:
specifier: ^1.4.10
version: 1.4.10(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.7)(better-sqlite3@12.5.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(better-sqlite3@12.5.0)(drizzle-orm@0.33.0(@electric-sql/pglite@0.3.2)(@prisma/client@5.22.0(prisma@7.2.0(@types/react@19.2.7)(better-sqlite3@12.5.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(@types/pg@8.15.6)(@types/react@19.2.7)(better-sqlite3@12.5.0)(kysely@0.28.8)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.7)(better-sqlite3@12.5.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react@19.2.3))(mysql2@3.15.3)(next@16.1.1(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.16.3)(prisma@7.2.0(@types/react@19.2.7)(better-sqlite3@12.5.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ class-variance-authority:
+ specifier: ^0.7.1
+ version: 0.7.1
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
dotenv:
specifier: ^17.2.3
version: 17.2.3
@@ -48,6 +54,9 @@ importers:
sonner:
specifier: ^2.0.7
version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ tailwind-merge:
+ specifier: ^3.4.0
+ version: 3.4.0
unstorage:
specifier: ^1.17.3
version: 1.17.3
@@ -1480,9 +1489,16 @@ packages:
citty@0.1.6:
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
+ class-variance-authority@0.7.1:
+ resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -3024,6 +3040,9 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ tailwind-merge@3.4.0:
+ resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
+
tailwindcss@4.1.18:
resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
@@ -4778,8 +4797,14 @@ snapshots:
dependencies:
consola: 3.4.2
+ class-variance-authority@0.7.1:
+ dependencies:
+ clsx: 2.1.1
+
client-only@0.0.1: {}
+ clsx@2.1.1: {}
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -6407,6 +6432,8 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ tailwind-merge@3.4.0: {}
+
tailwindcss@4.1.18: {}
tapable@2.3.0: {}
diff --git a/src/app/globals.css b/src/app/globals.css
index f5e78cf..2497a55 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,20 +1,154 @@
@import "tailwindcss";
+/**
+ * Design System CSS 变量
+ *
+ * 定义全局 CSS 变量用于主题切换和动态样式
+ */
:root {
- --background: #ffffff;
- --foreground: #171717;
+ /* 颜色系统 */
+ --color-primary-50: #f0f9f8;
+ --color-primary-100: #e0f2f0;
+ --color-primary-200: #bce6e1;
+ --color-primary-300: #8dd4cc;
+ --color-primary-400: #5ec2b7;
+ --color-primary-500: #35786f;
+ --color-primary-600: #2a605b;
+ --color-primary-700: #1f4844;
+ --color-primary-800: #183835;
+ --color-primary-900: #122826;
+ --color-primary-950: #0a1413;
+
+ /* 语义色 */
+ --color-success-500: #22c55e;
+ --color-warning-500: #f59e0b;
+ --color-error-500: #ef4444;
+ --color-info-500: #3b82f6;
+
+ /* 基础颜色 */
+ --background: #ffffff;
+ --foreground: #111827;
+ --foreground-secondary: #4b5563;
+ --foreground-tertiary: #6b7280;
+ --foreground-disabled: #9ca3af;
+
+ /* 背景 */
+ --background-secondary: #f3f4f6;
+ --background-tertiary: #e5e7eb;
+
+ /* 边框 */
+ --border: #d1d5db;
+ --border-secondary: #e5e7eb;
+ --border-focus: #35786f;
+
+ /* 圆角 */
+ --radius-sm: 0.125rem;
+ --radius-md: 0.375rem;
+ --radius-lg: 0.5rem;
+ --radius-xl: 0.75rem;
+ --radius-2xl: 1rem;
+ --radius-3xl: 1.5rem;
+ --radius-full: 9999px;
+
+ /* 阴影 */
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
+ --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
+ --shadow-primary: 0 4px 14px 0 rgba(53, 120, 111, 0.39);
+
+ /* 间距 */
+ --spacing-xs: 0.25rem;
+ --spacing-sm: 0.5rem;
+ --spacing-md: 1rem;
+ --spacing-lg: 1.5rem;
+ --spacing-xl: 2rem;
+ --spacing-2xl: 3rem;
+
+ /* 过渡 */
+ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
+ --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
+ --transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+/**
+ * 全局基础样式
+ */
+* {
+ box-sizing: border-box;
+}
+
+html {
+ height: 100%;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
body {
- background: var(--background);
- color: var(--foreground);
- font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ background: var(--background);
+ color: var(--foreground);
+ font-family: var(--font-geist-sans), -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
+ font-size: 1rem;
+ line-height: 1.5;
+ text-rendering: optimizeLegibility;
}
-.code-block {
- font-family: var(--font-geist-mono), monospace;
+/**
+ * 代码块字体
+ */
+.code-block,
+code,
+kbd,
+pre,
+samp {
+ font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Monaco, Consolas, monospace;
}
+/**
+ * 导航栏按钮样式
+ */
.navbar-btn {
- @apply border-0 bg-transparent hover:bg-black/30 shadow-none;
+ @apply border-0 bg-transparent hover:bg-black/30 shadow-none;
+ transition: background-color var(--transition-fast);
+}
+
+/**
+ * 焦点可见性优化
+ */
+:focus-visible {
+ outline: 2px solid var(--border-focus);
+ outline-offset: 2px;
+}
+
+/**
+ * 选择文本样式
+ */
+::selection {
+ background-color: var(--color-primary-200);
+ color: var(--color-primary-900);
+}
+
+/**
+ * 滚动条样式
+ */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--background-secondary);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--foreground-tertiary);
+ border-radius: var(--radius-full);
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--foreground-secondary);
}
diff --git a/src/design-system/README.md b/src/design-system/README.md
new file mode 100644
index 0000000..6d5aa49
--- /dev/null
+++ b/src/design-system/README.md
@@ -0,0 +1,585 @@
+# Design System
+
+完整的设计系统,提供可复用的 UI 组件和设计令牌,确保整个应用的一致性。
+
+## 目录结构
+
+```
+src/design-system/
+├── tokens/ # 设计令牌(颜色、间距、字体等)
+├── lib/ # 工具函数
+├── base/ # 基础组件
+│ ├── button/
+│ ├── input/
+│ ├── textarea/
+│ ├── card/
+│ ├── checkbox/
+│ ├── radio/
+│ ├── switch/
+│ └── select/
+├── feedback/ # 反馈组件
+│ ├── alert/
+│ ├── progress/
+│ ├── skeleton/
+│ └── toast/
+├── overlay/ # 覆盖组件
+│ └── modal/
+├── data-display/ # 数据展示组件
+│ ├── badge/
+│ └── divider/
+├── layout/ # 布局组件
+│ ├── container/
+│ ├── grid/
+│ └── stack/
+├── navigation/ # 导航组件
+│ └── tabs/
+└── index.ts # 统一导出
+```
+
+## 快速开始
+
+### 安装依赖
+
+```bash
+pnpm add class-variance-authority clsx tailwind-merge
+```
+
+### 导入组件
+
+```tsx
+// 方式 1: 从主入口导入(简单但 tree-shaking 较差)
+import { Button, Input, Card } from '@/design-system';
+
+// 方式 2: 从子路径导入(更好的 tree-shaking)
+import { Button } from '@/design-system/base/button';
+import { Input } from '@/design-system/base/input';
+import { Card } from '@/design-system/base/card';
+```
+
+### 使用组件
+
+```tsx
+import { Button, Card } from '@/design-system';
+
+export function MyComponent() {
+ return (
+
+ 标题
+ 内容
+
+
+ );
+}
+```
+
+## 组件列表
+
+### 基础组件
+
+| 组件 | 说明 | 状态 |
+|------|------|------|
+| [Button](#button) | 按钮 | ✅ |
+| [Input](#input) | 输入框 | ✅ |
+| [Textarea](#textarea) | 多行文本输入 | ✅ |
+| [Card](#card) | 卡片容器 | ✅ |
+| [Checkbox](#checkbox) | 复选框 | ✅ |
+| [Radio](#radio) | 单选按钮 | ✅ |
+| [Switch](#switch) | 开关 | ✅ |
+| [Select](#select) | 下拉选择框 | ✅ |
+
+### 反馈组件
+
+| 组件 | 说明 | 状态 |
+|------|------|------|
+| [Alert](#alert) | 警告提示 | ✅ |
+| [Progress](#progress) | 进度条 | ✅ |
+| [Skeleton](#skeleton) | 骨架屏 | ✅ |
+| [Toast](#toast) | 通知提示 | ✅ |
+
+### 覆盖组件
+
+| 组件 | 说明 | 状态 |
+|------|------|------|
+| [Modal](#modal) | 模态框 | ✅ |
+
+### 数据展示组件
+
+| 组件 | 说明 | 状态 |
+|------|------|------|
+| [Badge](#badge) | 徽章 | ✅ |
+| [Divider](#divider) | 分隔线 | ✅ |
+
+### 布局组件
+
+| 组件 | 说明 | 状态 |
+|------|------|------|
+| [Container](#container) | 容器 | ✅ |
+| [Grid](#grid) | 网格布局 | ✅ |
+| [Stack](#stack) | 堆叠布局 | ✅ |
+
+### 导航组件
+
+| 组件 | 说明 | 状态 |
+|------|------|------|
+| [Tabs](#tabs) | 标签页 | ✅ |
+
+## 组件 API
+
+### Button
+
+按钮组件,支持多种变体和尺寸。
+
+```tsx
+import { Button } from '@/design-system';
+
+
+```
+
+**变体 (variant)**: `primary` | `secondary` | `success` | `warning` | `error` | `ghost` | `outline` | `link`
+
+**尺寸 (size)**: `sm` | `md` | `lg`
+
+**快捷组件**: `PrimaryButton`, `SecondaryButton`, `SuccessButton`, `WarningButton`, `ErrorButton`, `GhostButton`, `OutlineButton`, `LinkButton`
+
+### Input
+
+输入框组件。
+
+```tsx
+import { Input } from '@/design-system';
+
+
+```
+
+**变体 (variant)**: `default` | `bordered` | `filled` | `search`
+
+**尺寸 (size)**: `sm` | `md` | `lg`
+
+### Textarea
+
+多行文本输入组件。
+
+```tsx
+import { Textarea } from '@/design-system';
+
+
+```
+
+**变体 (variant)**: `default` | `bordered` | `filled`
+
+### Card
+
+卡片容器组件。
+
+```tsx
+import { Card, CardHeader, CardTitle, CardBody, CardFooter } from '@/design-system';
+
+
+
+ 标题
+
+
+ 内容
+
+
+
+
+
+```
+
+**变体 (variant)**: `default` | `bordered` | `elevated` | `flat`
+
+**内边距 (padding)**: `none` | `xs` | `sm` | `md` | `lg` | `xl`
+
+### Checkbox
+
+复选框组件。
+
+```tsx
+import { Checkbox } from '@/design-system';
+
+
+ 同意条款
+
+```
+
+### Radio
+
+单选按钮组件。
+
+```tsx
+import { Radio, RadioGroup } from '@/design-system';
+
+
+ 选项 1
+ 选项 2
+
+```
+
+### Switch
+
+开关组件。
+
+```tsx
+import { Switch } from '@/design-system';
+
+
+```
+
+### Alert
+
+警告提示组件。
+
+```tsx
+import { Alert } from '@/design-system';
+
+
+ 操作成功完成
+
+```
+
+**变体 (variant)**: `info` | `success` | `warning` | `error`
+
+### Progress
+
+进度条组件。
+
+```tsx
+import { Progress } from '@/design-system';
+
+
+```
+
+### Skeleton
+
+骨架屏组件。
+
+```tsx
+import { Skeleton, TextSkeleton, CardSkeleton } from '@/design-system';
+
+
+
+
+```
+
+### Toast
+
+通知提示组件(基于 sonner)。
+
+```tsx
+import { toast } from '@/design-system';
+
+toast.success("操作成功!");
+toast.error("发生错误");
+toast.promise(promise, {
+ loading: "加载中...",
+ success: "加载成功",
+ error: "加载失败",
+});
+```
+
+### Modal
+
+模态框组件。
+
+```tsx
+import { Modal } from '@/design-system';
+
+ setOpen(false)}>
+
+ 标题
+
+
+ 内容
+
+
+
+
+
+
+```
+
+### Badge
+
+徽章组件。
+
+```tsx
+import { Badge } from '@/design-system';
+
+成功
+
+```
+
+**变体 (variant)**: `default` | `primary` | `success` | `warning` | `error` | `info`
+
+### Divider
+
+分隔线组件。
+
+```tsx
+import { Divider } from '@/design-system';
+
+
+或者
+
+```
+
+### Container
+
+容器组件。
+
+```tsx
+import { Container } from '@/design-system';
+
+
+ 内容
+
+```
+
+### Grid
+
+网格布局组件。
+
+```tsx
+import { Grid } from '@/design-system';
+
+
+ 项目 1
+ 项目 2
+ 项目 3
+
+```
+
+### Stack
+
+堆叠布局组件。
+
+```tsx
+import { Stack, VStack, HStack } from '@/design-system';
+
+
+ 项目 1
+ 项目 2
+
+```
+
+### Tabs
+
+标签页组件。
+
+```tsx
+import { Tabs } from '@/design-system';
+
+
+
+ 标签 1
+ 标签 2
+
+
+ 内容 1
+
+
+ 内容 2
+
+
+```
+
+## 设计令牌
+
+### 颜色
+
+```tsx
+import { colors } from '@/design-system/tokens';
+
+// 主色
+colors.primary.500 // #35786f
+
+// 语义色
+colors.success.500 // #22c55e
+colors.warning.500 // #f59e0b
+colors.error.500 // #ef4444
+colors.info.500 // #3b82f6
+```
+
+在组件中使用:
+
+```tsx
+
主色背景
+成功文本
+```
+
+### 间距
+
+基于 8pt 网格系统:
+
+```tsx
+ // 16px
+
// 24px
+
// 32px
+```
+
+### 字体
+
+```tsx
+
小文本
+
正常文本
+
大文本
+
半粗体
+
粗体
+```
+
+### 圆角
+
+```tsx
+
// 8px
+
// 12px
+
// 16px
+```
+
+### 阴影
+
+```tsx
+
// 小阴影
+
// 中阴影
+
// 大阴影
+
// 超大阴影
+```
+
+## 工具函数
+
+### cn
+
+合并 Tailwind CSS 类名的工具函数。
+
+```tsx
+import { cn } from '@/design-system';
+
+const className = cn(
+ 'base-class',
+ isActive && 'active-class',
+ 'another-class'
+);
+```
+
+## 最佳实践
+
+### 1. 组件导入
+
+对于更好的 tree-shaking,建议从子路径导入:
+
+```tsx
+// ✅ 推荐
+import { Button } from '@/design-system/base/button';
+
+// ❌ 不推荐(但也可以)
+import { Button } from '@/design-system';
+```
+
+### 2. 样式覆盖
+
+使用 `className` 属性覆盖样式:
+
+```tsx
+
+```
+
+### 3. 组合组件
+
+利用组件组合来构建复杂 UI:
+
+```tsx
+
+
+ 标题
+
+
+
+
+
+
+
+
+```
+
+### 4. 可访问性
+
+所有组件都内置了可访问性支持:
+
+- 正确的 ARIA 属性
+- 键盘导航支持
+- 焦点管理
+- 屏幕阅读器友好
+
+## 迁移指南
+
+### 从旧组件迁移
+
+旧的组件路径:
+
+```tsx
+import { Button } from '@/components/ui/Button';
+import { Input } from '@/components/ui/Input';
+```
+
+新的组件路径:
+
+```tsx
+import { Button } from '@/design-system/base/button';
+import { Input } from '@/design-system/base/input';
+```
+
+### API 变化
+
+大部分 API 保持兼容,但有以下变化:
+
+1. **颜色不再使用硬编码值**
+ ```tsx
+ // 旧
+ style={{ backgroundColor: '#35786f' }}
+
+ // 新
+ className="bg-primary-500"
+ ```
+
+2. **变体命名更加一致**
+ ```tsx
+ // 旧
+
+
+ // 新
+
+ ```
+
+3. **新增语义色变体**
+ ```tsx
+
+
+
+ ```
+
+## 贡献
+
+添加新组件时,请遵循以下规范:
+
+1. 在对应的目录下创建组件
+2. 使用 `cva` 定义变体样式
+3. 使用 `forwardRef` 支持 ref 转发
+4. 添加完整的 TypeScript 类型
+5. 编写详细的 JSDoc 注释和示例
+6. 在导出文件中添加导出
+
+## 许可证
+
+AGPL-3.0-only
diff --git a/src/design-system/base/button/button.tsx b/src/design-system/base/button/button.tsx
new file mode 100644
index 0000000..2f8df5e
--- /dev/null
+++ b/src/design-system/base/button/button.tsx
@@ -0,0 +1,273 @@
+"use client";
+
+import React from "react";
+import Link from "next/link";
+import Image from "next/image";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Button 组件
+ *
+ * Design System 中的按钮组件,支持多种变体、尺寸和状态。
+ * 自动处理 Link/button 切换,支持图标和加载状态。
+ *
+ * @example
+ * ```tsx
+ * // Primary 按钮
+ *
+ *
+ * // 带图标的按钮
+ *
}>
+ * 带图标
+ *
+ *
+ * // 作为链接使用
+ *
+ *
+ * // 加载状态
+ *
+ * ```
+ */
+
+/**
+ * 按钮变体样式
+ */
+const buttonVariants = cva(
+ // 基础样式
+ "inline-flex items-center justify-center gap-2 rounded-xl font-semibold shadow transition-all duration-250 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ primary: "bg-primary-500 text-white hover:bg-primary-600 shadow-md",
+ secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 shadow-sm",
+ success: "bg-success-500 text-white hover:bg-success-600 shadow-md",
+ warning: "bg-warning-500 text-white hover:bg-warning-600 shadow-md",
+ error: "bg-error-500 text-white hover:bg-error-600 shadow-md",
+ ghost: "bg-transparent text-gray-700 hover:bg-gray-100 shadow-none",
+ outline: "border-2 border-gray-300 text-gray-700 hover:bg-gray-50 shadow-none",
+ link: "text-primary-500 hover:text-primary-600 hover:underline shadow-none px-0",
+ },
+ size: {
+ sm: "h-8 px-3 text-sm",
+ md: "h-10 px-4 text-base",
+ lg: "h-12 px-6 text-lg",
+ },
+ fullWidth: {
+ true: "w-full",
+ false: "",
+ },
+ },
+ compoundVariants: [
+ // 链接变体不应用高度和圆角
+ {
+ variant: "link",
+ size: "sm",
+ className: "h-auto px-0",
+ },
+ {
+ variant: "link",
+ size: "md",
+ className: "h-auto px-0",
+ },
+ {
+ variant: "link",
+ size: "lg",
+ className: "h-auto px-0",
+ },
+ ],
+ defaultVariants: {
+ variant: "secondary",
+ size: "md",
+ fullWidth: false,
+ },
+ }
+);
+
+export type ButtonVariant = VariantProps
["variant"];
+export type ButtonSize = VariantProps["size"];
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ // 内容
+ children?: React.ReactNode;
+
+ // 导航
+ href?: string;
+ openInNewTab?: boolean;
+
+ // 图标
+ leftIcon?: React.ReactNode;
+ rightIcon?: React.ReactNode;
+ iconSrc?: string; // For Next.js Image icons
+ iconAlt?: string;
+
+ // 状态
+ loading?: boolean;
+ selected?: boolean;
+
+ // 样式
+ className?: string;
+}
+
+/**
+ * Button 组件
+ */
+export function Button({
+ variant = "secondary",
+ size = "md",
+ fullWidth = false,
+ href,
+ openInNewTab = false,
+ iconSrc,
+ iconAlt,
+ leftIcon,
+ rightIcon,
+ children,
+ className,
+ loading = false,
+ selected = false,
+ disabled,
+ type = "button",
+ ...props
+}: ButtonProps) {
+ // 计算样式
+ const computedClass = cn(
+ buttonVariants({ variant, size, fullWidth }),
+ selected && variant === "secondary" && "bg-gray-200",
+ className
+ );
+
+ // 图标尺寸映射
+ const iconSize = { sm: 14, md: 16, lg: 20 }[size];
+
+ // 渲染 SVG 图标
+ const renderSvgIcon = (icon: React.ReactNode, position: "left" | "right") => {
+ if (!icon) return null;
+ return (
+
+ {icon}
+
+ );
+ };
+
+ // 渲染 Next.js Image 图标
+ const renderImageIcon = () => {
+ if (!iconSrc) return null;
+ return (
+
+ );
+ };
+
+ // 渲染加载图标
+ const renderLoadingIcon = () => {
+ if (!loading) return null;
+ return (
+
+ );
+ };
+
+ // 组装内容
+ const content = (
+ <>
+ {loading && renderLoadingIcon()}
+ {renderImageIcon()}
+ {renderSvgIcon(leftIcon, "left")}
+ {children}
+ {renderSvgIcon(rightIcon, "right")}
+ >
+ );
+
+ // 如果提供了 href,渲染为 Link
+ if (href) {
+ return (
+
+ {content}
+
+ );
+ }
+
+ // 否则渲染为 button
+ return (
+
+ );
+}
+
+/**
+ * 预定义的按钮快捷组件
+ */
+export const PrimaryButton = (props: Omit) => (
+
+);
+
+export const SecondaryButton = (props: Omit) => (
+
+);
+
+export const SuccessButton = (props: Omit) => (
+
+);
+
+export const WarningButton = (props: Omit) => (
+
+);
+
+export const ErrorButton = (props: Omit) => (
+
+);
+
+export const GhostButton = (props: Omit) => (
+
+);
+
+export const OutlineButton = (props: Omit) => (
+
+);
+
+export const LinkButton = (props: Omit) => (
+
+);
diff --git a/src/design-system/base/button/index.ts b/src/design-system/base/button/index.ts
new file mode 100644
index 0000000..eaf5eea
--- /dev/null
+++ b/src/design-system/base/button/index.ts
@@ -0,0 +1 @@
+export * from './button';
diff --git a/src/design-system/base/card/card.tsx b/src/design-system/base/card/card.tsx
new file mode 100644
index 0000000..efb4080
--- /dev/null
+++ b/src/design-system/base/card/card.tsx
@@ -0,0 +1,198 @@
+import React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Card 卡片组件
+ *
+ * Design System 中的卡片容器组件,提供统一的内容包装样式。
+ *
+ * @example
+ * ```tsx
+ * // 默认卡片
+ *
+ * 卡片内容
+ *
+ *
+ * // 带边框的卡片
+ *
+ * 带边框的内容
+ *
+ *
+ * // 无内边距卡片
+ *
+ *
+ *
+ *
+ * // 可点击的卡片
+ *
+ * 点击我
+ *
+ * ```
+ */
+
+/**
+ * 卡片变体样式
+ */
+const cardVariants = cva(
+ // 基础样式
+ "rounded-2xl bg-white transition-all duration-250",
+ {
+ variants: {
+ variant: {
+ default: "shadow-xl",
+ bordered: "border-2 border-gray-200 shadow-sm",
+ elevated: "shadow-2xl",
+ flat: "border border-gray-200 shadow-none",
+ },
+ padding: {
+ none: "",
+ xs: "p-3",
+ sm: "p-4",
+ md: "p-6",
+ lg: "p-8",
+ xl: "p-10",
+ },
+ clickable: {
+ true: "cursor-pointer hover:shadow-primary/25 hover:-translate-y-0.5 active:translate-y-0",
+ false: "",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ padding: "md",
+ clickable: false,
+ },
+ }
+);
+
+export type CardVariant = VariantProps["variant"];
+export type CardPadding = VariantProps["padding"];
+
+export interface CardProps
+ extends React.HTMLAttributes,
+ VariantProps {
+ // 子元素
+ children: React.ReactNode;
+}
+
+/**
+ * Card 卡片组件
+ */
+export function Card({
+ variant = "default",
+ padding = "md",
+ clickable = false,
+ className,
+ children,
+ ...props
+}: CardProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+/**
+ * CardSection - 卡片内容区块
+ * 用于组织卡片内部的多个内容区块
+ */
+export interface CardSectionProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+ noPadding?: boolean;
+}
+
+export function CardSection({
+ noPadding = false,
+ className,
+ children,
+ ...props
+}: CardSectionProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+/**
+ * CardHeader - 卡片头部
+ */
+export interface CardHeaderProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+}
+
+export function CardHeader({
+ className,
+ children,
+ ...props
+}: CardHeaderProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+/**
+ * CardTitle - 卡片标题
+ */
+export interface CardTitleProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+}
+
+export function CardTitle({
+ className,
+ children,
+ ...props
+}: CardTitleProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+/**
+ * CardBody - 卡片主体
+ */
+export const CardBody = CardSection;
+
+/**
+ * CardFooter - 卡片底部
+ */
+export interface CardFooterProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+}
+
+export function CardFooter({
+ className,
+ children,
+ ...props
+}: CardFooterProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/design-system/base/card/index.ts b/src/design-system/base/card/index.ts
new file mode 100644
index 0000000..cb5809f
--- /dev/null
+++ b/src/design-system/base/card/index.ts
@@ -0,0 +1 @@
+export * from './card';
diff --git a/src/design-system/base/checkbox/checkbox.tsx b/src/design-system/base/checkbox/checkbox.tsx
new file mode 100644
index 0000000..a9472ec
--- /dev/null
+++ b/src/design-system/base/checkbox/checkbox.tsx
@@ -0,0 +1,170 @@
+"use client";
+
+import React, { forwardRef } from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Checkbox 复选框组件
+ *
+ * Design System 中的复选框组件,支持多种状态和尺寸。
+ *
+ * @example
+ * ```tsx
+ * // 默认复选框
+ * 同意条款
+ *
+ * // 受控组件
+ *
+ * 同意条款
+ *
+ *
+ * // 错误状态
+ * 必选项
+ * ```
+ */
+
+/**
+ * 复选框变体样式
+ */
+const checkboxVariants = cva(
+ // 基础样式
+ "peer h-4 w-4 shrink-0 rounded border-2 transition-all duration-250 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default: "border-gray-300 checked:bg-primary-500 checked:border-primary-500",
+ success: "border-gray-300 checked:bg-success-500 checked:border-success-500",
+ warning: "border-gray-300 checked:bg-warning-500 checked:border-warning-500",
+ error: "border-gray-300 checked:bg-error-500 checked:border-error-500",
+ },
+ size: {
+ sm: "h-3.5 w-3.5",
+ md: "h-4 w-4",
+ lg: "h-5 w-5",
+ },
+ error: {
+ true: "border-error-500",
+ false: "",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "md",
+ error: false,
+ },
+ }
+);
+
+export type CheckboxVariant = VariantProps["variant"];
+export type CheckboxSize = VariantProps["size"];
+
+export interface CheckboxProps
+ extends Omit, "size">,
+ VariantProps {
+ // 标签文本
+ label?: React.ReactNode;
+ // 标签位置
+ labelPosition?: "left" | "right";
+ // 自定义复选框类名
+ checkboxClassName?: string;
+}
+
+/**
+ * Checkbox 复选框组件
+ */
+export const Checkbox = forwardRef(
+ (
+ {
+ variant = "default",
+ size = "md",
+ error = false,
+ label,
+ labelPosition = "right",
+ className,
+ checkboxClassName,
+ disabled,
+ ...props
+ },
+ ref
+ ) => {
+ const checkboxId = React.useId();
+
+ const renderCheckbox = () => (
+
+ );
+
+ const renderLabel = () => {
+ if (!label) return null;
+
+ return (
+
+ );
+ };
+
+ if (!label) {
+ return renderCheckbox();
+ }
+
+ return (
+
+ {labelPosition === "left" && renderLabel()}
+ {renderCheckbox()}
+ {labelPosition === "right" && renderLabel()}
+
+ );
+ }
+);
+
+Checkbox.displayName = "Checkbox";
+
+/**
+ * CheckboxGroup - 复选框组
+ */
+export interface CheckboxGroupProps {
+ children: React.ReactNode;
+ label?: string;
+ error?: string;
+ required?: boolean;
+ className?: string;
+}
+
+export function CheckboxGroup({
+ children,
+ label,
+ error,
+ required,
+ className,
+}: CheckboxGroupProps) {
+ return (
+
+ {label && (
+
+ {label}
+ {required && *}
+
+ )}
+
{children}
+ {error &&
{error}
}
+
+ );
+}
diff --git a/src/design-system/base/checkbox/index.ts b/src/design-system/base/checkbox/index.ts
new file mode 100644
index 0000000..8d78b3e
--- /dev/null
+++ b/src/design-system/base/checkbox/index.ts
@@ -0,0 +1 @@
+export * from './checkbox';
diff --git a/src/design-system/base/input/index.ts b/src/design-system/base/input/index.ts
new file mode 100644
index 0000000..e3365cb
--- /dev/null
+++ b/src/design-system/base/input/index.ts
@@ -0,0 +1 @@
+export * from './input';
diff --git a/src/design-system/base/input/input.tsx b/src/design-system/base/input/input.tsx
new file mode 100644
index 0000000..35e1c09
--- /dev/null
+++ b/src/design-system/base/input/input.tsx
@@ -0,0 +1,151 @@
+"use client";
+
+import React, { forwardRef } from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Input 输入框组件
+ *
+ * Design System 中的输入框组件,支持多种样式变体和尺寸。
+ * 完全可访问,支持焦点状态和错误状态。
+ *
+ * @example
+ * ```tsx
+ * // 默认样式
+ *
+ *
+ * // 带边框样式
+ *
+ *
+ * // 填充样式
+ *
+ *
+ * // 错误状态
+ *
+ *
+ * // 禁用状态
+ *
+ * ```
+ */
+
+/**
+ * 输入框变体样式
+ */
+const inputVariants = cva(
+ // 基础样式
+ "flex w-full rounded-xl border px-3 py-2 text-base transition-all duration-250 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default: "border-b-2 border-gray-300 bg-transparent rounded-t-xl",
+ bordered: "border-gray-300 bg-white",
+ filled: "border-transparent bg-gray-100",
+ search: "border-gray-200 bg-white pl-10 rounded-full",
+ },
+ size: {
+ sm: "h-9 px-3 text-sm",
+ md: "h-10 px-4 text-base",
+ lg: "h-12 px-5 text-lg",
+ },
+ error: {
+ true: "border-error-500 focus-visible:ring-error-500",
+ false: "",
+ },
+ },
+ compoundVariants: [
+ // 填充变体的错误状态
+ {
+ variant: "filled",
+ error: true,
+ className: "bg-error-50",
+ },
+ ],
+ defaultVariants: {
+ variant: "default",
+ size: "md",
+ error: false,
+ },
+ }
+);
+
+export type InputVariant = VariantProps["variant"];
+export type InputSize = VariantProps["size"];
+
+export interface InputProps
+ extends Omit, "size">,
+ VariantProps {
+ // 左侧图标(通常用于搜索框)
+ leftIcon?: React.ReactNode;
+ // 右侧图标(例如清除按钮)
+ rightIcon?: React.ReactNode;
+ // 容器类名(用于包裹图标和输入框)
+ containerClassName?: string;
+}
+
+/**
+ * Input 输入框组件
+ */
+export const Input = forwardRef(
+ (
+ {
+ variant = "default",
+ size = "md",
+ error = false,
+ className,
+ containerClassName,
+ leftIcon,
+ rightIcon,
+ type = "text",
+ ...props
+ },
+ ref
+ ) => {
+ // 如果有左侧图标,使用相对定位的容器
+ if (leftIcon) {
+ return (
+
+ {/* 左侧图标 */}
+
+ {leftIcon}
+
+ {/* 输入框 */}
+
+ {/* 右侧图标 */}
+ {rightIcon && (
+
+ {rightIcon}
+
+ )}
+
+ );
+ }
+
+ // 普通输入框
+ return (
+
+
+ {rightIcon && (
+
+ {rightIcon}
+
+ )}
+
+ );
+ }
+);
+
+Input.displayName = "Input";
diff --git a/src/design-system/base/radio/index.ts b/src/design-system/base/radio/index.ts
new file mode 100644
index 0000000..1140e08
--- /dev/null
+++ b/src/design-system/base/radio/index.ts
@@ -0,0 +1 @@
+export * from './radio';
diff --git a/src/design-system/base/radio/radio.tsx b/src/design-system/base/radio/radio.tsx
new file mode 100644
index 0000000..70cd076
--- /dev/null
+++ b/src/design-system/base/radio/radio.tsx
@@ -0,0 +1,219 @@
+"use client";
+
+import React, { forwardRef } from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Radio 单选按钮组件
+ *
+ * Design System 中的单选按钮组件,支持多种状态和尺寸。
+ *
+ * @example
+ * ```tsx
+ * // 默认单选按钮
+ * 选项 1
+ * 选项 2
+ *
+ * // 受控组件
+ * setValue(e.target.value)}
+ * >
+ * 选项 1
+ *
+ * ```
+ */
+
+/**
+ * 单选按钮变体样式
+ */
+const radioVariants = cva(
+ // 基础样式
+ "peer h-4 w-4 shrink-0 rounded-full border-2 transition-all duration-250 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 appearance-none cursor-pointer",
+ {
+ variants: {
+ variant: {
+ default: "border-gray-300 checked:border-primary-500",
+ success: "border-gray-300 checked:border-success-500",
+ warning: "border-gray-300 checked:border-warning-500",
+ error: "border-gray-300 checked:border-error-500",
+ },
+ size: {
+ sm: "h-3.5 w-3.5",
+ md: "h-4 w-4",
+ lg: "h-5 w-5",
+ },
+ error: {
+ true: "border-error-500",
+ false: "",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "md",
+ error: false,
+ },
+ }
+);
+
+export type RadioVariant = VariantProps["variant"];
+export type RadioSize = VariantProps["size"];
+
+export interface RadioProps
+ extends Omit, "size">,
+ VariantProps {
+ // 标签文本
+ label?: React.ReactNode;
+ // 标签位置
+ labelPosition?: "left" | "right";
+ // 自定义单选按钮类名
+ radioClassName?: string;
+}
+
+/**
+ * Radio 单选按钮组件
+ */
+export const Radio = forwardRef(
+ (
+ {
+ variant = "default",
+ size = "md",
+ error = false,
+ label,
+ labelPosition = "right",
+ className,
+ radioClassName,
+ disabled,
+ ...props
+ },
+ ref
+ ) => {
+ const radioId = React.useId();
+
+ const renderRadio = () => (
+
+ );
+
+ const renderLabel = () => {
+ if (!label) return null;
+
+ return (
+
+ );
+ };
+
+ if (!label) {
+ return renderRadio();
+ }
+
+ return (
+
+ {labelPosition === "left" && renderLabel()}
+ {renderRadio()}
+ {labelPosition === "right" && renderLabel()}
+
+ );
+ }
+);
+
+Radio.displayName = "Radio";
+
+/**
+ * RadioGroup - 单选按钮组
+ */
+export interface RadioGroupProps {
+ children: React.ReactNode;
+ name: string;
+ label?: string;
+ error?: string;
+ required?: boolean;
+ value?: string;
+ onChange?: (value: string) => void;
+ className?: string;
+ orientation?: "vertical" | "horizontal";
+}
+
+export function RadioGroup({
+ children,
+ name,
+ label,
+ error,
+ required,
+ value,
+ onChange,
+ className,
+ orientation = "vertical",
+}: RadioGroupProps) {
+ // 为每个 Radio 注入 name 和 onChange
+ const enhancedChildren = React.Children.map(children, (child) => {
+ if (React.isValidElement(child)) {
+ return React.cloneElement(child, {
+ name,
+ checked: value !== undefined ? child.props.value === value : undefined,
+ onChange: (e: React.ChangeEvent) => {
+ onChange?.(e.target.value);
+ child.props.onChange?.(e);
+ },
+ });
+ }
+ return child;
+ });
+
+ return (
+
+ {label && (
+
+ {label}
+ {required && *}
+
+ )}
+
+ {enhancedChildren}
+
+ {error &&
{error}
}
+
+ );
+}
diff --git a/src/design-system/base/select/index.ts b/src/design-system/base/select/index.ts
new file mode 100644
index 0000000..c739673
--- /dev/null
+++ b/src/design-system/base/select/index.ts
@@ -0,0 +1 @@
+export * from './select';
diff --git a/src/design-system/base/select/select.tsx b/src/design-system/base/select/select.tsx
new file mode 100644
index 0000000..7adc38d
--- /dev/null
+++ b/src/design-system/base/select/select.tsx
@@ -0,0 +1,112 @@
+"use client";
+
+import React, { forwardRef } from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Select 下拉选择框组件
+ *
+ * Design System 中的下拉选择框组件。
+ *
+ * @example
+ * ```tsx
+ *
+ * ```
+ */
+
+/**
+ * Select 变体样式
+ */
+const selectVariants = cva(
+ // 基础样式
+ "flex w-full appearance-none items-center justify-between rounded-xl border px-3 py-2 pr-8 text-base transition-all duration-250 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default: "border-b-2 border-gray-300 bg-transparent rounded-t-xl",
+ bordered: "border-gray-300 bg-white",
+ filled: "border-transparent bg-gray-100",
+ },
+ size: {
+ sm: "h-9 px-3 text-sm",
+ md: "h-10 px-4 text-base",
+ lg: "h-12 px-5 text-lg",
+ },
+ error: {
+ true: "border-error-500 focus-visible:ring-error-500",
+ false: "",
+ },
+ },
+ compoundVariants: [
+ {
+ variant: "filled",
+ error: true,
+ className: "bg-error-50",
+ },
+ ],
+ defaultVariants: {
+ variant: "default",
+ size: "md",
+ error: false,
+ },
+ }
+);
+
+export type SelectVariant = VariantProps["variant"];
+export type SelectSize = VariantProps["size"];
+
+export interface SelectProps
+ extends Omit, "size">,
+ VariantProps {}
+
+/**
+ * Select 下拉选择框组件
+ */
+export const Select = forwardRef(
+ (
+ {
+ variant = "default",
+ size = "md",
+ error = false,
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ return (
+
+
+ {/* 下拉箭头图标 */}
+
+
+ );
+ }
+);
+
+Select.displayName = "Select";
diff --git a/src/design-system/base/switch/index.ts b/src/design-system/base/switch/index.ts
new file mode 100644
index 0000000..4dd2256
--- /dev/null
+++ b/src/design-system/base/switch/index.ts
@@ -0,0 +1 @@
+export * from './switch';
diff --git a/src/design-system/base/switch/switch.tsx b/src/design-system/base/switch/switch.tsx
new file mode 100644
index 0000000..43a9c64
--- /dev/null
+++ b/src/design-system/base/switch/switch.tsx
@@ -0,0 +1,179 @@
+"use client";
+
+import React, { forwardRef, useState } from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Switch 开关组件
+ *
+ * Design System 中的开关组件,用于二进制状态切换。
+ *
+ * @example
+ * ```tsx
+ * // 默认开关
+ *
+ *
+ * // 带标签
+ *
+ *
+ * // 不同尺寸
+ *
+ *
+ * ```
+ */
+
+/**
+ * 开关变体样式
+ */
+const switchVariants = cva(
+ // 基础样式
+ "peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 transition-all duration-250 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 appearance-none",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-gray-300 bg-gray-100 checked:border-primary-500 checked:bg-primary-500",
+ success:
+ "border-gray-300 bg-gray-100 checked:border-success-500 checked:bg-success-500",
+ warning:
+ "border-gray-300 bg-gray-100 checked:border-warning-500 checked:bg-warning-500",
+ error:
+ "border-gray-300 bg-gray-100 checked:border-error-500 checked:bg-error-500",
+ },
+ size: {
+ sm: "h-5 w-9",
+ md: "h-6 w-11",
+ lg: "h-7 w-13",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "md",
+ },
+ }
+);
+
+export type SwitchVariant = VariantProps["variant"];
+export type SwitchSize = VariantProps["size"];
+
+export interface SwitchProps
+ extends Omit, "size">,
+ VariantProps {
+ // 标签文本
+ label?: React.ReactNode;
+ // 标签位置
+ labelPosition?: "left" | "right";
+ // 自定义开关类名
+ switchClassName?: string;
+}
+
+/**
+ * Switch 开关组件
+ */
+export const Switch = forwardRef(
+ (
+ {
+ variant = "default",
+ size = "md",
+ label,
+ labelPosition = "right",
+ className,
+ switchClassName,
+ disabled,
+ checked,
+ defaultChecked,
+ onChange,
+ ...props
+ },
+ ref
+ ) => {
+ const switchId = React.useId();
+ const [internalChecked, setInternalChecked] = useState(
+ checked ?? defaultChecked ?? false
+ );
+
+ // 处理受控和非受控模式
+ const isControlled = checked !== undefined;
+ const isChecked = isControlled ? checked : internalChecked;
+
+ const handleChange = (e: React.ChangeEvent) => {
+ if (!isControlled) {
+ setInternalChecked(e.target.checked);
+ }
+ onChange?.(e);
+ };
+
+ // 滑块大小
+ const thumbSize = {
+ sm: "h-3.5 w-3.5",
+ md: "h-4 w-4",
+ lg: "h-5 w-5",
+ }[size];
+
+ // 滑块位移
+ const thumbTranslate = {
+ sm: isChecked ? "translate-x-4" : "translate-x-0.5",
+ md: isChecked ? "translate-x-5" : "translate-x-0.5",
+ lg: isChecked ? "translate-x-6" : "translate-x-0.5",
+ }[size];
+
+ const renderSwitch = () => (
+
+ );
+
+ const renderLabel = () => {
+ if (!label) return null;
+
+ return (
+
+ );
+ };
+
+ if (!label) {
+ return renderSwitch();
+ }
+
+ return (
+
+ {labelPosition === "left" && renderLabel()}
+ {renderSwitch()}
+ {labelPosition === "right" && renderLabel()}
+
+ );
+ }
+);
+
+Switch.displayName = "Switch";
diff --git a/src/design-system/base/textarea/index.ts b/src/design-system/base/textarea/index.ts
new file mode 100644
index 0000000..ac29789
--- /dev/null
+++ b/src/design-system/base/textarea/index.ts
@@ -0,0 +1 @@
+export * from './textarea';
diff --git a/src/design-system/base/textarea/textarea.tsx b/src/design-system/base/textarea/textarea.tsx
new file mode 100644
index 0000000..4da01bd
--- /dev/null
+++ b/src/design-system/base/textarea/textarea.tsx
@@ -0,0 +1,104 @@
+"use client";
+
+import React, { forwardRef } from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Textarea 多行文本输入组件
+ *
+ * Design System 中的多行文本输入组件,支持多种样式变体。
+ *
+ * @example
+ * ```tsx
+ * // 默认样式
+ *
+ *
+ * // 带边框样式
+ *
+ *
+ * // 填充样式
+ *
+ * ```
+ */
+
+/**
+ * Textarea 变体样式
+ */
+const textareaVariants = cva(
+ // 基础样式
+ "flex w-full rounded-xl border px-3 py-2 text-base transition-all duration-250 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none",
+ {
+ variants: {
+ variant: {
+ default: "border-b-2 border-gray-300 bg-transparent rounded-t-xl",
+ bordered: "border-gray-300 bg-white",
+ filled: "border-transparent bg-gray-100",
+ },
+ error: {
+ true: "border-error-500 focus-visible:ring-error-500",
+ false: "",
+ },
+ },
+ compoundVariants: [
+ {
+ variant: "filled",
+ error: true,
+ className: "bg-error-50",
+ },
+ ],
+ defaultVariants: {
+ variant: "default",
+ error: false,
+ },
+ }
+);
+
+export type TextareaVariant = VariantProps["variant"];
+
+export interface TextareaProps
+ extends React.TextareaHTMLAttributes,
+ VariantProps {
+ // 自动调整高度
+ autoResize?: boolean;
+}
+
+/**
+ * Textarea 多行文本输入组件
+ */
+export const Textarea = forwardRef(
+ (
+ {
+ variant = "default",
+ error = false,
+ className,
+ autoResize = false,
+ onChange,
+ rows = 3,
+ ...props
+ },
+ ref
+ ) => {
+ // 自动调整高度的 change 处理
+ const handleChange = (e: React.ChangeEvent) => {
+ if (autoResize) {
+ const target = e.target;
+ target.style.height = "auto";
+ target.style.height = `${target.scrollHeight}px`;
+ }
+ onChange?.(e);
+ };
+
+ return (
+
+ );
+ }
+);
+
+Textarea.displayName = "Textarea";
diff --git a/src/design-system/data-display/badge/badge.tsx b/src/design-system/data-display/badge/badge.tsx
new file mode 100644
index 0000000..5a396ad
--- /dev/null
+++ b/src/design-system/data-display/badge/badge.tsx
@@ -0,0 +1,157 @@
+"use client";
+
+import React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Badge 徽章组件
+ *
+ * Design System 中的徽章组件,用于显示状态、标签等信息。
+ *
+ * @example
+ * ```tsx
+ * // 默认徽章
+ * 新
+ *
+ * // 不同变体
+ * 成功
+ * 警告
+ * 错误
+ *
+ * // 不同尺寸
+ * 小
+ * 大
+ *
+ * // 圆形徽章
+ *
+ * ```
+ */
+
+/**
+ * Badge 变体样式
+ */
+const badgeVariants = cva(
+ // 基础样式
+ "inline-flex items-center justify-center rounded-full font-medium transition-colors duration-250",
+ {
+ variants: {
+ variant: {
+ default: "bg-gray-100 text-gray-800",
+ primary: "bg-primary-100 text-primary-800",
+ success: "bg-success-100 text-success-800",
+ warning: "bg-warning-100 text-warning-800",
+ error: "bg-error-100 text-error-800",
+ info: "bg-info-100 text-info-800",
+ },
+ size: {
+ sm: "px-2 py-0.5 text-xs",
+ md: "px-2.5 py-1 text-sm",
+ lg: "px-3 py-1.5 text-base",
+ },
+ dot: {
+ true: "px-2 py-1",
+ false: "",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "md",
+ dot: false,
+ },
+ }
+);
+
+export type BadgeVariant = VariantProps["variant"];
+export type BadgeSize = VariantProps["size"];
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {
+ // 子元素
+ children?: React.ReactNode;
+ // 是否为圆点样式(不显示文字)
+ dot?: boolean;
+ // 圆点颜色(仅当 dot=true 时有效)
+ dotColor?: string;
+}
+
+/**
+ * Badge 徽章组件
+ */
+export function Badge({
+ variant = "default",
+ size = "md",
+ dot = false,
+ dotColor,
+ className,
+ children,
+ ...props
+}: BadgeProps) {
+ // 圆点颜色映射
+ const dotColors = {
+ default: "bg-gray-400",
+ primary: "bg-primary-500",
+ success: "bg-success-500",
+ warning: "bg-warning-500",
+ error: "bg-error-500",
+ info: "bg-info-500",
+ };
+
+ return (
+
+ {dot && (
+
+ )}
+ {!dot && children}
+
+ );
+}
+
+/**
+ * StatusBadge - 状态徽章
+ */
+export interface StatusBadgeProps extends Omit {
+ status: "online" | "offline" | "busy" | "away";
+ label?: string;
+}
+
+export function StatusBadge({ status, label, ...props }: StatusBadgeProps) {
+ const statusConfig = {
+ online: { variant: "success" as const, defaultLabel: "在线" },
+ offline: { variant: "default" as const, defaultLabel: "离线" },
+ busy: { variant: "error" as const, defaultLabel: "忙碌" },
+ away: { variant: "warning" as const, defaultLabel: "离开" },
+ };
+
+ const config = statusConfig[status];
+
+ return (
+
+ {label || config.defaultLabel}
+
+ );
+}
+
+/**
+ * CounterBadge - 计数徽章
+ */
+export interface CounterBadgeProps extends Omit {
+ count: number;
+ max?: number;
+}
+
+export function CounterBadge({ count, max = 99, ...props }: CounterBadgeProps) {
+ const displayCount = count > max ? `${max}+` : count;
+
+ return (
+
+ {displayCount}
+
+ );
+}
diff --git a/src/design-system/data-display/badge/index.ts b/src/design-system/data-display/badge/index.ts
new file mode 100644
index 0000000..1566eee
--- /dev/null
+++ b/src/design-system/data-display/badge/index.ts
@@ -0,0 +1 @@
+export * from './badge';
diff --git a/src/design-system/data-display/divider/divider.tsx b/src/design-system/data-display/divider/divider.tsx
new file mode 100644
index 0000000..b3a7995
--- /dev/null
+++ b/src/design-system/data-display/divider/divider.tsx
@@ -0,0 +1,102 @@
+"use client";
+
+import React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Divider 分隔线组件
+ *
+ * Design System 中的分隔线组件,用于分隔内容区域。
+ *
+ * @example
+ * ```tsx
+ * // 水平分隔线
+ *
+ *
+ * // 带文字的分隔线
+ * 或者
+ *
+ * // 垂直分隔线
+ *
+ *
+ * // 不同样式
+ *
+ *
+ * ```
+ */
+
+/**
+ * Divider 变体样式
+ */
+const dividerVariants = cva(
+ // 基础样式
+ "border-gray-300",
+ {
+ variants: {
+ variant: {
+ solid: "border-solid",
+ dashed: "border-dashed",
+ dotted: "border-dotted",
+ },
+ orientation: {
+ horizontal: "w-full border-t",
+ vertical: "h-full border-l",
+ },
+ },
+ defaultVariants: {
+ variant: "solid",
+ orientation: "horizontal",
+ },
+ }
+);
+
+export type DividerVariant = VariantProps["variant"];
+export type DividerOrientation = VariantProps["orientation"];
+
+export interface DividerProps
+ extends React.HTMLAttributes,
+ VariantProps {
+ // 子元素(用于带文字的分隔线)
+ children?: React.ReactNode;
+ // 文字位置(仅水平分隔线有效)
+ labelPosition?: "center" | "left" | "right";
+}
+
+/**
+ * Divider 分隔线组件
+ */
+export function Divider({
+ variant = "solid",
+ orientation = "horizontal",
+ labelPosition = "center",
+ children,
+ className,
+ ...props
+}: DividerProps) {
+ // 带文字的水平分隔线
+ if (children && orientation === "horizontal") {
+ const labelAlignment = {
+ left: "justify-start",
+ center: "justify-center",
+ right: "justify-end",
+ }[labelPosition];
+
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/design-system/data-display/divider/index.ts b/src/design-system/data-display/divider/index.ts
new file mode 100644
index 0000000..bf4ed01
--- /dev/null
+++ b/src/design-system/data-display/divider/index.ts
@@ -0,0 +1 @@
+export * from './divider';
diff --git a/src/design-system/feedback/alert/alert.tsx b/src/design-system/feedback/alert/alert.tsx
new file mode 100644
index 0000000..fdef3b1
--- /dev/null
+++ b/src/design-system/feedback/alert/alert.tsx
@@ -0,0 +1,202 @@
+"use client";
+
+import React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Alert 警告提示组件
+ *
+ * Design System 中的警告提示组件,用于显示重要信息。
+ *
+ * @example
+ * ```tsx
+ * // 默认提示
+ * 这是一条普通提示
+ *
+ * // 成功提示
+ * 操作成功!
+ *
+ * // 错误提示(带标题)
+ *
+ * 发生了一些问题
+ *
+ *
+ * // 可关闭的提示
+ *
+ * 请注意此警告
+ *
+ * ```
+ */
+
+/**
+ * Alert 变体样式
+ */
+const alertVariants = cva(
+ // 基础样式
+ "rounded-xl border-2 px-4 py-3 shadow-sm transition-all duration-250",
+ {
+ variants: {
+ variant: {
+ info: "border-info-500 bg-info-50 text-info-900",
+ success: "border-success-500 bg-success-50 text-success-900",
+ warning: "border-warning-500 bg-warning-50 text-warning-900",
+ error: "border-error-500 bg-error-50 text-error-900",
+ },
+ },
+ defaultVariants: {
+ variant: "info",
+ },
+ }
+);
+
+export type AlertVariant = VariantProps["variant"];
+
+export interface AlertProps
+ extends React.HTMLAttributes,
+ VariantProps {
+ // 标题
+ title?: string;
+ // 是否可关闭
+ closable?: boolean;
+ // 关闭回调
+ onClose?: () => void;
+ // 自定义图标
+ icon?: React.ReactNode;
+}
+
+// 默认图标
+const defaultIcons = {
+ info: (
+
+ ),
+ success: (
+
+ ),
+ warning: (
+
+ ),
+ error: (
+
+ ),
+};
+
+/**
+ * Alert 警告提示组件
+ */
+export function Alert({
+ variant = "info",
+ title,
+ closable = false,
+ onClose,
+ icon,
+ className,
+ children,
+ ...props
+}: AlertProps) {
+ const [visible, setVisible] = React.useState(true);
+
+ const handleClose = () => {
+ setVisible(false);
+ onClose?.();
+ };
+
+ if (!visible) return null;
+
+ // 图标颜色
+ const iconColors = {
+ info: "text-info-500",
+ success: "text-success-500",
+ warning: "text-warning-500",
+ error: "text-error-500",
+ };
+
+ return (
+
+
+ {/* 图标 */}
+
+ {icon || defaultIcons[variant]}
+
+
+ {/* 内容 */}
+
+ {title && (
+
{title}
+ )}
+
{children}
+
+
+ {/* 关闭按钮 */}
+ {closable && (
+
+ )}
+
+
+ );
+}
+
+/**
+ * 快捷组件
+ */
+export const InfoAlert = (props: Omit) => (
+
+);
+
+export const SuccessAlert = (props: Omit) => (
+
+);
+
+export const WarningAlert = (props: Omit) => (
+
+);
+
+export const ErrorAlert = (props: Omit) => (
+
+);
diff --git a/src/design-system/feedback/alert/index.ts b/src/design-system/feedback/alert/index.ts
new file mode 100644
index 0000000..ab34032
--- /dev/null
+++ b/src/design-system/feedback/alert/index.ts
@@ -0,0 +1 @@
+export * from './alert';
diff --git a/src/design-system/feedback/progress/index.ts b/src/design-system/feedback/progress/index.ts
new file mode 100644
index 0000000..2ee5781
--- /dev/null
+++ b/src/design-system/feedback/progress/index.ts
@@ -0,0 +1 @@
+export * from './progress';
diff --git a/src/design-system/feedback/progress/progress.tsx b/src/design-system/feedback/progress/progress.tsx
new file mode 100644
index 0000000..2918162
--- /dev/null
+++ b/src/design-system/feedback/progress/progress.tsx
@@ -0,0 +1,214 @@
+"use client";
+
+import React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Progress 进度条组件
+ *
+ * Design System 中的进度条组件,用于显示任务完成进度。
+ *
+ * @example
+ * ```tsx
+ * // 默认进度条
+ *
+ *
+ * // 不同尺寸
+ *
+ *
+ *
+ * // 不同变体
+ *
+ *
+ *
+ *
+ * // 无标签
+ *
+ *
+ * // 自定义颜色
+ *
+ * ```
+ */
+
+/**
+ * Progress 变体样式
+ */
+const progressVariants = cva(
+ // 基础样式
+ "overflow-hidden rounded-full bg-gray-200 transition-all duration-250",
+ {
+ variants: {
+ size: {
+ sm: "h-1.5",
+ md: "h-2",
+ lg: "h-3",
+ },
+ variant: {
+ default: "",
+ success: "",
+ warning: "",
+ error: "",
+ },
+ },
+ defaultVariants: {
+ size: "md",
+ variant: "default",
+ },
+ }
+);
+
+export type ProgressSize = VariantProps["size"];
+export type ProgressVariant = VariantProps["variant"];
+
+export interface ProgressProps
+ extends React.HTMLAttributes,
+ VariantProps {
+ // 进度值(0-100)
+ value: number;
+ // 是否显示百分比标签
+ showLabel?: boolean;
+ // 自定义标签
+ label?: string;
+ // 是否显示动画
+ animated?: boolean;
+ // 自定义颜色(覆盖 variant)
+ color?: string;
+}
+
+/**
+ * Progress 进度条组件
+ */
+export function Progress({
+ value = 0,
+ size = "md",
+ variant = "default",
+ showLabel = true,
+ label,
+ animated = true,
+ color,
+ className,
+ ...props
+}: ProgressProps) {
+ // 确保值在 0-100 之间
+ const clampedValue = Math.min(100, Math.max(0, value));
+
+ // 计算颜色
+ const getColor = () => {
+ if (color) return color;
+ const colors = {
+ default: "bg-primary-500",
+ success: "bg-success-500",
+ warning: "bg-warning-500",
+ error: "bg-error-500",
+ };
+ return colors[variant];
+ };
+
+ // 格式化标签
+ const formatLabel = () => {
+ if (label !== undefined) return label;
+ return `${Math.round(clampedValue)}%`;
+ };
+
+ return (
+
+
+
+ {showLabel && (
+
+ {formatLabel()}
+
+ )}
+
+
+ );
+}
+
+/**
+ * CircularProgress - 环形进度条
+ */
+export interface CircularProgressProps extends React.SVGProps {
+ value: number;
+ size?: number;
+ strokeWidth?: number;
+ variant?: ProgressVariant;
+ showLabel?: boolean;
+ label?: string;
+}
+
+export function CircularProgress({
+ value = 0,
+ size = 120,
+ strokeWidth = 8,
+ variant = "default",
+ showLabel = true,
+ label,
+ className,
+ ...props
+}: CircularProgressProps) {
+ const clampedValue = Math.min(100, Math.max(0, value));
+ const radius = (size - strokeWidth) / 2;
+ const circumference = 2 * Math.PI * radius;
+ const offset = circumference - (clampedValue / 100) * circumference;
+
+ const colors = {
+ default: "#35786f",
+ success: "#22c55e",
+ warning: "#f59e0b",
+ error: "#ef4444",
+ };
+ const strokeColor = colors[variant];
+
+ return (
+
+
+ {showLabel && (
+
+ {label !== undefined ? label : `${Math.round(clampedValue)}%`}
+
+ )}
+
+ );
+}
diff --git a/src/design-system/feedback/skeleton/index.ts b/src/design-system/feedback/skeleton/index.ts
new file mode 100644
index 0000000..25c51ad
--- /dev/null
+++ b/src/design-system/feedback/skeleton/index.ts
@@ -0,0 +1 @@
+export * from './skeleton';
diff --git a/src/design-system/feedback/skeleton/skeleton.tsx b/src/design-system/feedback/skeleton/skeleton.tsx
new file mode 100644
index 0000000..2db4277
--- /dev/null
+++ b/src/design-system/feedback/skeleton/skeleton.tsx
@@ -0,0 +1,192 @@
+"use client";
+
+import React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Skeleton 骨架屏组件
+ *
+ * Design System 中的骨架屏组件,用于内容加载时的占位显示。
+ *
+ * @example
+ * ```tsx
+ * // 默认骨架屏
+ *
+ *
+ * // 不同变体
+ *
+ *
+ *
+ *
+ * // 自定义动画
+ *
+ * ```
+ */
+
+/**
+ * Skeleton 变体样式
+ */
+const skeletonVariants = cva(
+ // 基础样式
+ "shrink-0 animate-pulse rounded",
+ {
+ variants: {
+ variant: {
+ text: "h-4 w-full",
+ circular: "rounded-full",
+ rectangular: "rounded-lg",
+ },
+ animated: {
+ true: "animate-pulse",
+ false: "",
+ },
+ },
+ defaultVariants: {
+ variant: "text",
+ animated: true,
+ },
+ }
+);
+
+export type SkeletonVariant = VariantProps["variant"];
+
+export interface SkeletonProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+/**
+ * Skeleton 骨架屏组件
+ */
+export function Skeleton({
+ variant = "text",
+ animated = true,
+ className,
+ ...props
+}: SkeletonProps) {
+ return (
+
+ );
+}
+
+/**
+ * 预设的骨架屏组合
+ */
+
+/**
+ * 文本骨架屏(多行)
+ */
+export interface TextSkeletonProps {
+ lines?: number;
+ className?: string;
+}
+
+export function TextSkeleton({ lines = 3, className }: TextSkeletonProps) {
+ return (
+
+ {Array.from({ length: lines }).map((_, i) => (
+
+ ))}
+
+ );
+}
+
+/**
+ * 卡片骨架屏
+ */
+export interface CardSkeletonProps {
+ showAvatar?: boolean;
+ className?: string;
+}
+
+export function CardSkeleton({
+ showAvatar = false,
+ className,
+}: CardSkeletonProps) {
+ return (
+
+ {showAvatar && (
+
+ )}
+
+
+ );
+}
+
+/**
+ * 列表项骨架屏
+ */
+export interface ListItemSkeletonProps {
+ showAvatar?: boolean;
+ className?: string;
+}
+
+export function ListItemSkeleton({
+ showAvatar = true,
+ className,
+}: ListItemSkeletonProps) {
+ return (
+
+ {showAvatar &&
}
+
+
+
+
+
+ );
+}
+
+/**
+ * 表格骨架屏
+ */
+export interface TableSkeletonProps {
+ rows?: number;
+ columns?: number;
+ className?: string;
+}
+
+export function TableSkeleton({
+ rows = 5,
+ columns = 4,
+ className,
+}: TableSkeletonProps) {
+ return (
+
+ {/* 表头 */}
+
+ {Array.from({ length: columns }).map((_, i) => (
+
+ ))}
+
+ {/* 表体 */}
+ {Array.from({ length: rows }).map((_, rowIndex) => (
+
+ {Array.from({ length: columns }).map((_, colIndex) => (
+
+ ))}
+
+ ))}
+
+ );
+}
diff --git a/src/design-system/feedback/toast/index.ts b/src/design-system/feedback/toast/index.ts
new file mode 100644
index 0000000..7ff6d41
--- /dev/null
+++ b/src/design-system/feedback/toast/index.ts
@@ -0,0 +1 @@
+export * from './toast';
diff --git a/src/design-system/feedback/toast/toast.tsx b/src/design-system/feedback/toast/toast.tsx
new file mode 100644
index 0000000..59fa200
--- /dev/null
+++ b/src/design-system/feedback/toast/toast.tsx
@@ -0,0 +1,108 @@
+"use client";
+
+/**
+ * Toast 组件
+ *
+ * 基于项目已安装的 sonner 库封装的 Toast 通知组件。
+ * 提供类型安全的 API 和预设样式。
+ *
+ * @example
+ * ```tsx
+ * import { toast } from '@/design-system/feedback/toast';
+ *
+ * // 基础用法
+ * toast.success("操作成功!");
+ * toast.error("发生错误");
+ * toast.warning("请注意");
+ * toast.info("提示信息");
+ *
+ * // 自定义选项
+ * toast.success("操作成功!", {
+ * description: "您的更改已保存",
+ * duration: 5000,
+ * });
+ *
+ * // Promise Toast
+ * toast.promise(
+ * fetchData(),
+ * {
+ * loading: "加载中...",
+ * success: "加载成功",
+ * error: "加载失败",
+ * }
+ * );
+ * ```
+ */
+
+import { toast as sonnerToast } from "sonner";
+
+export type ToastProps = {
+ description?: string;
+ duration?: number;
+ id?: string;
+ onDismiss?: () => void;
+};
+
+/**
+ * Toast 通知组件
+ */
+export const toast = {
+ success: (message: string, props?: ToastProps) => {
+ return sonnerToast.success(message, {
+ description: props?.description,
+ duration: props?.duration,
+ id: props?.id,
+ onDismiss: props?.onDismiss,
+ });
+ },
+
+ error: (message: string, props?: ToastProps) => {
+ return sonnerToast.error(message, {
+ description: props?.description,
+ duration: props?.duration,
+ id: props?.id,
+ onDismiss: props?.onDismiss,
+ });
+ },
+
+ warning: (message: string, props?: ToastProps) => {
+ return sonnerToast.warning(message, {
+ description: props?.description,
+ duration: props?.duration,
+ id: props?.id,
+ onDismiss: props?.onDismiss,
+ });
+ },
+
+ info: (message: string, props?: ToastProps) => {
+ return sonnerToast.info(message, {
+ description: props?.description,
+ duration: props?.duration,
+ id: props?.id,
+ onDismiss: props?.onDismiss,
+ });
+ },
+
+ promise: (
+ promise: Promise,
+ {
+ loading,
+ success,
+ error,
+ }: {
+ loading: string;
+ success: string | ((data: T) => string);
+ error: string | ((error: Error) => string);
+ }
+ ) => {
+ return sonnerToast.promise(promise, {
+ loading,
+ success,
+ error,
+ });
+ },
+
+ dismiss: (id?: string) => {
+ sonnerToast.dismiss(id);
+ },
+};
diff --git a/src/design-system/index.ts b/src/design-system/index.ts
new file mode 100644
index 0000000..8e73cf2
--- /dev/null
+++ b/src/design-system/index.ts
@@ -0,0 +1,52 @@
+/**
+ * Design System 统一导出
+ *
+ * 这是 Design System 的主入口,所有组件和工具都可以从这里导入。
+ *
+ * @example
+ * ```tsx
+ * // 从主入口导入
+ * import { Button, Input, Card } from '@/design-system';
+ *
+ * // 或者从子路径导入(更好的 tree-shaking)
+ * import { Button } from '@/design-system/base/button';
+ * import { Input } from '@/design-system/base/input';
+ * ```
+ */
+
+// 设计令牌
+export * from './tokens';
+
+// 工具函数
+export * from './lib/utils';
+
+// 基础组件
+export * from './base/button';
+export * from './base/input';
+export * from './base/textarea';
+export * from './base/card';
+export * from './base/checkbox';
+export * from './base/radio';
+export * from './base/switch';
+export * from './base/select';
+
+// 反馈组件
+export * from './feedback/alert';
+export * from './feedback/progress';
+export * from './feedback/skeleton';
+export * from './feedback/toast';
+
+// 覆盖组件
+export * from './overlay/modal';
+
+// 数据展示组件
+export * from './data-display/badge';
+export * from './data-display/divider';
+
+// 布局组件
+export * from './layout/container';
+export * from './layout/grid';
+export * from './layout/stack';
+
+// 导航组件
+export * from './navigation/tabs';
diff --git a/src/design-system/layout/container/container.tsx b/src/design-system/layout/container/container.tsx
new file mode 100644
index 0000000..b77a600
--- /dev/null
+++ b/src/design-system/layout/container/container.tsx
@@ -0,0 +1,103 @@
+"use client";
+
+import React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Container 容器组件
+ *
+ * Design System 中的容器组件,用于约束内容宽度并居中。
+ *
+ * @example
+ * ```tsx
+ * // 默认容器
+ *
+ * 内容被居中并限制最大宽度
+ *
+ *
+ * // 不同尺寸
+ * 小容器
+ * 大容器
+ *
+ * // 全宽容器
+ * 全宽容器
+ *
+ * // 带内边距
+ * 带内边距的容器
+ * ```
+ */
+
+/**
+ * Container 变体样式
+ */
+const containerVariants = cva(
+ // 基础样式
+ "mx-auto",
+ {
+ variants: {
+ size: {
+ xs: "max-w-xs",
+ sm: "max-w-sm",
+ md: "max-w-md",
+ lg: "max-w-lg",
+ xl: "max-w-xl",
+ "2xl": "max-w-2xl",
+ "3xl": "max-w-3xl",
+ "4xl": "max-w-4xl",
+ "5xl": "max-w-5xl",
+ "6xl": "max-w-6xl",
+ "7xl": "max-w-7xl",
+ full: "max-w-full",
+ },
+ padding: {
+ none: "",
+ xs: "px-2",
+ sm: "px-4",
+ md: "px-6",
+ lg: "px-8",
+ xl: "px-10",
+ },
+ fullWidth: {
+ true: "w-full",
+ false: "",
+ },
+ },
+ defaultVariants: {
+ size: "7xl",
+ padding: "md",
+ fullWidth: false,
+ },
+ }
+);
+
+export type ContainerSize = VariantProps["size"];
+export type ContainerPadding = VariantProps["padding"];
+
+export interface ContainerProps
+ extends React.HTMLAttributes,
+ VariantProps {
+ // 子元素
+ children: React.ReactNode;
+}
+
+/**
+ * Container 容器组件
+ */
+export function Container({
+ size = "7xl",
+ padding = "md",
+ fullWidth = false,
+ className,
+ children,
+ ...props
+}: ContainerProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/design-system/layout/container/index.ts b/src/design-system/layout/container/index.ts
new file mode 100644
index 0000000..85ee15b
--- /dev/null
+++ b/src/design-system/layout/container/index.ts
@@ -0,0 +1 @@
+export * from './container';
diff --git a/src/design-system/layout/grid/grid.tsx b/src/design-system/layout/grid/grid.tsx
new file mode 100644
index 0000000..2d0ac7e
--- /dev/null
+++ b/src/design-system/layout/grid/grid.tsx
@@ -0,0 +1,179 @@
+"use client";
+
+import React from "react";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Grid 网格布局组件
+ *
+ * Design System 中的网格布局组件,基于 CSS Grid。
+ *
+ * @example
+ * ```tsx
+ * // 默认网格(2列)
+ *
+ * 项目 1
+ * 项目 2
+ *
+ *
+ * // 自定义列数
+ *
+ * 项目 1
+ * 项目 2
+ * 项目 3
+ *
+ *
+ * // 响应式网格
+ *
+ * 项目 1
+ * 项目 2
+ * 项目 3
+ *
+ *
+ * // 带间距的网格
+ *
+ * 项目 1
+ * 项目 2
+ *
+ * ```
+ */
+
+export interface ResponsiveValue {
+ sm?: number;
+ md?: number;
+ lg?: number;
+ xl?: number;
+ "2xl"?: number;
+}
+
+export interface GridProps extends React.HTMLAttributes {
+ // 列数
+ cols?: number | ResponsiveValue;
+ // 行间距
+ rowGap?: number | string;
+ // 列间距
+ colGap?: number | string;
+ // 间距(同时设置行列间距)
+ gap?: number | string;
+ // 子元素
+ children: React.ReactNode;
+}
+
+/**
+ * 生成网格类名
+ */
+function generateGridClass(cols?: number | ResponsiveValue): string {
+ if (!cols) return "grid-cols-1 md:grid-cols-2";
+
+ if (typeof cols === "number") {
+ return `grid-cols-${cols}`;
+ }
+
+ // 响应式列数
+ const classes = ["grid-cols-1"]; // 默认 1 列
+
+ if (cols.sm) classes.push(`sm:grid-cols-${cols.sm}`);
+ if (cols.md) classes.push(`md:grid-cols-${cols.md}`);
+ if (cols.lg) classes.push(`lg:grid-cols-${cols.lg}`);
+ if (cols.xl) classes.push(`xl:grid-cols-${cols.xl}`);
+ if (cols["2xl"]) classes.push(`"2xl":grid-cols-${cols["2xl"]}`);
+
+ // 如果没有指定 md,使用默认 2 列
+ if (!cols.md && !cols.lg && !cols.xl && !cols["2xl"]) {
+ classes.push("md:grid-cols-2");
+ }
+
+ return classes.join(" ");
+}
+
+/**
+ * Grid 网格布局组件
+ */
+export function Grid({
+ cols,
+ rowGap,
+ colGap,
+ gap,
+ className,
+ children,
+ ...props
+}: GridProps) {
+ const gridClass = generateGridClass(cols);
+
+ return (
+
+ {children}
+
+ );
+}
+
+/**
+ * GridItem - 网格项
+ */
+export interface GridItemProps extends React.HTMLAttributes {
+ // 列跨度
+ colSpan?: number | ResponsiveValue;
+ // 行跨度
+ rowSpan?: number;
+ // 子元素
+ children: React.ReactNode;
+}
+
+/**
+ * 生成跨度类名
+ */
+function generateSpanClass(
+ type: "col" | "row",
+ span?: number | ResponsiveValue
+): string {
+ if (!span) return "";
+
+ if (typeof span === "number") {
+ return `${type === "col" ? "col" : "row"}-span-${span}`;
+ }
+
+ // 响应式跨度
+ const classes: string[] = [];
+
+ if (span.sm) classes.push(`sm:${type === "col" ? "col" : "row"}-span-${span.sm}`);
+ if (span.md) classes.push(`md:${type === "col" ? "col" : "row"}-span-${span.md}`);
+ if (span.lg) classes.push(`lg:${type === "col" ? "col" : "row"}-span-${span.lg}`);
+ if (span.xl) classes.push(`xl:${type === "col" ? "col" : "row"}-span-${span.xl}`);
+ if (span["2xl"]) classes.push(`"2xl":${type === "col" ? "col" : "row"}-span-${span["2xl"]}`);
+
+ return classes.join(" ");
+}
+
+/**
+ * GridItem 网格项组件
+ */
+export function GridItem({
+ colSpan,
+ rowSpan,
+ className,
+ children,
+ ...props
+}: GridItemProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/design-system/layout/grid/index.ts b/src/design-system/layout/grid/index.ts
new file mode 100644
index 0000000..d24d1bd
--- /dev/null
+++ b/src/design-system/layout/grid/index.ts
@@ -0,0 +1 @@
+export * from './grid';
diff --git a/src/design-system/layout/stack/index.ts b/src/design-system/layout/stack/index.ts
new file mode 100644
index 0000000..d39a8e6
--- /dev/null
+++ b/src/design-system/layout/stack/index.ts
@@ -0,0 +1 @@
+export * from './stack';
diff --git a/src/design-system/layout/stack/stack.tsx b/src/design-system/layout/stack/stack.tsx
new file mode 100644
index 0000000..8b07c45
--- /dev/null
+++ b/src/design-system/layout/stack/stack.tsx
@@ -0,0 +1,140 @@
+"use client";
+
+import React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Stack 堆叠布局组件
+ *
+ * Design System 中的堆叠布局组件,用于垂直或水平排列子元素。
+ *
+ * @example
+ * ```tsx
+ * // 垂直堆叠
+ *
+ * 项目 1
+ * 项目 2
+ * 项目 3
+ *
+ *
+ * // 水平堆叠
+ *
+ * 项目 1
+ * 项目 2
+ * 项目 3
+ *
+ *
+ * // 自定义间距
+ *
+ * 项目 1
+ * 项目 2
+ *
+ *
+ * // 居中对齐
+ *
+ * 项目 1
+ * 项目 2
+ *
+ * ```
+ */
+
+/**
+ * Stack 变体样式
+ */
+const stackVariants = cva(
+ // 基础样式
+ "flex",
+ {
+ variants: {
+ direction: {
+ column: "flex-col",
+ row: "flex-row",
+ },
+ align: {
+ start: "items-start",
+ center: "items-center",
+ end: "items-end",
+ stretch: "items-stretch",
+ },
+ justify: {
+ start: "justify-start",
+ center: "justify-center",
+ end: "justify-end",
+ between: "justify-between",
+ },
+ wrap: {
+ true: "flex-wrap",
+ false: "flex-nowrap",
+ },
+ },
+ defaultVariants: {
+ direction: "column",
+ align: "start",
+ justify: "start",
+ wrap: false,
+ },
+ }
+);
+
+export type StackDirection = VariantProps["direction"];
+export type StackAlign = VariantProps["align"];
+export type StackJustify = VariantProps["justify"];
+
+export interface StackProps
+ extends React.HTMLAttributes,
+ VariantProps {
+ // 子元素
+ children: React.ReactNode;
+ // 间距(使用 Tailwind spacing)
+ gap?: number | string;
+ // 是否为内联布局
+ inline?: boolean;
+}
+
+/**
+ * Stack 堆叠布局组件
+ */
+export function Stack({
+ direction = "column",
+ align = "start",
+ justify = "start",
+ wrap = false,
+ gap,
+ inline = false,
+ className,
+ children,
+ ...props
+}: StackProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+/**
+ * VStack - 垂直堆叠组件(快捷方式)
+ */
+export interface VStackProps extends Omit {}
+
+export function VStack(props: VStackProps) {
+ return ;
+}
+
+/**
+ * HStack - 水平堆叠组件(快捷方式)
+ */
+export interface HStackProps extends Omit {}
+
+export function HStack(props: HStackProps) {
+ return ;
+}
diff --git a/src/design-system/lib/utils.ts b/src/design-system/lib/utils.ts
new file mode 100644
index 0000000..13c4568
--- /dev/null
+++ b/src/design-system/lib/utils.ts
@@ -0,0 +1,23 @@
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+/**
+ * 合并 Tailwind CSS 类名的工具函数
+ *
+ * 使用 clsx 处理条件类名,然后使用 tailwind-merge 解决 Tailwind 类名冲突
+ *
+ * @param inputs - 类名(字符串、对象、数组等)
+ * @returns 合并后的类名字符串
+ *
+ * @example
+ * ```tsx
+ * cn('px-4 py-2', isActive && 'bg-primary-500', 'text-white')
+ * // => 'px-4 py-2 bg-primary-500 text-white'
+ *
+ * cn('px-4 px-6') // 自动解决冲突
+ * // => 'px-6'
+ * ```
+ */
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/src/design-system/navigation/tabs/index.ts b/src/design-system/navigation/tabs/index.ts
new file mode 100644
index 0000000..c2d1b4e
--- /dev/null
+++ b/src/design-system/navigation/tabs/index.ts
@@ -0,0 +1 @@
+export * from './tabs';
diff --git a/src/design-system/navigation/tabs/tabs.tsx b/src/design-system/navigation/tabs/tabs.tsx
new file mode 100644
index 0000000..dd655e1
--- /dev/null
+++ b/src/design-system/navigation/tabs/tabs.tsx
@@ -0,0 +1,193 @@
+"use client";
+
+import React, { useState } from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Tabs 标签页组件
+ *
+ * Design System 中的标签页组件,用于内容分组和切换。
+ *
+ * @example
+ * ```tsx
+ * function MyComponent() {
+ * const [activeTab, setActiveTab] = useState("tab1");
+ *
+ * return (
+ *
+ *
+ * 标签 1
+ * 标签 2
+ * 标签 3
+ *
+ *
+ * 内容 1
+ *
+ *
+ * 内容 2
+ *
+ *
+ * 内容 3
+ *
+ *
+ * );
+ * }
+ * ```
+ */
+
+export interface TabsProps {
+ value: string;
+ onValueChange: (value: string) => void;
+ children: React.ReactNode;
+ className?: string;
+ variant?: "line" | "enclosed" | "soft";
+}
+
+/**
+ * Tabs 组件
+ */
+export function Tabs({
+ value,
+ onValueChange,
+ children,
+ className,
+ variant = "line",
+}: TabsProps) {
+ return (
+
+ {React.Children.map(children, (child) => {
+ if (React.isValidElement(child)) {
+ return React.cloneElement(child, {
+ value,
+ onValueChange,
+ variant,
+ } as any);
+ }
+ return child;
+ })}
+
+ );
+}
+
+/**
+ * Tabs.List - 标签列表
+ */
+export interface TabsListProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+ variant?: "line" | "enclosed" | "soft";
+}
+
+const listVariants = cva(
+ "flex",
+ {
+ variants: {
+ variant: {
+ line: "border-b border-gray-200",
+ enclosed: "bg-gray-100 p-1 rounded-lg gap-1",
+ soft: "gap-2",
+ },
+ },
+ defaultVariants: {
+ variant: "line",
+ },
+ }
+);
+
+export function TabsList({
+ children,
+ variant = "line",
+ className,
+ ...props
+}: TabsListProps) {
+ return (
+
+ {React.Children.map(children, (child) => {
+ if (React.isValidElement(child)) {
+ return React.cloneElement(child, {
+ variant,
+ } as any);
+ }
+ return child;
+ })}
+
+ );
+}
+
+/**
+ * Tabs.Trigger - 标签触发器
+ */
+export interface TabsTriggerProps extends React.ButtonHTMLAttributes {
+ value: string;
+ children: React.ReactNode;
+ variant?: "line" | "enclosed" | "soft";
+}
+
+const triggerVariants = cva(
+ "px-4 py-2 text-sm font-medium transition-all duration-250 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ line: "border-b-2 -mb-px rounded-t-lg data-[state=active]:border-primary-500 data-[state=active]:text-primary-600 text-gray-600 hover:text-gray-900 border-transparent",
+ enclosed: "rounded-md data-[state=active]:bg-white data-[state=active]:shadow-sm text-gray-600 hover:text-gray-900",
+ soft: "rounded-lg data-[state=active]:bg-primary-50 data-[state=active]:text-primary-700 text-gray-600 hover:text-gray-900 hover:bg-gray-100",
+ },
+ },
+ defaultVariants: {
+ variant: "line",
+ },
+ }
+);
+
+export function TabsTrigger({
+ value,
+ children,
+ variant = "line",
+ className,
+ ...props
+}: TabsTriggerProps) {
+ return (
+
+ );
+}
+
+/**
+ * Tabs.Content - 标签内容
+ */
+export interface TabsContentProps extends React.HTMLAttributes {
+ value: string;
+ children: React.ReactNode;
+}
+
+export function TabsContent({
+ value,
+ children,
+ className,
+ ...props
+}: TabsContentProps) {
+ if (value !== props["data-state"]) return null;
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/design-system/overlay/modal/index.ts b/src/design-system/overlay/modal/index.ts
new file mode 100644
index 0000000..133aa74
--- /dev/null
+++ b/src/design-system/overlay/modal/index.ts
@@ -0,0 +1 @@
+export * from './modal';
diff --git a/src/design-system/overlay/modal/modal.tsx b/src/design-system/overlay/modal/modal.tsx
new file mode 100644
index 0000000..8e1af6d
--- /dev/null
+++ b/src/design-system/overlay/modal/modal.tsx
@@ -0,0 +1,238 @@
+"use client";
+
+import React, { useEffect } from "react";
+import { cn } from "@/design-system/lib/utils";
+
+/**
+ * Modal 模态框组件
+ *
+ * 全屏遮罩的模态对话框组件。
+ *
+ * @example
+ * ```tsx
+ * function MyComponent() {
+ * const [open, setOpen] = useState(false);
+ *
+ * return (
+ * <>
+ *
+ * setOpen(false)}>
+ *
+ * 标题
+ *
+ *
+ * 模态框内容
+ *
+ *
+ *
+ *
+ *
+ *
+ * >
+ * );
+ * }
+ * ```
+ */
+
+export interface ModalProps {
+ open: boolean;
+ onClose: () => void;
+ children: React.ReactNode;
+ size?: "sm" | "md" | "lg" | "xl" | "full";
+ closeOnOverlayClick?: boolean;
+ closeOnEscape?: boolean;
+ className?: string;
+}
+
+const sizeClasses = {
+ sm: "max-w-md",
+ md: "max-w-lg",
+ lg: "max-w-2xl",
+ xl: "max-w-4xl",
+ full: "max-w-full mx-4",
+};
+
+/**
+ * Modal 组件
+ */
+export function Modal({
+ open,
+ onClose,
+ children,
+ size = "md",
+ closeOnOverlayClick = true,
+ closeOnEscape = true,
+ className,
+}: ModalProps) {
+ // ESC 键关闭
+ useEffect(() => {
+ if (!open || !closeOnEscape) return;
+
+ const handleEscape = (e: KeyboardEvent) => {
+ if (e.key === "Escape") onClose();
+ };
+
+ document.addEventListener("keydown", handleEscape);
+ return () => document.removeEventListener("keydown", handleEscape);
+ }, [open, closeOnEscape, onClose]);
+
+ // 禁止背景滚动
+ useEffect(() => {
+ if (open) {
+ document.body.style.overflow = "hidden";
+ return () => {
+ document.body.style.overflow = "";
+ };
+ }
+ }, [open]);
+
+ if (!open) return null;
+
+ return (
+
+ {/* 遮罩层 */}
+
+
+ {/* 模态框内容 */}
+
+ {children}
+
+
+ );
+}
+
+/**
+ * Modal.Header - 模态框头部
+ */
+export interface ModalHeaderProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+}
+
+Modal.Header = function ModalHeader({
+ children,
+ className,
+ ...props
+}: ModalHeaderProps) {
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Modal.Title - 模态框标题
+ */
+export interface ModalTitleProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+}
+
+Modal.Title = function ModalTitle({
+ children,
+ className,
+ ...props
+}: ModalTitleProps) {
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Modal.Body - 模态框主体
+ */
+export interface ModalBodyProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+}
+
+Modal.Body = function ModalBody({
+ children,
+ className,
+ ...props
+}: ModalBodyProps) {
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Modal.Footer - 模态框底部
+ */
+export interface ModalFooterProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+ align?: "left" | "center" | "right";
+}
+
+Modal.Footer = function ModalFooter({
+ children,
+ align = "right",
+ className,
+ ...props
+}: ModalFooterProps) {
+ const alignClasses = {
+ left: "justify-start",
+ center: "justify-center",
+ right: "justify-end",
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Modal.CloseButton - 关闭按钮
+ */
+export interface ModalCloseButtonProps extends React.ButtonHTMLAttributes {}
+
+Modal.CloseButton = function ModalCloseButton({
+ className,
+ onClick,
+ ...props
+}: ModalCloseButtonProps) {
+ return (
+
+ );
+};
diff --git a/src/design-system/tokens/borders.ts b/src/design-system/tokens/borders.ts
new file mode 100644
index 0000000..40a5b69
--- /dev/null
+++ b/src/design-system/tokens/borders.ts
@@ -0,0 +1,74 @@
+/**
+ * 边框和圆角系统设计令牌
+ */
+
+/**
+ * 圆角半径
+ */
+export const borderRadius = {
+ none: '0',
+ sm: '0.125rem', // 2px
+ DEFAULT: '0.25rem', // 4px
+ md: '0.375rem', // 6px
+ lg: '0.5rem', // 8px
+ xl: '0.75rem', // 12px
+ '2xl': '1rem', // 16px
+ '3xl': '1.5rem', // 24px
+ full: '9999px',
+} as const;
+
+/**
+ * 语义化圆角
+ */
+export const semanticBorderRadius = {
+ // 按钮
+ button: {
+ sm: borderRadius.lg,
+ md: borderRadius.xl,
+ lg: borderRadius['2xl'],
+ },
+
+ // 输入框
+ input: {
+ sm: borderRadius.md,
+ md: borderRadius.lg,
+ lg: borderRadius.xl,
+ },
+
+ // 卡片
+ card: {
+ sm: borderRadius.xl,
+ md: borderRadius['2xl'],
+ lg: borderRadius['3xl'],
+ },
+
+ // 模态框
+ modal: borderRadius['2xl'],
+
+ // 徽章/标签
+ badge: borderRadius.full,
+
+ // 圆形按钮/图标
+ circle: borderRadius.full,
+} as const;
+
+/**
+ * 边框宽度
+ */
+export const borderWidth = {
+ DEFAULT: '1px',
+ 0: '0',
+ 2: '2px',
+ 4: '4px',
+ 8: '8px',
+} as const;
+
+/**
+ * 边框样式
+ */
+export const borderStyle = {
+ solid: 'solid',
+ dashed: 'dashed',
+ dotted: 'dotted',
+ double: 'double',
+} as const;
diff --git a/src/design-system/tokens/colors.ts b/src/design-system/tokens/colors.ts
new file mode 100644
index 0000000..be4f95b
--- /dev/null
+++ b/src/design-system/tokens/colors.ts
@@ -0,0 +1,162 @@
+/**
+ * 颜色系统设计令牌
+ *
+ * 基于 8 色阶系统(50-900),提供完整的颜色语义化命名
+ *
+ * 主色:Teal (#35786f)
+ * - 用于主要操作按钮、链接、重要元素
+ *
+ * 语义色:
+ * - success: 成功状态
+ * - warning: 警告状态
+ * - error: 错误/危险状态
+ * - info: 信息提示
+ */
+
+/**
+ * 主色 - Teal
+ */
+export const primary = {
+ 50: '#f0f9f8',
+ 100: '#e0f2f0',
+ 200: '#bce6e1',
+ 300: '#8dd4cc',
+ 400: '#5ec2b7',
+ 500: '#35786f',
+ 600: '#2a605b',
+ 700: '#1f4844',
+ 800: '#183835',
+ 900: '#122826',
+ 950: '#0a1413',
+} as const;
+
+/**
+ * 中性色 - Gray
+ */
+export const gray = {
+ 50: '#f9fafb',
+ 100: '#f3f4f6',
+ 200: '#e5e7eb',
+ 300: '#d1d5db',
+ 400: '#9ca3af',
+ 500: '#6b7280',
+ 600: '#4b5563',
+ 700: '#374151',
+ 800: '#1f2937',
+ 900: '#111827',
+ 950: '#030712',
+} as const;
+
+/**
+ * 语义色 - Success
+ */
+export const success = {
+ 50: '#f0fdf4',
+ 100: '#dcfce7',
+ 200: '#bbf7d0',
+ 300: '#86efac',
+ 400: '#4ade80',
+ 500: '#22c55e',
+ 600: '#16a34a',
+ 700: '#15803d',
+ 800: '#166534',
+ 900: '#14532d',
+ 950: '#052e16',
+} as const;
+
+/**
+ * 语义色 - Warning
+ */
+export const warning = {
+ 50: '#fffbeb',
+ 100: '#fef3c7',
+ 200: '#fde68a',
+ 300: '#fcd34d',
+ 400: '#fbbf24',
+ 500: '#f59e0b',
+ 600: '#d97706',
+ 700: '#b45309',
+ 800: '#92400e',
+ 900: '#78350f',
+ 950: '#451a03',
+} as const;
+
+/**
+ * 语义色 - Error
+ */
+export const error = {
+ 50: '#fef2f2',
+ 100: '#fee2e2',
+ 200: '#fecaca',
+ 300: '#fca5a5',
+ 400: '#f87171',
+ 500: '#ef4444',
+ 600: '#dc2626',
+ 700: '#b91c1c',
+ 800: '#991b1b',
+ 900: '#7f1d1d',
+ 950: '#450a0a',
+} as const;
+
+/**
+ * 语义色 - Info
+ */
+export const info = {
+ 50: '#eff6ff',
+ 100: '#dbeafe',
+ 200: '#bfdbfe',
+ 300: '#93c5fd',
+ 400: '#60a5fa',
+ 500: '#3b82f6',
+ 600: '#2563eb',
+ 700: '#1d4ed8',
+ 800: '#1e40af',
+ 900: '#1e3a8a',
+ 950: '#172554',
+} as const;
+
+/**
+ * 完整的颜色令牌集合
+ */
+export const colors = {
+ primary,
+ gray,
+ success,
+ warning,
+ error,
+ info,
+
+ // 语义别名
+ semantic: {
+ success: success,
+ warning: warning,
+ error: error,
+ info: info,
+ },
+
+ // 通用别名
+ white: '#ffffff',
+ black: '#000000',
+ transparent: 'transparent',
+
+ // 前景色
+ foreground: gray[900],
+ 'foreground-secondary': gray[600],
+ 'foreground-tertiary': gray[500],
+ 'foreground-disabled': gray[400],
+
+ // 背景色
+ background: gray[50],
+ 'background-secondary': gray[100],
+ 'background-tertiary': gray[200],
+
+ // 边框色
+ border: gray[300],
+ 'border-secondary': gray[200],
+ 'border-focus': primary[500],
+
+ // 阴影
+ shadow: 'rgba(0, 0, 0, 0.1)',
+} as const;
+
+export type Colors = typeof colors;
diff --git a/src/design-system/tokens/index.ts b/src/design-system/tokens/index.ts
new file mode 100644
index 0000000..a8ba5fb
--- /dev/null
+++ b/src/design-system/tokens/index.ts
@@ -0,0 +1,38 @@
+/**
+ * Design System 设计令牌统一导出
+ *
+ * 包含所有设计系统的原始令牌
+ */
+
+export * from './colors';
+export * from './spacing';
+export * from './typography';
+export * from './borders';
+export * from './shadows';
+
+import type { Colors } from './colors';
+
+/**
+ * 完整的设计令牌类型
+ */
+export interface DesignTokens {
+ colors: Colors;
+ spacing: typeof import('./spacing').spacing;
+ semanticSpacing: typeof import('./spacing').semanticSpacing;
+ sizes: typeof import('./spacing').sizes;
+ fontFamily: typeof import('./typography').fontFamily;
+ fontSize: typeof import('./typography').fontSize;
+ fontWeight: typeof import('./typography').fontWeight;
+ letterSpacing: typeof import('./typography').letterSpacing;
+ typography: typeof import('./typography').typography;
+ borderRadius: typeof import('./borders').borderRadius;
+ borderWidth: typeof import('./borders').borderWidth;
+ boxShadow: typeof import('./shadows').boxShadow;
+}
+
+/**
+ * 设计令牌常量(供 TypeScript 类型使用)
+ */
+export const tokens = {
+ colors: {} as Colors,
+} as const;
diff --git a/src/design-system/tokens/shadows.ts b/src/design-system/tokens/shadows.ts
new file mode 100644
index 0000000..ed24f7b
--- /dev/null
+++ b/src/design-system/tokens/shadows.ts
@@ -0,0 +1,61 @@
+/**
+ * 阴影系统设计令牌
+ *
+ * 提供多层次的阴影效果,用于创建深度和层次感
+ */
+
+/**
+ * 阴影级别
+ */
+export const boxShadow = {
+ sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
+ DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)',
+ md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
+ lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
+ xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
+ '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
+ inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.05)',
+ none: 'none',
+} as const;
+
+/**
+ * 语义化阴影
+ */
+export const semanticShadow = {
+ // 按钮
+ button: {
+ sm: boxShadow.sm,
+ md: boxShadow.DEFAULT,
+ lg: boxShadow.md,
+ },
+
+ // 卡片
+ card: {
+ default: boxShadow.xl,
+ bordered: 'none',
+ elevated: boxShadow['2xl'],
+ },
+
+ // 模态框/弹窗
+ modal: boxShadow['2xl'],
+ dropdown: boxShadow.lg,
+ popover: boxShadow.lg,
+ tooltip: boxShadow.md,
+
+ // 导航栏
+ navbar: boxShadow.sm,
+
+ // 输入框 focus
+ focus: `0 0 0 3px rgba(53, 120, 111, 0.1)`, // primary-500 的 10% 透明度
+} as const;
+
+/**
+ * 颜色阴影(用于特定元素的阴影)
+ */
+export const coloredShadow = {
+ primary: `0 4px 14px 0 rgba(53, 120, 111, 0.39)`,
+ success: `0 4px 14px 0 rgba(34, 197, 94, 0.39)`,
+ warning: `0 4px 14px 0 rgba(245, 158, 11, 0.39)`,
+ error: `0 4px 14px 0 rgba(239, 68, 68, 0.39)`,
+ info: `0 4px 14px 0 rgba(59, 130, 246, 0.39)`,
+} as const;
diff --git a/src/design-system/tokens/spacing.ts b/src/design-system/tokens/spacing.ts
new file mode 100644
index 0000000..43a81c1
--- /dev/null
+++ b/src/design-system/tokens/spacing.ts
@@ -0,0 +1,117 @@
+/**
+ * 间距系统设计令牌
+ *
+ * 基于 8pt 基准网格系统,提供一致的间距和尺寸
+ * 单位:rem (1rem = 16px)
+ */
+
+/**
+ * 基础间距刻度
+ */
+export const spacing = {
+ 0: '0',
+ 0.5: '0.125rem', // 2px
+ 1: '0.25rem', // 4px
+ 1.5: '0.375rem', // 6px
+ 2: '0.5rem', // 8px
+ 2.5: '0.625rem', // 10px
+ 3: '0.75rem', // 12px
+ 3.5: '0.875rem', // 14px
+ 4: '1rem', // 16px
+ 5: '1.25rem', // 20px
+ 6: '1.5rem', // 24px
+ 7: '1.75rem', // 28px
+ 8: '2rem', // 32px
+ 9: '2.25rem', // 36px
+ 10: '2.5rem', // 40px
+ 11: '2.75rem', // 44px
+ 12: '3rem', // 48px
+ 14: '3.5rem', // 56px
+ 16: '4rem', // 64px
+ 20: '5rem', // 80px
+ 24: '6rem', // 96px
+ 28: '7rem', // 112px
+ 32: '8rem', // 128px
+ 36: '9rem', // 144px
+ 40: '10rem', // 160px
+ 44: '11rem', // 176px
+ 48: '12rem', // 192px
+ 52: '13rem', // 208px
+ 56: '14rem', // 224px
+ 60: '15rem', // 240px
+ 64: '16rem', // 256px
+ 72: '18rem', // 288px
+ 80: '20rem', // 320px
+ 96: '24rem', // 384px
+} as const;
+
+/**
+ * 语义化间距
+ */
+export const semanticSpacing = {
+ // 组件内边距
+ padding: {
+ xs: spacing[1], // 4px
+ sm: spacing[2], // 8px
+ md: spacing[4], // 16px
+ lg: spacing[6], // 24px
+ xl: spacing[8], // 32px
+ '2xl': spacing[12], // 48px
+ },
+
+ // 组件间距(gap)
+ gap: {
+ xs: spacing[1], // 4px
+ sm: spacing[2], // 8px
+ md: spacing[4], // 16px
+ lg: spacing[6], // 24px
+ xl: spacing[8], // 32px
+ },
+
+ // 布局间距
+ layout: {
+ sm: spacing[4], // 16px
+ md: spacing[8], // 32px
+ lg: spacing[12], // 48px
+ xl: spacing[16], // 64px
+ },
+
+ // 容器宽度
+ container: {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+ },
+} as const;
+
+/**
+ * 常用尺寸(用于组件大小)
+ */
+export const sizes = {
+ // 图标尺寸
+ icon: {
+ xs: '0.75rem', // 12px
+ sm: '1rem', // 16px
+ md: '1.25rem', // 20px
+ lg: '1.5rem', // 24px
+ xl: '2rem', // 32px
+ },
+
+ // 按钮高度
+ button: {
+ sm: '2rem', // 32px
+ md: '2.5rem', // 40px
+ lg: '3rem', // 48px
+ },
+
+ // 输入框高度
+ input: {
+ sm: '2rem', // 32px
+ md: '2.5rem', // 40px
+ lg: '3rem', // 48px
+ },
+} as const;
+
+export { spacing as default };
diff --git a/src/design-system/tokens/typography.ts b/src/design-system/tokens/typography.ts
new file mode 100644
index 0000000..b1254b5
--- /dev/null
+++ b/src/design-system/tokens/typography.ts
@@ -0,0 +1,145 @@
+/**
+ * 字体系统设计令牌
+ *
+ * 定义字体家族、字号、行高和字重
+ */
+
+/**
+ * 字体家族
+ */
+export const fontFamily = {
+ sans: [
+ 'var(--font-geist-sans)',
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ 'system-ui',
+ 'sans-serif',
+ ],
+ mono: [
+ 'var(--font-geist-mono)',
+ 'ui-monospace',
+ 'SFMono-Regular',
+ 'Monaco',
+ 'Consolas',
+ 'monospace',
+ ],
+} as const;
+
+/**
+ * 字体大小和行高
+ */
+export const fontSize = {
+ xs: ['0.75rem', { lineHeight: '1rem' }], // 12px / 16px
+ sm: ['0.875rem', { lineHeight: '1.25rem' }], // 14px / 20px
+ base: ['1rem', { lineHeight: '1.5rem' }], // 16px / 24px
+ lg: ['1.125rem', { lineHeight: '1.75rem' }], // 18px / 28px
+ xl: ['1.25rem', { lineHeight: '1.75rem' }], // 20px / 28px
+ '2xl': ['1.5rem', { lineHeight: '2rem' }], // 24px / 32px
+ '3xl': ['1.875rem', { lineHeight: '2.25rem' }], // 30px / 36px
+ '4xl': ['2.25rem', { lineHeight: '2.5rem' }], // 36px / 40px
+ '5xl': ['3rem', { lineHeight: '1' }], // 48px / 48px
+ '6xl': ['3.75rem', { lineHeight: '1' }], // 60px / 60px
+ '7xl': ['4.5rem', { lineHeight: '1' }], // 72px / 72px
+ '8xl': ['6rem', { lineHeight: '1' }], // 96px / 96px
+ '9xl': ['8rem', { lineHeight: '1' }], // 128px / 128px
+} as const;
+
+/**
+ * 字重
+ */
+export const fontWeight = {
+ thin: '100',
+ extralight: '200',
+ light: '300',
+ normal: '400',
+ medium: '500',
+ semibold: '600',
+ bold: '700',
+ extrabold: '800',
+ black: '900',
+} as const;
+
+/**
+ * 字母间距
+ */
+export const letterSpacing = {
+ tighter: '-0.05em',
+ tight: '-0.025em',
+ normal: '0em',
+ wide: '0.025em',
+ wider: '0.05em',
+ widest: '0.1em',
+} as const;
+
+/**
+ * 语义化排版
+ */
+export const typography = {
+ // 标题
+ h1: {
+ fontSize: fontSize['3xl'][0],
+ fontWeight: fontWeight.bold,
+ lineHeight: fontSize['3xl'][1].lineHeight,
+ },
+ h2: {
+ fontSize: fontSize['2xl'][0],
+ fontWeight: fontWeight.semibold,
+ lineHeight: fontSize['2xl'][1].lineHeight,
+ },
+ h3: {
+ fontSize: fontSize.xl[0],
+ fontWeight: fontWeight.semibold,
+ lineHeight: fontSize.xl[1].lineHeight,
+ },
+ h4: {
+ fontSize: fontSize.lg[0],
+ fontWeight: fontWeight.semibold,
+ lineHeight: fontSize.lg[1].lineHeight,
+ },
+ h5: {
+ fontSize: fontSize.base[0],
+ fontWeight: fontWeight.medium,
+ lineHeight: fontSize.base[1].lineHeight,
+ },
+ h6: {
+ fontSize: fontSize.sm[0],
+ fontWeight: fontWeight.medium,
+ lineHeight: fontSize.sm[1].lineHeight,
+ },
+
+ // 正文
+ body: {
+ fontSize: fontSize.base[0],
+ fontWeight: fontWeight.normal,
+ lineHeight: fontSize.base[1].lineHeight,
+ },
+ 'body-sm': {
+ fontSize: fontSize.sm[0],
+ fontWeight: fontWeight.normal,
+ lineHeight: fontSize.sm[1].lineHeight,
+ },
+ 'body-lg': {
+ fontSize: fontSize.lg[0],
+ fontWeight: fontWeight.normal,
+ lineHeight: fontSize.lg[1].lineHeight,
+ },
+
+ // 标签/说明
+ label: {
+ fontSize: fontSize.sm[0],
+ fontWeight: fontWeight.medium,
+ lineHeight: fontSize.sm[1].lineHeight,
+ },
+ caption: {
+ fontSize: fontSize.xs[0],
+ fontWeight: fontWeight.normal,
+ lineHeight: fontSize.xs[1].lineHeight,
+ },
+
+ // 代码
+ code: {
+ fontSize: fontSize.sm[0],
+ fontWeight: fontWeight.normal,
+ fontFamily: fontFamily.mono.join(', '),
+ },
+} as const;
diff --git a/tailwind.config.ts b/tailwind.config.ts
new file mode 100644
index 0000000..e8f0957
--- /dev/null
+++ b/tailwind.config.ts
@@ -0,0 +1,233 @@
+import type { Config } from "tailwindcss";
+
+/**
+ * Tailwind CSS 配置
+ *
+ * 集成 Design System 设计令牌到 Tailwind 工具类
+ */
+const config: Config = {
+ content: [
+ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
+ ],
+ theme: {
+ extend: {
+ // 颜色系统
+ colors: {
+ // 主色 - Teal
+ primary: {
+ 50: '#f0f9f8',
+ 100: '#e0f2f0',
+ 200: '#bce6e1',
+ 300: '#8dd4cc',
+ 400: '#5ec2b7',
+ 500: '#35786f',
+ 600: '#2a605b',
+ 700: '#1f4844',
+ 800: '#183835',
+ 900: '#122826',
+ 950: '#0a1413',
+ },
+ // 中性色
+ gray: {
+ 50: '#f9fafb',
+ 100: '#f3f4f6',
+ 200: '#e5e7eb',
+ 300: '#d1d5db',
+ 400: '#9ca3af',
+ 500: '#6b7280',
+ 600: '#4b5563',
+ 700: '#374151',
+ 800: '#1f2937',
+ 900: '#111827',
+ 950: '#030712',
+ },
+ // 语义色 - Success
+ success: {
+ 50: '#f0fdf4',
+ 100: '#dcfce7',
+ 200: '#bbf7d0',
+ 300: '#86efac',
+ 400: '#4ade80',
+ 500: '#22c55e',
+ 600: '#16a34a',
+ 700: '#15803d',
+ 800: '#166534',
+ 900: '#14532d',
+ 950: '#052e16',
+ },
+ // 语义色 - Warning
+ warning: {
+ 50: '#fffbeb',
+ 100: '#fef3c7',
+ 200: '#fde68a',
+ 300: '#fcd34d',
+ 400: '#fbbf24',
+ 500: '#f59e0b',
+ 600: '#d97706',
+ 700: '#b45309',
+ 800: '#92400e',
+ 900: '#78350f',
+ 950: '#451a03',
+ },
+ // 语义色 - Error
+ error: {
+ 50: '#fef2f2',
+ 100: '#fee2e2',
+ 200: '#fecaca',
+ 300: '#fca5a5',
+ 400: '#f87171',
+ 500: '#ef4444',
+ 600: '#dc2626',
+ 700: '#b91c1c',
+ 800: '#991b1b',
+ 900: '#7f1d1d',
+ 950: '#450a0a',
+ },
+ // 语义色 - Info
+ info: {
+ 50: '#eff6ff',
+ 100: '#dbeafe',
+ 200: '#bfdbfe',
+ 300: '#93c5fd',
+ 400: '#60a5fa',
+ 500: '#3b82f6',
+ 600: '#2563eb',
+ 700: '#1d4ed8',
+ 800: '#1e40af',
+ 900: '#1e3a8a',
+ 950: '#172554',
+ },
+ // 语义别名
+ background: 'var(--background)',
+ foreground: 'var(--foreground)',
+ },
+
+ // 间距系统(基于 8pt 网格)
+ spacing: {
+ 18: '4.5rem',
+ 88: '22rem',
+ 128: '32rem',
+ },
+
+ // 字体家族
+ fontFamily: {
+ sans: [
+ 'var(--font-geist-sans)',
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ 'system-ui',
+ 'sans-serif',
+ ],
+ mono: [
+ 'var(--font-geist-mono)',
+ 'ui-monospace',
+ 'SFMono-Regular',
+ 'Monaco',
+ 'Consolas',
+ 'monospace',
+ ],
+ },
+
+ // 字体大小和行高
+ fontSize: {
+ xs: ['0.75rem', { lineHeight: '1rem' }],
+ sm: ['0.875rem', { lineHeight: '1.25rem' }],
+ base: ['1rem', { lineHeight: '1.5rem' }],
+ lg: ['1.125rem', { lineHeight: '1.75rem' }],
+ xl: ['1.25rem', { lineHeight: '1.75rem' }],
+ '2xl': ['1.5rem', { lineHeight: '2rem' }],
+ '3xl': ['1.875rem', { lineHeight: '2.25rem' }],
+ '4xl': ['2.25rem', { lineHeight: '2.5rem' }],
+ '5xl': ['3rem', { lineHeight: '1' }],
+ '6xl': ['3.75rem', { lineHeight: '1' }],
+ '7xl': ['4.5rem', { lineHeight: '1' }],
+ '8xl': ['6rem', { lineHeight: '1' }],
+ '9xl': ['8rem', { lineHeight: '1' }],
+ },
+
+ // 字重
+ fontWeight: {
+ thin: '100',
+ extralight: '200',
+ light: '300',
+ normal: '400',
+ medium: '500',
+ semibold: '600',
+ bold: '700',
+ extrabold: '800',
+ black: '900',
+ },
+
+ // 圆角
+ borderRadius: {
+ sm: '0.125rem',
+ DEFAULT: '0.25rem',
+ md: '0.375rem',
+ lg: '0.5rem',
+ xl: '0.75rem',
+ '2xl': '1rem',
+ '3xl': '1.5rem',
+ },
+
+ // 阴影
+ boxShadow: {
+ sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
+ DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)',
+ md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
+ lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
+ xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
+ '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
+ inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.05)',
+ // 语义阴影
+ 'primary': '0 4px 14px 0 rgba(53, 120, 111, 0.39)',
+ 'success': '0 4px 14px 0 rgba(34, 197, 94, 0.39)',
+ 'warning': '0 4px 14px 0 rgba(245, 158, 11, 0.39)',
+ 'error': '0 4px 14px 0 rgba(239, 68, 68, 0.39)',
+ 'info': '0 4px 14px 0 rgba(59, 130, 246, 0.39)',
+ },
+
+ // 容器最大宽度
+ maxWidth: {
+ 'xs': '20rem',
+ 'sm': '24rem',
+ 'md': '28rem',
+ 'lg': '32rem',
+ 'xl': '36rem',
+ '2xl': '42rem',
+ '3xl': '48rem',
+ '4xl': '56rem',
+ '5xl': '64rem',
+ '6xl': '72rem',
+ '7xl': '80rem',
+ '8xl': '88rem',
+ },
+
+ // Z-index 层级
+ zIndex: {
+ dropdown: 1000,
+ sticky: 1020,
+ fixed: 1030,
+ modalBackdrop: 1040,
+ modal: 1050,
+ popover: 1060,
+ tooltip: 1070,
+ },
+
+ // 动画时长
+ transitionDuration: {
+ '250': '250ms',
+ '350': '350ms',
+ },
+
+ // 断点
+ screens: {
+ 'xs': '475px',
+ },
+ },
+ },
+ plugins: [],
+};
+
+export default config;