Claude Code Plugins

Community-maintained marketplace

Feedback

react-native-component-generator

@Dexploarer/claudius-skills
1
0

Generates React Native components with platform-specific code, TypeScript support, and mobile-first patterns. Use when user asks to "create React Native component", "generate RN component", "scaffold mobile component", or "create cross-platform component".

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name react-native-component-generator
description Generates React Native components with platform-specific code, TypeScript support, and mobile-first patterns. Use when user asks to "create React Native component", "generate RN component", "scaffold mobile component", or "create cross-platform component".
allowed-tools Write, Read, Glob

React Native Component Generator

Generates React Native components with platform-specific optimizations, TypeScript support, and mobile best practices.

When to Use

  • "Create a React Native Button component"
  • "Generate mobile component"
  • "Scaffold cross-platform component"
  • "Create RN component with platform code"
  • "Generate native module wrapper"

Instructions

1. Gather Requirements

Ask for:

  • Component name (PascalCase)
  • Platform support (iOS, Android, or both)
  • Props and their types
  • Native modules needed
  • Styling approach (StyleSheet, styled-components, etc.)
  • Animations needed

2. Generate Base Component

Basic Functional Component:

// components/Button/Button.tsx
import React from 'react';
import {
  TouchableOpacity,
  Text,
  StyleSheet,
  TouchableOpacityProps,
  ViewStyle,
  TextStyle,
} from 'react-native';

interface ButtonProps extends TouchableOpacityProps {
  title: string;
  onPress: () => void;
  variant?: 'primary' | 'secondary' | 'outline';
  size?: 'small' | 'medium' | 'large';
}

export const Button: React.FC<ButtonProps> = ({
  title,
  onPress,
  variant = 'primary',
  size = 'medium',
  style,
  ...props
}) => {
  return (
    <TouchableOpacity
      style={[
        styles.button,
        styles[variant],
        styles[size],
        style,
      ]}
      onPress={onPress}
      activeOpacity={0.7}
      {...props}
    >
      <Text style={[styles.text, styles[`${variant}Text`]]}>
        {title}
      </Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  button: {
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
  },
  primary: {
    backgroundColor: '#007AFF',
  },
  secondary: {
    backgroundColor: '#6c757d',
  },
  outline: {
    backgroundColor: 'transparent',
    borderWidth: 1,
    borderColor: '#007AFF',
  },
  small: {
    paddingVertical: 8,
    paddingHorizontal: 16,
  },
  medium: {
    paddingVertical: 12,
    paddingHorizontal: 24,
  },
  large: {
    paddingVertical: 16,
    paddingHorizontal: 32,
  },
  text: {
    fontSize: 16,
    fontWeight: '600',
  },
  primaryText: {
    color: '#ffffff',
  },
  secondaryText: {
    color: '#ffffff',
  },
  outlineText: {
    color: '#007AFF',
  },
});

Component with State and Hooks:

// components/SearchInput/SearchInput.tsx
import React, { useState, useCallback, useEffect } from 'react';
import {
  TextInput,
  View,
  StyleSheet,
  TextInputProps,
  TouchableOpacity,
  ActivityIndicator,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';

interface SearchInputProps extends Omit<TextInputProps, 'onChangeText'> {
  onSearch: (query: string) => void;
  onClear?: () => void;
  debounceMs?: number;
  loading?: boolean;
}

export const SearchInput: React.FC<SearchInputProps> = ({
  onSearch,
  onClear,
  debounceMs = 300,
  loading = false,
  placeholder = 'Search...',
  ...props
}) => {
  const [query, setQuery] = useState('');

  useEffect(() => {
    const timer = setTimeout(() => {
      if (query) {
        onSearch(query);
      }
    }, debounceMs);

    return () => clearTimeout(timer);
  }, [query, debounceMs, onSearch]);

  const handleClear = useCallback(() => {
    setQuery('');
    onClear?.();
  }, [onClear]);

  return (
    <View style={styles.container}>
      <Ionicons name="search" size={20} color="#666" style={styles.icon} />
      <TextInput
        style={styles.input}
        value={query}
        onChangeText={setQuery}
        placeholder={placeholder}
        placeholderTextColor="#999"
        autoCapitalize="none"
        autoCorrect={false}
        {...props}
      />
      {loading && <ActivityIndicator size="small" color="#007AFF" />}
      {query.length > 0 && !loading && (
        <TouchableOpacity onPress={handleClear}>
          <Ionicons name="close-circle" size={20} color="#666" />
        </TouchableOpacity>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f0f0f0',
    borderRadius: 10,
    paddingHorizontal: 12,
    height: 44,
  },
  icon: {
    marginRight: 8,
  },
  input: {
    flex: 1,
    fontSize: 16,
    color: '#000',
  },
});

3. Platform-Specific Code

Using Platform Module:

// components/Header/Header.tsx
import React from 'react';
import { View, Text, StyleSheet, Platform, StatusBar } from 'react-native';

interface HeaderProps {
  title: string;
}

export const Header: React.FC<HeaderProps> = ({ title }) => {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>{title}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    paddingTop: Platform.select({
      ios: 50,
      android: StatusBar.currentHeight || 0,
      default: 0,
    }),
    paddingBottom: 10,
    paddingHorizontal: 16,
    backgroundColor: '#007AFF',
    ...Platform.select({
      ios: {
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.1,
        shadowRadius: 4,
      },
      android: {
        elevation: 4,
      },
    }),
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#fff',
    ...Platform.select({
      ios: {
        fontFamily: 'System',
      },
      android: {
        fontFamily: 'Roboto',
      },
    }),
  },
});

