From ad573318a7b8cfe2a2715da0912e9c2b9480bfc5 Mon Sep 17 00:00:00 2001 From: SolitudeRA Date: Tue, 20 Jan 2026 06:40:19 +0900 Subject: [PATCH] ai: update cline rules and docs --- .clinerules | 142 ++++++++++++++++++++--- docs/cline/README.md | 16 ++- docs/cline/TASK_ADD_PAGE.md | 144 ++++++++++++++++++++--- docs/cline/TASK_API_CHANGE.md | 124 +++++++++++++++++--- docs/cline/TASK_NEW_COMPONENT.md | 188 +++++++++++++++++++++++++++---- 5 files changed, 537 insertions(+), 77 deletions(-) diff --git a/.clinerules b/.clinerules index 4cfa637..762dedf 100644 --- a/.clinerules +++ b/.clinerules @@ -5,15 +5,20 @@ 这是一个基于 **Astro + React + TypeScript + TailwindCSS** 的个人博客前端项目,与 **Ghost CMS** 集成作为 headless CMS。 ### 技术栈 -- **框架**: Astro 5.x (静态站点生成) -- **UI 库**: React 19.x -- **类型系统**: TypeScript 5.x (严格模式) -- **样式**: TailwindCSS 4.x + Emotion CSS-in-JS -- **状态管理**: Jotai -- **动画**: Motion (Framer Motion) -- **CMS**: Ghost Content API -- **测试**: Vitest -- **包管理器**: pnpm + +| 类别 | 技术 | 版本 | +|------|------|------| +| **框架** | Astro | 5.x | +| **UI 库** | React | 19.x | +| **类型系统** | TypeScript | 5.x (严格模式) | +| **样式** | TailwindCSS + Emotion CSS-in-JS | 4.x | +| **UI 组件** | Radix UI | 最新 | +| **状态管理** | Jotai | 2.x | +| **动画** | Motion (Framer Motion) | 12.x | +| **代码高亮** | Shiki | 3.x | +| **CMS** | Ghost Content API | - | +| **测试** | Vitest | 4.x | +| **包管理器** | pnpm | 10.x | --- @@ -25,22 +30,50 @@ src/ │ ├── adapters/ # 数据转换器 (Ghost → 前端格式) │ ├── clients/ # API 客户端 │ ├── config/ # 环境变量配置 -│ ├── ghost/ # Ghost API 模块 (posts, settings) -│ ├── utils/ # 工具函数 (缓存, 错误处理) +│ ├── ghost/ # Ghost API 模块 (posts, settings, types) +│ ├── utils/ # 工具函数 +│ │ ├── cache.ts # 请求缓存 +│ │ ├── codeHighlight.ts # Shiki 代码高亮 +│ │ ├── errorHandlers.ts # 错误处理 +│ │ ├── sanitize.ts # HTML 清理 (DOMPurify) +│ │ └── url.ts # URL 工具 │ └── __tests__/ # API 测试 ├── components/ # UI 组件 -│ ├── common/ # 通用组件 (Button, Card, Badge 等) +│ ├── common/ # 通用组件 (Button, Card, Badge, Tooltip 等) +│ │ └── lib/ # 组件工具函数 (cn, utils) │ ├── home/ # 首页组件 -│ ├── i18n/ # 国际化组件 -│ ├── layout/ # 布局组件 (Navbar, Dock) +│ ├── i18n/ # 国际化组件 (FallbackNotice, LanguageSwitcher) +│ ├── layout/ # 布局组件 +│ │ ├── dock/ # Dock 导航栏 +│ │ └── navbar/ # 顶部导航栏 │ ├── pages/ # 页面专用组件 +│ │ ├── about/ +│ │ ├── contact/ +│ │ └── privacy-policy/ │ ├── posts/ # 文章展示组件 -│ └── utils/ # 工具组件 +│ │ ├── detail/ # 文章详情组件 +│ │ └── view/ # 文章列表视图组件 +│ └── utils/ # 工具组件 (GoogleAnalytics 等) ├── layouts/ # Astro 布局 -├── lib/ # 核心库 (i18n) +│ └── base/ # 基础布局 +├── lib/ # 核心库 +│ └── i18n.ts # 国际化工具函数 ├── pages/ # Astro 页面路由 +│ ├── [lang]/ # 多语言动态路由 +│ │ └── p/ # 文章详情页 +│ └── posts/ # 文章页面 ├── stores/ # Jotai 状态管理 -├── styles/ # 全局样式 +├── styles/ # 样式系统 +│ ├── index.css # 样式入口 +│ ├── tailwind-settings.css # TailwindCSS 配置 +│ ├── modules/ # 组件模块样式 +│ │ └── article/ # 文章样式 +│ │ └── content/ # 文章内容样式 +│ ├── theme/ # 主题系统 +│ │ ├── base.css # 主题基础 +│ │ ├── dark/ # 暗色主题 +│ │ └── light/ # 亮色主题 +│ └── utilities/ # 工具类 └── types/ # TypeScript 类型定义 ``` @@ -64,6 +97,47 @@ src/ import { getPosts } from '@api/ghost/posts'; import { Button } from '@components/common'; import { postViewAtom } from '@stores/postViewAtom'; +import { cn } from '@components/common/lib/utils'; +``` + +--- + +## 样式系统 + +### 主题架构 + +``` +src/styles/theme/ +├── base.css # 共享变量和基础样式 +├── dark/ # 暗色主题 +│ ├── semantic.css # 语义化颜色变量 +│ ├── text.css # 文字颜色 +│ ├── page.css # 页面背景 +│ ├── navbar.css # 导航栏 +│ ├── dock.css # Dock 栏 +│ ├── card.css # 卡片 +│ ├── article.css # 文章 +│ └── toc.css # 目录 +└── light/ # 亮色主题 (同结构) +``` + +### 样式优先级 + +1. **TailwindCSS 类** (推荐) - 使用 `className` +2. **CSS 变量** - 使用主题变量 (如 `var(--background)`) +3. **Emotion CSS-in-JS** - 仅在需要动态样式时使用 + +### 常用 CSS 变量 + +```css +/* 语义化颜色 */ +--background /* 页面背景 */ +--foreground /* 主要文字 */ +--muted /* 次要背景 */ +--muted-foreground /* 次要文字 */ +--primary /* 主要强调色 */ +--accent /* 次要强调色 */ +--border /* 边框 */ ``` --- @@ -83,6 +157,14 @@ import { postViewAtom } from '@stores/postViewAtom'; - `/{lang}/` - 文章列表 - `/{lang}/p/{key}` - 文章详情 +### 常用 i18n 函数 + +```typescript +import { LOCALES, type Locale, DEFAULT_LOCALE } from '@lib/i18n'; +import { filterPostsByLocale, extractLocaleFromTags } from '@lib/i18n'; +import { buildPostPath, buildLocalePath } from '@lib/i18n'; +``` + --- ## 组件开发规范 @@ -100,6 +182,26 @@ import { postViewAtom } from '@stores/postViewAtom'; - React 组件: `PascalCase.tsx` - 测试文件: `*.test.ts` (单元) / `*.integration.test.ts` (集成) +### 常用 UI 库 + +```typescript +// Radix UI (无样式原语组件) +import * as Tooltip from '@radix-ui/react-tooltip'; +import * as Switch from '@radix-ui/react-switch'; +import { Slot } from '@radix-ui/react-slot'; + +// 图标 +import { Menu, X, Sun, Moon } from 'lucide-react'; +import { FaGithub, FaTwitter } from 'react-icons/fa'; + +// 动画 +import { motion, AnimatePresence } from 'motion/react'; + +// 样式工具 +import { cn } from '@components/common/lib/utils'; +import { cva, type VariantProps } from 'class-variance-authority'; +``` + --- ## 常用标签前缀 @@ -109,6 +211,7 @@ import { postViewAtom } from '@stores/postViewAtom'; | `type-` | 文章类型 | `type-article`, `type-gallery`, `type-video`, `type-music` | | `category-` | 分类 | `category-tech`, `category-life` | | `series-` | 系列 | `series-astro-tutorial` | +| `hash-` | 内部标签 (不显示) | `hash-lang-zh`, `hash-i18n-intro` | --- @@ -121,8 +224,11 @@ pnpm preview # 预览生产构建 pnpm test # 运行测试 (watch 模式) pnpm test:unit # 仅运行单元测试 pnpm test:integration # 仅运行集成测试 +pnpm test:run # 运行全部测试 (单次) +pnpm test:coverage # 生成测试覆盖率报告 pnpm format # 格式化代码 pnpm astro check # TypeScript 类型检查 +pnpm astro sync # 生成 Astro 类型定义 ``` --- @@ -143,6 +249,8 @@ pnpm astro check # TypeScript 类型检查 2. **类型生成**: schema 变更后运行 `pnpm astro sync` 3. **测试**: 集成测试需要配置真实的 Ghost API 4. **图片**: 远程图片域名需在 `astro.config.mjs` 中配置 +5. **HTML 清理**: 使用 `sanitize.ts` 处理用户内容 +6. **代码高亮**: 使用 `codeHighlight.ts` 处理代码块 --- diff --git a/docs/cline/README.md b/docs/cline/README.md index 20aebea..5377981 100644 --- a/docs/cline/README.md +++ b/docs/cline/README.md @@ -18,11 +18,11 @@ docs/cline/ ## 📋 任务上下文文件 -| 文件 | 用途 | -| ----------------------- | ---------------------- | -| `TASK_NEW_COMPONENT.md` | 创建新 UI 组件时的参考 | -| `TASK_ADD_PAGE.md` | 添加新页面路由时的参考 | -| `TASK_API_CHANGE.md` | 修改 API 层时的参考 | +| 文件 | 用途 | +| ----------------------- | --------------------------------- | +| `TASK_NEW_COMPONENT.md` | 创建新 UI 组件 (React/Astro) | +| `TASK_ADD_PAGE.md` | 添加新页面路由 (多语言/RSS) | +| `TASK_API_CHANGE.md` | 修改 API 层 (Ghost API/缓存/测试) | ## 📄 代码模板 @@ -70,3 +70,9 @@ docs/cline/ [注意事项] ``` + +## 📚 相关链接 + +- [.clinerules](../../.clinerules) - 项目规则 (主入口) +- [docs/DEVELOPMENT.md](../DEVELOPMENT.md) - 开发指南 +- [docs/ARCHITECTURE.md](../ARCHITECTURE.md) - 架构设计 diff --git a/docs/cline/TASK_ADD_PAGE.md b/docs/cline/TASK_ADD_PAGE.md index 4b175ce..f0227d2 100644 --- a/docs/cline/TASK_ADD_PAGE.md +++ b/docs/cline/TASK_ADD_PAGE.md @@ -12,11 +12,15 @@ src/pages/ ├── index.astro # 首页 (重定向到默认语言) ├── contact.astro # 无多语言的静态页面 +├── rss.xml.ts # 全站 RSS 订阅 +├── posts/ # 文章相关页面 +│ └── [post].astro # 文章详情 (旧路由) └── [lang]/ # 多语言动态路由 ├── index.astro # /{lang}/ 首页 - ├── about.astro # /{lang}/about - ├── post-view.astro # /{lang}/post-view - ├── privacy-policy.astro # /{lang}/privacy-policy + ├── about.astro # /{lang}/about 关于页 + ├── post-view.astro # /{lang}/post-view 文章列表 + ├── privacy-policy.astro # /{lang}/privacy-policy 隐私政策 + ├── rss.xml.ts # /{lang}/rss.xml 语言专属 RSS └── p/ └── [key].astro # /{lang}/p/{key} 文章详情 ``` @@ -28,6 +32,7 @@ src/pages/ | `pages/foo.astro` | `/foo` | | `pages/[lang]/foo.astro` | `/zh/foo`, `/ja/foo`, `/en/foo` | | `pages/[lang]/bar/[id].astro` | `/zh/bar/123` | +| `pages/rss.xml.ts` | `/rss.xml` | --- @@ -53,10 +58,15 @@ export async function getStaticPaths() { } const { lang } = Astro.props; + +// 页面元数据 +const siteTitle = '页面标题'; +const coverImageUrl = null; // 封面图片 URL,可为 URL | string | null --- - -
+ +
+

