AgentSkillsCN

React Native Guidelines

React Native开发指南

SKILL.md

React Native Component Guidelines

This skill provides conventions for React Native/Expo mobile development.

Directory Structure

code
apps/mobile/src/
├── components/
│   ├── ui/                     # Design system primitives (Button, Input, Card, etc.)
│   │   └── [ComponentName]/
│   │       ├── [ComponentName].tsx
│   │       ├── [ComponentName].test.tsx
│   │       ├── [ComponentName].stories.tsx
│   │       └── index.ts
│   └── common/                 # Shared business components (Avatar, MessageBubble, etc.)
├── features/                   # Feature-specific code
│   └── [feature-name]/
│       ├── components/
│       ├── hooks/
│       └── screens/
├── hooks/                      # App-wide shared hooks
├── screens/                    # Top-level screens (navigation entry points)
└── lib/                        # Utilities, constants, types

Component Structure

Every component must be a folder with:

  • ComponentName.tsx - Implementation
  • ComponentName.test.tsx - Unit tests (required)
  • ComponentName.stories.tsx - Storybook story (required for ui/common)
  • index.ts - Barrel export

Component Pattern

tsx
import { View, Text, Pressable } from 'react-native';

export interface ButtonProps {
  label: string;
  onPress: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

export function Button({ label, onPress, variant = 'primary', disabled = false }: ButtonProps) {
  return (
    <Pressable
      className={`px-4 py-2 rounded-lg ${variant === 'primary' ? 'bg-accent' : 'bg-background'} ${disabled ? 'opacity-50' : ''}`}
      onPress={onPress}
      disabled={disabled}
    >
      <Text className={`text-center font-medium ${variant === 'primary' ? 'text-background' : 'text-foreground'}`}>
        {label}
      </Text>
    </Pressable>
  );
}

Barrel File (index.ts)

ts
export { Button } from './Button';
export type { ButtonProps } from './Button';

Rules

  • Named exports only (no default exports)
  • Props interface named [ComponentName]Props and exported
  • Keep components focused on presentation; extract logic to hooks

Styling

Use uniwind (NativeWind) classes only. StyleSheet.create() is FORBIDDEN.

RuleStatus
Use uniwind className propRequired
Inline classNamesRequired
Use theme colorsRequired
StyleSheet.create()Forbidden
Inline style propForbidden (except dynamic values)
Hardcoded color valuesForbidden

Theme Colors

Colors must come from theme in global.css:

ClassUsage
bg-background / text-backgroundMain background
bg-foreground / text-foregroundMain text
bg-accent / text-accentAccent/highlight

Never use hardcoded colors like bg-white, text-black, bg-blue-500.

Correct

tsx
<View className="flex-1 bg-background p-4">
  <Text className="text-lg font-bold text-foreground">Title</Text>
</View>

Incorrect

tsx
// Hardcoded colors
<View className="bg-white">

// StyleSheet.create
const styles = StyleSheet.create({ container: { flex: 1 } });

// Inline style
<View style={{ flex: 1, backgroundColor: 'white' }} />

Hooks

Hook TypeLocation
Component-specificInside component folder
Feature-sharedfeatures/[feature]/hooks/
App-widehooks/

Pattern

ts
export interface UseAuthReturn {
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  isLoading: boolean;
}

export function useAuth(): UseAuthReturn {
  // implementation
}

Screens

  • Orchestrate components, no business logic
  • Data fetching in hooks, not screens
  • NO Storybook stories (use Maestro E2E)

Testing

Unit Tests (Required)

tsx
import { render, screen, fireEvent } from '@testing-library/react-native';
import { Button } from './Button';

describe('Button', () => {
  it('renders label correctly', () => {
    render(<Button label="Click me" onPress={() => {}} />);
    expect(screen.getByText('Click me')).toBeOnTheScreen();
  });

  it('calls onPress when pressed', () => {
    const onPress = jest.fn();
    render(<Button label="Click me" onPress={onPress} />);
    fireEvent.press(screen.getByText('Click me'));
    expect(onPress).toHaveBeenCalledTimes(1);
  });
});

E2E Tests (Maestro)

yaml
# .maestro/flows/auth/login.yaml
appId: com.sunsay.attune
---
- launchApp
- tapOn: "Email"
- inputText: "test@example.com"
- tapOn: "Sign In"
- assertVisible: "Welcome"

Storybook

Required for ui/ and common/ components:

tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'ui/Button',
  component: Button,
  args: {
    label: 'Button',
    onPress: () => console.log('pressed'),
  },
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = { args: { variant: 'primary' } };
export const Disabled: Story = { args: { disabled: true } };

Naming Conventions

ItemConventionExample
Component folderPascalCaseButton/
Component filePascalCaseButton.tsx
Hook filecamelCaseuseAuth.ts
Feature folderkebab-casefeatures/user-profile/

Checklist

  • Component in correct location
  • All required files present
  • Props interface exported
  • Styled with uniwind only
  • Theme colors only (no hardcoded)
  • Unit tests cover main functionality
  • Storybook story (for ui/common)
  • testID for interactive elements