Using Platform-Specific Files:

// components/DatePicker/DatePicker.ios.tsx
import React from 'react';
import { DatePickerIOS } from '@react-native-community/datetimepicker';

interface DatePickerProps {
  date: Date;
  onDateChange: (date: Date) => void;
}

export const DatePicker: React.FC<DatePickerProps> = ({ date, onDateChange }) => {
  return <DatePickerIOS date={date} onDateChange={onDateChange} mode="date" />;
};
// components/DatePicker/DatePicker.android.tsx
import React, { useState } from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';

interface DatePickerProps {
  date: Date;
  onDateChange: (date: Date) => void;
}

export const DatePicker: React.FC<DatePickerProps> = ({ date, onDateChange }) => {
  const [show, setShow] = useState(false);

  const onChange = (_event: any, selectedDate?: Date) => {
    setShow(false);
    if (selectedDate) {
      onDateChange(selectedDate);
    }
  };

  return (
    <>
      <TouchableOpacity onPress={() => setShow(true)} style={styles.button}>
        <Text>{date.toLocaleDateString()}</Text>
      </TouchableOpacity>
      {show && (
        <DateTimePicker value={date} mode="date" display="default" onChange={onChange} />
      )}
    </>
  );
};

const styles = StyleSheet.create({
  button: {
    padding: 12,
    backgroundColor: '#f0f0f0',
    borderRadius: 8,
  },
});
// components/DatePicker/index.ts
export { DatePicker } from './DatePicker';

4. Animated Components

Basic Animation:

// components/FadeIn/FadeIn.tsx
import React, { useEffect, useRef } from 'react';
import { Animated, ViewProps } from 'react-native';

interface FadeInProps extends ViewProps {
  duration?: number;
  delay?: number;
}

export const FadeIn: React.FC<FadeInProps> = ({
  children,
  duration = 300,
  delay = 0,
  style,
  ...props
}) => {
  const fadeAnim = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration,
      delay,
      useNativeDriver: true,
    }).start();
  }, [fadeAnim, duration, delay]);

  return (
    <Animated.View style={[style, { opacity: fadeAnim }]} {...props}>
      {children}
    </Animated.View>
  );
};

Complex Animation:

// components/SlideUp/SlideUp.tsx
import React, { useEffect, useRef } from 'react';
import { Animated, Dimensions } from 'react-native';

const { height } = Dimensions.get('window');

interface SlideUpProps {
  visible: boolean;
  onClose: () => void;
  children: React.ReactNode;
}

export const SlideUp: React.FC<SlideUpProps> = ({ visible, onClose, children }) => {
  const slideAnim = useRef(new Animated.Value(height)).current;

  useEffect(() => {
    if (visible) {
      Animated.spring(slideAnim, {
        toValue: 0,
        useNativeDriver: true,
        tension: 65,
        friction: 11,
      }).start();
    } else {
      Animated.timing(slideAnim, {
        toValue: height,
        duration: 250,
        useNativeDriver: true,
      }).start();
    }
  }, [visible, slideAnim]);

  if (!visible) return null;

  return (
    <Animated.View
      style={{
        position: 'absolute',
        bottom: 0,
        left: 0,
        right: 0,
        backgroundColor: '#fff',
        borderTopLeftRadius: 20,
        borderTopRightRadius: 20,
        padding: 20,
        transform: [{ translateY: slideAnim }],
      }}
    >
      {children}
    </Animated.View>
  );
};