{siteTitle}

@@ -67,7 +77,12 @@ const { lang } = Astro.props; ```astro --- import { getPosts } from '@api/ghost/posts'; -import { filterPostsByLocale } from '@lib/i18n'; +import { filterPostsByLocale, LOCALES, type Locale } from '@lib/i18n'; + +interface Props { + lang: Locale; + posts: Post[]; +} export async function getStaticPaths() { const allPosts = await getPosts(); @@ -76,10 +91,15 @@ export async function getStaticPaths() { const localizedPosts = filterPostsByLocale(allPosts, lang); return { params: { lang }, - props: { lang, posts: localizedPosts }, + props: { + lang, + posts: localizedPosts.map((p) => p.post), + }, }; }); } + +const { lang, posts } = Astro.props; --- ``` @@ -89,7 +109,12 @@ export async function getStaticPaths() { --- // src/pages/[lang]/category/[slug].astro import { getPosts } from '@api/ghost/posts'; -import { LOCALES } from '@lib/i18n'; +import { LOCALES, type Locale } from '@lib/i18n'; + +interface Props { + lang: Locale; + post: Post; +} export async function getStaticPaths() { const posts = await getPosts(); @@ -106,9 +131,51 @@ export async function getStaticPaths() { return paths; } + +const { lang, post } = Astro.props; --- ``` +### 4. 创建 RSS 订阅页面 + +```typescript +// src/pages/[lang]/rss.xml.ts +import rss from '@astrojs/rss'; +import type { APIContext } from 'astro'; +import { getPosts } from '@api/ghost/posts'; +import { + LOCALES, + type Locale, + filterPostsByLocale, + buildPostPath, +} from '@lib/i18n'; + +export async function getStaticPaths() { + return LOCALES.map((lang) => ({ + params: { lang }, + props: { lang }, + })); +} + +export async function GET(context: APIContext) { + const lang = context.params.lang as Locale; + const allPosts = await getPosts(); + const localizedPosts = filterPostsByLocale(allPosts, lang); + + return rss({ + title: `Site Title - ${lang}`, + description: 'Site description', + site: context.site!, + items: localizedPosts.map(({ post }) => ({ + title: post.title, + pubDate: new Date(post.published_at), + description: post.excerpt, + link: buildPostPath(lang, post.slug), + })), + }); +} +``` + --- ## 📁 相关文件 @@ -122,17 +189,33 @@ export async function getStaticPaths() { --- -## 🔧 SEO 配置 - -### 添加 meta 标签 +## 🔧 BaseLayout Props -```astro - +```typescript +interface BaseLayoutProps { + siteTitle?: string; // 页面标题 + coverImageUrl?: URL | string | null; // 封面图片 + locale?: Locale; // 当前语言 +} ``` -### 多语言 hreflang (自动处理) +--- -BaseLayout 会自动生成 hreflang 标签。 +## 🌐 多语言相关函数 + +```typescript +import { + LOCALES, // ['zh', 'ja', 'en'] + type Locale, // 'zh' | 'ja' | 'en' + DEFAULT_LOCALE, // 'zh' + LOCALE_NAMES, // { zh: '中文', ja: '日本語', en: 'English' } + LOCALE_HTML_LANG, // { zh: 'zh-CN', ja: 'ja', en: 'en' } + filterPostsByLocale, // 过滤多语言文章 + buildPostPath, // 构建文章路径 + buildLocalePath, // 构建语言路径 + getUIText, // 获取 UI 翻译文本 +} from '@lib/i18n'; +``` --- @@ -142,3 +225,34 @@ BaseLayout 会自动生成 hreflang 标签。 2. **类型生成**: 新页面后运行 `pnpm astro sync` 3. **路由冲突**: 避免静态路由和动态路由冲突 4. **构建测试**: 运行 `pnpm build` 验证静态生成 +5. **SEO**: BaseLayout 会自动生成 hreflang 标签 + +--- + +## 💡 常见模式 + +### 重定向到默认语言 + +```astro +--- +// src/pages/index.astro +import { DEFAULT_LOCALE } from '@lib/i18n'; + +return Astro.redirect(`/${DEFAULT_LOCALE}/`); +--- +``` + +### 404 页面 + +```astro +--- +// src/pages/404.astro +import BaseLayout from '@layouts/base/BaseLayout.astro'; +--- + + +
+

404 - 页面不存在

+
+
+``` diff --git a/docs/cline/TASK_API_CHANGE.md b/docs/cline/TASK_API_CHANGE.md index e7c52eb..6efb530 100644 --- a/docs/cline/TASK_API_CHANGE.md +++ b/docs/cline/TASK_API_CHANGE.md @@ -11,20 +11,39 @@ ``` src/api/ ├── config/ -│ └── env.ts # 环境变量配置 +│ └── env.ts # 环境变量配置 ├── clients/ -│ └── ghost.ts # Axios 客户端实例 +│ └── ghost.ts # Axios 客户端实例 ├── ghost/ -│ ├── posts.ts # 文章 API -│ ├── settings.ts # 站点设置 API -│ └── types.ts # 类型定义 +│ ├── posts.ts # 文章 API +│ ├── settings.ts # 站点设置 API +│ └── types.ts # 类型定义 ├── adapters/ -│ └── ghost.ts # 数据转换器 +│ └── ghost.ts # 数据转换器 ├── utils/ -│ ├── cache.ts # 请求缓存 -│ ├── errorHandlers.ts # 错误处理 -│ └── url.ts # URL 工具 -└── __tests__/ # 测试文件 +│ ├── cache.ts # 请求缓存 +│ ├── codeHighlight.ts # Shiki 代码高亮 +│ ├── errorHandlers.ts # 错误处理 +│ ├── sanitize.ts # HTML 清理 (DOMPurify) +│ └── url.ts # URL 工具 +└── __tests__/ # 测试文件 + ├── setup.ts # 单元测试 setup + ├── adapters/ + │ └── ghost.test.ts # 适配器测试 + ├── clients/ + │ ├── ghost.test.ts # 客户端单元测试 + │ └── ghost.integration.test.ts # 客户端集成测试 + ├── ghost/ + │ ├── posts.test.ts # 文章 API 单元测试 + │ ├── posts.integration.test.ts # 文章 API 集成测试 + │ ├── settings.test.ts # 设置 API 单元测试 + │ └── settings.integration.test.ts # 设置 API 集成测试 + ├── lib/ + │ └── i18n.test.ts # i18n 工具测试 + └── utils/ + ├── cache.test.ts # 缓存测试 + ├── errorHandlers.test.ts # 错误处理测试 + └── sanitize.test.ts # HTML 清理测试 ``` ### 数据流向 @@ -103,7 +122,7 @@ export async function getPosts(options?: GetPostsOptions) { } ``` -### 3. 添加缓存 +### 3. 使用缓存 ```typescript import { withCache, clearCache } from '@api/utils/cache'; @@ -115,6 +134,24 @@ const data = await withCache('cache-key', fetchFunction); clearCache('cache-key'); ``` +### 4. 使用 HTML 清理 + +```typescript +import { sanitizeHtml } from '@api/utils/sanitize'; + +// 清理 HTML 内容 +const cleanHtml = sanitizeHtml(rawHtml); +``` + +### 5. 使用代码高亮 + +```typescript +import { highlightCode } from '@api/utils/codeHighlight'; + +// 高亮代码块 +const highlightedHtml = await highlightCode(html); +``` + --- ## 🧪 测试要求 @@ -123,13 +160,24 @@ clearCache('cache-key'); ```typescript // src/api/__tests__/ghost/newResource.test.ts -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { getNewResources } from '@api/ghost/newResource'; +// Mock 依赖 vi.mock('@api/clients/ghost'); describe('getNewResources', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + it('应该返回资源列表', async () => { + const mockData = [{ id: '1', name: 'Test' }]; + // 设置 mock 返回值 // ... + + const result = await getNewResources(); + expect(result).toEqual(mockData); }); }); ``` @@ -138,9 +186,15 @@ describe('getNewResources', () => { ```typescript // src/api/__tests__/ghost/newResource.integration.test.ts +import { describe, it, expect } from 'vitest'; +import { getNewResources } from '@api/ghost/newResource'; + describe('Integration: getNewResources', () => { it('应该从真实 API 获取数据', async () => { // 需要配置 .env + const result = await getNewResources(); + expect(result).toBeDefined(); + expect(Array.isArray(result)).toBe(true); }); }); ``` @@ -148,9 +202,11 @@ describe('Integration: getNewResources', () => { ### 运行测试 ```bash -pnpm test:unit # 单元测试 -pnpm test:integration # 集成测试 -pnpm test:run # 全部测试 +pnpm test # watch 模式 +pnpm test:unit # 单元测试 +pnpm test:integration # 集成测试 +pnpm test:run # 全部测试 (单次) +pnpm test:coverage # 测试覆盖率 ``` --- @@ -163,6 +219,8 @@ pnpm test:run # 全部测试 | `src/api/clients/ghost.ts` | Axios 实例 | | `src/api/utils/cache.ts` | 缓存工具 | | `src/api/utils/errorHandlers.ts` | 错误处理 | +| `src/api/utils/sanitize.ts` | HTML 清理 | +| `src/api/utils/codeHighlight.ts` | 代码高亮 | | `docs/cline/templates/unit-test.test.ts` | 测试模板 | --- @@ -178,6 +236,7 @@ pnpm test:run # 全部测试 | `/posts/slug/:slug` | 按 slug 获取 | | `/tags` | 标签列表 | | `/settings` | 站点设置 | +| `/authors` | 作者列表 | ### 过滤语法 @@ -185,11 +244,17 @@ pnpm test:run # 全部测试 // 标签过滤 filter: 'tag:hash-lang-zh'; -// 多条件 +// 多条件 (AND) filter: 'tag:hash-lang-zh+featured:true'; +// 多条件 (OR) +filter: 'tag:category-tech,tag:category-life'; + // 日期过滤 filter: 'published_at:>2024-01-01'; + +// 排除 +filter: 'tag:-hash-lang-en'; ``` ### include 参数 @@ -198,6 +263,31 @@ filter: 'published_at:>2024-01-01'; include: 'tags,authors'; // 包含标签和作者信息 ``` +### 分页参数 + +```typescript +{ + page: 1, + limit: 10, + // 或使用 all + limit: 'all', +} +``` + +--- + +## 🏷️ 标签处理系统 + +```typescript +// src/api/adapters/ghost.ts +const TAG_PREFIXES = { + TYPE: 'type-', // 文章类型: type-article, type-gallery + CATEGORY: 'category-', // 分类: category-tech + SERIES: 'series-', // 系列: series-tutorial + HASH: 'hash-', // 内部标签: hash-lang-zh, hash-i18n-key +} as const; +``` + --- ## ⚠️ 注意事项 @@ -207,3 +297,5 @@ include: 'tags,authors'; // 包含标签和作者信息 3. **错误处理**: 使用 `errorHandlers.ts` 统一处理 4. **测试覆盖**: 新功能必须有单元测试 5. **文档更新**: 重大变更更新 ARCHITECTURE.md +6. **HTML 清理**: 用户内容必须使用 `sanitize.ts` 处理 +7. **环境变量**: 新增环境变量需更新 `.env.example` diff --git a/docs/cline/TASK_NEW_COMPONENT.md b/docs/cline/TASK_NEW_COMPONENT.md index 0577b4d..c1b3761 100644 --- a/docs/cline/TASK_NEW_COMPONENT.md +++ b/docs/cline/TASK_NEW_COMPONENT.md @@ -10,15 +10,21 @@ ``` src/components/ -├── common/ # 通用基础组件 (Button, Card, Badge) -├── home/ # 首页专用组件 -├── layout/ # 布局组件 (Navbar, Dock) -├── posts/ # 文章展示组件 -│ ├── view/ # 列表视图组件 -│ └── detail/ # 详情页组件 -└── pages/ # 页面专用组件 - ├── about/ - └── contact/ +├── common/ # 通用基础组件 (Button, Card, Badge, Tooltip, Switch 等) +│ └── lib/ # 组件工具函数 (cn, utils) +├── home/ # 首页专用组件 +├── i18n/ # 国际化组件 (FallbackNotice, PostLanguageSwitcher) +├── layout/ # 布局组件 +│ ├── dock/ # Dock 底部导航栏 +│ └── navbar/ # 顶部导航栏 +├── pages/ # 页面专用组件 +│ ├── about/ +│ ├── contact/ +│ └── privacy-policy/ +├── posts/ # 文章展示组件 +│ ├── detail/ # 文章详情组件 (PostContent, PostHeader, TableOfContents) +│ └── view/ # 列表视图组件 (PostViewCarousel, PostViewPagination) +└── utils/ # 工具组件 (GoogleAnalytics 等) ``` ### 文件命名 @@ -94,7 +100,13 @@ import MyComponent from '@components/common/MyComponent';
``` -**方案 B: Emotion CSS-in-JS** +**方案 B: CSS 变量 + Tailwind** + +```tsx +
+``` + +**方案 C: Emotion CSS-in-JS (仅需要动态样式时)** ```tsx import { css } from '@emotion/react'; @@ -109,30 +121,158 @@ const style = css` --- +## 🔧 常用依赖 + +### Radix UI (无样式原语组件) + +```typescript +import * as Tooltip from '@radix-ui/react-tooltip'; +import * as Switch from '@radix-ui/react-switch'; +import { Slot } from '@radix-ui/react-slot'; +``` + +**Tooltip 使用示例:** + +```tsx +import * as Tooltip from '@radix-ui/react-tooltip'; + +export function MyTooltip({ children, content }: Props) { + return ( + + + {children} + + + {content} + + + + + + ); +} +``` + +### 图标库 + +```typescript +// Lucide React (推荐) +import { Menu, X, Sun, Moon, ChevronRight } from 'lucide-react'; + +// React Icons (多图标库合集) +import { FaGithub, FaTwitter } from 'react-icons/fa'; +import { SiDiscord } from 'react-icons/si'; +``` + +### 动画 (Motion) + +```typescript +import { motion, AnimatePresence } from 'motion/react'; + +// 基础动画 + + 内容 + + +// 条件渲染动画 + + {isVisible && ( + + 内容 + + )} + +``` + +### 样式工具 + +```typescript +// cn() - 合并 className +import { cn } from '@components/common/lib/utils'; + +
+ +// cva - Class Variance Authority (变体组件) +import { cva, type VariantProps } from 'class-variance-authority'; + +const buttonVariants = cva('rounded-lg font-medium', { + variants: { + variant: { + default: 'bg-primary text-primary-foreground', + outline: 'border border-input bg-background', + }, + size: { + sm: 'h-8 px-3 text-sm', + md: 'h-10 px-4', + lg: 'h-12 px-6 text-lg', + }, + }, + defaultVariants: { + variant: 'default', + size: 'md', + }, +}); +``` + +### 状态管理 (Jotai) + +```typescript +import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; + +// 定义 atom +const countAtom = atom(0); + +// 读取和写入 +const [count, setCount] = useAtom(countAtom); + +// 仅读取 +const count = useAtomValue(countAtom); + +// 仅写入 +const setCount = useSetAtom(countAtom); +``` + +--- + ## 📁 相关文件 | 文件 | 用途 | | ------------------------------------------ | -------------- | | `src/components/common/lib/utils.ts` | cn() 工具函数 | -| `src/styles/theme.css` | 主题变量定义 | +| `src/styles/theme/` | 主题变量定义 | | `docs/cline/templates/react-component.tsx` | React 组件模板 | -| `docs/cline/templates/astro-page.astro` | Astro 页面模板 | --- -## 🔧 常用依赖 +## ⚠️ 注意事项 -```typescript -// 样式工具 -import { cn } from '@components/common/lib/utils'; +1. **客户端指令**: React 交互组件需要在 Astro 中使用 `client:*` 指令 -// 动画 -import { motion, AnimatePresence } from 'motion/react'; + ```astro + + + + + + + ``` -// 状态管理 -import { useAtom, useAtomValue, useSetAtom } from 'jotai'; +2. **导出方式**: 确保使用正确的导出方式 + - 默认导出: `export default function Component()` + - 命名导出: `export function Component()` -// 图标 -import { IconName } from 'lucide-react'; -import { IconName } from 'react-icons/xx'; -``` +3. **类型定义**: 始终为 Props 定义 TypeScript 接口 + +4. **可访问性**: 使用语义化标签,添加 `role` 和 `aria-*` 属性