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'; + +