5. List Components

Optimized FlatList:

// components/UserList/UserList.tsx
import React, { useCallback } from 'react';
import {
  FlatList,
  View,
  Text,
  StyleSheet,
  ActivityIndicator,
  RefreshControl,
} from 'react-native';

interface User {
  id: string;
  name: string;
  email: string;
}

interface UserListProps {
  users: User[];
  loading?: boolean;
  onRefresh?: () => void;
  onEndReached?: () => void;
  refreshing?: boolean;
}

export const UserList: React.FC<UserListProps> = ({
  users,
  loading,
  onRefresh,
  onEndReached,
  refreshing = false,
}) => {
  const renderItem = useCallback(({ item }: { item: User }) => (
    <View style={styles.item}>
      <Text style={styles.name}>{item.name}</Text>
      <Text style={styles.email}>{item.email}</Text>
    </View>
  ), []);

  const keyExtractor = useCallback((item: User) => item.id, []);

  const renderFooter = useCallback(() => {
    if (!loading) return null;
    return (
      <View style={styles.footer}>
        <ActivityIndicator size="small" color="#007AFF" />
      </View>
    );
  }, [loading]);

  const renderEmpty = useCallback(() => (
    <View style={styles.empty}>
      <Text style={styles.emptyText}>No users found</Text>
    </View>
  ), []);

  return (
    <FlatList
      data={users}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      ListFooterComponent={renderFooter}
      ListEmptyComponent={renderEmpty}
      onEndReached={onEndReached}
      onEndReachedThreshold={0.5}
      refreshControl={
        onRefresh ? (
          <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
        ) : undefined
      }
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      initialNumToRender={10}
      windowSize={5}
    />
  );
};

const styles = StyleSheet.create({
  item: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  name: {
    fontSize: 16,
    fontWeight: '600',
    marginBottom: 4,
  },
  email: {
    fontSize: 14,
    color: '#666',
  },
  footer: {
    paddingVertical: 20,
    alignItems: 'center',
  },
  empty: {
    padding: 40,
    alignItems: 'center',
  },
  emptyText: {
    fontSize: 16,
    color: '#999',
  },
});

6. Form Components

Input with Validation:

// components/TextInputField/TextInputField.tsx
import React, { useState } from 'react';
import { View, TextInput, Text, StyleSheet, TextInputProps } from 'react-native';

interface TextInputFieldProps extends TextInputProps {
  label: string;
  error?: string;
  required?: boolean;
  validate?: (value: string) => string | undefined;
}

export const TextInputField: React.FC<TextInputFieldProps> = ({
  label,
  error,
  required,
  validate,
  onBlur,
  onChangeText,
  ...props
}) => {
  const [localError, setLocalError] = useState<string>();

  const handleBlur = (e: any) => {
    if (validate && props.value) {
      setLocalError(validate(props.value));
    }
    onBlur?.(e);
  };

  const handleChange = (text: string) => {
    setLocalError(undefined);
    onChangeText?.(text);
  };

  const displayError = error || localError;

  return (
    <View style={styles.container}>
      <Text style={styles.label}>
        {label}
        {required && <Text style={styles.required}> *</Text>}
      </Text>
      <TextInput
        style={[styles.input, displayError && styles.inputError]}
        onBlur={handleBlur}
        onChangeText={handleChange}
        {...props}
      />
      {displayError && <Text style={styles.errorText}>{displayError}</Text>}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    marginBottom: 16,
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    marginBottom: 8,
    color: '#333',
  },
  required: {
    color: '#ff0000',
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    backgroundColor: '#fff',
  },
  inputError: {
    borderColor: '#ff0000',
  },
  errorText: {
    color: '#ff0000',
    fontSize: 12,
    marginTop: 4,
  },
});

7. Native Module Integration

Using Native Modules:

// components/BiometricAuth/BiometricAuth.tsx
import React from 'react';
import { Alert, Platform } from 'react-native';
import TouchID from 'react-native-touch-id';
import ReactNativeBiometrics from 'react-native-biometrics';

interface BiometricAuthProps {
  onSuccess: () => void;
  onError: (error: string) => void;
}

