315 lines
10 KiB
Markdown
315 lines
10 KiB
Markdown
|
|
# CLAUDE.md
|
|||
|
|
|
|||
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|||
|
|
|
|||
|
|
## user rools
|
|||
|
|
每次开头请叫我哥哥
|
|||
|
|
|
|||
|
|
请严格遵守ui设计规范,使用响应式布局设计,不要硬编码
|
|||
|
|
请使用项目里面封装好的ui组件,如果没有请提醒我
|
|||
|
|
## Project Overview
|
|||
|
|
|
|||
|
|
React Native + Expo mobile application built on **Ignite CLI boilerplate** (v11.4.0). Uses TypeScript, React Navigation 7, and NativeWind (Tailwind CSS).
|
|||
|
|
|
|||
|
|
## Common Commands
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pnpm install # Install dependencies
|
|||
|
|
pnpm run start # Start Expo dev client
|
|||
|
|
pnpm run android # Run on Android (expo run:android)
|
|||
|
|
pnpm run ios # Run on iOS (expo run:ios)
|
|||
|
|
pnpm run compile # TypeScript type check (tsc --noEmit)
|
|||
|
|
pnpm run lint # ESLint with auto-fix
|
|||
|
|
pnpm run lint:check # ESLint without fix
|
|||
|
|
pnpm run test # Run Jest tests
|
|||
|
|
pnpm run test:watch # Run Jest in watch mode
|
|||
|
|
|
|||
|
|
# EAS Builds (requires expo-cli)
|
|||
|
|
pnpm run build:ios:sim # iOS simulator build
|
|||
|
|
pnpm run build:android:sim # Android emulator build
|
|||
|
|
pnpm run prebuild:clean # Regenerate native projects
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Architecture
|
|||
|
|
|
|||
|
|
### Directory Structure
|
|||
|
|
|
|||
|
|
- `app/` - Main application source
|
|||
|
|
- `components/` - 12个基础 UI 组件(详见下方组件列表)
|
|||
|
|
- `screens/` - Screen components connected to navigation
|
|||
|
|
- `screens/DemoShowroomScreen/demos/` - 组件演示页面(展示组件用法,非实际组件)
|
|||
|
|
- `navigators/` - React Navigation configuration (AppNavigator, DemoNavigator)
|
|||
|
|
- `context/` - React Context providers (AuthContext, ThemeContext, ToastContext)
|
|||
|
|
- `services/api/` - API layer using Apisauce (`authApi.ts` for auth, `index.ts` for base API)
|
|||
|
|
- `theme/` - Design tokens (colors, spacing, typography) and theme system
|
|||
|
|
- `i18n/` - Internationalization with i18next (8 languages: en, zh, ar, es, fr, hi, ja, ko)
|
|||
|
|
- `utils/` - Utilities including MMKV storage wrapper
|
|||
|
|
- `config/` - Environment-based configuration (dev/prod)
|
|||
|
|
- `devtools/` - Reactotron configuration
|
|||
|
|
|
|||
|
|
### Key Patterns
|
|||
|
|
|
|||
|
|
**State Management**: React Context + MMKV for persistence. No Redux.
|
|||
|
|
|
|||
|
|
**Theming**: Use `useAppTheme()` hook and `ThemedStyle<T>` pattern:
|
|||
|
|
```typescript
|
|||
|
|
const $container: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({
|
|||
|
|
backgroundColor: colors.background,
|
|||
|
|
padding: spacing.lg,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Navigation**: Two-layer structure - AppStack (auth/main screens) and DemoNavigator (bottom tabs). Type-safe routes defined in `navigationTypes.ts`. Auth screens: AuthWelcome, Login, Register, ForgotPassword. Main screens: Welcome, Profile, Settings, Security, Theme, Language, About, etc.
|
|||
|
|
|
|||
|
|
**API Layer**: Singleton `Api` class in `services/api/index.ts` using Apisauce with typed responses.
|
|||
|
|
|
|||
|
|
**Styling**: NativeWind (Tailwind CSS) configured. Use `className` prop for Tailwind styles.
|
|||
|
|
|
|||
|
|
**Authentication**: `AuthContext` provides full auth flow with step-based state machines for login, register, and password reset. Supports email/password and Google Sign-In. Uses JWT tokens stored in MMKV.
|
|||
|
|
|
|||
|
|
## UI 设计规范
|
|||
|
|
|
|||
|
|
### 参考文件
|
|||
|
|
|
|||
|
|
| 类型 | 文件路径 | 说明 |
|
|||
|
|
|------|----------|------|
|
|||
|
|
| **设计令牌** | `app/theme/colors.ts` | 颜色系统(浅色主题) |
|
|||
|
|
| | `app/theme/colorsDark.ts` | 颜色系统(深色主题) |
|
|||
|
|
| | `app/theme/spacing.ts` | 间距系统 |
|
|||
|
|
| | `app/theme/typography.ts` | 字体排版 |
|
|||
|
|
| | `app/theme/styles.ts` | 全局样式($styles.container 等) |
|
|||
|
|
| **UI 参考页面** | `app/screens/DemoCommunityScreen.tsx` | **最佳参考** - 标准页面结构、ListItem 用法、Section 标题 |
|
|||
|
|
| | `app/screens/WelcomeScreen.tsx` | useHeader hook 用法 |
|
|||
|
|
| | `app/screens/ProfileScreen.tsx` | 设置页面、用户卡片 |
|
|||
|
|
| **组件演示** | `app/screens/DemoShowroomScreen/demos/` | 所有组件的用法示例(类似 Storybook) |
|
|||
|
|
|
|||
|
|
### 设计令牌
|
|||
|
|
|
|||
|
|
**颜色语义**(定义在 `app/theme/colors.ts`):
|
|||
|
|
```typescript
|
|||
|
|
text: palette.neutral800 // 主要文本
|
|||
|
|
textDim: palette.neutral600 // 次要文本
|
|||
|
|
background: palette.neutral200 // 屏幕背景
|
|||
|
|
border: palette.neutral400 // 边框
|
|||
|
|
tint: palette.primary500 // 主色调(按钮、链接)
|
|||
|
|
separator: palette.neutral300 // 分隔线
|
|||
|
|
error: palette.angry500 // 错误
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**间距系统**(`app/theme/spacing.ts`):
|
|||
|
|
```typescript
|
|||
|
|
xxxs: 2, xxs: 4, xs: 8, sm: 12, md: 16, lg: 24, xl: 32, xxl: 48, xxxl: 64
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**字体大小**(`app/components/Text.tsx`):
|
|||
|
|
```typescript
|
|||
|
|
xxl: 36, xl: 24, lg: 20, md: 18, sm: 16(默认), xs: 14, xxs: 12
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Header 使用规范
|
|||
|
|
|
|||
|
|
**必须使用 `useHeader` hook**,不要直接在 Screen 内部放置 `<Header>` 组件。
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// ✅ 正确写法
|
|||
|
|
import { useHeader } from "@/utils/useHeader"
|
|||
|
|
|
|||
|
|
useHeader({
|
|||
|
|
title: translate("screenName:title"),
|
|||
|
|
leftIcon: "back",
|
|||
|
|
onLeftPress: () => navigation.goBack(),
|
|||
|
|
}, [])
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Screen safeAreaEdges={["bottom"]} contentContainerStyle={[$styles.container, themed($container)]}>
|
|||
|
|
{/* 内容 */}
|
|||
|
|
</Screen>
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const $container: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
|||
|
|
paddingTop: spacing.lg, // 覆盖 $styles.container 的 56px
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**原因**: Header 组件默认添加顶部安全区域 padding,直接放在 Screen 内容中会导致 Header 上方出现额外空白。`useHeader` 通过 `navigation.setOptions()` 将 Header 设置为导航层的一部分。
|
|||
|
|
|
|||
|
|
### 容器间距规范
|
|||
|
|
|
|||
|
|
`$styles.container`(定义在 `app/theme/styles.ts`):
|
|||
|
|
```typescript
|
|||
|
|
container: {
|
|||
|
|
paddingTop: spacing.lg + spacing.xl, // = 56px(为无 Header 的 Tab 页面设计)
|
|||
|
|
paddingHorizontal: spacing.lg, // = 24px
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**使用 useHeader 的页面**需在 `$container` 中覆盖 paddingTop 为 `spacing.lg` (24px)。
|
|||
|
|
|
|||
|
|
**不要**在页面内容中再添加额外的 paddingTop 或 marginTop,避免重复间距。
|
|||
|
|
|
|||
|
|
### ListItem 使用规范
|
|||
|
|
|
|||
|
|
**参考**: `app/screens/DemoCommunityScreen.tsx`
|
|||
|
|
|
|||
|
|
**默认无分隔线**,保持简洁风格:
|
|||
|
|
```typescript
|
|||
|
|
<ListItem
|
|||
|
|
tx="setting:option1"
|
|||
|
|
leftIcon="settings"
|
|||
|
|
rightIcon="caretRight"
|
|||
|
|
onPress={handlePress}
|
|||
|
|
/>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
只有**连续列表项**需要视觉分隔时,才使用 `bottomSeparator`,且**最后一项不需要**:
|
|||
|
|
```typescript
|
|||
|
|
<ListItem text="Item 1" bottomSeparator />
|
|||
|
|
<ListItem text="Item 2" bottomSeparator />
|
|||
|
|
<ListItem text="Item 3" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Section 标题规范
|
|||
|
|
|
|||
|
|
**参考**: `app/screens/DemoCommunityScreen.tsx` 的 `$sectionTitle` 样式
|
|||
|
|
|
|||
|
|
使用 `preset="subheading"` 作为 section 标题:
|
|||
|
|
```typescript
|
|||
|
|
<Text preset="subheading" tx="section:title" style={themed($sectionTitle)} />
|
|||
|
|
|
|||
|
|
const $sectionTitle: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
|||
|
|
marginTop: spacing.xxl, // 48px
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 页面结构模板
|
|||
|
|
|
|||
|
|
**有 Header 的页面**(如设置、详情页):
|
|||
|
|
```typescript
|
|||
|
|
import { useHeader } from "@/utils/useHeader"
|
|||
|
|
|
|||
|
|
export const ExampleScreen: FC<Props> = ({ navigation }) => {
|
|||
|
|
const { themed } = useAppTheme()
|
|||
|
|
|
|||
|
|
useHeader({
|
|||
|
|
title: translate("exampleScreen:title"),
|
|||
|
|
leftIcon: "back",
|
|||
|
|
onLeftPress: () => navigation.goBack(),
|
|||
|
|
}, [])
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Screen
|
|||
|
|
preset="scroll"
|
|||
|
|
safeAreaEdges={["bottom"]}
|
|||
|
|
contentContainerStyle={[$styles.container, themed($container)]}
|
|||
|
|
>
|
|||
|
|
{/* 内容 */}
|
|||
|
|
</Screen>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const $container: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
|||
|
|
paddingTop: spacing.lg,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**无 Header 的 Tab 页面**(参考 `DemoCommunityScreen.tsx`):
|
|||
|
|
```typescript
|
|||
|
|
export const ExampleTabScreen: FC<Props> = () => {
|
|||
|
|
const { themed } = useAppTheme()
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Screen preset="scroll" contentContainerStyle={$styles.container}>
|
|||
|
|
<Text preset="heading" tx="screen:title" style={themed($title)} />
|
|||
|
|
<Text tx="screen:description" style={themed($description)} />
|
|||
|
|
|
|||
|
|
<Text preset="subheading" tx="screen:section1" style={themed($sectionTitle)} />
|
|||
|
|
<ListItem ... />
|
|||
|
|
<ListItem ... />
|
|||
|
|
</Screen>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const $title: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
|||
|
|
marginBottom: spacing.sm,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $description: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
|||
|
|
marginBottom: spacing.lg,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $sectionTitle: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
|||
|
|
marginTop: spacing.xxl,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 组件列表 (`app/components/`)
|
|||
|
|
|
|||
|
|
| 组件 | 用途 |
|
|||
|
|
|------|------|
|
|||
|
|
| `Screen.tsx` | 屏幕容器(SafeArea + 键盘适配 + 滚动) |
|
|||
|
|
| `Header.tsx` | 页面头部导航栏 |
|
|||
|
|
| `Card.tsx` | 卡片容器 |
|
|||
|
|
| `TextField.tsx` | 文本输入框 |
|
|||
|
|
| `Button.tsx` | 按钮(default/filled/reversed 预设) |
|
|||
|
|
| `EmptyState.tsx` | 空状态占位 |
|
|||
|
|
| `ListItem.tsx` | 列表项 |
|
|||
|
|
| `Icon.tsx` | 图标 |
|
|||
|
|
| `Text.tsx` | 文本(heading/subheading/bold 等预设) |
|
|||
|
|
| `AutoImage.tsx` | 自适应图片 |
|
|||
|
|
| `Toggle/` | 开关/复选框/单选框 |
|
|||
|
|
| `Avatar.tsx` | 用户头像(使用 expo-image 缓存) |
|
|||
|
|
| `Modal.tsx` | 模态框(bottom/center 预设) |
|
|||
|
|
| `Dialog.tsx` | 对话框(确认/提示) |
|
|||
|
|
|
|||
|
|
**注意**: `screens/DemoShowroomScreen/demos/` 目录下的 `Demo*.tsx` 文件是组件**演示页面**,用于展示组件的各种用法和变体,类似 Storybook,不是实际组件。
|
|||
|
|
|
|||
|
|
### 自适应布局
|
|||
|
|
|
|||
|
|
**Screen 组件预设**:
|
|||
|
|
- `preset="fixed"` - 固定布局,不可滚动
|
|||
|
|
- `preset="scroll"` - 始终可滚动
|
|||
|
|
- `preset="auto"` - 智能判断:内容超出屏幕时才启用滚动
|
|||
|
|
|
|||
|
|
**安全区域**: 使用 `useSafeAreaInsetsStyle` hook 处理刘海屏
|
|||
|
|
```typescript
|
|||
|
|
<Screen safeAreaEdges={["top", "bottom"]}>
|
|||
|
|
{/* 内容自动避开刘海和底部指示条 */}
|
|||
|
|
</Screen>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**间距系统** (`app/theme/spacing.ts`):
|
|||
|
|
```typescript
|
|||
|
|
xxxs: 2, xxs: 4, xs: 8, sm: 12, md: 16, lg: 24, xl: 32, xxl: 48, xxxl: 64
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**字体大小** (`app/components/Text.tsx`) - 固定像素值,无响应式:
|
|||
|
|
```typescript
|
|||
|
|
xxl: 36, xl: 24, lg: 20, md: 18, sm: 16(默认), xs: 14, xxs: 12
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Path Aliases
|
|||
|
|
|
|||
|
|
- `@/*` → `app/*`
|
|||
|
|
- `@assets/*` → `assets/*`
|
|||
|
|
|
|||
|
|
## Import Restrictions (ESLint enforced)
|
|||
|
|
|
|||
|
|
- Use `Text`, `Button`, `TextInput` from `@/components`, not from `react-native`
|
|||
|
|
- Use `SafeAreaView` from `react-native-safe-area-context`, not from `react-native`
|
|||
|
|
- Import named exports from `react` (not `import React from 'react'`)
|
|||
|
|
|
|||
|
|
## Testing
|
|||
|
|
|
|||
|
|
Jest with Testing Library for React Native. Test files use `.test.ts(x)` suffix. Setup in `test/setup.ts`.
|
|||
|
|
|
|||
|
|
Run single test:
|
|||
|
|
```bash
|
|||
|
|
pnpm run test -- --testPathPattern="storage"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Web Support
|
|||
|
|
|
|||
|
|
This app runs on web via Expo. Use `pnpm run start` and press `w` to open web. Note: React Navigation's `headerRight` may not work reliably on web - consider placing header elements directly inside Screen components if targeting web.
|
|||
|
|
|
|||
|
|
## Running on Physical Devices
|