export const useBiometricAuth = ({ onSuccess, onError }: BiometricAuthProps) => {
  const authenticate = async () => {
    try {
      if (Platform.OS === 'ios') {
        const isSupported = await TouchID.isSupported();
        if (!isSupported) {
          onError('Biometric authentication not supported');
          return;
        }

        await TouchID.authenticate('Authenticate to continue', {
          fallbackLabel: 'Use Passcode',
        });
        onSuccess();
      } else {
        const rnBiometrics = new ReactNativeBiometrics();
        const { available } = await rnBiometrics.isSensorAvailable();

        if (!available) {
          onError('Biometric authentication not available');
          return;
        }

        const { success } = await rnBiometrics.simplePrompt({
          promptMessage: 'Authenticate to continue',
        });

        if (success) {
          onSuccess();
        } else {
          onError('Authentication failed');
        }
      }
    } catch (error: any) {
      onError(error.message);
    }
  };

  return { authenticate };
};

8. Responsive Components

Using Dimensions:

// components/ResponsiveCard/ResponsiveCard.tsx
import React from 'react';
import { View, Text, StyleSheet, Dimensions, ScaledSize } from 'react-native';
import { useDeviceOrientation } from '@react-native-community/hooks';

const getStyles = (window: ScaledSize) => {
  const { width } = window;
  const isTablet = width >= 768;

  return StyleSheet.create({
    card: {
      padding: isTablet ? 24 : 16,
      margin: isTablet ? 16 : 8,
      backgroundColor: '#fff',
      borderRadius: isTablet ? 12 : 8,
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 2 },
      shadowOpacity: 0.1,
      shadowRadius: 4,
      elevation: 3,
    },
    title: {
      fontSize: isTablet ? 24 : 18,
      fontWeight: 'bold',
      marginBottom: isTablet ? 12 : 8,
    },
  });
};

export const ResponsiveCard: React.FC<{ title: string; children: React.ReactNode }> = ({
  title,
  children,
}) => {
  const window = Dimensions.get('window');
  const styles = getStyles(window);

  return (
    <View style={styles.card}>
      <Text style={styles.title}>{title}</Text>
      {children}
    </View>
  );
};

9. Index File

Create barrel export:

// components/index.ts
export { Button } from './Button/Button';
export { SearchInput } from './SearchInput/SearchInput';
export { Header } from './Header/Header';
export { DatePicker } from './DatePicker';
export { FadeIn } from './FadeIn/FadeIn';
export { SlideUp } from './SlideUp/SlideUp';
export { UserList } from './UserList/UserList';
export { TextInputField } from './TextInputField/TextInputField';
export { ResponsiveCard } from './ResponsiveCard/ResponsiveCard';

10. Tests

Component Test:

// components/Button/__tests__/Button.test.tsx
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from '../Button';

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

  it('calls onPress when pressed', () => {
    const onPress = jest.fn();
    const { getByText } = render(<Button title="Click me" onPress={onPress} />);

    fireEvent.press(getByText('Click me'));
    expect(onPress).toHaveBeenCalledTimes(1);
  });

  it('applies correct variant styles', () => {
    const { getByText, rerender } = render(
      <Button title="Primary" onPress={() => {}} variant="primary" />
    );

    let button = getByText('Primary').parent;
    expect(button?.props.style).toContainEqual(
      expect.objectContaining({ backgroundColor: '#007AFF' })
    );

    rerender(<Button title="Outline" onPress={() => {}} variant="outline" />);

    button = getByText('Outline').parent;
    expect(button?.props.style).toContainEqual(
      expect.objectContaining({ backgroundColor: 'transparent' })
    );
  });
});

Best Practices

DO:

  • Use TypeScript for type safety
  • Optimize FlatList performance
  • Use Platform-specific code when needed
  • Implement proper keyboard handling
  • Use useCallback for list renders
  • Test on both platforms
  • Follow platform design guidelines

DON'T:

  • Use inline styles in render
  • Forget to use useNativeDriver for animations
  • Ignore safe area insets
  • Over-render components
  • Use ScrollView for long lists
  • Hardcode dimensions
  • Ignore accessibility

Checklist

  • Component created with TypeScript
  • Props interface defined
  • Platform-specific code handled
  • Styles optimized with StyleSheet
  • Accessibility props added
  • Tests written
  • Index file updated
  • Performance optimized
  • Documentation added